moflo 4.12.3 → 4.12.4-rc.2

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.
Files changed (98) hide show
  1. package/.claude/agents/core/coder.md +2 -3
  2. package/.claude/agents/core/researcher.md +3 -4
  3. package/.claude/agents/core/reviewer.md +6 -8
  4. package/.claude/guidance/shipped/moflo-spell-engine.md +13 -10
  5. package/.claude/helpers/gate-hook.mjs +67 -2
  6. package/.claude/helpers/gate.cjs +173 -12
  7. package/.claude/helpers/hook-handler.cjs +0 -0
  8. package/.claude/helpers/prompt-hook.mjs +0 -0
  9. package/.claude/skills/connector-builder/SKILL.md +5 -4
  10. package/.claude/skills/fl/phases.md +1 -1
  11. package/.claude/skills/fl/sdd.md +1 -1
  12. package/.claude/skills/spell-builder/SKILL.md +5 -4
  13. package/.claude/skills/spell-builder/architecture.md +4 -4
  14. package/.claude/skills/spell-builder/connectors/github-cli/README.md +22 -17
  15. package/.claude/skills/spell-builder/connectors/http/README.md +18 -11
  16. package/.claude/skills/spell-builder/connectors/local-outlook/README.md +8 -7
  17. package/.claude/skills/spell-builder/connectors/playwright/README.md +10 -8
  18. package/.claude/skills/spell-builder/steps/agent/README.md +25 -21
  19. package/.claude/skills/verify/SKILL.md +24 -3
  20. package/bin/gate-hook.mjs +67 -2
  21. package/bin/gate.cjs +173 -12
  22. package/bin/generate-code-map.mjs +70 -17
  23. package/bin/index-all.mjs +38 -0
  24. package/bin/index-guidance.mjs +8 -41
  25. package/bin/index-patterns.mjs +2 -2
  26. package/bin/index-tests.mjs +6 -4
  27. package/bin/lib/guidance-config.mjs +119 -0
  28. package/bin/lib/index-fingerprint.mjs +0 -0
  29. package/dist/src/cli/commands/agent.js +1 -89
  30. package/dist/src/cli/commands/analyze.js +1 -1
  31. package/dist/src/cli/commands/benchmark.js +11 -8
  32. package/dist/src/cli/commands/claims.js +2 -2
  33. package/dist/src/cli/commands/completions.js +1 -1
  34. package/dist/src/cli/commands/config.js +256 -96
  35. package/dist/src/cli/commands/deployment.js +6 -6
  36. package/dist/src/cli/commands/diagnose.js +1 -1
  37. package/dist/src/cli/commands/doctor-checks-config.js +18 -22
  38. package/dist/src/cli/commands/doctor-checks-deep.js +1 -1
  39. package/dist/src/cli/commands/doctor-fixes.js +36 -11
  40. package/dist/src/cli/commands/doctor.js +1 -1
  41. package/dist/src/cli/commands/embeddings.js +64 -23
  42. package/dist/src/cli/commands/github.js +1 -1
  43. package/dist/src/cli/commands/guidance.js +10 -10
  44. package/dist/src/cli/commands/hive-mind.js +8 -70
  45. package/dist/src/cli/commands/hooks.js +32 -219
  46. package/dist/src/cli/commands/mcp.js +5 -2
  47. package/dist/src/cli/commands/memory.js +59 -46
  48. package/dist/src/cli/commands/neural.js +21 -21
  49. package/dist/src/cli/commands/performance.js +14 -7
  50. package/dist/src/cli/commands/plugins.js +2 -2
  51. package/dist/src/cli/commands/providers.js +2 -2
  52. package/dist/src/cli/commands/retire.js +1 -1
  53. package/dist/src/cli/commands/route.js +1 -1
  54. package/dist/src/cli/commands/security.js +4 -4
  55. package/dist/src/cli/commands/status.js +18 -13
  56. package/dist/src/cli/commands/task.js +8 -0
  57. package/dist/src/cli/config/cli-config-store.js +326 -0
  58. package/dist/src/cli/hooks/workers/index.js +15 -9
  59. package/dist/src/cli/index.js +43 -33
  60. package/dist/src/cli/init/claudemd-generator.js +3 -3
  61. package/dist/src/cli/init/helpers-generator.js +203 -20
  62. package/dist/src/cli/init/moflo-yaml-template.js +11 -3
  63. package/dist/src/cli/init/settings-generator.js +10 -2
  64. package/dist/src/cli/mcp-client.js +12 -2
  65. package/dist/src/cli/mcp-server.js +5 -23
  66. package/dist/src/cli/mcp-tools/analysis-tools.js +103 -0
  67. package/dist/src/cli/mcp-tools/coverage-tools.js +206 -0
  68. package/dist/src/cli/mcp-tools/hive-mind-tools.js +71 -0
  69. package/dist/src/cli/mcp-tools/hooks-tools.js +25 -9
  70. package/dist/src/cli/mcp-tools/memory-admin-tools.js +538 -0
  71. package/dist/src/cli/mcp-tools/memory-tools.js +26 -5
  72. package/dist/src/cli/mcp-tools/neural-tools.js +56 -87
  73. package/dist/src/cli/mcp-tools/performance-tools.js +76 -39
  74. package/dist/src/cli/mcp-tools/progress-tools.js +142 -0
  75. package/dist/src/cli/mcp-tools/security-tools.js +1 -1
  76. package/dist/src/cli/mcp-tools/session-tools.js +116 -0
  77. package/dist/src/cli/mcp-tools/synthetic.js +74 -0
  78. package/dist/src/cli/mcp-tools/system-tools.js +126 -78
  79. package/dist/src/cli/mcp-tools/task-tools.js +101 -1
  80. package/dist/src/cli/movector/coverage-router.js +48 -6
  81. package/dist/src/cli/movector/diff-classifier.js +8 -6
  82. package/dist/src/cli/movector/flash-attention.js +1 -1
  83. package/dist/src/cli/movector/vector-db.js +1 -1
  84. package/dist/src/cli/parser.js +81 -12
  85. package/dist/src/cli/services/daemon-lock.js +5 -2
  86. package/dist/src/cli/services/hook-block-hash.js +3 -1
  87. package/dist/src/cli/services/moflo-version.js +62 -0
  88. package/dist/src/cli/services/movector-training.js +1 -1
  89. package/dist/src/cli/services/spell-gate.js +6 -4
  90. package/dist/src/cli/services/worker-daemon.js +37 -7
  91. package/dist/src/cli/shared/utils/load-average.js +59 -0
  92. package/dist/src/cli/spells/commands/agent-command.js +55 -25
  93. package/dist/src/cli/suggest.js +1 -1
  94. package/dist/src/cli/swarm/swarm-persistence.js +112 -0
  95. package/dist/src/cli/swarm/unified-coordinator.js +118 -3
  96. package/dist/src/cli/version.js +1 -1
  97. package/package.json +5 -3
  98. package/dist/src/cli/mcp-tools/github-tools.js +0 -318
@@ -2,7 +2,7 @@
2
2
  * V3 CLI Completions Command
3
3
  * Shell completions for bash, zsh, fish, powershell
4
4
  *
5
- * Created with motailz.com
5
+ * Created with cielolimitada.com
6
6
  */
7
7
  import { output } from '../output.js';
8
8
  // Get all top-level commands for completions
@@ -1,9 +1,58 @@
1
1
  /**
2
2
  * V3 CLI Config Command
3
3
  * Configuration management
4
+ *
5
+ * Every subcommand here reads and writes a real file — see
6
+ * `../config/cli-config-store.ts` for the store and for why that is worth
7
+ * stating: this family used to print "Creating ..." and "Configuration
8
+ * updated" without touching the filesystem, which made `flo doctor --fix`
9
+ * report the `Config File` warning as fixed forever.
10
+ *
11
+ * `show` and `generate` are the odd pair out: they operate on **moflo.yaml**
12
+ * via `../config/moflo-config.ts`, not on the JSON config.
4
13
  */
14
+ import { existsSync, readFileSync } from 'node:fs';
15
+ import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
5
16
  import { output } from '../output.js';
6
17
  import { confirm } from '../prompt.js';
18
+ import { resolveStateRoot } from '../services/project-root.js';
19
+ import { CliConfigParseError, RESETTABLE_SECTIONS, cliConfigPath, defaultCliConfig, findCliConfigFile, flattenConfig, getConfigValue, loadCliConfig, resetSection, saveCliConfig, setConfigValue, writeJsonFile, writeTextFile, } from '../config/cli-config-store.js';
20
+ /**
21
+ * Anchor config reads/writes at the state root — `resolveStateRoot`, not
22
+ * `findProjectRoot`, because this command CREATES `.moflo/` content and must
23
+ * land on the monorepo's canonical anchor rather than minting an island in a
24
+ * sub-workspace (#1315).
25
+ */
26
+ function configRoot(ctx) {
27
+ return resolveStateRoot({ cwd: ctx.cwd || process.cwd() });
28
+ }
29
+ /**
30
+ * Path relative to the project root, for display. Builds the `./` prefix from
31
+ * `path.sep` so Windows shows `.\.moflo\config.json` rather than a mixed
32
+ * `./.moflo\config.json`.
33
+ */
34
+ function displayPath(root, target) {
35
+ const rel = relative(root, target);
36
+ return rel && !rel.startsWith('..') ? `.${sep}${rel}` : target;
37
+ }
38
+ /**
39
+ * Load config, printing the parse error and returning `null` when the file on
40
+ * disk is corrupt. Callers exit 1 on `null` rather than silently falling back
41
+ * to defaults — a config that cannot be read is a failure, not a default.
42
+ */
43
+ function loadOrReport(root) {
44
+ try {
45
+ return loadCliConfig(root);
46
+ }
47
+ catch (err) {
48
+ if (err instanceof CliConfigParseError) {
49
+ output.printError(err.message);
50
+ output.writeln(output.dim(' Fix the JSON syntax, or run `flo config init --force` to rewrite it.'));
51
+ return null;
52
+ }
53
+ throw err;
54
+ }
55
+ }
7
56
  // Init configuration
8
57
  const initCommand = {
9
58
  name: 'init',
@@ -32,54 +81,27 @@ const initCommand = {
32
81
  action: async (ctx) => {
33
82
  const sparc = ctx.flags.sparc;
34
83
  const v3 = ctx.flags.v3;
84
+ const force = ctx.flags.force;
85
+ const root = configRoot(ctx);
86
+ const existing = findCliConfigFile(root);
87
+ if (existing && !force) {
88
+ output.printError(`Configuration already exists: ${displayPath(root, existing)}`);
89
+ output.writeln(output.dim(' Re-run with --force to overwrite it.'));
90
+ return { success: false, exitCode: 1 };
91
+ }
35
92
  output.writeln();
36
93
  output.printInfo('Initializing MoFlo configuration...');
37
94
  output.writeln();
38
- // Create default configuration
39
- const config = {
40
- version: '3.0.0',
41
- v3Mode: v3,
42
- sparc: sparc,
43
- agents: {
44
- defaultType: 'coder',
45
- maxConcurrent: 15,
46
- autoSpawn: true,
47
- timeout: 300
48
- },
49
- swarm: {
50
- topology: 'hybrid',
51
- maxAgents: 15,
52
- autoScale: true,
53
- coordinationStrategy: 'consensus'
54
- },
55
- memory: {
56
- backend: 'hybrid',
57
- path: './data/memory',
58
- cacheSize: 256,
59
- enableHNSW: true
60
- },
61
- mcp: {
62
- transport: 'stdio',
63
- autoStart: true,
64
- tools: 'all'
65
- },
66
- providers: [
67
- { name: 'anthropic', priority: 1, enabled: true },
68
- { name: 'openrouter', priority: 2, enabled: false },
69
- { name: 'ollama', priority: 3, enabled: false }
70
- ]
71
- };
72
- output.writeln(output.dim(' Creating claude-flow.config.json...'));
73
- output.writeln(output.dim(' Creating .moflo/ directory...'));
74
- if (sparc) {
75
- output.writeln(output.dim(' Initializing SPARC methodology...'));
76
- output.writeln(output.dim(' Creating SPARC workflow files...'));
77
- }
78
- if (v3) {
79
- output.writeln(output.dim(' Enabling V3 15-agent coordination...'));
80
- output.writeln(output.dim(' Configuring AgentDB integration...'));
81
- output.writeln(output.dim(' Setting up Flash Attention optimization...'));
95
+ const config = defaultCliConfig({ v3, sparc });
96
+ const target = existing ?? cliConfigPath(root);
97
+ try {
98
+ saveCliConfig(root, config, { path: target });
82
99
  }
100
+ catch (err) {
101
+ output.printError(`Failed to write ${displayPath(root, target)}: ${err.message}`);
102
+ return { success: false, exitCode: 1 };
103
+ }
104
+ output.writeln(output.dim(` Wrote ${displayPath(root, target)}`));
83
105
  output.writeln();
84
106
  output.printTable({
85
107
  columns: [
@@ -98,7 +120,7 @@ const initCommand = {
98
120
  });
99
121
  output.writeln();
100
122
  output.printSuccess('Configuration initialized');
101
- output.writeln(output.dim(' Config file: ./claude-flow.config.json'));
123
+ output.writeln(output.dim(` Config file: ${displayPath(root, target)}`));
102
124
  return { success: true, data: config };
103
125
  }
104
126
  };
@@ -120,39 +142,32 @@ const getCommand = {
120
142
  ],
121
143
  action: async (ctx) => {
122
144
  const key = ctx.flags.key || ctx.args[0];
123
- // Default config values (loaded from actual config when available)
124
- const configValues = {
125
- 'version': '3.0.0',
126
- 'v3Mode': true,
127
- 'swarm.topology': 'hybrid',
128
- 'swarm.maxAgents': 15,
129
- 'swarm.autoScale': true,
130
- 'memory.backend': 'hybrid',
131
- 'memory.cacheSize': 256,
132
- 'mcp.transport': 'stdio',
133
- 'agents.defaultType': 'coder',
134
- 'agents.maxConcurrent': 15
135
- };
145
+ const root = configRoot(ctx);
146
+ const loaded = loadOrReport(root);
147
+ if (!loaded)
148
+ return { success: false, exitCode: 1 };
149
+ const { config, path } = loaded;
136
150
  if (!key) {
137
- // Show all config
151
+ const configValues = flattenConfig(config);
138
152
  if (ctx.flags.format === 'json') {
139
153
  output.printJson(configValues);
140
154
  return { success: true, data: configValues };
141
155
  }
142
156
  output.writeln();
143
157
  output.writeln(output.bold('Current Configuration'));
158
+ output.writeln(output.dim(path ? ` ${displayPath(root, path)}` : ' (no config file — showing defaults)'));
144
159
  output.writeln();
145
160
  output.printTable({
146
161
  columns: [
147
- { key: 'key', header: 'Key', width: 25 },
162
+ { key: 'key', header: 'Key', width: 30 },
148
163
  { key: 'value', header: 'Value', width: 30 }
149
164
  ],
150
165
  data: Object.entries(configValues).map(([k, v]) => ({ key: k, value: String(v) }))
151
166
  });
152
167
  return { success: true, data: configValues };
153
168
  }
154
- const value = configValues[key];
155
- if (value === undefined) {
169
+ const { found, value } = getConfigValue(config, key);
170
+ if (!found) {
156
171
  output.printError(`Configuration key not found: ${key}`);
157
172
  return { success: false, exitCode: 1 };
158
173
  }
@@ -160,7 +175,7 @@ const getCommand = {
160
175
  output.printJson({ key, value });
161
176
  }
162
177
  else {
163
- output.writeln(`${key} = ${value}`);
178
+ output.writeln(`${key} = ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`);
164
179
  }
165
180
  return { success: true, data: { key, value } };
166
181
  }
@@ -170,19 +185,21 @@ const setCommand = {
170
185
  name: 'set',
171
186
  description: 'Set configuration value',
172
187
  options: [
188
+ // Not `required` at the parser level: these are also accepted
189
+ // positionally (`flo config set swarm.maxAgents 20`, this command's own
190
+ // documented example), and a parser-level requirement rejects that form
191
+ // before the action can read ctx.args. The action validates instead.
173
192
  {
174
193
  name: 'key',
175
194
  short: 'k',
176
195
  description: 'Configuration key',
177
- type: 'string',
178
- required: true
196
+ type: 'string'
179
197
  },
180
198
  {
181
199
  name: 'value',
182
200
  short: 'v',
183
201
  description: 'Configuration value',
184
- type: 'string',
185
- required: true
202
+ type: 'string'
186
203
  }
187
204
  ],
188
205
  examples: [
@@ -196,9 +213,27 @@ const setCommand = {
196
213
  output.printError('Both key and value are required');
197
214
  return { success: false, exitCode: 1 };
198
215
  }
199
- output.printInfo(`Setting ${key} = ${value}`);
200
- output.printSuccess('Configuration updated');
201
- return { success: true, data: { key, value } };
216
+ const root = configRoot(ctx);
217
+ const loaded = loadOrReport(root);
218
+ if (!loaded)
219
+ return { success: false, exitCode: 1 };
220
+ const { config } = loaded;
221
+ const result = setConfigValue(config, key, String(value));
222
+ if (!result.ok) {
223
+ output.printError(`Cannot set ${key}: ${result.error}`);
224
+ return { success: false, exitCode: 1 };
225
+ }
226
+ let target;
227
+ try {
228
+ target = saveCliConfig(root, config, { path: loaded.path ?? undefined });
229
+ }
230
+ catch (err) {
231
+ output.printError(`Failed to write configuration: ${err.message}`);
232
+ return { success: false, exitCode: 1 };
233
+ }
234
+ output.printInfo(`Setting ${key} = ${String(result.value)}`);
235
+ output.printSuccess(`Configuration updated (${displayPath(root, target)})`);
236
+ return { success: true, data: { key, value: result.value } };
202
237
  }
203
238
  };
204
239
  // List providers
@@ -230,12 +265,58 @@ const providersCommand = {
230
265
  }
231
266
  ],
232
267
  action: async (ctx) => {
233
- const providers = [
234
- { name: 'anthropic', model: 'claude-3-5-sonnet-20241022', priority: 1, enabled: true, status: 'Active' },
235
- { name: 'openrouter', model: 'claude-3.5-sonnet', priority: 2, enabled: false, status: 'Disabled' },
236
- { name: 'ollama', model: 'llama3.2', priority: 3, enabled: false, status: 'Disabled' },
237
- { name: 'gemini', model: 'gemini-2.0-flash', priority: 4, enabled: false, status: 'Disabled' }
238
- ];
268
+ const root = configRoot(ctx);
269
+ const loaded = loadOrReport(root);
270
+ if (!loaded)
271
+ return { success: false, exitCode: 1 };
272
+ const { config } = loaded;
273
+ const add = ctx.flags.add;
274
+ const remove = ctx.flags.remove;
275
+ const enable = ctx.flags.enable;
276
+ const disable = ctx.flags.disable;
277
+ const find = (name) => config.providers.find((p) => p.name.toLowerCase() === name.toLowerCase());
278
+ let mutated = false;
279
+ if (add) {
280
+ if (find(add)) {
281
+ output.printError(`Provider already exists: ${add}`);
282
+ return { success: false, exitCode: 1 };
283
+ }
284
+ const priority = config.providers.reduce((max, p) => Math.max(max, p.priority), 0) + 1;
285
+ config.providers.push({ name: add, priority, enabled: false });
286
+ mutated = true;
287
+ }
288
+ for (const [name, action] of [[remove, 'remove'], [enable, 'enable'], [disable, 'disable']]) {
289
+ if (!name)
290
+ continue;
291
+ const provider = find(name);
292
+ if (!provider) {
293
+ output.printError(`Provider not found: ${name}`);
294
+ return { success: false, exitCode: 1 };
295
+ }
296
+ if (action === 'remove') {
297
+ config.providers = config.providers.filter((p) => p !== provider);
298
+ }
299
+ else {
300
+ provider.enabled = action === 'enable';
301
+ }
302
+ mutated = true;
303
+ }
304
+ if (mutated) {
305
+ let target;
306
+ try {
307
+ target = saveCliConfig(root, config, { path: loaded.path ?? undefined });
308
+ }
309
+ catch (err) {
310
+ output.printError(`Failed to write configuration: ${err.message}`);
311
+ return { success: false, exitCode: 1 };
312
+ }
313
+ output.printSuccess(`Providers updated (${displayPath(root, target)})`);
314
+ }
315
+ const providers = config.providers.map((p) => ({
316
+ ...p,
317
+ model: p.model ?? '',
318
+ status: p.enabled ? 'Active' : 'Disabled'
319
+ }));
239
320
  if (ctx.flags.format === 'json') {
240
321
  output.printJson(providers);
241
322
  return { success: true, data: providers };
@@ -277,12 +358,17 @@ const resetCommand = {
277
358
  name: 'section',
278
359
  description: 'Reset specific section only',
279
360
  type: 'string',
280
- choices: ['agents', 'swarm', 'memory', 'mcp', 'providers', 'all']
361
+ choices: [...RESETTABLE_SECTIONS]
281
362
  }
282
363
  ],
283
364
  action: async (ctx) => {
284
365
  const force = ctx.flags.force;
285
- const section = ctx.flags.section || 'all';
366
+ const section = (ctx.flags.section || 'all');
367
+ if (!RESETTABLE_SECTIONS.includes(section)) {
368
+ output.printError(`Unknown section: ${section}`);
369
+ output.writeln(output.dim(` Valid sections: ${RESETTABLE_SECTIONS.join(', ')}`));
370
+ return { success: false, exitCode: 1 };
371
+ }
286
372
  if (!force && ctx.interactive) {
287
373
  const confirmed = await confirm({
288
374
  message: `Reset ${section} configuration to defaults?`,
@@ -293,9 +379,21 @@ const resetCommand = {
293
379
  return { success: true };
294
380
  }
295
381
  }
382
+ const root = configRoot(ctx);
383
+ const loaded = loadOrReport(root);
384
+ if (!loaded)
385
+ return { success: false, exitCode: 1 };
296
386
  output.printInfo(`Resetting ${section} configuration...`);
297
- output.printSuccess('Configuration reset to defaults');
298
- return { success: true, data: { section, reset: true } };
387
+ let target;
388
+ try {
389
+ target = saveCliConfig(root, resetSection(loaded.config, section), { path: loaded.path ?? undefined });
390
+ }
391
+ catch (err) {
392
+ output.printError(`Failed to write configuration: ${err.message}`);
393
+ return { success: false, exitCode: 1 };
394
+ }
395
+ output.printSuccess(`Configuration reset to defaults (${displayPath(root, target)})`);
396
+ return { success: true, data: { section, reset: true, path: target } };
299
397
  }
300
398
  };
301
399
  // Export configuration
@@ -319,20 +417,39 @@ const exportCommand = {
319
417
  }
320
418
  ],
321
419
  action: async (ctx) => {
322
- const outputPath = ctx.flags.output || './claude-flow.config.export.json';
323
- const config = {
324
- version: '3.0.0',
325
- exportedAt: new Date().toISOString(),
326
- agents: { defaultType: 'coder', maxConcurrent: 15 },
327
- swarm: { topology: 'hybrid', maxAgents: 15 },
328
- memory: { backend: 'hybrid', cacheSize: 256 },
329
- mcp: { transport: 'stdio', tools: 'all' }
330
- };
331
- output.printInfo(`Exporting configuration to ${outputPath}...`);
332
- output.printJson(config);
420
+ const format = ctx.flags.format || 'json';
421
+ if (format !== 'json' && format !== 'yaml') {
422
+ output.printError(`Unsupported export format: ${format}`);
423
+ output.writeln(output.dim(' Valid formats: json, yaml'));
424
+ return { success: false, exitCode: 1 };
425
+ }
426
+ const root = configRoot(ctx);
427
+ const loaded = loadOrReport(root);
428
+ if (!loaded)
429
+ return { success: false, exitCode: 1 };
430
+ const requested = ctx.flags.output;
431
+ const defaultName = `moflo.config.export.${format === 'yaml' ? 'yaml' : 'json'}`;
432
+ const outputPath = requested
433
+ ? (isAbsolute(requested) ? requested : resolve(root, requested))
434
+ : resolve(root, defaultName);
435
+ const config = { ...loaded.config, exportedAt: new Date().toISOString() };
436
+ output.printInfo(`Exporting configuration to ${displayPath(root, outputPath)}...`);
437
+ try {
438
+ if (format === 'yaml') {
439
+ const { dump } = await import('js-yaml');
440
+ writeTextFile(outputPath, dump(config));
441
+ }
442
+ else {
443
+ writeJsonFile(outputPath, config);
444
+ }
445
+ }
446
+ catch (err) {
447
+ output.printError(`Failed to write ${displayPath(root, outputPath)}: ${err.message}`);
448
+ return { success: false, exitCode: 1 };
449
+ }
333
450
  output.writeln();
334
451
  output.printSuccess('Configuration exported');
335
- return { success: true, data: { path: outputPath, config } };
452
+ return { success: true, data: { path: outputPath, format, config } };
336
453
  }
337
454
  };
338
455
  // Import configuration
@@ -340,12 +457,13 @@ const importCommand = {
340
457
  name: 'import',
341
458
  description: 'Import configuration',
342
459
  options: [
460
+ // Also accepted positionally (`flo config import ./cfg.json`) — see the
461
+ // note on `set`.
343
462
  {
344
463
  name: 'file',
345
464
  short: 'f',
346
465
  description: 'Configuration file path',
347
- type: 'string',
348
- required: true
466
+ type: 'string'
349
467
  },
350
468
  {
351
469
  name: 'merge',
@@ -361,15 +479,57 @@ const importCommand = {
361
479
  output.printError('File path is required');
362
480
  return { success: false, exitCode: 1 };
363
481
  }
482
+ const root = configRoot(ctx);
483
+ const source = isAbsolute(file) ? file : resolve(root, file);
484
+ if (!existsSync(source)) {
485
+ output.printError(`Configuration file not found: ${file}`);
486
+ return { success: false, exitCode: 1 };
487
+ }
488
+ let incoming;
489
+ try {
490
+ const raw = readFileSync(source, 'utf8');
491
+ if (extname(source).toLowerCase() === '.yaml' || extname(source).toLowerCase() === '.yml') {
492
+ const { load } = await import('js-yaml');
493
+ incoming = load(raw);
494
+ }
495
+ else {
496
+ incoming = JSON.parse(raw);
497
+ }
498
+ }
499
+ catch (err) {
500
+ output.printError(`Could not parse ${file}: ${err.message}`);
501
+ return { success: false, exitCode: 1 };
502
+ }
503
+ if (typeof incoming !== 'object' || incoming === null || Array.isArray(incoming)) {
504
+ output.printError(`Not a configuration object: ${file}`);
505
+ return { success: false, exitCode: 1 };
506
+ }
364
507
  output.printInfo(`Importing configuration from ${file}...`);
508
+ // Merge mode layers the file over what is on disk; replace mode layers it
509
+ // over defaults, so an incomplete file still yields a complete config.
510
+ let base;
365
511
  if (merge) {
512
+ const loaded = loadOrReport(root);
513
+ if (!loaded)
514
+ return { success: false, exitCode: 1 };
515
+ base = loaded.config;
366
516
  output.writeln(output.dim(' Merging with existing configuration...'));
367
517
  }
368
518
  else {
519
+ base = defaultCliConfig();
369
520
  output.writeln(output.dim(' Replacing existing configuration...'));
370
521
  }
371
- output.printSuccess('Configuration imported');
372
- return { success: true, data: { file, merge, imported: true } };
522
+ const imported = { ...base, ...incoming };
523
+ let target;
524
+ try {
525
+ target = saveCliConfig(root, imported);
526
+ }
527
+ catch (err) {
528
+ output.printError(`Failed to write configuration: ${err.message}`);
529
+ return { success: false, exitCode: 1 };
530
+ }
531
+ output.printSuccess(`Configuration imported (${displayPath(root, target)})`);
532
+ return { success: true, data: { file, merge, imported: true, path: target } };
373
533
  }
374
534
  };
375
535
  // Show moflo project config (merged with defaults)
@@ -2,7 +2,7 @@
2
2
  * V3 CLI Deployment Command
3
3
  * Deployment management, environments, rollbacks
4
4
  *
5
- * Created with ❤️ by motailz.com
5
+ * Created with ❤️ by cielolimitada.com
6
6
  */
7
7
  import { output } from '../output.js';
8
8
  // Deploy subcommand
@@ -14,7 +14,7 @@ const deployCommand = {
14
14
  { name: 'version', short: 'v', type: 'string', description: 'Version to deploy', default: 'latest' },
15
15
  { name: 'dry-run', short: 'd', type: 'boolean', description: 'Simulate deployment without changes' },
16
16
  { name: 'force', short: 'f', type: 'boolean', description: 'Force deployment without checks' },
17
- { name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default: 'true' },
17
+ { name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default: true },
18
18
  ],
19
19
  examples: [
20
20
  { command: 'flo deployment deploy -e prod', description: 'Deploy to production' },
@@ -100,7 +100,7 @@ const rollbackCommand = {
100
100
  options: [
101
101
  { name: 'env', short: 'e', type: 'string', description: 'Environment to rollback', required: true },
102
102
  { name: 'version', short: 'v', type: 'string', description: 'Specific version to rollback to' },
103
- { name: 'steps', short: 's', type: 'number', description: 'Number of versions to rollback', default: '1' },
103
+ { name: 'steps', short: 's', type: 'number', description: 'Number of versions to rollback', default: 1 },
104
104
  ],
105
105
  examples: [
106
106
  { command: 'flo deployment rollback -e prod', description: 'Rollback production' },
@@ -141,7 +141,7 @@ const historyCommand = {
141
141
  description: 'View deployment history',
142
142
  options: [
143
143
  { name: 'env', short: 'e', type: 'string', description: 'Filter by environment' },
144
- { name: 'limit', short: 'l', type: 'number', description: 'Number of entries', default: '10' },
144
+ { name: 'limit', short: 'l', type: 'number', description: 'Number of entries', default: 10 },
145
145
  ],
146
146
  examples: [
147
147
  { command: 'flo deployment history', description: 'Show all history' },
@@ -214,7 +214,7 @@ const logsCommand = {
214
214
  { name: 'deployment', short: 'd', type: 'string', description: 'Deployment ID' },
215
215
  { name: 'env', short: 'e', type: 'string', description: 'Environment' },
216
216
  { name: 'follow', short: 'f', type: 'boolean', description: 'Follow log output' },
217
- { name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: '50' },
217
+ { name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: 50 },
218
218
  ],
219
219
  examples: [
220
220
  { command: 'flo deployment logs -e prod', description: 'View production logs' },
@@ -281,7 +281,7 @@ export const deploymentCommand = {
281
281
  'Deployment previews for PRs',
282
282
  ]);
283
283
  output.writeln();
284
- output.writeln(output.dim('Created with ❤️ by motailz.com'));
284
+ output.writeln(output.dim('Created with ❤️ by cielolimitada.com'));
285
285
  return { success: true };
286
286
  },
287
287
  };
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * All test data is cleaned up after each test — no code or state is left behind.
10
10
  *
11
- * Created with motailz.com
11
+ * Created with cielolimitada.com
12
12
  */
13
13
  import { output } from '../output.js';
14
14
  import { errorDetail } from '../shared/utils/error-detail.js';
@@ -10,41 +10,37 @@ import { findProjectDaemonPids, getDaemonLockHolder, getDaemonLockPayload, } fro
10
10
  import { resolveClientPort, LEGACY_DEFAULT_PORT, probeDaemonHealthWithRetry as probeDaemonHealthIdentity, normalizeProjectRoot, } from '../services/daemon-port.js';
11
11
  import { COMMON_WALK_SKIP_NAMES, LEGACY_SWARM_DIR, memoryDbCandidatePaths, memoryDbPath, } from '../services/moflo-paths.js';
12
12
  import { probeDbIntegrity } from '../services/memory-db-integrity-repair.js';
13
+ import { CLI_CONFIG_CANDIDATES, CLI_CONFIG_YAML_CANDIDATES } from '../config/cli-config-store.js';
13
14
  import { findProjectRoot, resolveStateRoot } from '../services/project-root.js';
14
15
  import { errorDetail } from '../shared/utils/error-detail.js';
15
16
  export async function checkConfigFile() {
16
- // JSON configs (parse-validated). LEGACY-CONFIG: `.claude-flow.json` and
17
- // `claude-flow.config.json` filenames are still recognised so consumers
18
- // upgrading from pre-#699 moflo builds keep working
19
- // without manual rename. Drift guard exempts these via LEGACY-CONFIG marker.
20
- const jsonPaths = [
21
- '.moflo/config.json',
22
- 'moflo.config.json',
23
- 'claude-flow.config.json', // LEGACY-CONFIG: pre-#699 fallback
24
- '.claude-flow.json', // LEGACY-CONFIG: pre-#699 fallback
25
- ];
26
- for (const configPath of jsonPaths) {
17
+ // Resolve against the state root, not the bare cwd: running `flo doctor`
18
+ // from a subdirectory used to report "no config file" for a project that has
19
+ // one, and the auto-fix (which writes at the state root) could never clear
20
+ // the warning it raised. Same anchor as `flo config` (#1315).
21
+ const root = resolveStateRoot();
22
+ // JSON configs (parse-validated). The candidate list lives in the config
23
+ // store so this check, `flo config`, and the doctor auto-fix all agree on
24
+ // which files count. LEGACY-CONFIG: `claude-flow.*` names are still
25
+ // recognised there so consumers upgrading from pre-#699 moflo builds keep
26
+ // working without a manual rename.
27
+ for (const candidate of CLI_CONFIG_CANDIDATES) {
28
+ const configPath = join(root, candidate);
27
29
  if (existsSync(configPath)) {
28
30
  try {
29
31
  const content = readFileSync(configPath, 'utf8');
30
32
  JSON.parse(content);
31
- return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
33
+ return { name: 'Config File', status: 'pass', message: `Found: ${candidate}` };
32
34
  }
33
35
  catch {
34
- return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${configPath}`, fix: 'Fix JSON syntax in config file' };
36
+ return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${candidate}`, fix: 'Fix JSON syntax in config file' };
35
37
  }
36
38
  }
37
39
  }
38
40
  // YAML configs (existence-checked only — no heavy yaml parser dependency).
39
- const yamlPaths = [
40
- '.moflo/config.yaml',
41
- '.moflo/config.yml',
42
- 'moflo.config.yaml',
43
- 'claude-flow.config.yaml', // LEGACY-CONFIG: pre-#699 fallback
44
- ];
45
- for (const configPath of yamlPaths) {
46
- if (existsSync(configPath)) {
47
- return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
41
+ for (const candidate of CLI_CONFIG_YAML_CANDIDATES) {
42
+ if (existsSync(join(root, candidate))) {
43
+ return { name: 'Config File', status: 'pass', message: `Found: ${candidate}` };
48
44
  }
49
45
  }
50
46
  return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'npx moflo config init' };
@@ -11,7 +11,7 @@
11
11
  * so these checks work both in the dev repo AND in consumer projects where
12
12
  * moflo is installed under node_modules/moflo/.
13
13
  *
14
- * Created with motailz.com
14
+ * Created with cielolimitada.com
15
15
  */
16
16
  import { existsSync, readFileSync } from 'fs';
17
17
  import { join } from 'path';