moflo 4.10.11 → 4.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/guidance/shipped/moflo-core-guidance.md +16 -0
- package/.claude/guidance/shipped/moflo-memory-protocol.md +171 -11
- package/.claude/helpers/gate.cjs +139 -14
- package/.claude/skills/publish/SKILL.md +46 -8
- package/bin/gate.cjs +139 -14
- package/bin/lib/moflo-paths.mjs +74 -4
- package/bin/session-start-launcher.mjs +173 -5
- package/dist/src/cli/commands/doctor-checks-config.js +141 -3
- package/dist/src/cli/commands/doctor-fixes.js +202 -10
- package/dist/src/cli/commands/doctor-registry.js +9 -1
- package/dist/src/cli/commands/init.js +33 -0
- package/dist/src/cli/commands/memory.js +11 -4
- package/dist/src/cli/commands/swarm.js +29 -60
- package/dist/src/cli/init/claudemd-generator.js +6 -2
- package/dist/src/cli/init/helpers-generator.js +23 -3
- package/dist/src/cli/init/moflo-init.js +4 -2
- package/dist/src/cli/init/settings-generator.js +9 -3
- package/dist/src/cli/mcp-server.js +104 -2
- package/dist/src/cli/memory/ewc-consolidation.js +22 -6
- package/dist/src/cli/memory/sona-optimizer.js +25 -7
- package/dist/src/cli/movector/lora-adapter.js +22 -7
- package/dist/src/cli/movector/moe-router.js +22 -6
- package/dist/src/cli/services/hook-block-hash.js +5 -2
- package/dist/src/cli/services/hook-wiring.js +38 -4
- package/dist/src/cli/services/moflo-paths.js +36 -0
- package/dist/src/cli/services/project-root.js +84 -25
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { homedir } from 'node:os';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
+
import { findProjectRoot } from './project-root.js';
|
|
24
25
|
export const MOFLO_DIR = '.moflo';
|
|
25
26
|
/** Canonical memory DB filename (post-#727). Lives at `<root>/.moflo/moflo.db`. */
|
|
26
27
|
export const MEMORY_DB_FILE = 'moflo.db';
|
|
@@ -68,6 +69,25 @@ export function legacyHnswIndexPath(projectRoot) {
|
|
|
68
69
|
export function legacyMemoryDbBakPath(projectRoot) {
|
|
69
70
|
return join(projectRoot, LEGACY_SWARM_DIR, `${LEGACY_MEMORY_DB_FILE}${LEGACY_MEMORY_DB_BAK_SUFFIX}`);
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a runtime-state path under `.moflo/<subdir>/<filename>` at the
|
|
74
|
+
* project root. Lazy by design — every call routes through `findProjectRoot()`
|
|
75
|
+
* so the path never bakes in `process.cwd()` at module-load time (#1168 bug
|
|
76
|
+
* class). Use this for any persisted runtime state the daemon, MCP server,
|
|
77
|
+
* or neural runtime writes (weights, fisher matrix, routing patterns, etc.).
|
|
78
|
+
*/
|
|
79
|
+
export function runtimePath(subdir, filename) {
|
|
80
|
+
return join(mofloDir(findProjectRoot()), subdir, filename);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Legacy `.swarm/<filename>` path at the project root. Read-only fallback for
|
|
84
|
+
* consumers who saved state under the pre-#1168 location; never written by
|
|
85
|
+
* production code (enforced by the drift-guard in
|
|
86
|
+
* `published-package-drift-guard.test.ts`).
|
|
87
|
+
*/
|
|
88
|
+
export function legacySwarmPath(filename) {
|
|
89
|
+
return join(findProjectRoot(), LEGACY_SWARM_DIR, filename);
|
|
90
|
+
}
|
|
71
91
|
/**
|
|
72
92
|
* Memory-DB probe order used by every reader that does best-effort detection
|
|
73
93
|
* (statusline, doctor, swarm status, hooks aggregator). Canonical first so
|
|
@@ -84,4 +104,20 @@ export function memoryDbCandidatePaths(projectRoot) {
|
|
|
84
104
|
join(projectRoot, '.claude', LEGACY_MEMORY_DB_FILE),
|
|
85
105
|
];
|
|
86
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Common skip-list for any walk that enumerates a project's children looking
|
|
109
|
+
* for moflo state. Shared by `bin/session-start-launcher.mjs` (depth-1 walk)
|
|
110
|
+
* and `doctor-checks-config.ts` (depth-5 BFS) so the two can't silently
|
|
111
|
+
* diverge.
|
|
112
|
+
*
|
|
113
|
+
* Twin of `bin/lib/moflo-paths.mjs:COMMON_WALK_SKIP_NAMES`. Matched
|
|
114
|
+
* case-insensitively at every call site — Windows NTFS + macOS APFS are
|
|
115
|
+
* case-insensitive by default.
|
|
116
|
+
*/
|
|
117
|
+
export const COMMON_WALK_SKIP_NAMES = new Set([
|
|
118
|
+
'node_modules', '.git', '.svn', '.hg',
|
|
119
|
+
'dist', 'build', 'out', 'target', '.next', '.nuxt', '.cache',
|
|
120
|
+
'coverage', '.idea', '.vscode', '.turbo', '.svelte-kit',
|
|
121
|
+
'vendor', '__pycache__', '.venv', 'venv', '.tox',
|
|
122
|
+
]);
|
|
87
123
|
//# sourceMappingURL=moflo-paths.js.map
|
|
@@ -8,32 +8,73 @@
|
|
|
8
8
|
* resolve through this single algorithm or its JS twin — otherwise different
|
|
9
9
|
* writers land on different DBs and the bridge reads stale data.
|
|
10
10
|
*
|
|
11
|
-
* Algorithm (#1057) —
|
|
11
|
+
* Algorithm (#1057, #1174) — three-pass walk so memory markers always win
|
|
12
|
+
* across the ENTIRE ancestor chain (not just at the first level they appear):
|
|
12
13
|
* 1. `process.env.CLAUDE_PROJECT_DIR`, if set (Claude Code / explicit override).
|
|
13
|
-
* 2. **
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
14
|
+
* 2. **Pass A — memory markers (topmost wins).** Walk from
|
|
15
|
+
* `opts.cwd ?? process.cwd()` up to the filesystem root, collecting EVERY
|
|
16
|
+
* level that has `.moflo/moflo.db` OR `.swarm/memory.db`. Return the
|
|
17
|
+
* topmost (highest ancestor) match. This is the #1174 fix — pre-#1174 the
|
|
18
|
+
* walk stopped at the nearest hit, fragmenting monorepos into daemon
|
|
19
|
+
* islands.
|
|
20
|
+
* 3. **Pass B — project marker pair (nearest wins).** Only reached when no
|
|
21
|
+
* moflo state exists anywhere up the tree. Walk again looking for
|
|
22
|
+
* `<dir>/CLAUDE.md` AND `<dir>/package.json` at the same level; return
|
|
23
|
+
* the nearest match.
|
|
24
|
+
* 4. **Pass C — bare project markers (nearest wins).** Walk again looking
|
|
25
|
+
* for `<dir>/package.json` OR `<dir>/.git`; return the nearest match.
|
|
26
|
+
* 5. Fall back to `opts.cwd ?? process.cwd()`.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
*
|
|
28
|
+
* `node_modules` segments are always skipped (npx run can land cwd inside one).
|
|
29
|
+
*
|
|
30
|
+
* Why topmost (Pass A)? When a monorepo has nested `.moflo/moflo.db` directories
|
|
31
|
+
* — typically because `flo init` was run from a subworkspace before #1174 — the
|
|
32
|
+
* MCP server, daemon, CLI, and gate hooks ALL must agree on a single anchor.
|
|
33
|
+
* Topmost wins means the root daemon is canonical; sub-daemons become
|
|
34
|
+
* detectable residue that `flo doctor --fix` archives. Nearest-wins fragments
|
|
35
|
+
* state silently because every cwd resolves to a different anchor.
|
|
36
|
+
*
|
|
37
|
+
* Why nearest (Pass B/C)? Pass B/C only fires when there's no moflo state at
|
|
38
|
+
* all. In a fresh checkout the user expects `flo init` to anchor at the
|
|
39
|
+
* project they're in, not at some ancestor `.git`/`package.json` directory.
|
|
31
40
|
*
|
|
32
41
|
* Story #229 history: this function was first extracted from workflow-tools.ts;
|
|
33
|
-
* #1057 brought it into alignment with bridge-core.getProjectRoot()
|
|
42
|
+
* #1057 brought it into alignment with bridge-core.getProjectRoot(); #1174
|
|
43
|
+
* changed Pass A from nearest-wins to topmost-wins to fix monorepo daemon
|
|
44
|
+
* fragmentation.
|
|
34
45
|
*/
|
|
35
46
|
import { existsSync } from 'node:fs';
|
|
36
47
|
import { resolve, dirname, parse, join, basename } from 'node:path';
|
|
48
|
+
/**
|
|
49
|
+
* Walk strictly upward from `dir` (exclusive) and return the nearest ancestor
|
|
50
|
+
* that has `.moflo/moflo.db`, or `null` if none exists below the filesystem
|
|
51
|
+
* root.
|
|
52
|
+
*
|
|
53
|
+
* Used by `flo init` and the session-start launcher to detect nested-.moflo
|
|
54
|
+
* situations (#1174). Post-resolver-fix `findProjectRoot` returns the topmost
|
|
55
|
+
* memory marker, so encountering an ancestor here means either:
|
|
56
|
+
* 1. `CLAUDE_PROJECT_DIR` explicitly overrode to a sub-directory
|
|
57
|
+
* (legitimate user action — log a warning but don't refuse), or
|
|
58
|
+
* 2. The caller is operating on a directory that's about to become a new
|
|
59
|
+
* nested .moflo/ island (e.g. `flo init` in a sub-workspace).
|
|
60
|
+
*
|
|
61
|
+
* Algorithmic twin of `bin/lib/moflo-paths.mjs:findAncestorMofloRoot()`.
|
|
62
|
+
*/
|
|
63
|
+
export function findAncestorMofloRoot(dir) {
|
|
64
|
+
const start = resolve(dir);
|
|
65
|
+
const fsRoot = parse(start).root;
|
|
66
|
+
let cursor = dirname(start);
|
|
67
|
+
while (cursor !== fsRoot) {
|
|
68
|
+
if (existsSync(join(cursor, '.moflo', 'moflo.db'))) {
|
|
69
|
+
return cursor;
|
|
70
|
+
}
|
|
71
|
+
const parent = dirname(cursor);
|
|
72
|
+
if (parent === cursor)
|
|
73
|
+
break;
|
|
74
|
+
cursor = parent;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
37
78
|
export function findProjectRoot(opts) {
|
|
38
79
|
const honorEnv = opts?.honorEnv !== false;
|
|
39
80
|
if (honorEnv && process.env.CLAUDE_PROJECT_DIR) {
|
|
@@ -42,17 +83,35 @@ export function findProjectRoot(opts) {
|
|
|
42
83
|
const startDir = opts?.cwd ?? process.cwd();
|
|
43
84
|
const start = resolve(startDir);
|
|
44
85
|
const fsRoot = parse(start).root;
|
|
45
|
-
//
|
|
86
|
+
// Pass A — memory markers, topmost wins (#1174).
|
|
87
|
+
// Collect every ancestor with `.moflo/moflo.db` or `.swarm/memory.db`, then
|
|
88
|
+
// return the highest one. Guarantees the root daemon is canonical in a
|
|
89
|
+
// monorepo with nested .moflo/ residue.
|
|
90
|
+
let topmostMemoryMarker = null;
|
|
46
91
|
let dir = start;
|
|
47
92
|
while (dir !== fsRoot) {
|
|
48
93
|
if (basename(dir) === 'node_modules') {
|
|
49
94
|
dir = dirname(dir);
|
|
50
95
|
continue;
|
|
51
96
|
}
|
|
52
|
-
if (existsSync(join(dir, '.moflo', 'moflo.db')))
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
if (existsSync(join(dir, '.moflo', 'moflo.db')) || existsSync(join(dir, '.swarm', 'memory.db'))) {
|
|
98
|
+
topmostMemoryMarker = dir;
|
|
99
|
+
}
|
|
100
|
+
const parent = dirname(dir);
|
|
101
|
+
if (parent === dir)
|
|
102
|
+
break;
|
|
103
|
+
dir = parent;
|
|
104
|
+
}
|
|
105
|
+
if (topmostMemoryMarker)
|
|
106
|
+
return topmostMemoryMarker;
|
|
107
|
+
// Pass B — project marker pair, nearest wins. Only reached when no moflo
|
|
108
|
+
// state exists anywhere up the tree.
|
|
109
|
+
dir = start;
|
|
110
|
+
while (dir !== fsRoot) {
|
|
111
|
+
if (basename(dir) === 'node_modules') {
|
|
112
|
+
dir = dirname(dir);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
56
115
|
if (existsSync(join(dir, 'CLAUDE.md')) && existsSync(join(dir, 'package.json'))) {
|
|
57
116
|
return dir;
|
|
58
117
|
}
|
|
@@ -61,7 +120,7 @@ export function findProjectRoot(opts) {
|
|
|
61
120
|
break;
|
|
62
121
|
dir = parent;
|
|
63
122
|
}
|
|
64
|
-
//
|
|
123
|
+
// Pass C — bare package.json or .git, nearest wins.
|
|
65
124
|
dir = start;
|
|
66
125
|
while (dir !== fsRoot) {
|
|
67
126
|
if (basename(dir) === 'node_modules') {
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.13",
|
|
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",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
96
96
|
"@typescript-eslint/parser": "^7.18.0",
|
|
97
97
|
"eslint": "^8.0.0",
|
|
98
|
-
"moflo": "^4.10.
|
|
98
|
+
"moflo": "^4.10.12",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|