moflo 4.9.0-rc.9 → 4.9.1
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/gate-hook.mjs +7 -0
- package/.claude/helpers/gate.cjs +40 -11
- package/.claude/helpers/statusline.cjs +66 -23
- package/.claude/helpers/subagent-bootstrap.json +3 -0
- package/.claude/helpers/subagent-start.cjs +58 -22
- package/.claude/skills/swarm-advanced/SKILL.md +77 -0
- package/README.md +26 -21
- package/bin/gate-hook.mjs +7 -0
- package/bin/gate.cjs +40 -11
- package/bin/session-start-launcher.mjs +72 -29
- 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 +37 -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 +21 -22
- package/dist/src/cli/commands/epic.js +51 -26
- 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 +10 -9
- 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/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/embeddings/fastembed-embedding-service.js +4 -3
- package/dist/src/cli/embeddings/fastembed-inline/model-loader.js +27 -5
- package/dist/src/cli/embeddings/migration/migrate-store.js +3 -2
- 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 +41 -11
- package/dist/src/cli/init/moflo-init.js +46 -52
- 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/nightly-learner.js +2 -1
- package/dist/src/cli/memory/ewc-consolidation.js +2 -1
- 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 +9 -8
- 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/index.js +2 -0
- 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/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 +2 -2
- package/dist/src/cli/epic/execution-order.js +0 -58
|
@@ -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, testsRun: false, simplifyRun: 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 {
|
|
@@ -48,7 +78,7 @@ function loadGateConfig() {
|
|
|
48
78
|
var config = loadGateConfig();
|
|
49
79
|
var command = process.argv[2];
|
|
50
80
|
|
|
51
|
-
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules'];
|
|
81
|
+
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
|
|
52
82
|
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
53
83
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
54
84
|
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
@@ -77,7 +107,7 @@ switch (command) {
|
|
|
77
107
|
case 'check-before-scan': {
|
|
78
108
|
if (!config.memory_first) break;
|
|
79
109
|
var s = readState();
|
|
80
|
-
if (s.
|
|
110
|
+
if (!s.memoryRequired || isMemorySearchedFor(s)) break;
|
|
81
111
|
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
82
112
|
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
83
113
|
process.stderr.write('BLOCKED: Search memory before exploring files. Use mcp__moflo__memory_search.\n');
|
|
@@ -86,7 +116,7 @@ switch (command) {
|
|
|
86
116
|
case 'check-before-read': {
|
|
87
117
|
if (!config.memory_first) break;
|
|
88
118
|
var s = readState();
|
|
89
|
-
if (s.
|
|
119
|
+
if (!s.memoryRequired || isMemorySearchedFor(s)) break;
|
|
90
120
|
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
91
121
|
var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
|
|
92
122
|
if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
|
|
@@ -102,18 +132,14 @@ switch (command) {
|
|
|
102
132
|
}
|
|
103
133
|
case 'record-memory-searched': {
|
|
104
134
|
var s = readState();
|
|
105
|
-
if (
|
|
106
|
-
s.memorySearched = true;
|
|
107
|
-
writeState(s);
|
|
108
|
-
}
|
|
135
|
+
if (markMemorySearched(s)) writeState(s);
|
|
109
136
|
break;
|
|
110
137
|
}
|
|
111
138
|
case 'check-bash-memory': {
|
|
112
139
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
113
140
|
if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
|
|
114
141
|
var s = readState();
|
|
115
|
-
s
|
|
116
|
-
writeState(s);
|
|
142
|
+
if (markMemorySearched(s)) writeState(s);
|
|
117
143
|
}
|
|
118
144
|
break;
|
|
119
145
|
}
|
|
@@ -196,6 +222,9 @@ switch (command) {
|
|
|
196
222
|
case 'prompt-reminder': {
|
|
197
223
|
var s = readState();
|
|
198
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 = {};
|
|
199
228
|
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
200
229
|
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
201
230
|
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
@@ -218,7 +247,7 @@ switch (command) {
|
|
|
218
247
|
break;
|
|
219
248
|
}
|
|
220
249
|
case 'session-reset': {
|
|
221
|
-
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: 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 });
|
|
222
251
|
break;
|
|
223
252
|
}
|
|
224
253
|
default:
|
|
@@ -382,7 +382,7 @@ function getSwarmStatus() {
|
|
|
382
382
|
function getSystemMetrics() {
|
|
383
383
|
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
384
384
|
const learning = getLearningStats();
|
|
385
|
-
const
|
|
385
|
+
const embeddings = getEmbeddingsStats();
|
|
386
386
|
|
|
387
387
|
// Intelligence from learning.json
|
|
388
388
|
const learningData = readJSON(path.join(CWD, '.moflo', 'metrics', 'learning.json'));
|
|
@@ -393,7 +393,7 @@ function getSystemMetrics() {
|
|
|
393
393
|
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
394
394
|
} else {
|
|
395
395
|
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
|
|
396
|
-
const fromVectors =
|
|
396
|
+
const fromVectors = embeddings.vectorCount > 0 ? Math.min(100, Math.floor(embeddings.vectorCount / 100)) : 0;
|
|
397
397
|
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
398
398
|
}
|
|
399
399
|
|
|
@@ -423,7 +423,7 @@ function getSystemMetrics() {
|
|
|
423
423
|
subAgents = activityData.processes.estimated_agents;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
426
|
+
return { memoryMB, contextPct, intelligencePct, subAgents, embeddings };
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
// ADR status (count files only — don't read contents)
|
|
@@ -484,9 +484,9 @@ function getHooksStatus() {
|
|
|
484
484
|
return { enabled, total };
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
//
|
|
487
|
+
// Embeddings stats — reads from cache file written by embedding/memory ops.
|
|
488
488
|
// No subprocess spawning. Falls back to DB file size estimate if cache is missing.
|
|
489
|
-
function
|
|
489
|
+
function getEmbeddingsStats() {
|
|
490
490
|
let vectorCount = 0;
|
|
491
491
|
let dbSizeKB = 0;
|
|
492
492
|
let namespaces = 0;
|
|
@@ -601,20 +601,25 @@ function getIntegrationStatus() {
|
|
|
601
601
|
return { mcpServers, hasDatabase, hasApi };
|
|
602
602
|
}
|
|
603
603
|
|
|
604
|
-
// Upgrade notice (#636, #738, #743) — written by the session-start launcher
|
|
605
|
-
//
|
|
606
|
-
// work
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
//
|
|
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.
|
|
611
615
|
function getUpgradeNotice() {
|
|
612
616
|
const data = readJSON(path.join(CWD, '.moflo', 'upgrade-notice.json'));
|
|
613
617
|
if (!data || typeof data !== 'object') return null;
|
|
614
|
-
if (data.status !== 'in-progress') return null;
|
|
618
|
+
if (data.status !== 'in-progress' && data.status !== 'completed') return null;
|
|
615
619
|
const expiresAt = data.expiresAt ? new Date(data.expiresAt).getTime() : 0;
|
|
616
620
|
if (!expiresAt || Date.now() > expiresAt) return null;
|
|
617
621
|
return {
|
|
622
|
+
status: data.status,
|
|
618
623
|
kind: data.kind === 'repair' ? 'repair' : 'upgrade',
|
|
619
624
|
from: typeof data.from === 'string' ? data.from : '',
|
|
620
625
|
to: typeof data.to === 'string' ? data.to : '',
|
|
@@ -623,14 +628,20 @@ function getUpgradeNotice() {
|
|
|
623
628
|
|
|
624
629
|
function formatUpgradeNoticeSegment(notice) {
|
|
625
630
|
if (!notice) return '';
|
|
626
|
-
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;
|
|
627
636
|
if (notice.kind === 'repair') {
|
|
628
|
-
|
|
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';
|
|
629
643
|
}
|
|
630
|
-
|
|
631
|
-
? `${notice.from} → ${notice.to}`
|
|
632
|
-
: (notice.to || 'upgraded');
|
|
633
|
-
return `${c.brightYellow}📦 ${versions}${c.reset}${suffix}`;
|
|
644
|
+
return `${c.brightYellow}📦 ${body}${c.reset}${suffix}`;
|
|
634
645
|
}
|
|
635
646
|
|
|
636
647
|
// Session stats (pure file reads)
|
|
@@ -784,6 +795,25 @@ function generateDashboard() {
|
|
|
784
795
|
);
|
|
785
796
|
}
|
|
786
797
|
|
|
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}`);
|
|
812
|
+
}
|
|
813
|
+
lines.push(`${c.brightCyan}\uD83D\uDCCA Embeddings${c.reset} ${eParts.join(` ${c.dim}\u2502${c.reset} `)}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
787
817
|
// MCP line
|
|
788
818
|
if (SL_CONFIG.show_mcp) {
|
|
789
819
|
const parts = [];
|
|
@@ -795,7 +825,7 @@ function generateDashboard() {
|
|
|
795
825
|
}
|
|
796
826
|
if (integration.hasDatabase) parts.push(`${c.brightGreen}\u25C6${c.reset}DB`);
|
|
797
827
|
if (parts.length > 0) {
|
|
798
|
-
lines.push(`${c.brightCyan}\uD83D\
|
|
828
|
+
lines.push(`${c.brightCyan}\uD83D\uDD0C MCP${c.reset} ${parts.join(` ${c.dim}\u2502${c.reset} `)}`);
|
|
799
829
|
}
|
|
800
830
|
}
|
|
801
831
|
|
|
@@ -835,7 +865,7 @@ function generateCompactDashboard() {
|
|
|
835
865
|
pushUpgradeNoticeSegment(lines);
|
|
836
866
|
lines.push(header);
|
|
837
867
|
|
|
838
|
-
// Combined swarm + mcp line
|
|
868
|
+
// Combined swarm + embeddings + mcp line
|
|
839
869
|
const segments = [];
|
|
840
870
|
if (SL_CONFIG.show_swarm) {
|
|
841
871
|
const swarm = getSwarmStatus();
|
|
@@ -845,6 +875,18 @@ function generateCompactDashboard() {
|
|
|
845
875
|
`${c.brightYellow}\uD83E\uDD16${c.reset} ${swarmInd}[${agentsColor}${swarm.activeAgents}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}]`
|
|
846
876
|
);
|
|
847
877
|
}
|
|
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
|
+
}
|
|
889
|
+
}
|
|
848
890
|
if (SL_CONFIG.show_mcp) {
|
|
849
891
|
const integration = getIntegrationStatus();
|
|
850
892
|
if (integration.mcpServers.total > 0) {
|
|
@@ -863,15 +905,16 @@ function generateCompactDashboard() {
|
|
|
863
905
|
// JSON output
|
|
864
906
|
function generateJSON() {
|
|
865
907
|
const git = getGitInfo();
|
|
908
|
+
const system = getSystemMetrics();
|
|
866
909
|
return {
|
|
867
910
|
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
|
|
868
911
|
v3Progress: getV3Progress(),
|
|
869
912
|
security: getSecurityStatus(),
|
|
870
913
|
swarm: getSwarmStatus(),
|
|
871
|
-
system
|
|
914
|
+
system,
|
|
872
915
|
adrs: getADRStatus(),
|
|
873
916
|
hooks: getHooksStatus(),
|
|
874
|
-
|
|
917
|
+
embeddings: system.embeddings,
|
|
875
918
|
tests: getTestStats(),
|
|
876
919
|
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
877
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);
|
|
@@ -33,6 +33,83 @@ mcp__moflo__agent_spawn({ type: "researcher", name: "swarm-advanced" })
|
|
|
33
33
|
// 3. Orchestrate tasks
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Dynamic Scaling
|
|
37
|
+
Use `mcp__moflo__swarm_scale` to grow or shrink the agent pool to a target size
|
|
38
|
+
without re-initializing the swarm. Three strategies are available:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// Burst-spawn 8 workers all at once (load test, big batch)
|
|
42
|
+
mcp__moflo__swarm_scale({
|
|
43
|
+
targetAgents: 8,
|
|
44
|
+
scaleStrategy: "immediate",
|
|
45
|
+
agentTypes: ["worker"],
|
|
46
|
+
reason: "load-test ramp"
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Rate-limited ramp (1 agent / 200ms) — gentler on the coordinator
|
|
50
|
+
mcp__moflo__swarm_scale({
|
|
51
|
+
targetAgents: 12,
|
|
52
|
+
scaleStrategy: "gradual",
|
|
53
|
+
agentTypes: ["coder", "tester"]
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Adaptive: chunks scale with current coordinator load
|
|
57
|
+
mcp__moflo__swarm_scale({ targetAgents: 4, scaleStrategy: "adaptive" })
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The tool returns `{ previousAgents, currentAgents, scalingStatus, addedAgents,
|
|
61
|
+
removedAgents }` so callers can verify the swarm reached the target. Scale-down
|
|
62
|
+
prefers idle agents first, then oldest by heartbeat.
|
|
63
|
+
|
|
64
|
+
### Task Orchestration
|
|
65
|
+
|
|
66
|
+
The `task_*` family talks to the same UnifiedSwarmCoordinator that
|
|
67
|
+
`swarm_init` / `agent_spawn` use, so tasks created here flow through the
|
|
68
|
+
same scoring scheduler that load-balances across idle agents.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// Single task — coordinator picks the lowest-workload agent automatically
|
|
72
|
+
mcp__moflo__task_create({
|
|
73
|
+
type: "coding",
|
|
74
|
+
description: "Implement OAuth refresh flow",
|
|
75
|
+
priority: "high"
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Direct dispatch to a known agent (skip the scheduler)
|
|
79
|
+
mcp__moflo__task_assign({
|
|
80
|
+
taskId: "task_swarm-…_3",
|
|
81
|
+
agentId: "agent-coder-…"
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Domain-routed dispatch (queen / security / core / integration / support)
|
|
85
|
+
mcp__moflo__task_assign({
|
|
86
|
+
taskId: "task_swarm-…_4",
|
|
87
|
+
domain: "security"
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Submit a batch — load-balanced across available agents in one call.
|
|
91
|
+
// 5 tasks across 3 idle agents → no agent ends up with more than 2.
|
|
92
|
+
mcp__moflo__task_orchestrate({
|
|
93
|
+
tasks: [
|
|
94
|
+
{ type: "coding", description: "endpoint A" },
|
|
95
|
+
{ type: "coding", description: "endpoint B" },
|
|
96
|
+
{ type: "testing", description: "tests for A" },
|
|
97
|
+
{ type: "testing", description: "tests for B" },
|
|
98
|
+
{ type: "review", description: "PR review" }
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Mark a task done and record its outcome
|
|
103
|
+
mcp__moflo__task_complete({
|
|
104
|
+
taskId: "task_swarm-…_3",
|
|
105
|
+
result: { ok: true, summary: "merged in PR #842" }
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`task_orchestrate` returns `{ submitted, assigned, queued, rejected, tasks,
|
|
110
|
+
errors }` — `assigned` is the count whose agents accepted them on submit;
|
|
111
|
+
the rest are queued and will be picked up as agents go idle.
|
|
112
|
+
|
|
36
113
|
## Core Concepts
|
|
37
114
|
|
|
38
115
|
### Swarm Topologies
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Restart Claude Code (or your MCP client). That's it — memory, indexing, gates,
|
|
|
21
21
|
|
|
22
22
|
Or — just ask Claude to install MoFlo into your project and initialize it!
|
|
23
23
|
|
|
24
|
-
To verify everything is running, ask Claude to run `flo
|
|
24
|
+
To verify everything is running, ask Claude to run `flo healer` with full diagnostics after restarting. If anything fails, ask Claude to fix it with `flo healer --fix`. (`flo doctor` is still accepted as an alias.)
|
|
25
25
|
|
|
26
26
|
## Opinionated Defaults
|
|
27
27
|
|
|
@@ -97,7 +97,7 @@ In interactive mode (`flo init` without `--yes`), it shows what it found and let
|
|
|
97
97
|
If `flo init` detects an existing `.claude/settings.json` or `.claude-flow/` directory (from a prior Claude Flow or Ruflo installation), it treats the project as already initialized and runs in **update mode** — merging MoFlo's hooks and configuration into your existing setup without overwriting your data. Specifically:
|
|
98
98
|
|
|
99
99
|
- **Hooks** — If your `.claude/settings.json` already has MoFlo-style gate hooks (`flo gate`), the hooks step is skipped. Otherwise, MoFlo's hooks are written into the file (existing non-MoFlo hooks are not removed).
|
|
100
|
-
- **MCP servers** — MoFlo registers itself as the `moflo` server in `.mcp.json`. If you had `claude-flow` or `ruflo` MCP servers configured previously, those entries remain untouched — you can remove them manually once you've verified MoFlo is working. The `flo
|
|
100
|
+
- **MCP servers** — MoFlo registers itself as the `moflo` server in `.mcp.json`. If you had `claude-flow` or `ruflo` MCP servers configured previously, those entries remain untouched — you can remove them manually once you've verified MoFlo is working. The `flo healer` command checks for the `moflo` server specifically.
|
|
101
101
|
- **Config files** — `moflo.yaml`, `CLAUDE.md`, and `.claude/skills/flo/` follow the same skip-if-exists logic. Use `--force` to regenerate them.
|
|
102
102
|
|
|
103
103
|
To force a clean re-initialization over an existing setup:
|
|
@@ -162,7 +162,7 @@ code_map:
|
|
|
162
162
|
```bash
|
|
163
163
|
flo memory index-guidance # Index your guidance docs
|
|
164
164
|
flo memory code-map # Index your code structure
|
|
165
|
-
flo
|
|
165
|
+
flo healer # Verify everything works (alias: flo doctor)
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes. The first index may take a minute or two on large codebases (1,000+ files) but runs in the background — you can start working immediately. Subsequent indexes are incremental and typically finish in under a second. To reindex everything at once:
|
|
@@ -293,16 +293,16 @@ For simple epics with independent stories, `/flo <epic>` is all you need. For co
|
|
|
293
293
|
`flo epic` is the robust epic runner — it adds persistent state, resume from failure, and per-story auto-merge on top of `/flo`. It takes a GitHub epic issue number:
|
|
294
294
|
|
|
295
295
|
```bash
|
|
296
|
-
flo epic
|
|
297
|
-
flo epic
|
|
298
|
-
flo epic
|
|
296
|
+
flo epic 42 # Fetch epic #42, run all stories sequentially
|
|
297
|
+
flo epic 42 --dry-run # Preview execution plan without running
|
|
298
|
+
flo epic 42 --strategy auto-merge # Per-story PRs with auto-merge between stories
|
|
299
299
|
flo epic status 42 # Check progress (which stories passed/failed)
|
|
300
300
|
flo epic reset 42 # Reset state for re-run
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
-
`flo epic` fetches the epic from GitHub, extracts child stories from checklists, numbered references, and `## Stories` / `## Tasks` sections, then runs each through `/flo` with state tracking. If a story fails, you can fix the issue and `flo epic
|
|
303
|
+
`flo epic` fetches the epic from GitHub, extracts child stories from checklists, numbered references, and `## Stories` / `## Tasks` sections, then runs each through `/flo` with state tracking. If a story fails, you can fix the issue and re-run `flo epic 42` — it resumes from where it left off, skipping already-passed stories. (`flo epic run 42` is an explicit alias for the same shorthand.)
|
|
304
304
|
|
|
305
|
-
| | `/flo <epic>` | `flo epic
|
|
305
|
+
| | `/flo <epic>` | `flo epic <epic>` |
|
|
306
306
|
|---|---|---|
|
|
307
307
|
| **State tracking** | No | Yes (`epic-state` memory namespace) |
|
|
308
308
|
| **Resume from failure** | No | Yes (skips passed stories) |
|
|
@@ -484,16 +484,18 @@ flo gate session-reset # Reset gate state
|
|
|
484
484
|
### Diagnostics
|
|
485
485
|
|
|
486
486
|
```bash
|
|
487
|
-
flo
|
|
488
|
-
flo
|
|
487
|
+
flo healer # Quick health check (environment, deps, config)
|
|
488
|
+
flo healer --fix # Auto-fix issues (memory DB, daemon, config, MCP, zombies)
|
|
489
489
|
flo diagnose # Full integration test (memory, swarm, hive, hooks, neural)
|
|
490
490
|
flo diagnose --suite memory # Run only memory tests
|
|
491
491
|
flo diagnose --json # JSON output for CI/automation
|
|
492
492
|
```
|
|
493
493
|
|
|
494
|
-
|
|
494
|
+
`flo doctor` is still accepted as an alias for `flo healer` — every flag and subcommand below works under either name.
|
|
495
495
|
|
|
496
|
-
`flo
|
|
496
|
+
#### `flo healer` — Health Check
|
|
497
|
+
|
|
498
|
+
`flo healer` runs 28 parallel health checks against your environment and reports pass/warn/fail for each:
|
|
497
499
|
|
|
498
500
|
| Check | What it verifies |
|
|
499
501
|
|-------|-----------------|
|
|
@@ -526,7 +528,7 @@ flo diagnose --json # JSON output for CI/automation
|
|
|
526
528
|
| **MofloDb Bridge** | Memory DB adapter (sql.js + HNSW) is wired and routable |
|
|
527
529
|
| **Sandbox Tier** | Detects which sandbox backend is available (Docker / bwrap / sandbox-exec / none) |
|
|
528
530
|
|
|
529
|
-
**Auto-fix mode** (`flo
|
|
531
|
+
**Auto-fix mode** (`flo healer --fix`) attempts to repair each failing check automatically:
|
|
530
532
|
|
|
531
533
|
| Issue | What `--fix` does |
|
|
532
534
|
|-------|------------------|
|
|
@@ -539,21 +541,21 @@ flo diagnose --json # JSON output for CI/automation
|
|
|
539
541
|
| Claude Code CLI missing | Installs `@anthropic-ai/claude-code` globally |
|
|
540
542
|
| Zombie processes | Kills orphaned MoFlo processes (tracked + OS-level scan) |
|
|
541
543
|
|
|
542
|
-
After auto-fixing,
|
|
544
|
+
After auto-fixing, healer re-runs all checks and shows the updated results. Issues that can't be fixed automatically are listed with manual fix commands.
|
|
543
545
|
|
|
544
546
|
Additional flags:
|
|
545
547
|
|
|
546
548
|
```bash
|
|
547
|
-
flo
|
|
548
|
-
flo
|
|
549
|
-
flo
|
|
550
|
-
flo
|
|
551
|
-
flo
|
|
549
|
+
flo healer --install # Auto-install missing Claude Code CLI
|
|
550
|
+
flo healer --kill-zombies # Find and kill orphaned MoFlo processes
|
|
551
|
+
flo healer -c memory # Check only a specific component
|
|
552
|
+
flo healer -c embeddings # Check only embeddings health
|
|
553
|
+
flo healer --verbose # Verbose output
|
|
552
554
|
```
|
|
553
555
|
|
|
554
556
|
#### `flo diagnose` — Integration Tests
|
|
555
557
|
|
|
556
|
-
While `
|
|
558
|
+
While `healer` checks your environment, `diagnose` exercises every subsystem end-to-end: memory CRUD, embedding generation, semantic search, swarm lifecycle, hive-mind consensus, task management, hooks, config, neural patterns, and init idempotency. All test data is cleaned up after each test — nothing is left behind.
|
|
557
559
|
|
|
558
560
|
### GitHub Repository Setup
|
|
559
561
|
|
|
@@ -583,7 +585,7 @@ flo --version # Show version
|
|
|
583
585
|
|
|
584
586
|
### Hooks (enabled OOTB)
|
|
585
587
|
|
|
586
|
-
Hooks are shell commands that Claude Code runs automatically at specific points in its lifecycle. MoFlo installs
|
|
588
|
+
Hooks are shell commands that Claude Code runs automatically at specific points in its lifecycle. MoFlo installs 23 hook bindings across 8 lifecycle events. You don't invoke these — they fire automatically.
|
|
587
589
|
|
|
588
590
|
| Hook Event | What fires | What it does | Enabled OOTB |
|
|
589
591
|
|------------|-----------|-------------|:---:|
|
|
@@ -593,9 +595,12 @@ Hooks are shell commands that Claude Code runs automatically at specific points
|
|
|
593
595
|
| **PreToolUse: Bash** | `flo gate check-dangerous-command` | Safety check on shell commands | Yes |
|
|
594
596
|
| **PreToolUse: Bash** | `flo gate check-before-pr` | Validates PR readiness before `gh pr create` | Yes |
|
|
595
597
|
| **PostToolUse: Write/Edit** | `flo hooks post-edit` | Records edit outcome, optionally trains neural patterns | Yes |
|
|
598
|
+
| **PostToolUse: Write/Edit** | `flo gate reset-edit-gates` | Resets edit-related gate state after the write completes | Yes |
|
|
596
599
|
| **PostToolUse: Agent** | `flo hooks post-task` | Records task completion, feeds outcome into routing learner | Yes |
|
|
597
600
|
| **PostToolUse: TaskCreate** | `flo gate record-task-created` | Records that a task was registered (clears TaskCreate gate) | Yes |
|
|
598
601
|
| **PostToolUse: Bash** | `flo gate check-bash-memory` | Detects memory search commands in Bash (clears memory gate) | Yes |
|
|
602
|
+
| **PostToolUse: Bash** | `flo gate record-test-run` | Records test runs from Bash for the test-output gate | Yes |
|
|
603
|
+
| **PostToolUse: Skill** | `flo gate record-skill-run` | Records that a skill was invoked (clears skill-related gates) | Yes |
|
|
599
604
|
| **PostToolUse: memory_search** | `flo gate record-memory-searched` | Records that memory was searched (clears memory-first gate) | Yes |
|
|
600
605
|
| **PostToolUse: TaskUpdate** | `flo gate check-task-transition` | Validates task state transitions (prevents skipping states) | Yes |
|
|
601
606
|
| **PostToolUse: memory_store** | `flo gate record-learnings-stored` | Records that learnings were persisted to memory | Yes |
|
package/bin/gate-hook.mjs
CHANGED
|
@@ -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') {
|