moflo 4.9.21 → 4.9.23
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/agents/analysis/analyze-code-quality.md +0 -121
- package/.claude/agents/analysis/code-analyzer.md +5 -26
- package/.claude/agents/architecture/system-design/arch-system-design.md +0 -119
- package/.claude/agents/base-template-generator.md +0 -1
- package/.claude/agents/core/coder.md +0 -22
- package/.claude/agents/core/planner.md +0 -16
- package/.claude/agents/core/researcher.md +0 -16
- package/.claude/agents/core/reviewer.md +0 -17
- package/.claude/agents/core/tester.md +0 -19
- package/.claude/agents/custom/test-long-runner.md +0 -2
- package/.claude/agents/development/dev-backend-api.md +0 -167
- package/.claude/agents/development/dev-database.md +43 -0
- package/.claude/agents/development/dev-frontend.md +42 -0
- package/.claude/agents/devops/ci-cd/ops-cicd-github.md +0 -112
- package/.claude/agents/documentation/api-docs/docs-api-openapi.md +0 -111
- package/.claude/agents/security/security-auditor.md +45 -0
- package/.claude/guidance/shipped/moflo-cli-reference.md +19 -16
- package/.claude/guidance/shipped/moflo-core-guidance.md +0 -2
- package/.claude/guidance/shipped/moflo-guidance-rules.md +5 -5
- package/.claude/guidance/shipped/moflo-spell-runner.md +1 -0
- package/.claude/guidance/shipped/moflo-spell-scheduling.md +225 -0
- package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +1 -0
- package/.claude/helpers/gate.cjs +70 -3
- package/.claude/skills/fl/execution-modes.md +38 -15
- package/.claude/skills/fl/phases.md +67 -0
- package/.claude/skills/spell-schedule/SKILL.md +18 -5
- package/README.md +1 -1
- package/bin/gate.cjs +70 -3
- package/bin/index-guidance.mjs +32 -6
- package/bin/lib/retired-files.mjs +146 -0
- package/bin/session-start-launcher.mjs +116 -8
- package/dist/src/cli/appliance/rvfa-builder.js +1 -1
- package/dist/src/cli/commands/agent.js +3 -9
- package/dist/src/cli/commands/daemon.js +13 -17
- package/dist/src/cli/commands/hooks.js +4 -9
- package/dist/src/cli/commands/index.js +2 -0
- package/dist/src/cli/commands/retire.js +111 -0
- package/dist/src/cli/commands/spell-schedule.js +237 -49
- package/dist/src/cli/hooks/reasoningbank/index.js +7 -7
- package/dist/src/cli/init/executor.js +26 -54
- package/dist/src/cli/init/helpers-generator.js +66 -3
- package/dist/src/cli/init/settings-generator.js +17 -6
- package/dist/src/cli/mcp-tools/agent-tools.js +9 -27
- package/dist/src/cli/mcp-tools/hooks-tools.js +23 -21
- package/dist/src/cli/mcp-tools/memory-tools.js +16 -5
- package/dist/src/cli/memory/bridge-embedder.js +26 -6
- package/dist/src/cli/memory/bridge-entries.js +33 -15
- package/dist/src/cli/memory/controllers/semantic-router.js +18 -12
- package/dist/src/cli/memory/sona-optimizer.js +6 -6
- package/dist/src/cli/neural/domain/services/learning-service.js +3 -3
- package/dist/src/cli/services/agent-router.js +2 -5
- package/dist/src/cli/services/daemon-autostart-lifecycle.js +62 -0
- package/dist/src/cli/services/daemon-dashboard.js +187 -18
- package/dist/src/cli/services/daemon-readiness.js +19 -31
- package/dist/src/cli/services/ephemeral-namespace-purge.js +61 -33
- package/dist/src/cli/services/headless-worker-executor.js +7 -94
- package/dist/src/cli/services/hook-block-hash.js +4 -0
- package/dist/src/cli/services/worker-daemon.js +40 -66
- package/dist/src/cli/shared/events/example-usage.js +6 -6
- package/dist/src/cli/shared/hooks/task-hooks.js +8 -8
- package/dist/src/cli/spells/core/runner.js +12 -0
- package/dist/src/cli/spells/scheduler/scheduler.js +24 -9
- package/dist/src/cli/spells/schema/validator.js +2 -1
- package/dist/src/cli/spells/schema/validators/top-level.js +18 -0
- package/dist/src/cli/version.js +1 -1
- package/package.json +5 -2
- package/retired-files.json +1989 -0
- package/src/cli/data/model-registry.json +2 -2
- package/.claude/agents/consensus/byzantine-coordinator.md +0 -63
- package/.claude/agents/consensus/crdt-synchronizer.md +0 -997
- package/.claude/agents/consensus/gossip-coordinator.md +0 -63
- package/.claude/agents/consensus/performance-benchmarker.md +0 -851
- package/.claude/agents/consensus/quorum-manager.md +0 -823
- package/.claude/agents/consensus/raft-manager.md +0 -63
- package/.claude/agents/consensus/security-manager.md +0 -622
- package/.claude/agents/data/ml/data-ml-model.md +0 -193
- package/.claude/agents/github/code-review-swarm.md +0 -538
- package/.claude/agents/github/github-modes.md +0 -172
- package/.claude/agents/github/issue-tracker.md +0 -311
- package/.claude/agents/github/multi-repo-swarm.md +0 -551
- package/.claude/agents/github/pr-manager.md +0 -183
- package/.claude/agents/github/project-board-sync.md +0 -508
- package/.claude/agents/github/release-manager.md +0 -360
- package/.claude/agents/github/release-swarm.md +0 -580
- package/.claude/agents/github/repo-architect.md +0 -391
- package/.claude/agents/github/swarm-issue.md +0 -566
- package/.claude/agents/github/swarm-pr.md +0 -414
- package/.claude/agents/github/sync-coordinator.md +0 -426
- package/.claude/agents/github/workflow-automation.md +0 -606
- package/.claude/agents/goal/code-goal-planner.md +0 -440
- package/.claude/agents/goal/goal-planner.md +0 -168
- package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +0 -127
- package/.claude/agents/hive-mind/queen-coordinator.md +0 -198
- package/.claude/agents/hive-mind/scout-explorer.md +0 -233
- package/.claude/agents/hive-mind/swarm-memory-manager.md +0 -184
- package/.claude/agents/hive-mind/worker-specialist.md +0 -208
- package/.claude/agents/neural/safla-neural.md +0 -73
- package/.claude/agents/optimization/benchmark-suite.md +0 -665
- package/.claude/agents/optimization/load-balancer.md +0 -431
- package/.claude/agents/optimization/performance-monitor.md +0 -672
- package/.claude/agents/optimization/resource-allocator.md +0 -674
- package/.claude/agents/optimization/topology-optimizer.md +0 -808
- package/.claude/agents/reasoning/goal-planner.md +0 -67
- package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
- package/.claude/agents/sparc/architecture.md +0 -472
- package/.claude/agents/sparc/pseudocode.md +0 -318
- package/.claude/agents/sparc/refinement.md +0 -525
- package/.claude/agents/sparc/specification.md +0 -276
- package/.claude/agents/specialized/mobile/spec-mobile-react-native.md +0 -225
- package/.claude/agents/swarm/adaptive-coordinator.md +0 -391
- package/.claude/agents/swarm/hierarchical-coordinator.md +0 -321
- package/.claude/agents/swarm/mesh-coordinator.md +0 -383
- package/.claude/agents/testing/production-validator.md +0 -395
- package/.claude/agents/testing/tdd-london-swarm.md +0 -244
- package/.claude/agents/v3/adr-architect.md +0 -184
- package/.claude/agents/v3/aidefence-guardian.md +0 -277
- package/.claude/agents/v3/claims-authorizer.md +0 -208
- package/.claude/agents/v3/collective-intelligence-coordinator.md +0 -988
- package/.claude/agents/v3/ddd-domain-expert.md +0 -220
- package/.claude/agents/v3/injection-analyst.md +0 -232
- package/.claude/agents/v3/memory-specialist.md +0 -987
- package/.claude/agents/v3/performance-engineer.md +0 -1225
- package/.claude/agents/v3/pii-detector.md +0 -146
- package/.claude/agents/v3/reasoningbank-learner.md +0 -213
- package/.claude/agents/v3/security-architect-aidefence.md +0 -405
- package/.claude/agents/v3/security-architect.md +0 -865
- package/.claude/agents/v3/security-auditor.md +0 -771
- package/.claude/agents/v3/sparc-orchestrator.md +0 -182
- package/.claude/agents/v3/swarm-memory-manager.md +0 -142
- package/.claude/agents/v3/v3-integration-architect.md +0 -205
- package/.claude/commands/claude-flow-help.md +0 -103
- package/.claude/commands/claude-flow-memory.md +0 -107
- package/.claude/commands/claude-flow-swarm.md +0 -205
- package/.claude/commands/flo-simplify.md +0 -101
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/github/code-review-swarm.md +0 -514
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-modes.md +0 -146
- package/.claude/commands/github/github-swarm.md +0 -113
- package/.claude/commands/github/issue-tracker.md +0 -284
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/pr-manager.md +0 -164
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-manager.md +0 -332
- package/.claude/commands/github/release-swarm.md +0 -544
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/repo-architect.md +0 -361
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -285
- package/.claude/commands/github/sync-coordinator.md +0 -294
- package/.claude/commands/github/workflow-automation.md +0 -442
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/hooks/overview.md +0 -58
- package/.claude/commands/hooks/post-edit.md +0 -117
- package/.claude/commands/hooks/post-task.md +0 -112
- package/.claude/commands/hooks/pre-edit.md +0 -113
- package/.claude/commands/hooks/pre-task.md +0 -111
- package/.claude/commands/hooks/session-end.md +0 -118
- package/.claude/commands/hooks/setup.md +0 -103
- package/.claude/commands/sparc/analyzer.md +0 -42
- package/.claude/commands/sparc/architect.md +0 -43
- package/.claude/commands/sparc/ask.md +0 -86
- package/.claude/commands/sparc/batch-executor.md +0 -44
- package/.claude/commands/sparc/code.md +0 -78
- package/.claude/commands/sparc/coder.md +0 -44
- package/.claude/commands/sparc/debug.md +0 -72
- package/.claude/commands/sparc/debugger.md +0 -44
- package/.claude/commands/sparc/designer.md +0 -43
- package/.claude/commands/sparc/devops.md +0 -98
- package/.claude/commands/sparc/docs-writer.md +0 -69
- package/.claude/commands/sparc/documenter.md +0 -44
- package/.claude/commands/sparc/innovator.md +0 -44
- package/.claude/commands/sparc/integration.md +0 -72
- package/.claude/commands/sparc/mcp.md +0 -106
- package/.claude/commands/sparc/memory-manager.md +0 -44
- package/.claude/commands/sparc/optimizer.md +0 -44
- package/.claude/commands/sparc/orchestrator.md +0 -116
- package/.claude/commands/sparc/post-deployment-monitoring-mode.md +0 -72
- package/.claude/commands/sparc/refinement-optimization-mode.md +0 -72
- package/.claude/commands/sparc/researcher.md +0 -44
- package/.claude/commands/sparc/reviewer.md +0 -44
- package/.claude/commands/sparc/security-review.md +0 -69
- package/.claude/commands/sparc/sparc-modes.md +0 -139
- package/.claude/commands/sparc/sparc.md +0 -99
- package/.claude/commands/sparc/spec-pseudocode.md +0 -69
- package/.claude/commands/sparc/spell-manager.md +0 -44
- package/.claude/commands/sparc/supabase-admin.md +0 -337
- package/.claude/commands/sparc/swarm-coordinator.md +0 -44
- package/.claude/commands/sparc/tdd.md +0 -44
- package/.claude/commands/sparc/tester.md +0 -44
- package/.claude/commands/sparc/tutorial.md +0 -68
- package/.claude/commands/sparc.md +0 -151
|
@@ -15,8 +15,58 @@ import { callMCPTool } from '../mcp-client.js';
|
|
|
15
15
|
import { TOOL_MEMORY_STORE, TOOL_MEMORY_LIST, TOOL_MEMORY_RETRIEVE } from '../mcp-tools/tool-names.js';
|
|
16
16
|
import { handleMCPError } from '../services/cli-formatters.js';
|
|
17
17
|
import { ensureDaemonForScheduling } from '../services/daemon-readiness.js';
|
|
18
|
+
import { reconcileDaemonAutostart } from '../services/daemon-autostart-lifecycle.js';
|
|
19
|
+
import { isDaemonInstalled } from '../services/daemon-service.js';
|
|
18
20
|
import { validateSchedule, computeNextRun } from '../spells/scheduler/cron-parser.js';
|
|
19
21
|
const NAMESPACE_SCHEDULES = 'scheduled-spells';
|
|
22
|
+
const NAMESPACE_EXECUTIONS = 'schedule-executions';
|
|
23
|
+
const DEFAULT_EXECUTIONS_LIMIT = 10;
|
|
24
|
+
const MAX_NAMESPACE_FETCH = 1000;
|
|
25
|
+
async function loadNamespaceValues(namespace, limit = MAX_NAMESPACE_FETCH) {
|
|
26
|
+
const listResult = await callMCPTool(TOOL_MEMORY_LIST, {
|
|
27
|
+
namespace,
|
|
28
|
+
limit,
|
|
29
|
+
});
|
|
30
|
+
const entries = listResult.entries ?? [];
|
|
31
|
+
// Parallelize retrieves — serial-await over N entries was the cost the
|
|
32
|
+
// reconcile-after-mutation path was paying on every create/cancel.
|
|
33
|
+
const fetched = await Promise.all(entries.map(async (entry) => {
|
|
34
|
+
try {
|
|
35
|
+
const result = await callMCPTool(TOOL_MEMORY_RETRIEVE, {
|
|
36
|
+
namespace,
|
|
37
|
+
key: entry.key,
|
|
38
|
+
});
|
|
39
|
+
if (result.value === null || result.value === undefined)
|
|
40
|
+
return null;
|
|
41
|
+
const parsed = typeof result.value === 'string'
|
|
42
|
+
? JSON.parse(result.value)
|
|
43
|
+
: result.value;
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
output.printWarning(`Skipped malformed entry: ${entry.key}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
return fetched.filter((v) => v !== null);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Count enabled schedules in the `scheduled-spells` namespace. Drives the
|
|
55
|
+
* autostart reconcile after a create/cancel — see #960/#961.
|
|
56
|
+
*/
|
|
57
|
+
async function countEnabledSchedules() {
|
|
58
|
+
const records = await loadNamespaceValues(NAMESPACE_SCHEDULES);
|
|
59
|
+
return records.filter(r => r.enabled === true).length;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Run the autostart reconcile and surface its message/warning to the user.
|
|
63
|
+
*/
|
|
64
|
+
function emitReconcileResult(result) {
|
|
65
|
+
if (result.message)
|
|
66
|
+
output.printInfo(result.message);
|
|
67
|
+
if (result.warning)
|
|
68
|
+
output.printWarning(result.warning);
|
|
69
|
+
}
|
|
20
70
|
// ── Schedule Create ───────────────────────────────────────────────────────────
|
|
21
71
|
const createCommand = {
|
|
22
72
|
name: 'create',
|
|
@@ -26,11 +76,13 @@ const createCommand = {
|
|
|
26
76
|
{ name: 'cron', short: 'c', description: 'Cron expression (5-field)', type: 'string' },
|
|
27
77
|
{ name: 'interval', short: 'i', description: 'Interval (e.g., "6h", "30m", "1d")', type: 'string' },
|
|
28
78
|
{ name: 'at', short: 'a', description: 'One-time ISO 8601 datetime', type: 'string' },
|
|
79
|
+
{ name: 'no-autostart', description: 'Do not register the daemon as an OS login service', type: 'boolean' },
|
|
29
80
|
],
|
|
30
81
|
examples: [
|
|
31
82
|
{ command: 'moflo spell schedule create -n audit --cron "0 9 * * *"', description: 'Daily at 9am' },
|
|
32
83
|
{ command: 'moflo spell schedule create -n security-audit --interval 6h', description: 'Every 6 hours' },
|
|
33
84
|
{ command: 'moflo spell schedule create -n report --at 2026-04-01T09:00:00Z', description: 'One-time cast' },
|
|
85
|
+
{ command: 'moflo spell schedule create -n audit --interval 6h --no-autostart', description: 'Skip OS login service registration (e.g., container/CI)' },
|
|
34
86
|
],
|
|
35
87
|
action: async (ctx) => {
|
|
36
88
|
const name = ctx.flags.name || ctx.args[0];
|
|
@@ -86,15 +138,41 @@ const createCommand = {
|
|
|
86
138
|
source: 'adhoc',
|
|
87
139
|
};
|
|
88
140
|
try {
|
|
89
|
-
await callMCPTool(TOOL_MEMORY_STORE, {
|
|
141
|
+
const storeResult = await callMCPTool(TOOL_MEMORY_STORE, {
|
|
90
142
|
namespace: NAMESPACE_SCHEDULES,
|
|
91
143
|
key: id,
|
|
92
144
|
value: JSON.stringify(record),
|
|
145
|
+
upsert: true,
|
|
93
146
|
});
|
|
147
|
+
if (storeResult.success === false) {
|
|
148
|
+
output.printError(`Failed to save schedule: ${storeResult.error ?? 'unknown error'}`);
|
|
149
|
+
return { success: false, exitCode: 1 };
|
|
150
|
+
}
|
|
94
151
|
}
|
|
95
152
|
catch (error) {
|
|
96
153
|
return handleMCPError(error, 'save schedule');
|
|
97
154
|
}
|
|
155
|
+
// Reconcile OS-native autostart against the new enabled-schedule count.
|
|
156
|
+
// 0→1 installs the login service; 1→2/2→3/etc. is a no-op (idempotent).
|
|
157
|
+
// Short-circuit: a fresh create can only ever trigger an install (count
|
|
158
|
+
// just went up). If the service is already installed, the reconcile is a
|
|
159
|
+
// guaranteed noop — skip the count fetch entirely.
|
|
160
|
+
// Note: parser normalises --no-autostart to ctx.flags.noAutostart (#787).
|
|
161
|
+
const skipAutostart = ctx.flags.noAutostart === true;
|
|
162
|
+
const alreadyInstalled = readiness.daemonInstalled;
|
|
163
|
+
let reconcileTransition = 'noop';
|
|
164
|
+
if (!skipAutostart && !alreadyInstalled) {
|
|
165
|
+
const reconcile = reconcileDaemonAutostart({
|
|
166
|
+
projectRoot,
|
|
167
|
+
enabledScheduleCount: await countEnabledSchedules(),
|
|
168
|
+
skip: false,
|
|
169
|
+
});
|
|
170
|
+
emitReconcileResult(reconcile);
|
|
171
|
+
reconcileTransition = reconcile.transition;
|
|
172
|
+
}
|
|
173
|
+
const serviceState = reconcileTransition === 'installed' || alreadyInstalled
|
|
174
|
+
? 'installed'
|
|
175
|
+
: 'not installed';
|
|
98
176
|
if (ctx.flags.format === 'json') {
|
|
99
177
|
output.printJson(record);
|
|
100
178
|
return { success: true, data: record };
|
|
@@ -109,7 +187,7 @@ const createCommand = {
|
|
|
109
187
|
at ? `At: ${at}` : null,
|
|
110
188
|
`Next Cast: ${new Date(nextRunAt).toLocaleString()}`,
|
|
111
189
|
`Daemon: ${readiness.daemonRunning ? 'running' : 'not running'}`,
|
|
112
|
-
`Service: ${
|
|
190
|
+
`Service: ${serviceState}`,
|
|
113
191
|
].filter(Boolean).join('\n'), 'Scheduled Spell');
|
|
114
192
|
return { success: true, data: record };
|
|
115
193
|
},
|
|
@@ -127,61 +205,146 @@ const scheduleListCommand = {
|
|
|
127
205
|
aliases: ['ls'],
|
|
128
206
|
description: 'List all scheduled spells',
|
|
129
207
|
action: async (ctx) => {
|
|
208
|
+
let raw;
|
|
130
209
|
try {
|
|
131
|
-
|
|
132
|
-
namespace: NAMESPACE_SCHEDULES,
|
|
133
|
-
});
|
|
134
|
-
// Single-pass: parse + transform for display
|
|
135
|
-
const schedules = [];
|
|
136
|
-
for (const r of result.results ?? []) {
|
|
137
|
-
try {
|
|
138
|
-
const parsed = typeof r.value === 'string' ? JSON.parse(r.value) : r.value;
|
|
139
|
-
if (parsed) {
|
|
140
|
-
schedules.push({
|
|
141
|
-
id: parsed.id,
|
|
142
|
-
spellName: parsed.spellName,
|
|
143
|
-
timing: parsed.cron || parsed.interval || parsed.at || '-',
|
|
144
|
-
nextRun: parsed.nextRunAt ? new Date(parsed.nextRunAt).toLocaleString() : '-',
|
|
145
|
-
enabled: parsed.enabled,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
output.printWarning(`Skipped malformed schedule record: ${r.key}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (ctx.flags.format === 'json') {
|
|
154
|
-
output.printJson(schedules);
|
|
155
|
-
return { success: true, data: schedules };
|
|
156
|
-
}
|
|
157
|
-
if (schedules.length === 0) {
|
|
158
|
-
output.writeln();
|
|
159
|
-
output.printInfo('No scheduled spells');
|
|
160
|
-
return { success: true, data: [] };
|
|
161
|
-
}
|
|
162
|
-
output.writeln();
|
|
163
|
-
output.writeln(output.bold('Scheduled Spells'));
|
|
164
|
-
output.writeln();
|
|
165
|
-
output.printTable({ columns: SCHEDULE_COLUMNS, data: schedules });
|
|
166
|
-
output.writeln();
|
|
167
|
-
output.printInfo(`Total: ${schedules.length} schedule(s)`);
|
|
168
|
-
return { success: true, data: schedules };
|
|
210
|
+
raw = await loadNamespaceValues(NAMESPACE_SCHEDULES);
|
|
169
211
|
}
|
|
170
212
|
catch (error) {
|
|
171
213
|
return handleMCPError(error, 'list schedules');
|
|
172
214
|
}
|
|
215
|
+
const schedules = raw.map(parsed => ({
|
|
216
|
+
id: parsed.id,
|
|
217
|
+
spellName: parsed.spellName,
|
|
218
|
+
timing: parsed.cron || parsed.interval || parsed.at || '-',
|
|
219
|
+
nextRun: parsed.nextRunAt ? new Date(parsed.nextRunAt).toLocaleString() : '-',
|
|
220
|
+
enabled: parsed.enabled,
|
|
221
|
+
}));
|
|
222
|
+
if (ctx.flags.format === 'json') {
|
|
223
|
+
output.printJson(schedules);
|
|
224
|
+
return { success: true, data: schedules };
|
|
225
|
+
}
|
|
226
|
+
if (schedules.length === 0) {
|
|
227
|
+
output.writeln();
|
|
228
|
+
output.printInfo('No scheduled spells');
|
|
229
|
+
return { success: true, data: [] };
|
|
230
|
+
}
|
|
231
|
+
output.writeln();
|
|
232
|
+
output.writeln(output.bold('Scheduled Spells'));
|
|
233
|
+
output.writeln();
|
|
234
|
+
output.printTable({ columns: SCHEDULE_COLUMNS, data: schedules });
|
|
235
|
+
output.writeln();
|
|
236
|
+
output.printInfo(`Total: ${schedules.length} schedule(s)`);
|
|
237
|
+
return { success: true, data: schedules };
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
// ── Schedule Executions ───────────────────────────────────────────────────────
|
|
241
|
+
const EXECUTION_COLUMNS = [
|
|
242
|
+
{ key: 'startedAt', header: 'Started', width: 22 },
|
|
243
|
+
{ key: 'spellName', header: 'Spell', width: 20 },
|
|
244
|
+
{ key: 'status', header: 'Status', width: 10 },
|
|
245
|
+
{ key: 'duration', header: 'Duration', width: 10 },
|
|
246
|
+
{ key: 'manualRun', header: 'Manual', width: 7 },
|
|
247
|
+
{ key: 'scheduleId', header: 'Schedule', width: 30 },
|
|
248
|
+
];
|
|
249
|
+
function formatExecutionRow(parsed) {
|
|
250
|
+
const completed = typeof parsed.completedAt === 'number';
|
|
251
|
+
let status;
|
|
252
|
+
if (!completed) {
|
|
253
|
+
status = output.warning('running');
|
|
254
|
+
}
|
|
255
|
+
else if (parsed.success === true) {
|
|
256
|
+
status = output.success('success');
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
status = output.error('failed');
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
id: String(parsed.id ?? ''),
|
|
263
|
+
scheduleId: String(parsed.scheduleId ?? ''),
|
|
264
|
+
spellName: String(parsed.spellName ?? ''),
|
|
265
|
+
startedAt: typeof parsed.startedAt === 'number'
|
|
266
|
+
? new Date(parsed.startedAt).toLocaleString()
|
|
267
|
+
: '-',
|
|
268
|
+
status,
|
|
269
|
+
duration: typeof parsed.duration === 'number' ? `${parsed.duration}ms` : '-',
|
|
270
|
+
manualRun: parsed.manualRun === true ? 'yes' : '',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const executionsCommand = {
|
|
274
|
+
name: 'executions',
|
|
275
|
+
aliases: ['exec', 'history'],
|
|
276
|
+
description: 'Show recent scheduled spell executions',
|
|
277
|
+
options: [
|
|
278
|
+
{ name: 'schedule', short: 's', description: 'Filter by schedule ID', type: 'string' },
|
|
279
|
+
{ name: 'limit', short: 'l', description: `Max rows to return (default ${DEFAULT_EXECUTIONS_LIMIT})`, type: 'number' },
|
|
280
|
+
],
|
|
281
|
+
examples: [
|
|
282
|
+
{ command: 'moflo spell schedule executions', description: 'Most recent executions across all schedules' },
|
|
283
|
+
{ command: 'moflo spell schedule executions --schedule sched-adhoc-123', description: 'Filter by schedule ID' },
|
|
284
|
+
{ command: 'moflo spell schedule executions --limit 25', description: 'Show last 25 executions' },
|
|
285
|
+
],
|
|
286
|
+
action: async (ctx) => {
|
|
287
|
+
const scheduleFilter = ctx.flags.schedule;
|
|
288
|
+
const rawLimit = ctx.flags.limit;
|
|
289
|
+
const limit = typeof rawLimit === 'number' && rawLimit > 0
|
|
290
|
+
? Math.floor(rawLimit)
|
|
291
|
+
: DEFAULT_EXECUTIONS_LIMIT;
|
|
292
|
+
let raw;
|
|
293
|
+
try {
|
|
294
|
+
raw = await loadNamespaceValues(NAMESPACE_EXECUTIONS);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
return handleMCPError(error, 'list executions');
|
|
298
|
+
}
|
|
299
|
+
const parsed = raw.filter(v => typeof v.startedAt === 'number');
|
|
300
|
+
const filtered = scheduleFilter
|
|
301
|
+
? parsed.filter(e => e.scheduleId === scheduleFilter)
|
|
302
|
+
: parsed;
|
|
303
|
+
filtered.sort((a, b) => b.startedAt - a.startedAt);
|
|
304
|
+
const truncated = filtered.slice(0, limit);
|
|
305
|
+
if (ctx.flags.format === 'json') {
|
|
306
|
+
output.printJson(truncated);
|
|
307
|
+
return { success: true, data: truncated };
|
|
308
|
+
}
|
|
309
|
+
if (truncated.length === 0) {
|
|
310
|
+
output.writeln();
|
|
311
|
+
output.printInfo(scheduleFilter
|
|
312
|
+
? `No executions for schedule ${scheduleFilter}`
|
|
313
|
+
: 'No scheduled spell executions yet');
|
|
314
|
+
return { success: true, data: [] };
|
|
315
|
+
}
|
|
316
|
+
const rows = truncated.map(formatExecutionRow);
|
|
317
|
+
output.writeln();
|
|
318
|
+
output.writeln(output.bold(scheduleFilter
|
|
319
|
+
? `Executions for ${scheduleFilter}`
|
|
320
|
+
: 'Recent Scheduled Executions'));
|
|
321
|
+
output.writeln();
|
|
322
|
+
output.printTable({ columns: EXECUTION_COLUMNS, data: rows });
|
|
323
|
+
output.writeln();
|
|
324
|
+
output.printInfo(filtered.length > truncated.length
|
|
325
|
+
? `Showing ${truncated.length} of ${filtered.length} execution(s)`
|
|
326
|
+
: `Total: ${truncated.length} execution(s)`);
|
|
327
|
+
return { success: true, data: truncated };
|
|
173
328
|
},
|
|
174
329
|
};
|
|
175
330
|
// ── Schedule Cancel ───────────────────────────────────────────────────────────
|
|
176
331
|
const cancelCommand = {
|
|
177
332
|
name: 'cancel',
|
|
178
333
|
description: 'Cancel (disable) a scheduled spell',
|
|
334
|
+
options: [
|
|
335
|
+
{ name: 'keep-autostart', description: 'Keep the OS login service registered even if no schedules remain', type: 'boolean' },
|
|
336
|
+
],
|
|
337
|
+
examples: [
|
|
338
|
+
{ command: 'moflo spell schedule cancel sched-adhoc-123', description: 'Cancel and auto-uninstall daemon service if no schedules remain' },
|
|
339
|
+
{ command: 'moflo spell schedule cancel sched-adhoc-123 --keep-autostart', description: 'Cancel but keep the OS login service registered' },
|
|
340
|
+
],
|
|
179
341
|
action: async (ctx) => {
|
|
180
342
|
const scheduleId = ctx.args[0];
|
|
181
343
|
if (!scheduleId) {
|
|
182
344
|
output.printError('Schedule ID is required');
|
|
183
345
|
return { success: false, exitCode: 1 };
|
|
184
346
|
}
|
|
347
|
+
let updated;
|
|
185
348
|
try {
|
|
186
349
|
// Fetch the current schedule
|
|
187
350
|
const fetchResult = await callMCPTool(TOOL_MEMORY_RETRIEVE, {
|
|
@@ -195,19 +358,42 @@ const cancelCommand = {
|
|
|
195
358
|
const schedule = typeof fetchResult.value === 'string'
|
|
196
359
|
? JSON.parse(fetchResult.value)
|
|
197
360
|
: fetchResult.value;
|
|
198
|
-
// Disable it
|
|
199
|
-
|
|
200
|
-
|
|
361
|
+
// Disable it. upsert:true is critical — the cancel writes back to an
|
|
362
|
+
// existing key, and the historic default (insert-only) silently failed
|
|
363
|
+
// with a UNIQUE constraint violation on (namespace, key) — see #962.
|
|
364
|
+
updated = { ...schedule, enabled: false };
|
|
365
|
+
const storeResult = await callMCPTool(TOOL_MEMORY_STORE, {
|
|
201
366
|
namespace: NAMESPACE_SCHEDULES,
|
|
202
367
|
key: scheduleId,
|
|
203
368
|
value: JSON.stringify(updated),
|
|
369
|
+
upsert: true,
|
|
204
370
|
});
|
|
205
|
-
|
|
206
|
-
|
|
371
|
+
if (storeResult.success === false) {
|
|
372
|
+
output.printError(`Failed to cancel schedule: ${storeResult.error ?? 'unknown error'}`);
|
|
373
|
+
return { success: false, exitCode: 1 };
|
|
374
|
+
}
|
|
207
375
|
}
|
|
208
376
|
catch (error) {
|
|
209
377
|
return handleMCPError(error, 'cancel schedule');
|
|
210
378
|
}
|
|
379
|
+
output.printSuccess(`Schedule ${scheduleId} cancelled`);
|
|
380
|
+
// Reconcile OS-native autostart against the new enabled-schedule count.
|
|
381
|
+
// 1→0 uninstalls the login service; everything else is a no-op.
|
|
382
|
+
// Short-circuit: a fresh cancel can only ever trigger an uninstall (count
|
|
383
|
+
// just went down). If the service isn't currently installed, the reconcile
|
|
384
|
+
// is a guaranteed noop — skip the count fetch entirely.
|
|
385
|
+
// Note: parser normalises --keep-autostart to ctx.flags.keepAutostart (#787).
|
|
386
|
+
const projectRoot = ctx.cwd || process.cwd();
|
|
387
|
+
const skipAutostart = ctx.flags.keepAutostart === true;
|
|
388
|
+
if (!skipAutostart && isDaemonInstalled(projectRoot)) {
|
|
389
|
+
const reconcile = reconcileDaemonAutostart({
|
|
390
|
+
projectRoot,
|
|
391
|
+
enabledScheduleCount: await countEnabledSchedules(),
|
|
392
|
+
skip: false,
|
|
393
|
+
});
|
|
394
|
+
emitReconcileResult(reconcile);
|
|
395
|
+
}
|
|
396
|
+
return { success: true, data: updated };
|
|
211
397
|
},
|
|
212
398
|
};
|
|
213
399
|
// ── Schedule Command (parent) ─────────────────────────────────────────────────
|
|
@@ -215,10 +401,11 @@ const SCHEDULE_DOCS_URL = 'https://github.com/eric-cielo/moflo/blob/main/docs/SP
|
|
|
215
401
|
export const scheduleCommand = {
|
|
216
402
|
name: 'schedule',
|
|
217
403
|
description: `Manage scheduled spells (full reference: ${SCHEDULE_DOCS_URL})`,
|
|
218
|
-
subcommands: [createCommand, scheduleListCommand, cancelCommand],
|
|
404
|
+
subcommands: [createCommand, scheduleListCommand, executionsCommand, cancelCommand],
|
|
219
405
|
examples: [
|
|
220
406
|
{ command: 'moflo spell schedule create -n audit --cron "0 9 * * *"', description: 'Schedule daily audit' },
|
|
221
407
|
{ command: 'moflo spell schedule list', description: 'List all schedules' },
|
|
408
|
+
{ command: 'moflo spell schedule executions --schedule <id>', description: 'Show execution audit trail' },
|
|
222
409
|
{ command: 'moflo spell schedule cancel <id>', description: 'Cancel a schedule' },
|
|
223
410
|
],
|
|
224
411
|
action: async () => {
|
|
@@ -229,9 +416,10 @@ export const scheduleCommand = {
|
|
|
229
416
|
output.writeln();
|
|
230
417
|
output.writeln('Subcommands:');
|
|
231
418
|
output.printList([
|
|
232
|
-
`${output.highlight('create')}
|
|
233
|
-
`${output.highlight('list')}
|
|
234
|
-
`${output.highlight('
|
|
419
|
+
`${output.highlight('create')} - Create a scheduled spell`,
|
|
420
|
+
`${output.highlight('list')} - List all scheduled spells`,
|
|
421
|
+
`${output.highlight('executions')} - Show recent execution history`,
|
|
422
|
+
`${output.highlight('cancel')} - Cancel (disable) a schedule`,
|
|
235
423
|
]);
|
|
236
424
|
output.writeln();
|
|
237
425
|
output.writeln(`Full reference: ${SCHEDULE_DOCS_URL}`);
|
|
@@ -33,15 +33,15 @@ const DEFAULT_CONFIG = {
|
|
|
33
33
|
useMockEmbeddings: false,
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
|
-
* Agent mapping for routing
|
|
36
|
+
* Agent mapping for routing — keys must match agents shipped in .claude/agents/
|
|
37
|
+
* (or the canonical AgentType union).
|
|
37
38
|
*/
|
|
38
39
|
const AGENT_PATTERNS = {
|
|
39
|
-
'security-
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'memory-specialist': /memory|agentdb|hnsw|vector|embedding/i,
|
|
40
|
+
'security-auditor': /security|auth|cve|vuln|encrypt|password|token/i,
|
|
41
|
+
'tester': /test|spec|mock|coverage|tdd|assert/i,
|
|
42
|
+
'architect': /architect|design|ddd|domain|refactor|struct|perf|optim|fast|memory|cache|speed|slow/i,
|
|
43
|
+
'coordinator': /swarm|agent|coordinate|orchestrat|parallel|hive/i,
|
|
44
|
+
'researcher': /memory|agentdb|hnsw|vector|embedding|recall|persist/i,
|
|
45
45
|
'coder': /fix|bug|implement|create|add|build|error|code/i,
|
|
46
46
|
'reviewer': /review|quality|lint|check|audit/i,
|
|
47
47
|
};
|
|
@@ -59,41 +59,28 @@ export const INTERNAL_SKILLS = [
|
|
|
59
59
|
/**
|
|
60
60
|
* Commands to copy based on configuration
|
|
61
61
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
monitoring: [],
|
|
69
|
-
optimization: [],
|
|
70
|
-
sparc: ['sparc'],
|
|
71
|
-
};
|
|
62
|
+
// Empty after #949 retired the last shipped slash commands. moflo's user-facing
|
|
63
|
+
// functionality now lives entirely in `.claude/skills/` (canonical) and
|
|
64
|
+
// `.claude/agents/` (subagent surface). Kept as an empty-record placeholder
|
|
65
|
+
// because callers iterate `Object.entries(COMMANDS_MAP)` to gate per-category
|
|
66
|
+
// install — see init-copy-maps.test.ts for the iteration contract.
|
|
67
|
+
const COMMANDS_MAP = {};
|
|
72
68
|
/**
|
|
73
69
|
* Agents to copy based on configuration. Exported for integrity tests.
|
|
70
|
+
*
|
|
71
|
+
* Each value is a directory name under `.claude/agents/` that ships in the
|
|
72
|
+
* moflo package. After #932 retired ~50 ruflo-aspirational agents, the set
|
|
73
|
+
* is narrowed to actual development specialties Claude is likely to invoke.
|
|
74
74
|
*/
|
|
75
75
|
export const AGENTS_MAP = {
|
|
76
76
|
core: ['core'],
|
|
77
|
-
consensus: ['consensus'],
|
|
78
|
-
github: ['github'],
|
|
79
|
-
hiveMind: ['hive-mind'],
|
|
80
|
-
sparc: ['sparc'],
|
|
81
|
-
swarm: ['swarm'],
|
|
82
|
-
browser: ['browser'], // agent-browser integration
|
|
83
|
-
// V3-specific agents
|
|
84
|
-
v3: ['v3'],
|
|
85
|
-
optimization: ['optimization'],
|
|
86
|
-
testing: ['testing'],
|
|
87
77
|
analysis: ['analysis'],
|
|
88
78
|
architecture: ['architecture'],
|
|
79
|
+
custom: ['custom'],
|
|
89
80
|
development: ['development'],
|
|
90
81
|
devops: ['devops'],
|
|
91
82
|
documentation: ['documentation'],
|
|
92
|
-
|
|
93
|
-
goal: ['goal'],
|
|
94
|
-
sona: ['sona'],
|
|
95
|
-
data: ['data'],
|
|
96
|
-
custom: ['custom'],
|
|
83
|
+
security: ['security'],
|
|
97
84
|
};
|
|
98
85
|
/**
|
|
99
86
|
* Directory structure to create
|
|
@@ -1521,39 +1508,24 @@ npx moflo swarm monitor
|
|
|
1521
1508
|
### Core Development (5)
|
|
1522
1509
|
\`coder\`, \`reviewer\`, \`tester\`, \`planner\`, \`researcher\`
|
|
1523
1510
|
|
|
1524
|
-
###
|
|
1525
|
-
\`
|
|
1526
|
-
|
|
1527
|
-
### Swarm Coordination (5)
|
|
1528
|
-
\`hierarchical-coordinator\`, \`mesh-coordinator\`, \`adaptive-coordinator\`, \`collective-intelligence-coordinator\`, \`swarm-memory-manager\`
|
|
1529
|
-
|
|
1530
|
-
### Consensus & Distributed (7)
|
|
1531
|
-
\`byzantine-coordinator\`, \`raft-manager\`, \`gossip-coordinator\`, \`consensus-builder\`, \`crdt-synchronizer\`, \`quorum-manager\`, \`security-manager\`
|
|
1532
|
-
|
|
1533
|
-
### Performance & Optimization (5)
|
|
1534
|
-
\`perf-analyzer\`, \`performance-benchmarker\`, \`task-orchestrator\`, \`memory-coordinator\`, \`smart-agent\`
|
|
1535
|
-
|
|
1536
|
-
### GitHub & Repository (9)
|
|
1537
|
-
\`github-modes\`, \`pr-manager\`, \`code-review-swarm\`, \`issue-tracker\`, \`release-manager\`, \`workflow-automation\`, \`project-board-sync\`, \`repo-architect\`, \`multi-repo-swarm\`
|
|
1538
|
-
|
|
1539
|
-
### SPARC Methodology (6)
|
|
1540
|
-
\`sparc-coord\`, \`sparc-coder\`, \`specification\`, \`pseudocode\`, \`architecture\`, \`refinement\`
|
|
1511
|
+
### Code Analysis (2)
|
|
1512
|
+
\`code-analyzer\`, \`analyst\`
|
|
1541
1513
|
|
|
1542
|
-
### Specialized Development (
|
|
1543
|
-
\`backend-dev\`, \`
|
|
1514
|
+
### Specialized Development (5)
|
|
1515
|
+
\`backend-dev\`, \`frontend-dev\`, \`database-dev\`, \`cicd-engineer\`, \`api-docs\`
|
|
1544
1516
|
|
|
1545
|
-
###
|
|
1546
|
-
\`
|
|
1517
|
+
### Security (1)
|
|
1518
|
+
\`security-auditor\`
|
|
1547
1519
|
|
|
1548
1520
|
### Agent Routing by Task
|
|
1549
|
-
| Task Type | Recommended Agents |
|
|
1550
|
-
|
|
1551
|
-
| Bug Fix | researcher, coder, tester |
|
|
1552
|
-
| New Feature |
|
|
1553
|
-
| Refactoring |
|
|
1554
|
-
|
|
|
1555
|
-
|
|
|
1556
|
-
| Docs | researcher, api-docs |
|
|
1521
|
+
| Task Type | Recommended Agents |
|
|
1522
|
+
|-----------|--------------------|
|
|
1523
|
+
| Bug Fix | researcher, coder, tester |
|
|
1524
|
+
| New Feature | planner, coder, tester, reviewer |
|
|
1525
|
+
| Refactoring | coder, reviewer |
|
|
1526
|
+
| Security | security-auditor, reviewer |
|
|
1527
|
+
| GitHub workflow | coder, reviewer (with \`gh\` CLI) |
|
|
1528
|
+
| Docs | researcher, api-docs |
|
|
1557
1529
|
|
|
1558
1530
|
---
|
|
1559
1531
|
|
|
@@ -207,7 +207,7 @@ var path = require('path');
|
|
|
207
207
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\\/([a-z])\\//i, '$1:/');
|
|
208
208
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
209
209
|
|
|
210
|
-
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} };
|
|
210
|
+
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {}, flMode: null, swarmInitialized: false, hiveInitialized: false };
|
|
211
211
|
|
|
212
212
|
function readState() {
|
|
213
213
|
try {
|
|
@@ -255,7 +255,7 @@ function writeState(s) {
|
|
|
255
255
|
|
|
256
256
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
257
257
|
function loadGateConfig() {
|
|
258
|
-
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true };
|
|
258
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true };
|
|
259
259
|
try {
|
|
260
260
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
261
261
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -266,6 +266,7 @@ function loadGateConfig() {
|
|
|
266
266
|
if (/testing_gate:\\s*false/i.test(content)) defaults.testing_gate = false;
|
|
267
267
|
if (/simplify_gate:\\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
268
268
|
if (/learnings_gate:\\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
269
|
+
if (/swarm_invocation_gate:\\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
|
|
269
270
|
}
|
|
270
271
|
} catch (e) { /* use defaults */ }
|
|
271
272
|
return defaults;
|
|
@@ -305,6 +306,21 @@ var NS_NAV_RES = [
|
|
|
305
306
|
/\\b(class|function|method|component|service|entity|module)\\b/,
|
|
306
307
|
];
|
|
307
308
|
|
|
309
|
+
// Detect whether the current prompt invoked /fl or /flo with a swarm/hive flag
|
|
310
|
+
// (#952). When set, check-before-agent BLOCKS the Agent spawn until the matching
|
|
311
|
+
// MCP init has been recorded — the user explicitly opted in to the protected
|
|
312
|
+
// coordination surface, so falling back to raw Agent dispatch silently regresses
|
|
313
|
+
// headline moflo product capability.
|
|
314
|
+
//
|
|
315
|
+
// SYNC: duplicated verbatim in bin/gate.cjs.
|
|
316
|
+
function detectFlMode(promptText) {
|
|
317
|
+
var p = promptText || '';
|
|
318
|
+
if (!/^\\s*\\/(?:fl|flo)\\b/i.test(p)) return null;
|
|
319
|
+
if (/(?:^|\\s)(?:-s|--swarm)\\b/.test(p)) return 'swarm';
|
|
320
|
+
if (/(?:^|\\s)(?:-h|--hive)\\b/.test(p)) return 'hive';
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
308
324
|
function classifyNamespaceHint(promptText) {
|
|
309
325
|
var lower = (promptText || '').toLowerCase();
|
|
310
326
|
if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
@@ -334,6 +350,11 @@ function applyPromptStateReset(state, promptText) {
|
|
|
334
350
|
// Per-actor emission tracking — fresh window each prompt so subagents that
|
|
335
351
|
// spawn their own agents still see the hint on their first check-before-agent.
|
|
336
352
|
state.lastNamespaceHintEmittedBy = {};
|
|
353
|
+
// #952 — derive flMode from the user prompt and reset the matching init
|
|
354
|
+
// flag. Each /fl invocation must call its protected MCP init.
|
|
355
|
+
state.flMode = detectFlMode(promptText);
|
|
356
|
+
state.swarmInitialized = false;
|
|
357
|
+
state.hiveInitialized = false;
|
|
337
358
|
}
|
|
338
359
|
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;
|
|
339
360
|
var EDIT_RESET_SKIP_BOTH_RE = /\\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^|[\\\\\\/])(CHANGELOG(?:\\.md)?|\\.env\\.example|package-lock\\.json|pnpm-lock\\.yaml|yarn\\.lock|bun\\.lockb)$/i;
|
|
@@ -373,6 +394,46 @@ switch (command) {
|
|
|
373
394
|
writeState(s);
|
|
374
395
|
}
|
|
375
396
|
}
|
|
397
|
+
// #952 — when /fl was invoked with -s/-h, the protected MCP init must run
|
|
398
|
+
// BEFORE any Agent spawn. Hard block: the user explicitly opted in to
|
|
399
|
+
// moflo's coordination surface, so silently dispatching Agent calls
|
|
400
|
+
// without mcp__moflo__swarm_init / mcp__moflo__hive-mind_init is the
|
|
401
|
+
// failure mode this gate exists to prevent (CLAUDE.md "⛔ Protected
|
|
402
|
+
// functionality"). Other Agent uses remain advisory.
|
|
403
|
+
if (config.swarm_invocation_gate) {
|
|
404
|
+
if (s.flMode === 'swarm' && !s.swarmInitialized) {
|
|
405
|
+
process.stderr.write('BLOCKED: /fl was invoked with -s/--swarm but mcp__moflo__swarm_init has not been called.\\n');
|
|
406
|
+
process.stderr.write('Run mcp__moflo__swarm_init first, then mcp__moflo__agent_spawn for each role, then dispatch Agent.\\n');
|
|
407
|
+
process.stderr.write('See .claude/skills/fl/execution-modes.md "SWARM mode" and CLAUDE.md "⛔ Protected functionality".\\n');
|
|
408
|
+
process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\\n');
|
|
409
|
+
process.exit(2);
|
|
410
|
+
}
|
|
411
|
+
if (s.flMode === 'hive' && !s.hiveInitialized) {
|
|
412
|
+
process.stderr.write('BLOCKED: /fl was invoked with -h/--hive but mcp__moflo__hive-mind_init has not been called.\\n');
|
|
413
|
+
process.stderr.write('Run mcp__moflo__hive-mind_init first, then dispatch Agent or hive-mind workers.\\n');
|
|
414
|
+
process.stderr.write('See .claude/skills/fl/execution-modes.md "HIVE-MIND mode" and CLAUDE.md "⛔ Protected functionality".\\n');
|
|
415
|
+
process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\\n');
|
|
416
|
+
process.exit(2);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case 'record-swarm-init': {
|
|
422
|
+
// #952 — wired to mcp__moflo__swarm_init PostToolUse.
|
|
423
|
+
var s = readState();
|
|
424
|
+
if (!s.swarmInitialized) {
|
|
425
|
+
s.swarmInitialized = true;
|
|
426
|
+
writeState(s);
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case 'record-hive-init': {
|
|
431
|
+
// #952 — wired to mcp__moflo__hive-mind_init PostToolUse.
|
|
432
|
+
var s = readState();
|
|
433
|
+
if (!s.hiveInitialized) {
|
|
434
|
+
s.hiveInitialized = true;
|
|
435
|
+
writeState(s);
|
|
436
|
+
}
|
|
376
437
|
break;
|
|
377
438
|
}
|
|
378
439
|
case 'check-before-scan': {
|
|
@@ -533,7 +594,9 @@ switch (command) {
|
|
|
533
594
|
break;
|
|
534
595
|
}
|
|
535
596
|
case 'session-reset': {
|
|
536
|
-
|
|
597
|
+
// Derive from STATE_DEFAULTS so adding a new state field requires only one
|
|
598
|
+
// edit (the defaults object).
|
|
599
|
+
writeState(Object.assign({}, STATE_DEFAULTS, { sessionStart: new Date().toISOString() }));
|
|
537
600
|
break;
|
|
538
601
|
}
|
|
539
602
|
default:
|