moflo 4.11.5 → 4.11.6
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.
|
@@ -31,7 +31,7 @@ import { fileURLToPath } from 'url';
|
|
|
31
31
|
import { execSync, execFileSync, spawn } from 'child_process';
|
|
32
32
|
import { memoryDbPath, MOFLO_DIR, findProjectRoot } from './lib/moflo-paths.mjs';
|
|
33
33
|
import { openBackend } from './lib/get-backend.mjs';
|
|
34
|
-
import { applyIncrementalChunks,
|
|
34
|
+
import { applyIncrementalChunks, schemeTaggedContentHash } from './lib/incremental-write.mjs';
|
|
35
35
|
import { resolveMofloBin } from './lib/resolve-bin.mjs';
|
|
36
36
|
|
|
37
37
|
|
|
@@ -42,6 +42,16 @@ const NAMESPACE = 'code-map';
|
|
|
42
42
|
const DB_PATH = memoryDbPath(projectRoot);
|
|
43
43
|
const HASH_CACHE_PATH = resolve(projectRoot, MOFLO_DIR, 'code-map-hash.txt');
|
|
44
44
|
|
|
45
|
+
// Output-scheme version for the skip-cache (#1260). Bump whenever the emitted
|
|
46
|
+
// key scheme changes (chunk kinds, batch sizes, `file:` granularity) so a
|
|
47
|
+
// generator upgrade forces one rebuild + orphan sweep instead of silently
|
|
48
|
+
// skipping and leaving the previous scheme's rows orphaned. This is also the
|
|
49
|
+
// single source of truth for the code-map scheme now that `flo memory code-map`
|
|
50
|
+
// delegates here rather than shipping a second, divergent generator.
|
|
51
|
+
// v2: unified scheme — invalidates every pre-#1260 cache (bare hash or the
|
|
52
|
+
// old directory-level `flo memory code-map` output) exactly once on pickup.
|
|
53
|
+
const SCHEME_VERSION = 2;
|
|
54
|
+
|
|
45
55
|
// Directories to exclude from indexing
|
|
46
56
|
const EXCLUDE_DIRS = [
|
|
47
57
|
'node_modules', 'dist', 'build', '.next', 'coverage',
|
|
@@ -786,15 +796,16 @@ async function main() {
|
|
|
786
796
|
return;
|
|
787
797
|
}
|
|
788
798
|
|
|
789
|
-
// 2. Check hash for incremental skip
|
|
790
|
-
|
|
799
|
+
// 2. Check hash for incremental skip — scheme-versioned so a scheme change
|
|
800
|
+
// invalidates the cache and forces a rebuild + orphan sweep (#1260).
|
|
801
|
+
const currentHash = schemeTaggedContentHash(files, SCHEME_VERSION);
|
|
791
802
|
|
|
792
803
|
if (statsOnly) {
|
|
793
804
|
const db = await getDb();
|
|
794
805
|
const count = countNamespace(db);
|
|
795
806
|
db.close();
|
|
796
807
|
log(`Stats: ${files.length} source files, ${count} chunks in code-map namespace`);
|
|
797
|
-
log(`
|
|
808
|
+
log(`Scheme v${SCHEME_VERSION}, file list hash: ${currentHash.slice(0, 20)}...`);
|
|
798
809
|
return;
|
|
799
810
|
}
|
|
800
811
|
|
|
@@ -65,6 +65,29 @@ export function computeContentListHash(files) {
|
|
|
65
65
|
return hasher.digest('hex');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Scheme-versioned wrapper around {@link computeContentListHash}. Prefixes the
|
|
70
|
+
* content hash with `v<schemeVersion>:` so the indexer's skip-cache encodes the
|
|
71
|
+
* OUTPUT scheme, not just the input file list (#1260).
|
|
72
|
+
*
|
|
73
|
+
* Why this is load-bearing: the skip gate short-circuits the whole
|
|
74
|
+
* extract+write pipeline (including the orphan sweep) whenever the stored hash
|
|
75
|
+
* matches. If a generator upgrade changes the emitted key scheme (e.g. drops
|
|
76
|
+
* `file:` entries, or two entry points write divergent shapes) while the source
|
|
77
|
+
* file list is unchanged, an unversioned hash still matches → the generator
|
|
78
|
+
* skips → the previous scheme's orphaned rows persist forever. Bumping
|
|
79
|
+
* `schemeVersion` invalidates every stored cache exactly once, forcing a
|
|
80
|
+
* rebuild + orphan sweep that reconciles the namespace. Legacy bare-hash caches
|
|
81
|
+
* (no `v<n>:` prefix) also mismatch, so upgrading consumers self-heal.
|
|
82
|
+
*
|
|
83
|
+
* @param {string[]} files - absolute paths
|
|
84
|
+
* @param {number|string} schemeVersion - monotonically-bumped scheme tag
|
|
85
|
+
* @returns {string} `v<schemeVersion>:<sha256>`
|
|
86
|
+
*/
|
|
87
|
+
export function schemeTaggedContentHash(files, schemeVersion) {
|
|
88
|
+
return `v${schemeVersion}:${computeContentListHash(files)}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
68
91
|
/**
|
|
69
92
|
* Load `key → content` for every active row in the namespace, optionally
|
|
70
93
|
* scoped to keys starting with `keyPrefix` (one doc's chunks at a time —
|
|
@@ -10,7 +10,7 @@ import { select, confirm, input } from '../prompt.js';
|
|
|
10
10
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
11
11
|
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
12
12
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
13
|
-
import {
|
|
13
|
+
import { memoryDbPath } from '../services/moflo-paths.js';
|
|
14
14
|
import { resolveBridgeDbPath } from '../memory/bridge-core.js';
|
|
15
15
|
import { findProjectRoot } from '../services/project-root.js';
|
|
16
16
|
// Memory backends
|
|
@@ -1974,129 +1974,43 @@ const rebuildIndexCommand = {
|
|
|
1974
1974
|
}
|
|
1975
1975
|
};
|
|
1976
1976
|
// ============================================================================
|
|
1977
|
-
// code-map subcommand
|
|
1977
|
+
// code-map subcommand — delegates to the single shipped generator (#1260)
|
|
1978
1978
|
// ============================================================================
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
layout: 'app shell layout',
|
|
1999
|
-
themes: 'MUI theme configuration',
|
|
2000
|
-
api: 'API client layer',
|
|
2001
|
-
locales: 'i18n translation files',
|
|
2002
|
-
tests: 'test suites',
|
|
2003
|
-
e2e: 'end-to-end tests',
|
|
2004
|
-
};
|
|
2005
|
-
const TS_PATTERNS = [
|
|
2006
|
-
/^export\s+(?:default\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+([\w.]+))?(?:\s+implements\s+([\w,\s.]+))?/,
|
|
2007
|
-
/^export\s+(?:default\s+)?interface\s+(\w+)(?:\s+extends\s+([\w,\s.]+))?/,
|
|
2008
|
-
/^export\s+(?:default\s+)?type\s+(\w+)\s*[=<]/,
|
|
2009
|
-
/^export\s+(?:const\s+)?enum\s+(\w+)/,
|
|
2010
|
-
/^export\s+(?:default\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
2011
|
-
/^export\s+(?:default\s+)?const\s+(\w+)\s*[=:]/,
|
|
2012
|
-
];
|
|
2013
|
-
const ENTITY_DECORATOR = /@Entity\s*\(/;
|
|
2014
|
-
const IFACE_MAP_BATCH = 20;
|
|
2015
|
-
const TYPE_INDEX_BATCH = 80;
|
|
2016
|
-
function detectKind(line) {
|
|
2017
|
-
if (/\bclass\b/.test(line))
|
|
2018
|
-
return 'class';
|
|
2019
|
-
if (/\binterface\b/.test(line))
|
|
2020
|
-
return 'interface';
|
|
2021
|
-
if (/\btype\b/.test(line))
|
|
2022
|
-
return 'type';
|
|
2023
|
-
if (/\benum\b/.test(line))
|
|
2024
|
-
return 'enum';
|
|
2025
|
-
if (/\bfunction\b/.test(line))
|
|
2026
|
-
return 'function';
|
|
2027
|
-
if (/\bconst\b/.test(line))
|
|
2028
|
-
return 'const';
|
|
2029
|
-
return 'export';
|
|
2030
|
-
}
|
|
2031
|
-
function extractTypesFromFile(filePath, projectRoot) {
|
|
2032
|
-
const fullPath = pathModule.resolve(projectRoot, filePath);
|
|
2033
|
-
if (!fs.existsSync(fullPath))
|
|
2034
|
-
return [];
|
|
2035
|
-
let content;
|
|
2036
|
-
try {
|
|
2037
|
-
content = fs.readFileSync(fullPath, 'utf-8');
|
|
2038
|
-
}
|
|
2039
|
-
catch {
|
|
2040
|
-
return [];
|
|
2041
|
-
}
|
|
2042
|
-
const lines = content.split(/\r?\n/);
|
|
2043
|
-
const types = [];
|
|
2044
|
-
const seen = new Set();
|
|
2045
|
-
let isEntityNext = false;
|
|
2046
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2047
|
-
const line = lines[i].trim();
|
|
2048
|
-
if (ENTITY_DECORATOR.test(line)) {
|
|
2049
|
-
isEntityNext = true;
|
|
1979
|
+
/**
|
|
1980
|
+
* Resolve the file-level code-map generator (`bin/generate-code-map.mjs`) for
|
|
1981
|
+
* `cwd`, reusing moflo's canonical bin resolver so this dist CLI command and
|
|
1982
|
+
* the batch indexer (`.claude/scripts/index-all.mjs`) spawn the exact same
|
|
1983
|
+
* script — one generator, one key scheme (#1260).
|
|
1984
|
+
*
|
|
1985
|
+
* Cross-platform: the resolver is located via a cwd-relative path (never a
|
|
1986
|
+
* file-relative `../../../../` depth — that broke #1126) and imported as a
|
|
1987
|
+
* `file://` URL so Windows accepts the absolute path (Rule #1). Returns null
|
|
1988
|
+
* when no copy is found (broken install / not-yet-synced worktree).
|
|
1989
|
+
*/
|
|
1990
|
+
async function resolveCodeMapGenerator(cwd) {
|
|
1991
|
+
const { pathToFileURL } = await import('url');
|
|
1992
|
+
const resolverCandidates = [
|
|
1993
|
+
pathModule.join(cwd, 'node_modules', 'moflo', 'bin', 'lib', 'resolve-bin.mjs'),
|
|
1994
|
+
pathModule.join(cwd, 'bin', 'lib', 'resolve-bin.mjs'), // dev / source tree
|
|
1995
|
+
];
|
|
1996
|
+
for (const resolverPath of resolverCandidates) {
|
|
1997
|
+
if (!fs.existsSync(resolverPath))
|
|
2050
1998
|
continue;
|
|
1999
|
+
try {
|
|
2000
|
+
const { resolveMofloBin } = await import(pathToFileURL(resolverPath).href);
|
|
2001
|
+
return (resolveMofloBin(cwd, 'flo-codemap', 'generate-code-map.mjs', {
|
|
2002
|
+
includeDevFallback: true,
|
|
2003
|
+
}) ?? null);
|
|
2051
2004
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
if (m && m[1] && !seen.has(m[1])) {
|
|
2055
|
-
seen.add(m[1]);
|
|
2056
|
-
const kind = detectKind(line);
|
|
2057
|
-
const bases = (m[2] || '').trim();
|
|
2058
|
-
const implements_ = (m[3] || '').trim();
|
|
2059
|
-
types.push({
|
|
2060
|
-
name: m[1],
|
|
2061
|
-
kind,
|
|
2062
|
-
bases: bases || null,
|
|
2063
|
-
implements: implements_ || null,
|
|
2064
|
-
isEntity: isEntityNext,
|
|
2065
|
-
file: filePath,
|
|
2066
|
-
});
|
|
2067
|
-
isEntityNext = false;
|
|
2068
|
-
break;
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
if (isEntityNext && !line.startsWith('@') && !line.startsWith('export') && line.length > 0) {
|
|
2072
|
-
isEntityNext = false;
|
|
2005
|
+
catch {
|
|
2006
|
+
// Fall through to the next resolver candidate.
|
|
2073
2007
|
}
|
|
2074
2008
|
}
|
|
2075
|
-
return
|
|
2076
|
-
}
|
|
2077
|
-
function getProjectName(filePath) {
|
|
2078
|
-
const parts = filePath.split('/');
|
|
2079
|
-
if (parts[0] === 'packages' && parts.length >= 2)
|
|
2080
|
-
return `${parts[0]}/${parts[1]}`;
|
|
2081
|
-
if (parts[0] === 'back-office' && parts.length >= 2)
|
|
2082
|
-
return `${parts[0]}/${parts[1]}`;
|
|
2083
|
-
if (parts[0] === 'customer-portal' && parts.length >= 2)
|
|
2084
|
-
return `${parts[0]}/${parts[1]}`;
|
|
2085
|
-
if (parts[0] === 'admin-console' && parts.length >= 2)
|
|
2086
|
-
return `${parts[0]}/${parts[1]}`;
|
|
2087
|
-
if (parts[0] === 'webhooks' && parts.length >= 2)
|
|
2088
|
-
return `${parts[0]}/${parts[1]}`;
|
|
2089
|
-
if (parts[0] === 'mobile-app')
|
|
2090
|
-
return 'mobile-app';
|
|
2091
|
-
if (parts[0] === 'tests')
|
|
2092
|
-
return 'tests';
|
|
2093
|
-
if (parts[0] === 'scripts')
|
|
2094
|
-
return 'scripts';
|
|
2095
|
-
return parts[0];
|
|
2009
|
+
return null;
|
|
2096
2010
|
}
|
|
2097
2011
|
const codeMapCommand = {
|
|
2098
2012
|
name: 'code-map',
|
|
2099
|
-
description: 'Generate structural code map (project overviews, directory details, type indexes) into code-map namespace',
|
|
2013
|
+
description: 'Generate the structural code map (project overviews, directory details, type + file indexes) into the code-map namespace',
|
|
2100
2014
|
options: [
|
|
2101
2015
|
{
|
|
2102
2016
|
name: 'force',
|
|
@@ -2134,284 +2048,49 @@ const codeMapCommand = {
|
|
|
2134
2048
|
const verbose = ctx.flags.verbose;
|
|
2135
2049
|
const statsOnly = ctx.flags.stats;
|
|
2136
2050
|
const skipEmbeddings = ctx.flags.noEmbeddings;
|
|
2137
|
-
const NAMESPACE = 'code-map';
|
|
2138
|
-
const fs = await import('fs');
|
|
2139
|
-
const pathMod = await import('path');
|
|
2140
|
-
const { execSync } = await import('child_process');
|
|
2141
|
-
const { createHash } = await import('crypto');
|
|
2142
2051
|
const cwd = ctx.cwd || process.cwd();
|
|
2143
|
-
// Post-#1168: canonical at `.moflo/memory/code-map-hash.txt`. Legacy
|
|
2144
|
-
// `.swarm/code-map-hash.txt` is read-only fallback for upgrade scenarios.
|
|
2145
|
-
const hashCachePath = runtimePath('memory', 'code-map-hash.txt');
|
|
2146
|
-
const legacyHashCachePath = legacySwarmPath('code-map-hash.txt');
|
|
2147
2052
|
output.writeln();
|
|
2148
2053
|
output.writeln(output.bold('Generating Code Map'));
|
|
2149
2054
|
output.writeln(output.dim('─'.repeat(50)));
|
|
2150
|
-
//
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2055
|
+
// Single source of truth (#1260): delegate to the one shipped generator —
|
|
2056
|
+
// the same `bin/generate-code-map.mjs` that `index-all.mjs` runs. This
|
|
2057
|
+
// command previously carried a SECOND, divergent generator (directory-level
|
|
2058
|
+
// scheme with no `file:` entries, a path-list-only skip hash at a different
|
|
2059
|
+
// cache path, and a DELETE+reinsert write that nulled embeddings). The two
|
|
2060
|
+
// fought over the `code-map` namespace and accumulated orphaned rows that
|
|
2061
|
+
// neither skip-cache could reconcile. Delegating here makes divergence
|
|
2062
|
+
// structurally impossible.
|
|
2063
|
+
const script = await resolveCodeMapGenerator(cwd);
|
|
2064
|
+
if (!script) {
|
|
2065
|
+
output.printError('Could not locate the code-map generator (bin/generate-code-map.mjs). Is the moflo package installed?');
|
|
2157
2066
|
return { success: false, exitCode: 1 };
|
|
2158
2067
|
}
|
|
2159
|
-
const
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
stmt.free();
|
|
2180
|
-
db.close();
|
|
2181
|
-
output.writeln(`Stats: ${files.length} source files, ${count} chunks in code-map namespace`);
|
|
2182
|
-
output.writeln(`File list hash: ${currentHash.slice(0, 12)}...`);
|
|
2183
|
-
return { success: true };
|
|
2184
|
-
}
|
|
2185
|
-
// Check if unchanged — canonical first, then legacy `.swarm/` fallback.
|
|
2186
|
-
const cachedReadPath = fs.existsSync(hashCachePath)
|
|
2187
|
-
? hashCachePath
|
|
2188
|
-
: (fs.existsSync(legacyHashCachePath) ? legacyHashCachePath : null);
|
|
2189
|
-
if (!forceRegen && cachedReadPath) {
|
|
2190
|
-
const cached = fs.readFileSync(cachedReadPath, 'utf-8').trim();
|
|
2191
|
-
if (cached === currentHash) {
|
|
2192
|
-
const { db } = await openDb(cwd);
|
|
2193
|
-
const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ?`);
|
|
2194
|
-
stmt.bind([NAMESPACE]);
|
|
2195
|
-
const count = stmt.step() ? stmt.getAsObject().cnt : 0;
|
|
2196
|
-
stmt.free();
|
|
2197
|
-
db.close();
|
|
2198
|
-
if (count > 0) {
|
|
2199
|
-
output.writeln(output.dim(`Skipping -- file list unchanged (${count} chunks in DB)`));
|
|
2200
|
-
return { success: true };
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
// 3. Extract types
|
|
2205
|
-
output.writeln('Extracting type declarations...');
|
|
2206
|
-
const allTypes = [];
|
|
2207
|
-
const filesByProject = {};
|
|
2208
|
-
const typesByProject = {};
|
|
2209
|
-
const typesByDir = {};
|
|
2210
|
-
for (const file of files) {
|
|
2211
|
-
const project = getProjectName(file);
|
|
2212
|
-
if (!filesByProject[project])
|
|
2213
|
-
filesByProject[project] = [];
|
|
2214
|
-
filesByProject[project].push(file);
|
|
2215
|
-
const types = extractTypesFromFile(file, cwd);
|
|
2216
|
-
for (const t of types) {
|
|
2217
|
-
allTypes.push(t);
|
|
2218
|
-
if (!typesByProject[project])
|
|
2219
|
-
typesByProject[project] = [];
|
|
2220
|
-
typesByProject[project].push(t);
|
|
2221
|
-
const dir = pathModule.dirname(t.file).replace(/\\/g, '/');
|
|
2222
|
-
if (!typesByDir[dir])
|
|
2223
|
-
typesByDir[dir] = [];
|
|
2224
|
-
typesByDir[dir].push(t);
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
output.writeln(`Extracted ${allTypes.length} types from ${Object.keys(filesByProject).length} projects`);
|
|
2228
|
-
// 4. Generate chunks
|
|
2229
|
-
const allChunks = [];
|
|
2230
|
-
// Project overviews
|
|
2231
|
-
for (const [project, projFiles] of Object.entries(filesByProject)) {
|
|
2232
|
-
const types = typesByProject[project] || [];
|
|
2233
|
-
const dirMap = {};
|
|
2234
|
-
for (const t of types) {
|
|
2235
|
-
const rel = pathModule.relative(project, pathModule.dirname(t.file)).replace(/\\/g, '/') || '(root)';
|
|
2236
|
-
if (!dirMap[rel])
|
|
2237
|
-
dirMap[rel] = [];
|
|
2238
|
-
dirMap[rel].push(t.name);
|
|
2239
|
-
}
|
|
2240
|
-
// Detect primary language
|
|
2241
|
-
let tsx = 0, ts = 0, js = 0;
|
|
2242
|
-
for (const f of projFiles) {
|
|
2243
|
-
const ext = pathModule.extname(f);
|
|
2244
|
-
if (ext === '.tsx' || ext === '.jsx')
|
|
2245
|
-
tsx++;
|
|
2246
|
-
else if (ext === '.ts')
|
|
2247
|
-
ts++;
|
|
2248
|
-
else
|
|
2249
|
-
js++;
|
|
2250
|
-
}
|
|
2251
|
-
const lang = tsx > ts && tsx > js ? 'React/TypeScript' : ts >= js ? 'TypeScript' : 'JavaScript';
|
|
2252
|
-
let content = `# ${project} [${lang}, ${projFiles.length} files, ${types.length} types]\n\n`;
|
|
2253
|
-
for (const dir of Object.keys(dirMap).sort()) {
|
|
2254
|
-
const names = dirMap[dir];
|
|
2255
|
-
const lastDir = dir.split('/').pop() || '';
|
|
2256
|
-
const desc = DIR_DESCRIPTIONS[lastDir];
|
|
2257
|
-
const descStr = desc ? ` -- ${desc}` : '';
|
|
2258
|
-
const shown = names.slice(0, 8).join(', ');
|
|
2259
|
-
const overflow = names.length > 8 ? `, ... (+${names.length - 8} more)` : '';
|
|
2260
|
-
content += ` ${dir}${descStr}: ${shown}${overflow}\n`;
|
|
2261
|
-
}
|
|
2262
|
-
allChunks.push({
|
|
2263
|
-
key: `project:${project}`,
|
|
2264
|
-
content: content.trim(),
|
|
2265
|
-
metadata: { kind: 'project-overview', project, language: lang, fileCount: projFiles.length, typeCount: types.length },
|
|
2266
|
-
tags: ['project', project],
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
// Directory details
|
|
2270
|
-
for (const [dir, types] of Object.entries(typesByDir)) {
|
|
2271
|
-
if (types.length < 2)
|
|
2272
|
-
continue;
|
|
2273
|
-
const lastDir = dir.split('/').pop() || '';
|
|
2274
|
-
const desc = DIR_DESCRIPTIONS[lastDir];
|
|
2275
|
-
let content = `# ${dir} (${types.length} types)\n`;
|
|
2276
|
-
if (desc)
|
|
2277
|
-
content += `${desc}\n`;
|
|
2278
|
-
content += '\n';
|
|
2279
|
-
const sortedTypes = [...types].sort((a, b) => a.name.localeCompare(b.name));
|
|
2280
|
-
for (const t of sortedTypes) {
|
|
2281
|
-
const suffix = [];
|
|
2282
|
-
if (t.bases)
|
|
2283
|
-
suffix.push(`: ${t.bases}`);
|
|
2284
|
-
if (t.implements)
|
|
2285
|
-
suffix.push(`: ${t.implements}`);
|
|
2286
|
-
const suffixStr = suffix.length ? ` ${suffix.join(' ')}` : '';
|
|
2287
|
-
const fileName = pathModule.basename(t.file);
|
|
2288
|
-
content += ` ${t.name}${suffixStr} (${fileName})\n`;
|
|
2289
|
-
}
|
|
2290
|
-
allChunks.push({
|
|
2291
|
-
key: `dir:${dir}`,
|
|
2292
|
-
content: content.trim(),
|
|
2293
|
-
metadata: { kind: 'directory-detail', directory: dir, typeCount: types.length },
|
|
2294
|
-
tags: ['directory', dir.split('/')[0]],
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
|
-
// Interface maps
|
|
2298
|
-
const interfaces = new Map();
|
|
2299
|
-
for (const t of allTypes) {
|
|
2300
|
-
if (t.kind === 'interface' && !interfaces.has(t.name)) {
|
|
2301
|
-
interfaces.set(t.name, { defined: t.file, implementations: [] });
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
for (const t of allTypes) {
|
|
2305
|
-
if (t.kind !== 'class')
|
|
2306
|
-
continue;
|
|
2307
|
-
const impls = t.implements ? t.implements.split(',').map((s) => s.trim()) : [];
|
|
2308
|
-
const bases = t.bases ? [t.bases.trim()] : [];
|
|
2309
|
-
for (const iface of [...impls, ...bases]) {
|
|
2310
|
-
if (interfaces.has(iface)) {
|
|
2311
|
-
interfaces.get(iface).implementations.push({ name: t.name, project: getProjectName(t.file) });
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
const mapped = Array.from(interfaces.entries())
|
|
2316
|
-
.filter(([, v]) => v.implementations.length > 0)
|
|
2317
|
-
.sort(([a], [b]) => a.localeCompare(b));
|
|
2318
|
-
if (mapped.length > 0) {
|
|
2319
|
-
const totalBatches = Math.ceil(mapped.length / IFACE_MAP_BATCH);
|
|
2320
|
-
for (let i = 0; i < mapped.length; i += IFACE_MAP_BATCH) {
|
|
2321
|
-
const batch = mapped.slice(i, i + IFACE_MAP_BATCH);
|
|
2322
|
-
const batchNum = Math.floor(i / IFACE_MAP_BATCH) + 1;
|
|
2323
|
-
let content = `# Interface-to-Implementation Map (${batchNum}/${totalBatches})\n\n`;
|
|
2324
|
-
for (const [name, info] of batch) {
|
|
2325
|
-
const implStr = info.implementations.map(impl => `${impl.name} (${impl.project})`).join(', ');
|
|
2326
|
-
content += ` ${name} -> ${implStr}\n`;
|
|
2327
|
-
}
|
|
2328
|
-
allChunks.push({
|
|
2329
|
-
key: `iface-map:${batchNum}`,
|
|
2330
|
-
content: content.trim(),
|
|
2331
|
-
metadata: { kind: 'interface-map', batch: batchNum, totalBatches, count: batch.length },
|
|
2332
|
-
tags: ['interface-map'],
|
|
2333
|
-
});
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
// Type index
|
|
2337
|
-
const sortedAllTypes = [...allTypes].sort((a, b) => a.name.localeCompare(b.name));
|
|
2338
|
-
const typeIdxTotalBatches = Math.ceil(sortedAllTypes.length / TYPE_INDEX_BATCH);
|
|
2339
|
-
for (let i = 0; i < sortedAllTypes.length; i += TYPE_INDEX_BATCH) {
|
|
2340
|
-
const batch = sortedAllTypes.slice(i, i + TYPE_INDEX_BATCH);
|
|
2341
|
-
const batchNum = Math.floor(i / TYPE_INDEX_BATCH) + 1;
|
|
2342
|
-
let content = `# Type Index (batch ${batchNum}, ${batch.length} types)\n\n`;
|
|
2343
|
-
for (const t of batch) {
|
|
2344
|
-
const ext = pathModule.extname(t.file);
|
|
2345
|
-
const lang = (ext === '.tsx' || ext === '.jsx') ? 'tsx' : ext === '.ts' ? 'ts' : ext === '.mjs' ? 'esm' : 'js';
|
|
2346
|
-
content += ` ${t.name} -> ${t.file} [${lang}]\n`;
|
|
2347
|
-
}
|
|
2348
|
-
allChunks.push({
|
|
2349
|
-
key: `type-index:${batchNum}`,
|
|
2350
|
-
content: content.trim(),
|
|
2351
|
-
metadata: { kind: 'type-index', batch: batchNum, totalBatches: typeIdxTotalBatches, count: batch.length },
|
|
2352
|
-
tags: ['type-index'],
|
|
2068
|
+
const scriptArgs = [];
|
|
2069
|
+
if (forceRegen)
|
|
2070
|
+
scriptArgs.push('--force');
|
|
2071
|
+
if (verbose)
|
|
2072
|
+
scriptArgs.push('--verbose');
|
|
2073
|
+
if (statsOnly)
|
|
2074
|
+
scriptArgs.push('--stats');
|
|
2075
|
+
if (skipEmbeddings)
|
|
2076
|
+
scriptArgs.push('--no-embeddings');
|
|
2077
|
+
const { execFileSync } = await import('child_process');
|
|
2078
|
+
try {
|
|
2079
|
+
// process.execPath (not bare 'node') so the child uses the exact same
|
|
2080
|
+
// interpreter with no PATH dependency — robust on Windows where the
|
|
2081
|
+
// launching node need not be on PATH (Rule #1). execFileSync passes argv
|
|
2082
|
+
// as an array, so spaces in the path (e.g. "Program Files") are safe.
|
|
2083
|
+
execFileSync(process.execPath, [script, ...scriptArgs], {
|
|
2084
|
+
cwd,
|
|
2085
|
+
stdio: 'inherit',
|
|
2086
|
+
windowsHide: true,
|
|
2087
|
+
timeout: 5 * 60_000,
|
|
2353
2088
|
});
|
|
2354
2089
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
const ifaceCount = allChunks.filter(c => c.metadata.kind === 'interface-map').length;
|
|
2360
|
-
const typeIdxCount = allChunks.filter(c => c.metadata.kind === 'type-index').length;
|
|
2361
|
-
output.writeln(` Project overviews: ${projectCount}`);
|
|
2362
|
-
output.writeln(` Directory details: ${dirCount}`);
|
|
2363
|
-
output.writeln(` Interface maps: ${ifaceCount}`);
|
|
2364
|
-
output.writeln(` Type index: ${typeIdxCount}`);
|
|
2365
|
-
}
|
|
2366
|
-
// 5. Write to DB
|
|
2367
|
-
output.writeln('Writing to memory database...');
|
|
2368
|
-
const { db, dbPath } = await openDb(cwd);
|
|
2369
|
-
// Clear old code-map entries
|
|
2370
|
-
db.run(`DELETE FROM memory_entries WHERE namespace = ?`, [NAMESPACE]);
|
|
2371
|
-
for (const chunk of allChunks) {
|
|
2372
|
-
batchStoreEntry(db, chunk.key, NAMESPACE, chunk.content, chunk.metadata, chunk.tags);
|
|
2373
|
-
}
|
|
2374
|
-
saveAndCloseDb(db, dbPath);
|
|
2375
|
-
// Save hash
|
|
2376
|
-
const hashDir = pathModule.dirname(hashCachePath);
|
|
2377
|
-
if (!fs.existsSync(hashDir)) {
|
|
2378
|
-
fs.mkdirSync(hashDir, { recursive: true });
|
|
2379
|
-
}
|
|
2380
|
-
fs.writeFileSync(hashCachePath, currentHash, 'utf-8');
|
|
2381
|
-
output.printSuccess(`${allChunks.length} chunks written to code-map namespace`);
|
|
2382
|
-
// Generate embeddings unless skipped
|
|
2383
|
-
if (!skipEmbeddings) {
|
|
2384
|
-
output.writeln(output.dim('Generating embeddings for code-map entries...'));
|
|
2385
|
-
try {
|
|
2386
|
-
const { generateEmbedding } = await import('../memory/memory-initializer.js');
|
|
2387
|
-
const { db: db2, dbPath: dbPath2 } = await openDb(cwd);
|
|
2388
|
-
const stmt = db2.prepare(`SELECT id, content FROM memory_entries WHERE namespace = ? AND (embedding IS NULL OR embedding = '')`);
|
|
2389
|
-
stmt.bind([NAMESPACE]);
|
|
2390
|
-
const entries = [];
|
|
2391
|
-
while (stmt.step())
|
|
2392
|
-
entries.push(stmt.getAsObject());
|
|
2393
|
-
stmt.free();
|
|
2394
|
-
let embedded = 0;
|
|
2395
|
-
for (const entry of entries) {
|
|
2396
|
-
try {
|
|
2397
|
-
const text = entry.content.substring(0, 1500);
|
|
2398
|
-
const { embedding, dimensions, model } = await generateEmbedding(text);
|
|
2399
|
-
db2.run(`UPDATE memory_entries SET embedding = ?, embedding_model = ?, embedding_dimensions = ?, updated_at = ? WHERE id = ?`, [JSON.stringify(embedding), model, dimensions, Date.now(), entry.id]);
|
|
2400
|
-
embedded++;
|
|
2401
|
-
}
|
|
2402
|
-
catch { /* skip */ }
|
|
2403
|
-
}
|
|
2404
|
-
if (embedded > 0) {
|
|
2405
|
-
saveAndCloseDb(db2, dbPath2);
|
|
2406
|
-
output.printSuccess(`Generated ${embedded} embeddings`);
|
|
2407
|
-
}
|
|
2408
|
-
else {
|
|
2409
|
-
db2.close();
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
catch (err) {
|
|
2413
|
-
output.writeln(output.dim(` Embedding generation skipped: ${err.message}`));
|
|
2414
|
-
}
|
|
2090
|
+
catch (err) {
|
|
2091
|
+
const code = err.status;
|
|
2092
|
+
output.printError(`Code map generation failed${typeof code === 'number' ? ` (exit ${code})` : ''}: ${errorDetail(err)}`);
|
|
2093
|
+
return { success: false, exitCode: typeof code === 'number' ? code : 1 };
|
|
2415
2094
|
}
|
|
2416
2095
|
return { success: true };
|
|
2417
2096
|
}
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.11.
|
|
3
|
+
"version": "4.11.6",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
95
95
|
"@typescript-eslint/parser": "^7.18.0",
|
|
96
96
|
"eslint": "^8.0.0",
|
|
97
|
-
"moflo": "^4.11.
|
|
97
|
+
"moflo": "^4.11.5",
|
|
98
98
|
"tsx": "^4.21.0",
|
|
99
99
|
"typescript": "^5.9.3",
|
|
100
100
|
"vitest": "^4.0.0"
|