moflo 4.9.12 → 4.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ ---
2
+ name: eldar
3
+ description: Consult the Eldar — audit a project's moflo + Claude Code setup for portable, high-leverage gaps and guide remediation. Default mode is read-only audit with severity-ranked findings; --fix presents an interactive triage menu and walks the user through each chosen fix (healer, missing CLAUDE.md, sparse guidance, hook/MCP wiring, empty memory namespaces, stack→guidance gaps). Use when starting in a new project, when Claude feels lost or inefficient, when guidance/CLAUDE.md is sparse, or as a periodic health check.
4
+ arguments: "[--fix]"
5
+ ---
6
+
7
+ # /eldar — Consult the Eldar
8
+
9
+ The Eldar audit a project's moflo + Claude Code setup for portable, high-leverage gaps. **Audit is read-only by default; `--fix` walks through remediation.** The Eldar consult the **Healer** (`flo healer`, the thematic alias for `flo doctor`), they do not replace them.
10
+
11
+ **Arguments:** $ARGUMENTS
12
+
13
+ ## Modes
14
+
15
+ | Mode | Trigger | What it does |
16
+ |------|---------|--------------|
17
+ | Audit | no flag (default) | Read-only scan; produces categorized findings + top-3 recommendation |
18
+ | Fix | `--fix` | Audit, then interactive triage menu; user picks findings to address one at a time |
19
+
20
+ ## Step 0 — Memory First
21
+
22
+ Before any file reads, run:
23
+
24
+ ```
25
+ mcp__moflo__memory_search { query: "guidance rules project conventions stack", namespace: "guidance" }
26
+ ```
27
+
28
+ The memory-first gate blocks reads otherwise. The search also surfaces any project-specific conventions the Eldar should weigh in their findings.
29
+
30
+ ## Step 1 — Run the Audit
31
+
32
+ Walk the checklist below in order. Each check is a single category in the final report. Be explicit about what you find — both presence and absence. Severities: `error` (blocks productive work), `warn` (degrades quality), `info` (suggestion).
33
+
34
+ ### 1a. Setup Health — call the Healer
35
+
36
+ ```bash
37
+ npx moflo healer --json
38
+ ```
39
+
40
+ Parse the JSON output. Surface every `failed` check as `error`, every `warn` as `warn`. Do **not** invoke `flo doctor` directly — use the `healer` alias for thematic consistency.
41
+
42
+ ### 1b. Index Freshness
43
+
44
+ Check for `.moflo/moflo.db` (existence + mtime). Query memory namespaces to confirm guidance + code-map are populated:
45
+
46
+ ```
47
+ mcp__moflo__memory_stats — { namespace: "guidance" }
48
+ mcp__moflo__memory_stats — { namespace: "code-map" }
49
+ ```
50
+
51
+ Flag if `entries === 0` (warn) or db missing (error).
52
+
53
+ ### 1c. Version Skew
54
+
55
+ ```bash
56
+ npm view moflo version # latest published
57
+ node -e "console.log(require('./package.json').devDependencies?.moflo || require('./package.json').dependencies?.moflo || 'not-installed')"
58
+ ```
59
+
60
+ Compute minor-version delta. Warn if behind by ≥3 minors; info if behind by 1–2.
61
+
62
+ ### 1d. Model & Token Routing
63
+
64
+ ```
65
+ mcp__moflo__hooks_model-stats — {}
66
+ ```
67
+
68
+ If recent sonnet→opus escalation rate exceeds ~30%, flag as `info`: "router escalating frequently — see `.claude/guidance/shipped/moflo-claude-swarm-cohesion.md` for tuning". If stats unavailable (no history), skip silently.
69
+
70
+ ### 1e. CLAUDE.md
71
+
72
+ Check `CLAUDE.md` (and `.claude/CLAUDE.md`) for:
73
+
74
+ | Check | Threshold | Severity |
75
+ |-------|-----------|----------|
76
+ | Exists | required | error if missing |
77
+ | Line count | 20–500 | warn if outside range |
78
+ | Referenced files exist | every relative path it cites | warn per missing path |
79
+
80
+ Use `Grep` over the file content for `\.claude/[a-z-]+/[a-z-]+\.md` patterns and verify each path resolves.
81
+
82
+ ### 1f. Guidance Content
83
+
84
+ Count `.md` files under `.claude/guidance/` (recursive). Severity table:
85
+
86
+ | File count | Severity |
87
+ |------------|----------|
88
+ | 0 | warn — "no guidance docs; Claude has nothing project-specific to follow" |
89
+ | 1–2 | warn — "very sparse guidance" |
90
+ | 3–10 | info |
91
+ | 11+ | info |
92
+
93
+ ### 1g. Guidance Structure (only if 1f found ≥1 file)
94
+
95
+ Apply the universal rules from `.claude/guidance/shipped/moflo-guidance-rules.md`. For each `.md` file, check:
96
+
97
+ - Has `**Purpose:**` line right after H1
98
+ - Has `## See Also` at end
99
+ - Under 500 lines
100
+ - H2 headings are specific (not "Overview", "Configuration", "Examples")
101
+ - No hedged language in rule contexts (`should`, `might`, `consider`)
102
+
103
+ Do **not** duplicate `/guidance -a`'s logic verbatim — just produce a one-line summary per file (`<file>: <N issues>`). The Eldar surface gaps; `/guidance -a` does the deep audit.
104
+
105
+ ### 1h. Memory Health
106
+
107
+ For each of the canonical namespaces, check entry count:
108
+
109
+ ```
110
+ mcp__moflo__memory_stats — { namespace: "guidance" }
111
+ mcp__moflo__memory_stats — { namespace: "patterns" }
112
+ mcp__moflo__memory_stats — { namespace: "learnings" }
113
+ ```
114
+
115
+ Flag empty `learnings` as `info` (project hasn't accumulated decisions yet — fine for new projects). Flag empty `guidance` as `warn` (no indexed guidance means semantic search is degraded).
116
+
117
+ ### 1i. Hooks & MCP Wiring
118
+
119
+ Read `.claude/settings.json`. Check:
120
+
121
+ | Check | Severity |
122
+ |-------|----------|
123
+ | Session-start hook references the moflo launcher | error if missing |
124
+ | `mcpServers.moflo` is configured | error if missing |
125
+ | `hooks` section exists with at least pre-task/post-task entries | warn if absent |
126
+
127
+ If settings.json is malformed JSON, surface as `error`.
128
+
129
+ ### 1j. Settings Sanity
130
+
131
+ Spot-check `.claude/settings.json` for:
132
+
133
+ - `permissions` block exists (info if absent — every prompt becomes a confirmation)
134
+ - `env` block has at least the moflo entries the launcher writes
135
+ - `statusLine` is configured (info — quality-of-life, not blocking)
136
+
137
+ ### 1k. Spell Inventory
138
+
139
+ ```bash
140
+ npx moflo spell list
141
+ ```
142
+
143
+ Flag `info` if count is 0 (no spells registered — user may not know they exist).
144
+
145
+ ### 1l. Subagent Fleet
146
+
147
+ ```
148
+ Glob — { pattern: ".claude/agents/**/*.md" }
149
+ ```
150
+
151
+ Count the result. `info` if 0 (no project-specific subagents — user is relying entirely on built-ins).
152
+
153
+ ### 1m. Stack → Guidance Cross-Reference (highest leverage)
154
+
155
+ Detect the project's stack from manifests:
156
+
157
+ | Manifest | Detected stack |
158
+ |----------|----------------|
159
+ | `package.json` deps | Node — inspect for React, Next, Drizzle, Prisma, Express, NestJS, Vite, etc. |
160
+ | `pyproject.toml` / `requirements.txt` | Python — Django, FastAPI, SQLAlchemy, etc. |
161
+ | `Cargo.toml` | Rust — axum, tokio, sqlx, etc. |
162
+ | `go.mod` | Go — gin, sqlc, gorm, etc. |
163
+ | `Gemfile` | Ruby — Rails, Sidekiq, etc. |
164
+
165
+ For each detected technology, check whether `.claude/guidance/` mentions it (Grep for the technology name across the directory). Each `(detected stack item, no guidance match)` pair becomes one `info` finding: "uses Drizzle ORM but no DB-conventions guidance — high-leverage gap".
166
+
167
+ This is the **highest-impact finding** for new adopters. Lead with it in the recommendation.
168
+
169
+ ### 1n. Anti-Pattern from History (best-effort, optional)
170
+
171
+ If recent transcripts/commits are accessible, scan them for repeated manual work that an existing spell or agent already covers (e.g., 5+ separate `git status`/`git diff`/run-tests sequences in a session that `/simplify` would have handled). Surface as `info`: "consider /simplify for review loops". If unavailable, skip silently — never block the audit on this.
172
+
173
+ ## Step 2 — Render the Report
174
+
175
+ Output a single table grouped by category, sorted by severity (`error` → `warn` → `info`):
176
+
177
+ ```
178
+ ELDAR AUDIT — <project name>
179
+ ─────────────────────────────
180
+
181
+ Category Finding Severity
182
+ ─────────────────────────────────────────────────────────────────────────
183
+ Setup health Healer reports 0 errors, 1 warning warn
184
+ Index freshness Guidance index empty warn
185
+ CLAUDE.md File missing error
186
+ Guidance content 0 docs in .claude/guidance/ warn
187
+ Memory health guidance namespace empty warn
188
+ Stack → guidance Drizzle ORM in deps; no DB guidance info
189
+ Stack → guidance React Native; no mobile guidance info
190
+ Hooks & MCP wiring all wired ok
191
+ ... (etc) ...
192
+ ```
193
+
194
+ Then list the **top 3 ranked recommendations** in plain English, with rationale and citation:
195
+
196
+ ```
197
+ TOP 3 RECOMMENDATIONS
198
+ ─────────────────────
199
+
200
+ 1. Add CLAUDE.md (error)
201
+ Without it, Claude has no project entry point. Use the Eldar's
202
+ stack-aware scaffold via `/eldar --fix`.
203
+
204
+ 2. Add Drizzle conventions guidance (info — high leverage)
205
+ You use Drizzle ORM but have no DB-conventions doc. This is the
206
+ single highest-leverage gap for getting Claude to write idiomatic
207
+ queries and migrations in your codebase.
208
+ See: .claude/guidance/shipped/moflo-guidance-rules.md
209
+
210
+ 3. Run `flo healer --fix` (warn)
211
+ One auto-fixable warning. Run via `/eldar --fix` and select Healer.
212
+ ```
213
+
214
+ End the audit with a one-line prompt: "Run `/eldar --fix` to address these interactively."
215
+
216
+ ## Step 3 — Fix Mode (`--fix` flag only)
217
+
218
+ After the report, present a numbered triage menu:
219
+
220
+ ```
221
+ TRIAGE MENU
222
+ ───────────
223
+ [1] Add CLAUDE.md
224
+ [2] Add Drizzle conventions guidance
225
+ [3] Run flo healer --fix (1 warning)
226
+ [4] Add empty .claude/guidance/ docs to memory namespaces
227
+
228
+ Choose: all, none, or comma-separated numbers (e.g., 1,3): _
229
+ ```
230
+
231
+ Drive each chosen finding through its sub-flow. Confirm before any write.
232
+
233
+ ### 3a. CLAUDE.md scaffold
234
+
235
+ Ask the user 2–4 targeted questions based on detected stack:
236
+
237
+ 1. "What does this project do? (1-2 sentences for Claude's context)"
238
+ 2. "Primary tech stack confirmed: <detected list>. Anything missing?"
239
+ 3. "Any conventions Claude should follow (testing approach, branch model, etc.)?"
240
+ 4. "Any high-blast-radius areas Claude should be careful with?"
241
+
242
+ Compose a CLAUDE.md draft incorporating their answers + standard moflo memory-first rule. **Show the draft to the user before writing.** Never auto-fill opinionated content.
243
+
244
+ ### 3b. Stack → guidance authoring
245
+
246
+ For each chosen stack-gap finding:
247
+
248
+ - Hand off to `/guidance` skill for the heavy lifting — it already enforces the universal rules.
249
+ - Brief the user on what gap will be filled: "drafting Drizzle conventions doc covering query patterns, migrations, schema files".
250
+ - Ask 2–4 targeted questions about *their* conventions (not generic Drizzle tips — Claude should follow how *they* use it).
251
+ - The `/guidance` skill produces the draft and walks the user through the rules check.
252
+
253
+ ### 3c. Healer fixes
254
+
255
+ ```bash
256
+ npx moflo healer --fix
257
+ ```
258
+
259
+ Pass through the output verbatim. If the Healer reports manual-only fixes, surface them as next steps.
260
+
261
+ ### 3d. Hook/MCP wiring repair
262
+
263
+ Suggest:
264
+
265
+ ```bash
266
+ npx moflo init --upgrade
267
+ ```
268
+
269
+ This is the standard wiring repair path. If the user is wary of running init, surface the specific missing keys from `.claude/settings.json` and offer to write them directly.
270
+
271
+ ### 3e. Empty namespaces
272
+
273
+ Suggest concrete first entries based on detected stack. Example: "Your project uses Drizzle. Want me to seed `learnings` with the most common Drizzle gotchas as a starting set? You'd review each before storage."
274
+
275
+ If the user declines, that's fine — empty `learnings` is a valid state for a young project.
276
+
277
+ ### 3f. After each fix
278
+
279
+ After each chosen fix completes, ask: "Continue to next finding? (y/n)". Don't run them all in a batch — every change is high-leverage and deserves the user's attention.
280
+
281
+ ## Step 4 — Wrap-Up
282
+
283
+ After audit (or audit + chosen fixes), end with:
284
+
285
+ - **Audit-only**: One sentence — what was found, what to do next.
286
+ - **Fix mode**: One sentence per applied fix, plus a closing line on what remains.
287
+
288
+ Never leave the user without a clear next step.
289
+
290
+ ## Important
291
+
292
+ - **Memory-first is mandatory.** Step 0 runs the search; the gate blocks reads otherwise.
293
+ - **Call the Healer, not the Doctor.** `npx moflo healer` (alias) — never `flo doctor` — for thematic consistency.
294
+ - **No auto-write of opinionated content.** Every guidance doc, every CLAUDE.md draft, every namespace seed gets shown to the user first.
295
+ - **Portable only.** This skill ships to consumers via `.claude/skills/**/*.md` in the package files array. Never assume moflo source paths or moflo-internal state.
296
+ - **No kitchen sink.** The audit checklist is locked at the categories above. New checks require a specific portable benefit and an issue to discuss them.
297
+ - **Read-only by default.** `/eldar` (no flag) never writes. Only `--fix` writes, and only with per-finding confirmation.
298
+ - **Hand off to specialists.** `/guidance` for guidance authoring, `flo healer --fix` for setup repair, `flo init --upgrade` for wiring. The Eldar route, they don't reimplement.
299
+
300
+ ## See Also
301
+
302
+ - `.claude/guidance/shipped/moflo-guidance-rules.md` — Universal guidance writing rules used by `/guidance` and surfaced in 1g
303
+ - `.claude/skills/guidance/SKILL.md` — The skill `/eldar --fix` hands off to for guidance authoring
304
+ - `.claude/guidance/shipped/moflo-core-guidance.md` — moflo CLI / hooks / memory reference; useful when explaining wiring findings
305
+ - `.claude/guidance/shipped/moflo-claude-swarm-cohesion.md` — Subagent + task coordination reference cited in routing findings
package/README.md CHANGED
@@ -431,6 +431,18 @@ Inside your AI client, use the `/spell-builder` skill to create, edit, and valid
431
431
  /spell-builder # Start the spell builder
432
432
  ```
433
433
 
434
+ ### Other AI-client skills shipped with MoFlo
435
+
436
+ Beyond `/flo`, `/spell-builder`, and `/eldar`, MoFlo ships a handful of focused slash-command skills that work in any consumer project once you `flo init`:
437
+
438
+ | Skill | Purpose |
439
+ |-------|---------|
440
+ | `/guidance` | Author and audit guidance docs in `.claude/guidance/`. Default mode walks you through one doc; `/guidance -a` audits every doc against the universal guidance rules (Purpose lines, See Also, line counts, hedged language). |
441
+ | `/simplify` | Adaptive code review on the current diff. Tier-based fan-out — trivial edits get a self-review, small diffs get one routed agent, cross-cutting refactors get three parallel agents. Routes through the moflo model router for cost-aware execution. |
442
+ | `/spell-schedule` | Schedule a spell on the local moflo daemon (cron, interval, or one-time) without leaving the chat. For remote Anthropic-cloud agents on a schedule, use Claude Code's built-in `/schedule` instead. |
443
+
444
+ Run any of them with no arguments to see full usage, or browse the source in `.claude/skills/` (each skill is a single `SKILL.md` file).
445
+
434
446
  ### Epics
435
447
 
436
448
  Epics are a specialized process for handling GitHub issues that contain multiple child stories. When you pass a GitHub issue to `/flo` and it's detected as an epic, MoFlo processes each child story sequentially through the full `/flo` process (research → implement → test → PR).
@@ -553,6 +565,19 @@ flo healer -c embeddings # Check only embeddings health
553
565
  flo healer --verbose # Verbose output
554
566
  ```
555
567
 
568
+ #### `/eldar` — Consult the Eldar (project setup audit + wizard)
569
+
570
+ Where the Healer checks your moflo install, `/eldar` audits how Claude is set up to *use* the project — guidance, CLAUDE.md, memory namespaces, hook/MCP wiring, model routing, and stack-aware guidance gaps — then walks you through fixing whichever findings you pick. Use it when starting in a new project, when Claude feels lost or inefficient, or as a periodic health check.
571
+
572
+ ```
573
+ /eldar # Read-only audit; categorized report + top-3 ranked recommendation
574
+ /eldar --fix # Audit, then interactive triage menu — pick which findings to address
575
+ ```
576
+
577
+ The Eldar **consult** the Healer (they call `flo healer --json` as one of the audit checks) — they don't replace it. Categories audited include setup health, index freshness, version skew, model/token routing, CLAUDE.md size + reference integrity, guidance content + structure, memory health, hook/MCP wiring, settings sanity, spell + subagent inventory, **stack → guidance cross-reference** (detects tech from package.json/pyproject.toml/Cargo.toml/go.mod and flags every detected technology with no matching guidance doc — the highest-leverage finding for new adopters), and best-effort anti-pattern detection from history.
578
+
579
+ In `--fix` mode, each chosen finding drives the appropriate sub-flow: Healer for setup repair, the `/guidance` skill for guidance authoring (wizard, never autogen), a stack-aware scaffold for missing CLAUDE.md, `flo init --upgrade` for hook/MCP wiring. Every write is confirmed before it lands.
580
+
556
581
  #### `flo diagnose` — Integration Tests
557
582
 
558
583
  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.
package/bin/hooks.mjs CHANGED
@@ -22,7 +22,7 @@
22
22
  import { spawn } from 'child_process';
23
23
  import { existsSync, appendFileSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
24
24
  import { resolve, dirname } from 'path';
25
- import { fileURLToPath } from 'url';
25
+ import { fileURLToPath, pathToFileURL } from 'url';
26
26
  import { createProcessManager } from './lib/process-manager.mjs';
27
27
  import { shouldDaemonAutoStart } from './lib/daemon-config.mjs';
28
28
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
@@ -520,7 +520,7 @@ let _getDaemonLockHolder = null;
520
520
  try {
521
521
  const daemonLockPath = resolve(__dirname, '..', 'src', '@claude-flow', 'cli', 'dist', 'src', 'services', 'daemon-lock.js');
522
522
  if (existsSync(daemonLockPath)) {
523
- const mod = await import('file://' + daemonLockPath.replace(/\\/g, '/'));
523
+ const mod = await import(pathToFileURL(daemonLockPath).href);
524
524
  _getDaemonLockHolder = mod.getDaemonLockHolder;
525
525
  }
526
526
  } catch { /* fallback below */ }
@@ -28,6 +28,7 @@ import { fileURLToPath } from 'url';
28
28
  import { mofloResolveURL } from './lib/moflo-resolve.mjs';
29
29
  import { memoryDbPath } from './lib/moflo-paths.mjs';
30
30
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
31
+ import { createProcessManager } from './lib/process-manager.mjs';
31
32
  const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
32
33
 
33
34
 
@@ -872,36 +873,25 @@ if (!skipEmbeddings && needsEmbeddings) {
872
873
  console.log('');
873
874
  log('Spawning embedding generation in background...');
874
875
 
875
- const { spawn } = await import('child_process');
876
-
877
876
  const embeddingScript = resolveMofloBin(
878
877
  projectRoot, 'flo-embeddings', 'build-embeddings.mjs', { includeDevFallback: true },
879
878
  );
880
879
 
881
880
  if (embeddingScript) {
882
- const embeddingArgs = ['--namespace', NAMESPACE];
883
-
884
- // Create log file for background process output
885
- const logDir = resolve(projectRoot, '.moflo/logs');
886
- if (!existsSync(logDir)) {
887
- mkdirSync(logDir, { recursive: true });
881
+ // Register the spawn with the shared ProcessManager (#886). Stdout/stderr
882
+ // route through `.swarm/background.log` (pm.spawn default) instead of the
883
+ // bespoke `.moflo/logs/embeddings.log` so the registry, dedup, and
884
+ // session-end drain stay consistent with every other tracked spawn.
885
+ const pm = createProcessManager(projectRoot);
886
+ const result = pm.spawn('node', [embeddingScript, '--namespace', NAMESPACE], `build-embeddings-${NAMESPACE}`);
887
+ if (result.skipped) {
888
+ log(`Background embedding already running (PID: ${result.pid})`);
889
+ } else if (result.pid) {
890
+ log(`Background embedding started (PID: ${result.pid})`);
891
+ log(`Log file: .swarm/background.log`);
892
+ } else {
893
+ log('⚠️ Failed to spawn background embedding');
888
894
  }
889
- const logFile = resolve(logDir, 'embeddings.log');
890
- const { openSync } = await import('fs');
891
- const out = openSync(logFile, 'a');
892
- const err = openSync(logFile, 'a');
893
-
894
- // Spawn in background - don't wait for completion
895
- const proc = spawn('node', [embeddingScript, ...embeddingArgs], {
896
- stdio: ['ignore', out, err],
897
- cwd: projectRoot,
898
- detached: true,
899
- windowsHide: true // Suppress command windows on Windows
900
- });
901
- proc.unref(); // Allow parent to exit independently
902
-
903
- log(`Background embedding started (PID: ${proc.pid})`);
904
- log(`Log file: .moflo/logs/embeddings.log`);
905
895
  } else {
906
896
  log('⚠️ Embedding script not found, skipping embedding generation');
907
897
  }
@@ -27,11 +27,11 @@
27
27
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
28
28
  import { resolve, dirname, relative, basename, extname } from 'path';
29
29
  import { fileURLToPath } from 'url';
30
- import { spawn } from 'child_process';
31
30
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
32
31
  import { mofloResolveURL } from './lib/moflo-resolve.mjs';
33
32
  import { memoryDbPath, MOFLO_DIR } from './lib/moflo-paths.mjs';
34
33
  import { applyIncrementalChunks, computeContentListHash } from './lib/incremental-write.mjs';
34
+ import { createProcessManager } from './lib/process-manager.mjs';
35
35
 
36
36
  const __dirname = dirname(fileURLToPath(import.meta.url));
37
37
 
@@ -342,20 +342,23 @@ async function main() {
342
342
  // Save hash
343
343
  writeFileSync(HASH_CACHE_PATH, currentHash, 'utf-8');
344
344
 
345
- // Trigger embedding generation in background
345
+ // Trigger embedding generation in background. Register with the shared
346
+ // ProcessManager (#886) so doctor's zombie scan allowlists it and the
347
+ // session-end / smoke-teardown drain reaps it. Stable label dedupes against
348
+ // the index-all chain's later `build-embeddings` step when both run within
349
+ // the same lock window.
346
350
  try {
347
351
  const embeddingScript = resolveMofloBin(
348
352
  projectRoot, 'flo-embeddings', 'build-embeddings.mjs', { includeDevFallback: true },
349
353
  );
350
354
  if (embeddingScript) {
351
- const child = spawn('node', [embeddingScript, '--namespace', NAMESPACE], {
352
- cwd: projectRoot,
353
- detached: true,
354
- stdio: 'ignore',
355
- windowsHide: true,
356
- });
357
- child.unref();
358
- debug('Embedding generation started in background');
355
+ const pm = createProcessManager(projectRoot);
356
+ const result = pm.spawn('node', [embeddingScript, '--namespace', NAMESPACE], `build-embeddings-${NAMESPACE}`);
357
+ if (result.skipped) {
358
+ debug(`Embedding generation already running (PID: ${result.pid})`);
359
+ } else if (result.pid) {
360
+ debug(`Embedding generation started in background (PID: ${result.pid})`);
361
+ }
359
362
  }
360
363
  } catch { /* ignore */ }
361
364
 
@@ -409,7 +409,7 @@ try {
409
409
  ];
410
410
  const cherryPickPath = cherryPickPaths.find((p) => existsSync(p));
411
411
  if (cherryPickPath) {
412
- const mod = await import(`file://${cherryPickPath.replace(/\\/g, '/')}`);
412
+ const mod = await import(pathToFileURL(cherryPickPath).href);
413
413
  if (typeof mod.cherryPickLearningsFromLegacy === 'function') {
414
414
  const result = await mod.cherryPickLearningsFromLegacy({ projectRoot });
415
415
  if (result.copied > 0) {
@@ -858,7 +858,7 @@ try {
858
858
  ];
859
859
  const hwPath = hwPaths.find(p => existsSync(p));
860
860
  if (hwPath) {
861
- const mod = await import(`file://${hwPath.replace(/\\/g, '/')}`);
861
+ const mod = await import(pathToFileURL(hwPath).href);
862
862
  if (typeof mod.rewriteIncorrectHookWiring === 'function') {
863
863
  const { rewrites } = mod.rewriteIncorrectHookWiring(settings);
864
864
  if (rewrites.length > 0) {
@@ -958,8 +958,28 @@ async function runHookBlockDriftCheck() {
958
958
 
959
959
  if (report.drifted) {
960
960
  const wantRegenerate = autoUpdateConfig.hookBlockDrift === 'regenerate';
961
- const safeToRegenerate = wantRegenerate && report.extra.length === 0;
962
- if (safeToRegenerate && typeof mod.applyAdditiveRegeneration === 'function') {
961
+ // #896: regenerate is wholesale when available — the additive variant
962
+ // can't drop stale extras (e.g. the `gate.cjs session-reset` SessionStart
963
+ // hook removed in #842), which is the very case consumers hit. Older
964
+ // moflo installs without `applyWholesaleRegeneration` fall back to the
965
+ // additive path, which still heals purely-additive drift.
966
+ const wholesale = wantRegenerate && typeof mod.applyWholesaleRegeneration === 'function';
967
+ const additiveSafe = wantRegenerate && !wholesale &&
968
+ report.extra.length === 0 && typeof mod.applyAdditiveRegeneration === 'function';
969
+ if (wholesale) {
970
+ const { added, removed } = mod.applyWholesaleRegeneration(settings, report);
971
+ if (added > 0 || removed > 0) {
972
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
973
+ regenerated = true;
974
+ const parts = [];
975
+ if (added > 0) parts.push(`added ${plural(added, 'missing entry')}`);
976
+ if (removed > 0) parts.push(`removed ${plural(removed, 'stale entry')}`);
977
+ emitMutation(
978
+ 'regenerated hook block',
979
+ `${parts.join(', ')} (drift ${report.consumerHash} → ${report.referenceHash})`,
980
+ );
981
+ }
982
+ } else if (additiveSafe) {
963
983
  const { added } = mod.applyAdditiveRegeneration(settings, report);
964
984
  if (added > 0) {
965
985
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
@@ -1102,6 +1122,40 @@ try {
1102
1122
  }
1103
1123
  } catch { /* non-fatal */ }
1104
1124
 
1125
+ // ── 3d-yaml-create. Create moflo.yaml if missing (#895) ────────────────────
1126
+ // Sibling self-heal to 3d-yaml: that block APPENDS new sections to existing
1127
+ // yaml; this block CREATES the file from the canonical template when no yaml
1128
+ // exists at all. Without it, consumers picking up new defaults via npm-install
1129
+ // (e.g. PR #894 flipped model_routing.enabled to true) have no surface to
1130
+ // see/tune them — the DEFAULT_CONFIG fallback is invisible.
1131
+ //
1132
+ // Runs BEFORE 3d-yaml so the appender has a file to work with on the same
1133
+ // session. Steady-state hot path: the existsSync check below short-circuits
1134
+ // before any module load, so consumers with an existing yaml pay one stat call.
1135
+ try {
1136
+ if (!existsSync(resolve(projectRoot, 'moflo.yaml'))) {
1137
+ const tplPaths = [
1138
+ resolve(projectRoot, 'node_modules/moflo/dist/src/cli/init/moflo-yaml-template.js'),
1139
+ resolve(projectRoot, 'dist/src/cli/init/moflo-yaml-template.js'),
1140
+ ];
1141
+ const tplPath = tplPaths.find((p) => existsSync(p));
1142
+ if (tplPath) {
1143
+ const { ensureMofloYamlExists } = await import(pathToFileURL(tplPath).href);
1144
+ const result = ensureMofloYamlExists(projectRoot);
1145
+ if (result?.created) {
1146
+ emitMutation(
1147
+ 'created moflo.yaml',
1148
+ 'review defaults — model routing, sandbox, gates, hooks',
1149
+ );
1150
+ }
1151
+ }
1152
+ }
1153
+ } catch (err) {
1154
+ // Non-fatal — DEFAULT_CONFIG fallback still gives correct behavior; user
1155
+ // just loses the visible/tunable surface until next session retries (#854).
1156
+ emitWarning(`moflo.yaml create skipped (${errMessage(err)})`);
1157
+ }
1158
+
1105
1159
  // ── 3d-yaml. Append missing top-level sections to moflo.yaml ───────────────
1106
1160
  // Users must never be required to re-run `moflo init` after a version bump.
1107
1161
  // When moflo ships a new top-level config section (e.g. sandbox:), append it
@@ -1116,7 +1170,7 @@ try {
1116
1170
  const upgraderPath = upgraderPaths.find((p) => existsSync(p));
1117
1171
  const mofloYaml = resolve(projectRoot, 'moflo.yaml');
1118
1172
  if (upgraderPath && existsSync(mofloYaml)) {
1119
- const { ensureYamlSections } = await import(`file://${upgraderPath.replace(/\\/g, '/')}`);
1173
+ const { ensureYamlSections } = await import(pathToFileURL(upgraderPath).href);
1120
1174
  const appended = ensureYamlSections(mofloYaml);
1121
1175
  if (Array.isArray(appended) && appended.length > 0) {
1122
1176
  emitMutation(
@@ -1135,7 +1189,7 @@ try {
1135
1189
  const localShimLib = resolve(projectRoot, 'bin/lib/install-global-shim.mjs');
1136
1190
  const shimPath = existsSync(shimLib) ? shimLib : existsSync(localShimLib) ? localShimLib : null;
1137
1191
  if (shimPath) {
1138
- const { installGlobalShim } = await import(`file://${shimPath.replace(/\\/g, '/')}`);
1192
+ const { installGlobalShim } = await import(pathToFileURL(shimPath).href);
1139
1193
  const shimResult = installGlobalShim({ silent: true });
1140
1194
  if (shimResult?.installed) {
1141
1195
  emitMutation('installed global flo shim', 'bare `flo` now resolves to project install');
@@ -1162,7 +1216,7 @@ try {
1162
1216
  ];
1163
1217
  const migrationPath = migrationPaths.find((p) => existsSync(p));
1164
1218
  if (migrationPath) {
1165
- const mod = await import(`file://${migrationPath.replace(/\\/g, '/')}`);
1219
+ const mod = await import(pathToFileURL(migrationPath).href);
1166
1220
  if (typeof mod.runEmbeddingsMigrationIfNeeded === 'function') {
1167
1221
  await mod.runEmbeddingsMigrationIfNeeded({
1168
1222
  out: process.stderr,
@@ -1198,7 +1252,7 @@ try {
1198
1252
  ];
1199
1253
  const purgePath = purgePaths.find((p) => existsSync(p));
1200
1254
  if (purgePath) {
1201
- const { purgeSoftDeletedEntries } = await import(`file://${purgePath.replace(/\\/g, '/')}`);
1255
+ const { purgeSoftDeletedEntries } = await import(pathToFileURL(purgePath).href);
1202
1256
  const result = await purgeSoftDeletedEntries();
1203
1257
  if (result?.purged > 0) {
1204
1258
  emitMutation(
@@ -1230,7 +1284,7 @@ try {
1230
1284
  ];
1231
1285
  const purgePath = purgePaths.find((p) => existsSync(p));
1232
1286
  if (purgePath) {
1233
- const { purgeEphemeralNamespaces } = await import(`file://${purgePath.replace(/\\/g, '/')}`);
1287
+ const { purgeEphemeralNamespaces } = await import(pathToFileURL(purgePath).href);
1234
1288
  const result = await purgeEphemeralNamespaces();
1235
1289
  if (result?.purged > 0) {
1236
1290
  emitMutation(
@@ -593,6 +593,20 @@ export async function checkHookBlockDrift() {
593
593
  message: 'drift check skipped — claudeFlow.hooks.locked: true',
594
594
  };
595
595
  }
596
+ // #896: respect `auto_update.hook_block_drift: off` — opt-out for consumers
597
+ // who explicitly don't want drift surfaced (mirrors the launcher's behaviour).
598
+ try {
599
+ const { loadMofloConfig } = await import('../config/moflo-config.js');
600
+ const cfg = loadMofloConfig(projectDir);
601
+ if (cfg.auto_update.hook_block_drift === 'off') {
602
+ return {
603
+ name: 'Hook Block Drift',
604
+ status: 'pass',
605
+ message: 'drift check skipped — auto_update.hook_block_drift: off',
606
+ };
607
+ }
608
+ }
609
+ catch { /* config read failure — fall through to drift check */ }
596
610
  const report = computeHookBlockDrift(settings.hooks ?? {});
597
611
  if (!report.drifted) {
598
612
  return {