moflo 4.9.0-rc.8 → 4.9.0
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/helpers/auto-memory-hook.mjs +1 -1
- package/.claude/helpers/gate-hook.mjs +7 -0
- package/.claude/helpers/gate.cjs +100 -16
- package/.claude/helpers/intelligence.cjs +3 -3
- package/.claude/helpers/statusline.cjs +73 -50
- package/.claude/helpers/subagent-bootstrap.json +3 -0
- package/.claude/helpers/subagent-start.cjs +58 -22
- package/.claude/skills/fl/SKILL.md +86 -181
- package/.claude/skills/fl/epic.md +23 -33
- package/.claude/skills/fl/execution-modes.md +17 -17
- package/.claude/skills/fl/phases.md +48 -65
- package/.claude/skills/fl/ticket.md +46 -51
- package/.claude/skills/hive-mind-advanced/SKILL.md +4 -4
- package/.claude/skills/memory-patterns/SKILL.md +3 -3
- package/.claude/skills/swarm-advanced/SKILL.md +77 -0
- package/.claude/skills/vector-search/SKILL.md +1 -1
- package/README.md +68 -93
- package/bin/build-embeddings.mjs +17 -10
- package/bin/gate-hook.mjs +7 -0
- package/bin/gate.cjs +100 -16
- package/bin/index-all.mjs +20 -2
- package/bin/lib/moflo-paths.mjs +53 -4
- package/bin/migrations/knowledge-purge.mjs +107 -0
- package/bin/migrations/knowledge-to-learnings.mjs +27 -15
- package/bin/migrations/lib/markers.mjs +11 -0
- package/bin/run-migrations.mjs +13 -2
- package/bin/session-start-launcher.mjs +150 -37
- package/dist/src/cli/commands/analyze.js +10 -9
- package/dist/src/cli/commands/appliance-advanced.js +9 -11
- package/dist/src/cli/commands/appliance.js +7 -9
- package/dist/src/cli/commands/benchmark.js +4 -3
- package/dist/src/cli/commands/daemon.js +21 -25
- package/dist/src/cli/commands/deployment.js +1 -1
- package/dist/src/cli/commands/diagnose.js +10 -9
- package/dist/src/cli/commands/doctor-checks-deep.js +40 -21
- package/dist/src/cli/commands/doctor-checks-functional-shared.js +111 -0
- package/dist/src/cli/commands/doctor-checks-memory-access.js +307 -0
- package/dist/src/cli/commands/doctor-checks-swarm.js +364 -0
- package/dist/src/cli/commands/doctor.js +191 -56
- package/dist/src/cli/commands/embeddings.js +32 -52
- package/dist/src/cli/commands/epic.js +90 -20
- package/dist/src/cli/commands/github.js +12 -11
- package/dist/src/cli/commands/guidance.js +14 -13
- package/dist/src/cli/commands/hive-mind.js +10 -9
- package/dist/src/cli/commands/hooks.js +14 -13
- package/dist/src/cli/commands/init.js +11 -10
- package/dist/src/cli/commands/issues.js +4 -3
- package/dist/src/cli/commands/memory.js +47 -8
- package/dist/src/cli/commands/neural.js +13 -12
- package/dist/src/cli/commands/process.js +2 -2
- package/dist/src/cli/commands/progress.js +2 -1
- package/dist/src/cli/commands/route.js +10 -9
- package/dist/src/cli/commands/session.js +7 -7
- package/dist/src/cli/commands/spell-schedule.js +10 -57
- package/dist/src/cli/commands/start.js +3 -2
- package/dist/src/cli/commands/status.js +4 -4
- package/dist/src/cli/commands/task.js +1 -1
- package/dist/src/cli/commands/update.js +4 -4
- package/dist/src/cli/config/moflo-config.js +0 -3
- package/dist/src/cli/embeddings/fastembed-embedding-service.js +4 -3
- package/dist/src/cli/embeddings/fastembed-inline/model-loader.js +27 -5
- package/dist/src/cli/embeddings/index.js +1 -1
- package/dist/src/cli/embeddings/migration/migrate-store.js +3 -2
- package/dist/src/cli/embeddings/neural-integration.js +0 -9
- package/dist/src/cli/epic/index.js +1 -2
- package/dist/src/cli/epic/types.js +1 -6
- package/dist/src/cli/guidance/hooks.js +3 -2
- package/dist/src/cli/hooks/daemons/index.js +3 -2
- package/dist/src/cli/hooks/executor/index.js +2 -1
- package/dist/src/cli/hooks/workers/index.js +2 -1
- package/dist/src/cli/hooks/workers/session-hook.js +2 -1
- package/dist/src/cli/index.js +17 -8
- package/dist/src/cli/init/executor.js +116 -89
- package/dist/src/cli/init/helpers-generator.js +92 -15
- package/dist/src/cli/init/moflo-init.js +66 -56
- package/dist/src/cli/init/settings-generator.js +12 -2
- package/dist/src/cli/mcp-client.js +2 -1
- package/dist/src/cli/mcp-server.js +2 -1
- package/dist/src/cli/mcp-tools/agent-tools.js +332 -211
- package/dist/src/cli/mcp-tools/coordinator-views.js +23 -0
- package/dist/src/cli/mcp-tools/github-tools.js +2 -1
- package/dist/src/cli/mcp-tools/hive-mind-tools.js +145 -49
- package/dist/src/cli/mcp-tools/hooks-tools.js +8 -6
- package/dist/src/cli/mcp-tools/json-store.js +9 -6
- package/dist/src/cli/mcp-tools/memory-tools.js +4 -1
- package/dist/src/cli/mcp-tools/neural-tools.js +4 -2
- package/dist/src/cli/mcp-tools/session-tools.js +11 -7
- package/dist/src/cli/mcp-tools/spell-tools.js +4 -7
- package/dist/src/cli/mcp-tools/swarm-coordinator-singleton.js +119 -0
- package/dist/src/cli/mcp-tools/swarm-scale-handler.js +211 -0
- package/dist/src/cli/mcp-tools/swarm-tools.js +208 -27
- package/dist/src/cli/mcp-tools/task-tools.js +299 -166
- package/dist/src/cli/memory/bridge-core.js +2 -1
- package/dist/src/cli/memory/bridge-embedder.js +2 -1
- package/dist/src/cli/memory/bridge-entries.js +25 -14
- package/dist/src/cli/memory/controller-registry.js +3 -2
- package/dist/src/cli/memory/controllers/_shared.js +20 -0
- package/dist/src/cli/memory/controllers/nightly-learner.js +2 -1
- package/dist/src/cli/memory/ewc-consolidation.js +2 -1
- package/dist/src/cli/memory/hnsw-lite.js +142 -1
- package/dist/src/cli/memory/hnsw-persistence.js +101 -0
- package/dist/src/cli/memory/index.js +1 -0
- package/dist/src/cli/memory/intelligence.js +2 -1
- package/dist/src/cli/memory/memory-bridge.js +3 -1
- package/dist/src/cli/memory/memory-initializer.js +51 -65
- package/dist/src/cli/parser.js +27 -40
- package/dist/src/cli/plugins/manager.js +5 -4
- package/dist/src/cli/runtime/headless.js +2 -1
- package/dist/src/cli/services/daemon-dashboard.js +4 -3
- package/dist/src/cli/services/daemon-readiness.js +5 -8
- package/dist/src/cli/services/daemon-service.js +12 -7
- package/dist/src/cli/services/daemon-spell-executor.js +3 -2
- package/dist/src/cli/services/headless-worker-executor.js +2 -1
- package/dist/src/cli/services/hook-wiring.js +6 -0
- package/dist/src/cli/services/index.js +2 -0
- package/dist/src/cli/services/moflo-paths.js +72 -6
- package/dist/src/cli/services/moflo-require.js +3 -0
- package/dist/src/cli/services/movector-training.js +9 -16
- package/dist/src/cli/services/spell-gate.js +1 -1
- package/dist/src/cli/services/subagent-bootstrap.js +57 -0
- package/dist/src/cli/services/worker-daemon.js +5 -4
- package/dist/src/cli/services/worker-queue.js +4 -3
- package/dist/src/cli/shared/mcp/tool-registry.js +2 -1
- package/dist/src/cli/shared/plugins/official/maestro-plugin.js +3 -2
- package/dist/src/cli/shared/utils/error-detail.js +18 -0
- package/dist/src/cli/spells/commands/composite-command.js +2 -1
- package/dist/src/cli/spells/commands/github-command.js +5 -5
- package/dist/src/cli/spells/connectors/github-cli.js +32 -35
- package/dist/src/cli/spells/connectors/http-tool.js +2 -1
- package/dist/src/cli/spells/connectors/imap.js +2 -1
- package/dist/src/cli/spells/connectors/slack.js +2 -1
- package/dist/src/cli/spells/core/connector-accessor.js +2 -1
- package/dist/src/cli/spells/core/dry-run-validator.js +4 -2
- package/dist/src/cli/spells/core/gated-connector-accessor.js +2 -1
- package/dist/src/cli/spells/core/interpolation.js +7 -0
- package/dist/src/cli/spells/core/platform-sandbox.js +80 -59
- package/dist/src/cli/spells/core/prerequisite-checker.js +8 -3
- package/dist/src/cli/spells/core/rollback-orchestrator.js +2 -1
- package/dist/src/cli/spells/core/runner.js +19 -5
- package/dist/src/cli/spells/core/shell.js +19 -1
- package/dist/src/cli/spells/core/step-executor.js +4 -3
- package/dist/src/cli/spells/factory/runner-factory.js +2 -1
- package/dist/src/cli/spells/loaders/definition-loader.js +2 -1
- package/dist/src/cli/spells/loaders/directory-step-loader.js +2 -1
- package/dist/src/cli/spells/loaders/npm-step-loader.js +2 -1
- package/dist/src/cli/spells/registry/connector-registry.js +3 -2
- package/dist/src/cli/spells/scheduler/scheduler.js +2 -1
- package/dist/src/cli/swarm/swarm-persistence.js +144 -0
- package/dist/src/cli/swarm/unified-coordinator.js +260 -66
- package/dist/src/cli/transfer/ipfs/client.js +3 -2
- package/dist/src/cli/transfer/serialization/cfp.js +2 -1
- package/dist/src/cli/version.js +1 -1
- package/package.json +6 -5
- package/dist/src/cli/epic/execution-order.js +0 -58
- /package/{dist/src/cli/epic/spells/auto-merge.yaml → src/cli/spells/definitions/epic-auto-merge.yaml} +0 -0
- /package/{dist/src/cli/epic/spells/single-branch.yaml → src/cli/spells/definitions/epic-single-branch.yaml} +0 -0
|
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'url';
|
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = dirname(__filename);
|
|
18
18
|
const PROJECT_ROOT = join(__dirname, '../..');
|
|
19
|
-
const DATA_DIR = join(PROJECT_ROOT, '.
|
|
19
|
+
const DATA_DIR = join(PROJECT_ROOT, '.moflo', 'data');
|
|
20
20
|
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
21
21
|
|
|
22
22
|
const DIM = '\x1b[2m';
|
|
@@ -25,6 +25,13 @@ try { if (stdinData.trim()) hookContext = JSON.parse(stdinData); } catch (e) {}
|
|
|
25
25
|
// Pass tool info as env vars for gate.cjs
|
|
26
26
|
var env = Object.assign({}, process.env);
|
|
27
27
|
if (hookContext.tool_name) env.TOOL_NAME = hookContext.tool_name;
|
|
28
|
+
// Forward Claude Code's session_id so gate.cjs can enforce memory-first
|
|
29
|
+
// per-actor (#838) — each spawned subagent gets its own session_id, so a
|
|
30
|
+
// shared workflow-state.json no longer lets one subagent's directive be
|
|
31
|
+
// silently satisfied by the parent's earlier search.
|
|
32
|
+
if (typeof hookContext.session_id === 'string' && hookContext.session_id) {
|
|
33
|
+
env.HOOK_SESSION_ID = hookContext.session_id;
|
|
34
|
+
}
|
|
28
35
|
if (hookContext.tool_input && typeof hookContext.tool_input === 'object') {
|
|
29
36
|
Object.keys(hookContext.tool_input).forEach(function(key) {
|
|
30
37
|
if (typeof hookContext.tool_input[key] === 'string') {
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -6,7 +6,37 @@ var path = require('path');
|
|
|
6
6
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
7
7
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
8
8
|
|
|
9
|
-
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, learningsStored: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
9
|
+
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
10
|
+
|
|
11
|
+
// Per-actor memory-search tracking (#838). The legacy `memorySearched` boolean
|
|
12
|
+
// is session-wide, so once the parent searches memory, every spawned subagent
|
|
13
|
+
// inherits the satisfied flag and the directive's "WILL BLOCK" promise becomes
|
|
14
|
+
// false. When gate-hook.mjs forwards Claude Code's stdin `session_id` as
|
|
15
|
+
// HOOK_SESSION_ID, prefer the per-session map so each subagent must search
|
|
16
|
+
// memory itself before its first Glob/Grep/Read. Falls back to the legacy
|
|
17
|
+
// boolean when no session id is present (CLI invocations, tests, older hosts).
|
|
18
|
+
function isMemorySearchedFor(state) {
|
|
19
|
+
var sid = process.env.HOOK_SESSION_ID || '';
|
|
20
|
+
if (sid) {
|
|
21
|
+
var map = state.memorySearchedBy || {};
|
|
22
|
+
return map[sid] === true;
|
|
23
|
+
}
|
|
24
|
+
return state.memorySearched === true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Stamp the legacy bool plus (when HOOK_SESSION_ID is set) the per-actor map.
|
|
28
|
+
// Returns true if anything actually changed — callers gate writeState() on it
|
|
29
|
+
// to avoid redundant fsyncs in tight bash-memory loops.
|
|
30
|
+
function markMemorySearched(state) {
|
|
31
|
+
var sid = process.env.HOOK_SESSION_ID || '';
|
|
32
|
+
var changed = false;
|
|
33
|
+
if (state.memorySearched !== true) { state.memorySearched = true; changed = true; }
|
|
34
|
+
if (sid) {
|
|
35
|
+
if (!state.memorySearchedBy) state.memorySearchedBy = {};
|
|
36
|
+
if (state.memorySearchedBy[sid] !== true) { state.memorySearchedBy[sid] = true; changed = true; }
|
|
37
|
+
}
|
|
38
|
+
return changed;
|
|
39
|
+
}
|
|
10
40
|
|
|
11
41
|
function readState() {
|
|
12
42
|
try {
|
|
@@ -29,7 +59,7 @@ function writeState(s) {
|
|
|
29
59
|
|
|
30
60
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
31
61
|
function loadGateConfig() {
|
|
32
|
-
var defaults = { memory_first: true, task_create_first: true, context_tracking: true };
|
|
62
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true };
|
|
33
63
|
try {
|
|
34
64
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
35
65
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -37,6 +67,9 @@ function loadGateConfig() {
|
|
|
37
67
|
if (/memory_first:\s*false/i.test(content)) defaults.memory_first = false;
|
|
38
68
|
if (/task_create_first:\s*false/i.test(content)) defaults.task_create_first = false;
|
|
39
69
|
if (/context_tracking:\s*false/i.test(content)) defaults.context_tracking = false;
|
|
70
|
+
if (/testing_gate:\s*false/i.test(content)) defaults.testing_gate = false;
|
|
71
|
+
if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
72
|
+
if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
40
73
|
}
|
|
41
74
|
} catch (e) { /* use defaults */ }
|
|
42
75
|
return defaults;
|
|
@@ -49,6 +82,13 @@ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state
|
|
|
49
82
|
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
50
83
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
51
84
|
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
85
|
+
// Match npm/yarn/pnpm/bun test, npx vitest|jest|..., bare runners at command-start only,
|
|
86
|
+
// and language-native test commands. The bare-runner arm is anchored so that
|
|
87
|
+
// `npm install jest`, `grep -r vitest src/`, and similar don't false-positive.
|
|
88
|
+
var TEST_RUNNER_RE = /(?:^|[^a-z])(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(?:test|t)(?:[:\s]|$)|\b(?:npx|pnpx)\s+(?:vitest|jest|mocha|ava|tap|jasmine|pytest)\b|(?:^|;|&&|\|\|)\s*(?:vitest|jest|pytest|mocha|jasmine|tap|ava)\s|\b(?:cargo|go|deno|dotnet|mvn)\s+test\b|\bgradle\w*\s+test\b/i;
|
|
89
|
+
// Edits to these don't change runtime behaviour, so they don't invalidate prior test/simplify runs.
|
|
90
|
+
// Lock files and .gitignore are tracked but inert; package.json/*.yaml ARE source — they reset.
|
|
91
|
+
var EDIT_RESET_SKIP_RE = /\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^|[\\\/])(CHANGELOG(?:\.md)?|\.env\.example|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb)$/i;
|
|
52
92
|
|
|
53
93
|
switch (command) {
|
|
54
94
|
case 'check-before-agent': {
|
|
@@ -67,7 +107,7 @@ switch (command) {
|
|
|
67
107
|
case 'check-before-scan': {
|
|
68
108
|
if (!config.memory_first) break;
|
|
69
109
|
var s = readState();
|
|
70
|
-
if (s.
|
|
110
|
+
if (!s.memoryRequired || isMemorySearchedFor(s)) break;
|
|
71
111
|
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
72
112
|
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
73
113
|
process.stderr.write('BLOCKED: Search memory before exploring files. Use mcp__moflo__memory_search.\n');
|
|
@@ -76,7 +116,7 @@ switch (command) {
|
|
|
76
116
|
case 'check-before-read': {
|
|
77
117
|
if (!config.memory_first) break;
|
|
78
118
|
var s = readState();
|
|
79
|
-
if (s.
|
|
119
|
+
if (!s.memoryRequired || isMemorySearchedFor(s)) break;
|
|
80
120
|
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
81
121
|
var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
|
|
82
122
|
if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
|
|
@@ -92,16 +132,14 @@ switch (command) {
|
|
|
92
132
|
}
|
|
93
133
|
case 'record-memory-searched': {
|
|
94
134
|
var s = readState();
|
|
95
|
-
s
|
|
96
|
-
writeState(s);
|
|
135
|
+
if (markMemorySearched(s)) writeState(s);
|
|
97
136
|
break;
|
|
98
137
|
}
|
|
99
138
|
case 'check-bash-memory': {
|
|
100
139
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
101
140
|
if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
|
|
102
141
|
var s = readState();
|
|
103
|
-
s
|
|
104
|
-
writeState(s);
|
|
142
|
+
if (markMemorySearched(s)) writeState(s);
|
|
105
143
|
}
|
|
106
144
|
break;
|
|
107
145
|
}
|
|
@@ -113,21 +151,64 @@ switch (command) {
|
|
|
113
151
|
}
|
|
114
152
|
case 'record-learnings-stored': {
|
|
115
153
|
var s = readState();
|
|
116
|
-
s.learningsStored
|
|
117
|
-
|
|
154
|
+
if (!s.learningsStored) {
|
|
155
|
+
s.learningsStored = true;
|
|
156
|
+
writeState(s);
|
|
157
|
+
}
|
|
118
158
|
break;
|
|
119
159
|
}
|
|
120
|
-
case '
|
|
160
|
+
case 'record-test-run': {
|
|
121
161
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
122
|
-
if (
|
|
162
|
+
if (TEST_RUNNER_RE.test(cmd)) {
|
|
163
|
+
var s = readState();
|
|
164
|
+
if (!s.testsRun) {
|
|
165
|
+
s.testsRun = true;
|
|
166
|
+
writeState(s);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'record-skill-run': {
|
|
172
|
+
if ((process.env.TOOL_INPUT_skill || '') === 'simplify') {
|
|
123
173
|
var s = readState();
|
|
124
|
-
if (!s.
|
|
125
|
-
|
|
126
|
-
|
|
174
|
+
if (!s.simplifyRun) {
|
|
175
|
+
s.simplifyRun = true;
|
|
176
|
+
writeState(s);
|
|
127
177
|
}
|
|
128
178
|
}
|
|
129
179
|
break;
|
|
130
180
|
}
|
|
181
|
+
case 'reset-edit-gates': {
|
|
182
|
+
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
183
|
+
if (fp && EDIT_RESET_SKIP_RE.test(fp)) break;
|
|
184
|
+
var s = readState();
|
|
185
|
+
if (!s.testsRun && !s.simplifyRun) break;
|
|
186
|
+
s.testsRun = false;
|
|
187
|
+
s.simplifyRun = false;
|
|
188
|
+
writeState(s);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'check-before-pr': {
|
|
192
|
+
// Anchored to command-start (or chained via && / || / ;) so heredoc bodies
|
|
193
|
+
// and quoted strings that contain the literal "gh pr create" don't trip
|
|
194
|
+
// the gate during regular `git commit -m "...gh pr create..."` flows. The
|
|
195
|
+
// optional ENV=val prefix segment catches `GH_TOKEN=x gh pr create`.
|
|
196
|
+
var cmd = process.env.TOOL_INPUT_command || '';
|
|
197
|
+
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
|
198
|
+
var s = readState();
|
|
199
|
+
var missing = [];
|
|
200
|
+
if (config.testing_gate && !s.testsRun) missing.push('tests have not run since the last code edit (run npm test, vitest, jest, pytest, or similar)');
|
|
201
|
+
if (config.simplify_gate && !s.simplifyRun) missing.push('/simplify has not run since the last code edit');
|
|
202
|
+
if (config.learnings_gate && !s.learningsStored) missing.push('learnings have not been stored (call mcp__moflo__memory_store)');
|
|
203
|
+
if (missing.length === 0) break;
|
|
204
|
+
process.stderr.write('BLOCKED: gh pr create requires the following before opening a PR:\n');
|
|
205
|
+
for (var i = 0; i < missing.length; i++) {
|
|
206
|
+
process.stderr.write(' - ' + missing[i] + '\n');
|
|
207
|
+
}
|
|
208
|
+
process.stderr.write('Disable per-gate via moflo.yaml:\n');
|
|
209
|
+
process.stderr.write(' gates:\n testing_gate: false\n simplify_gate: false\n learnings_gate: false\n');
|
|
210
|
+
process.exit(2);
|
|
211
|
+
}
|
|
131
212
|
case 'check-dangerous-command': {
|
|
132
213
|
var cmd = (process.env.TOOL_INPUT_command || '').toLowerCase();
|
|
133
214
|
for (var i = 0; i < DANGEROUS.length; i++) {
|
|
@@ -141,6 +222,9 @@ switch (command) {
|
|
|
141
222
|
case 'prompt-reminder': {
|
|
142
223
|
var s = readState();
|
|
143
224
|
s.memorySearched = false;
|
|
225
|
+
// Wipe per-actor memory tracking too — a new user prompt is a fresh window
|
|
226
|
+
// for both parent AND any subagents the parent may spawn during this turn.
|
|
227
|
+
s.memorySearchedBy = {};
|
|
144
228
|
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
145
229
|
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
146
230
|
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
@@ -163,7 +247,7 @@ switch (command) {
|
|
|
163
247
|
break;
|
|
164
248
|
}
|
|
165
249
|
case 'session-reset': {
|
|
166
|
-
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, learningsStored: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
250
|
+
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
167
251
|
break;
|
|
168
252
|
}
|
|
169
253
|
default:
|
|
@@ -11,11 +11,11 @@ const path = require('path');
|
|
|
11
11
|
const os = require('os');
|
|
12
12
|
|
|
13
13
|
const PROJECT_ROOT = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
14
|
-
const DATA_DIR = path.join(PROJECT_ROOT, '.
|
|
14
|
+
const DATA_DIR = path.join(PROJECT_ROOT, '.moflo', 'data');
|
|
15
15
|
const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.json');
|
|
16
16
|
const RANKED_PATH = path.join(DATA_DIR, 'ranked-context.json');
|
|
17
17
|
const PENDING_PATH = path.join(DATA_DIR, 'pending-insights.jsonl');
|
|
18
|
-
const SESSION_DIR = path.join(PROJECT_ROOT, '.
|
|
18
|
+
const SESSION_DIR = path.join(PROJECT_ROOT, '.moflo', 'sessions');
|
|
19
19
|
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
|
20
20
|
|
|
21
21
|
function ensureDir(dir) {
|
|
@@ -55,7 +55,7 @@ function bootstrapFromMemoryFiles() {
|
|
|
55
55
|
var entries = [];
|
|
56
56
|
var candidates = [
|
|
57
57
|
path.join(os.homedir(), ".claude", "projects"),
|
|
58
|
-
path.join(PROJECT_ROOT, ".
|
|
58
|
+
path.join(PROJECT_ROOT, ".moflo", "memory"),
|
|
59
59
|
path.join(PROJECT_ROOT, ".claude", "memory"),
|
|
60
60
|
];
|
|
61
61
|
for (var i = 0; i < candidates.length; i++) {
|
|
@@ -51,7 +51,6 @@ function loadStatusLineConfig() {
|
|
|
51
51
|
show_mcp: true,
|
|
52
52
|
show_security: true,
|
|
53
53
|
show_adrs: true,
|
|
54
|
-
show_agentdb: true,
|
|
55
54
|
show_tests: true,
|
|
56
55
|
mode: 'compact',
|
|
57
56
|
};
|
|
@@ -383,7 +382,7 @@ function getSwarmStatus() {
|
|
|
383
382
|
function getSystemMetrics() {
|
|
384
383
|
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
385
384
|
const learning = getLearningStats();
|
|
386
|
-
const
|
|
385
|
+
const embeddings = getEmbeddingsStats();
|
|
387
386
|
|
|
388
387
|
// Intelligence from learning.json
|
|
389
388
|
const learningData = readJSON(path.join(CWD, '.moflo', 'metrics', 'learning.json'));
|
|
@@ -394,7 +393,7 @@ function getSystemMetrics() {
|
|
|
394
393
|
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
395
394
|
} else {
|
|
396
395
|
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
|
|
397
|
-
const fromVectors =
|
|
396
|
+
const fromVectors = embeddings.vectorCount > 0 ? Math.min(100, Math.floor(embeddings.vectorCount / 100)) : 0;
|
|
398
397
|
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
399
398
|
}
|
|
400
399
|
|
|
@@ -424,7 +423,7 @@ function getSystemMetrics() {
|
|
|
424
423
|
subAgents = activityData.processes.estimated_agents;
|
|
425
424
|
}
|
|
426
425
|
|
|
427
|
-
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
426
|
+
return { memoryMB, contextPct, intelligencePct, subAgents, embeddings };
|
|
428
427
|
}
|
|
429
428
|
|
|
430
429
|
// ADR status (count files only — don't read contents)
|
|
@@ -485,9 +484,9 @@ function getHooksStatus() {
|
|
|
485
484
|
return { enabled, total };
|
|
486
485
|
}
|
|
487
486
|
|
|
488
|
-
//
|
|
487
|
+
// Embeddings stats — reads from cache file written by embedding/memory ops.
|
|
489
488
|
// No subprocess spawning. Falls back to DB file size estimate if cache is missing.
|
|
490
|
-
function
|
|
489
|
+
function getEmbeddingsStats() {
|
|
491
490
|
let vectorCount = 0;
|
|
492
491
|
let dbSizeKB = 0;
|
|
493
492
|
let namespaces = 0;
|
|
@@ -602,20 +601,25 @@ function getIntegrationStatus() {
|
|
|
602
601
|
return { mcpServers, hasDatabase, hasApi };
|
|
603
602
|
}
|
|
604
603
|
|
|
605
|
-
// Upgrade notice (#636, #738, #743) — written by the session-start launcher
|
|
606
|
-
//
|
|
607
|
-
// work
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
//
|
|
604
|
+
// Upgrade notice (#636, #738, #743) — written by the session-start launcher.
|
|
605
|
+
// status='in-progress' — work is running; rendered with "(updating…)".
|
|
606
|
+
// status='completed' — work just finished; short-TTL post-upgrade badge so
|
|
607
|
+
// the user sees something on the very next render
|
|
608
|
+
// (Claude Code only paints the statusline AFTER the
|
|
609
|
+
// SessionStart hook returns, so the in-progress badge
|
|
610
|
+
// has effectively zero visibility window).
|
|
611
|
+
// Anything else is dropped (legacy "complete" pre-#738 files, zombie writes,
|
|
612
|
+
// future writer mistakes) so a stale notice can never turn the segment into a
|
|
613
|
+
// permanent column. Section 0-pre of the launcher also wipes any leftover at
|
|
614
|
+
// session start as a second line of defence.
|
|
612
615
|
function getUpgradeNotice() {
|
|
613
616
|
const data = readJSON(path.join(CWD, '.moflo', 'upgrade-notice.json'));
|
|
614
617
|
if (!data || typeof data !== 'object') return null;
|
|
615
|
-
if (data.status !== 'in-progress') return null;
|
|
618
|
+
if (data.status !== 'in-progress' && data.status !== 'completed') return null;
|
|
616
619
|
const expiresAt = data.expiresAt ? new Date(data.expiresAt).getTime() : 0;
|
|
617
620
|
if (!expiresAt || Date.now() > expiresAt) return null;
|
|
618
621
|
return {
|
|
622
|
+
status: data.status,
|
|
619
623
|
kind: data.kind === 'repair' ? 'repair' : 'upgrade',
|
|
620
624
|
from: typeof data.from === 'string' ? data.from : '',
|
|
621
625
|
to: typeof data.to === 'string' ? data.to : '',
|
|
@@ -624,14 +628,20 @@ function getUpgradeNotice() {
|
|
|
624
628
|
|
|
625
629
|
function formatUpgradeNoticeSegment(notice) {
|
|
626
630
|
if (!notice) return '';
|
|
627
|
-
const
|
|
631
|
+
const inFlight = notice.status === 'in-progress';
|
|
632
|
+
const suffix = inFlight ? ` ${c.dim}(updating…)${c.reset}` : '';
|
|
633
|
+
// Pick body text: repair > in-flight version range > completed "upgraded to"
|
|
634
|
+
// > bare "upgraded" fallback when no version is known.
|
|
635
|
+
let body;
|
|
628
636
|
if (notice.kind === 'repair') {
|
|
629
|
-
|
|
637
|
+
body = 'install repaired';
|
|
638
|
+
} else if (inFlight) {
|
|
639
|
+
body = notice.from && notice.to ? `${notice.from} → ${notice.to}` : (notice.to || 'upgraded');
|
|
640
|
+
} else {
|
|
641
|
+
const target = notice.to || notice.from || '';
|
|
642
|
+
body = target ? `upgraded to ${target}` : 'upgraded';
|
|
630
643
|
}
|
|
631
|
-
|
|
632
|
-
? `${notice.from} → ${notice.to}`
|
|
633
|
-
: (notice.to || 'upgraded');
|
|
634
|
-
return `${c.brightYellow}📦 ${versions}${c.reset}${suffix}`;
|
|
644
|
+
return `${c.brightYellow}📦 ${body}${c.reset}${suffix}`;
|
|
635
645
|
}
|
|
636
646
|
|
|
637
647
|
// Session stats (pure file reads)
|
|
@@ -785,28 +795,37 @@ function generateDashboard() {
|
|
|
785
795
|
);
|
|
786
796
|
}
|
|
787
797
|
|
|
788
|
-
//
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
|
|
803
|
-
integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
|
|
804
|
-
parts.push(`${c.cyan}MCP${c.reset} ${mcpCol}\u25CF${integration.mcpServers.enabled}/${integration.mcpServers.total}${c.reset}`);
|
|
798
|
+
// Embeddings line \u2014 vector store stats from .moflo/vector-stats.json.
|
|
799
|
+
// Reuses `system.embeddings` (already computed by getSystemMetrics()) instead
|
|
800
|
+
// of re-probing the cache file on every render.
|
|
801
|
+
{
|
|
802
|
+
const vec = system.embeddings;
|
|
803
|
+
if (vec.vectorCount > 0) {
|
|
804
|
+
const hnswInd = vec.hasHnsw ? `${c.brightGreen}\u26A1${c.reset}` : '';
|
|
805
|
+
const sizeDisp = vec.dbSizeKB >= 1024 ? `${(vec.dbSizeKB / 1024).toFixed(1)}MB` : `${vec.dbSizeKB}KB`;
|
|
806
|
+
const eParts = [
|
|
807
|
+
`${c.cyan}Vectors${c.reset} ${c.brightGreen}\u25CF${vec.vectorCount}${c.reset}${hnswInd}`,
|
|
808
|
+
`${c.cyan}Size${c.reset} ${c.brightWhite}${sizeDisp}${c.reset}`,
|
|
809
|
+
];
|
|
810
|
+
if (vec.namespaces > 0) {
|
|
811
|
+
eParts.push(`${c.cyan}NS${c.reset} ${c.brightWhite}${vec.namespaces}${c.reset}`);
|
|
805
812
|
}
|
|
806
|
-
|
|
813
|
+
lines.push(`${c.brightCyan}\uD83D\uDCCA Embeddings${c.reset} ${eParts.join(` ${c.dim}\u2502${c.reset} `)}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// MCP line
|
|
818
|
+
if (SL_CONFIG.show_mcp) {
|
|
819
|
+
const parts = [];
|
|
820
|
+
const integration = getIntegrationStatus();
|
|
821
|
+
if (integration.mcpServers.total > 0) {
|
|
822
|
+
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
|
|
823
|
+
integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
|
|
824
|
+
parts.push(`${c.cyan}MCP${c.reset} ${mcpCol}\u25CF${integration.mcpServers.enabled}/${integration.mcpServers.total}${c.reset}`);
|
|
807
825
|
}
|
|
826
|
+
if (integration.hasDatabase) parts.push(`${c.brightGreen}\u25C6${c.reset}DB`);
|
|
808
827
|
if (parts.length > 0) {
|
|
809
|
-
lines.push(`${c.brightCyan}\uD83D\
|
|
828
|
+
lines.push(`${c.brightCyan}\uD83D\uDD0C MCP${c.reset} ${parts.join(` ${c.dim}\u2502${c.reset} `)}`);
|
|
810
829
|
}
|
|
811
830
|
}
|
|
812
831
|
|
|
@@ -846,7 +865,7 @@ function generateCompactDashboard() {
|
|
|
846
865
|
pushUpgradeNoticeSegment(lines);
|
|
847
866
|
lines.push(header);
|
|
848
867
|
|
|
849
|
-
// Combined swarm +
|
|
868
|
+
// Combined swarm + embeddings + mcp line
|
|
850
869
|
const segments = [];
|
|
851
870
|
if (SL_CONFIG.show_swarm) {
|
|
852
871
|
const swarm = getSwarmStatus();
|
|
@@ -856,14 +875,17 @@ function generateCompactDashboard() {
|
|
|
856
875
|
`${c.brightYellow}\uD83E\uDD16${c.reset} ${swarmInd}[${agentsColor}${swarm.activeAgents}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}]`
|
|
857
876
|
);
|
|
858
877
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
878
|
+
// Embeddings \u2014 always-on when vectorCount > 0; self-hides on a fresh install.
|
|
879
|
+
// Compact doesn't call getSystemMetrics() so this is the only probe per render.
|
|
880
|
+
{
|
|
881
|
+
const vec = getEmbeddingsStats();
|
|
882
|
+
if (vec.vectorCount > 0) {
|
|
883
|
+
const hnswInd = vec.hasHnsw ? '\u26A1' : '';
|
|
884
|
+
const sizeDisp = vec.dbSizeKB >= 1024 ? `${(vec.dbSizeKB / 1024).toFixed(1)}MB` : `${vec.dbSizeKB}KB`;
|
|
885
|
+
segments.push(
|
|
886
|
+
`${c.brightCyan}\uD83D\uDCCA${c.reset} ${c.brightGreen}${vec.vectorCount}${hnswInd}${c.reset} ${c.dim}(${sizeDisp})${c.reset}`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
867
889
|
}
|
|
868
890
|
if (SL_CONFIG.show_mcp) {
|
|
869
891
|
const integration = getIntegrationStatus();
|
|
@@ -883,15 +905,16 @@ function generateCompactDashboard() {
|
|
|
883
905
|
// JSON output
|
|
884
906
|
function generateJSON() {
|
|
885
907
|
const git = getGitInfo();
|
|
908
|
+
const system = getSystemMetrics();
|
|
886
909
|
return {
|
|
887
910
|
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
|
|
888
911
|
v3Progress: getV3Progress(),
|
|
889
912
|
security: getSecurityStatus(),
|
|
890
913
|
swarm: getSwarmStatus(),
|
|
891
|
-
system
|
|
914
|
+
system,
|
|
892
915
|
adrs: getADRStatus(),
|
|
893
916
|
hooks: getHooksStatus(),
|
|
894
|
-
|
|
917
|
+
embeddings: system.embeddings,
|
|
895
918
|
tests: getTestStats(),
|
|
896
919
|
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
897
920
|
upgradeNotice: getUpgradeNotice(),
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{
|
|
2
|
+
"directive": "MANDATORY FIRST ACTION: Your very first tool call MUST be mcp__moflo__memory_search (any query, any namespace). The memory-first gate WILL BLOCK all Glob, Grep, and Read calls until you do this. After memory search, follow `.claude/guidance/shipped/moflo-subagents.md` protocol."
|
|
3
|
+
}
|
|
@@ -1,22 +1,58 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* SubagentStart Hook — injects a directive into every subagent's context
|
|
4
|
-
* telling it to read the subagent protocol guidance before doing any work.
|
|
5
|
-
*
|
|
6
|
-
* Output format: JSON with additionalContext (Claude Code hook protocol).
|
|
7
|
-
* Exit 0 = allow (SubagentStart cannot block).
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SubagentStart Hook — injects a directive into every subagent's context
|
|
4
|
+
* telling it to read the subagent protocol guidance before doing any work.
|
|
5
|
+
*
|
|
6
|
+
* Output format: JSON with additionalContext (Claude Code hook protocol).
|
|
7
|
+
* Exit 0 = allow (SubagentStart cannot block).
|
|
8
|
+
*
|
|
9
|
+
* Source of truth: ./subagent-bootstrap.json (sibling). The TS export at
|
|
10
|
+
* `src/cli/services/subagent-bootstrap.ts` reads the same file so future
|
|
11
|
+
* agent_spawn surfaces (epic #798 stories 3 + 9) inject byte-identical text.
|
|
12
|
+
*
|
|
13
|
+
* Inline FALLBACK keeps the hook functional if the JSON sibling is ever
|
|
14
|
+
* missing — a SubagentStart that emits nothing leaves the memory-first gate
|
|
15
|
+
* un-announced and silently regresses subagent behavior.
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
// Defense-in-depth copy of the canonical directive in subagent-bootstrap.json.
|
|
23
|
+
// Kept as a single-line literal so the parity test in tests/bin/subagent-start.test.ts
|
|
24
|
+
// can verify it matches the JSON via plain substring containment.
|
|
25
|
+
const FALLBACK_DIRECTIVE = 'MANDATORY FIRST ACTION: Your very first tool call MUST be mcp__moflo__memory_search (any query, any namespace). The memory-first gate WILL BLOCK all Glob, Grep, and Read calls until you do this. After memory search, follow `.claude/guidance/shipped/moflo-subagents.md` protocol.';
|
|
26
|
+
|
|
27
|
+
function loadDirective() {
|
|
28
|
+
const jsonPath = path.join(__dirname, 'subagent-bootstrap.json');
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = fs.readFileSync(jsonPath, 'utf8');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err && err.code !== 'ENOENT') {
|
|
34
|
+
process.stderr.write(`[subagent-start] read failed: ${err.message} — using inline fallback\n`);
|
|
35
|
+
}
|
|
36
|
+
return FALLBACK_DIRECTIVE;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(raw);
|
|
40
|
+
if (typeof data.directive === 'string' && data.directive.length > 0) {
|
|
41
|
+
return data.directive;
|
|
42
|
+
}
|
|
43
|
+
process.stderr.write('[subagent-start] subagent-bootstrap.json missing string `directive` — using inline fallback\n');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
process.stderr.write(`[subagent-start] subagent-bootstrap.json parse failed: ${err.message} — using inline fallback\n`);
|
|
46
|
+
}
|
|
47
|
+
return FALLBACK_DIRECTIVE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const output = {
|
|
51
|
+
hookSpecificOutput: {
|
|
52
|
+
hookEventName: 'SubagentStart',
|
|
53
|
+
additionalContext: loadDirective(),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
process.stdout.write(JSON.stringify(output));
|
|
58
|
+
process.exit(0);
|