moflo 4.9.25 → 4.9.26

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: healer
3
3
  description: Run moflo's Healer (`flo healer`, alias for `flo doctor`) from inside the Claude session. Audit-only by default; pass `--fix` to apply auto-repairs, `-c <component>` for a single check. Use when something feels off (missing moflo.yaml, daemon dead, statusline empty, hooks not firing) or as a periodic health check. Distinct from Claude Code's built-in `/doctor`, which diagnoses Claude Code itself, not moflo.
4
- arguments: "[--fix] [-c <component>]"
4
+ arguments: "[options]"
5
5
  ---
6
6
 
7
7
  # /healer — moflo Installation Healer
@@ -43,7 +43,7 @@ Thin wrapper around the `flo healer` CLI. All check + fix logic lives in the CLI
43
43
  - **Don't** re-document checks or fixes here. The CLI's `--help` and `src/cli/commands/doctor-*` are the source of truth.
44
44
  - **Don't** call `flo doctor` directly — use the `healer` alias for thematic consistency. They're equivalent CLI-side.
45
45
  - **Don't** swallow non-zero exit codes silently — surface them in the summary.
46
- - **Note for users:** Claude Code has its own built-in `/doctor` command that diagnoses Claude Code itself. This skill (`/healer`) diagnoses **moflo**, not Claude Code. The two are complementary, not duplicates and the healer also runs `claude doctor` internally as a delegated check (`Claude Code Doctor`) so Claude-side issues (auth, settings drift, IDE/extension state) surface in the same report. With `--fix`, the healer re-runs `claude doctor` interactively so you can see and act on its findings; Claude-side fixes typically need user gestures (re-auth, IDE reload) and aren't auto-applied.
46
+ - **Note for users:** Claude Code has its own built-in `/doctor` command that diagnoses Claude Code itself. This skill (`/healer`) diagnoses **moflo**, not Claude Code. The two are complementary, not duplicates. The healer rolls in the user-actionable parts of `claude doctor` (Claude Code version freshness vs npm latest) into its own `Claude Code CLI` check; the rest of `claude doctor` is a TUI on current releases and must be run interactively if you need its full report.
47
47
 
48
48
  ## See Also
49
49
 
@@ -10,6 +10,7 @@ import { execSync, exec } from 'child_process';
10
10
  import { promisify } from 'util';
11
11
  import { errorDetail } from '../shared/utils/error-detail.js';
12
12
  import { output } from '../output.js';
13
+ import { fetchLatestNpmVersion, parseVersion, isOutdated } from './doctor-version.js';
13
14
  const execAsync = promisify(exec);
14
15
  /**
15
16
  * Execute command asynchronously with proper environment inheritance.
@@ -132,11 +133,9 @@ export async function checkBuildTools() {
132
133
  }
133
134
  }
134
135
  export async function checkClaudeCode() {
136
+ let installedRaw;
135
137
  try {
136
- const version = await runCommand('claude --version');
137
- const versionMatch = version.match(/v?(\d+\.\d+\.\d+)/);
138
- const versionStr = versionMatch ? `v${versionMatch[1]}` : version;
139
- return { name: 'Claude Code CLI', status: 'pass', message: versionStr };
138
+ installedRaw = await runCommand('claude --version');
140
139
  }
141
140
  catch (e) {
142
141
  return {
@@ -146,83 +145,38 @@ export async function checkClaudeCode() {
146
145
  fix: 'npm install -g @anthropic-ai/claude-code',
147
146
  };
148
147
  }
149
- }
150
- /**
151
- * Delegate diagnostics to Claude Code's own `claude doctor` command and surface
152
- * the result. Catches Claude-side issues (settings drift, MCP/auth, IDE/extension
153
- * state, update channel) that moflo's own checks can't see since `claude` is
154
- * not a moflo-owned binary we don't try to parse its output structurally; we
155
- * just report exit code + a short tail. Skip silently when `claude` isn't
156
- * installed `checkClaudeCode` already covers that condition.
157
- */
158
- export async function checkClaudeCodeDoctor() {
148
+ const versionMatch = installedRaw.match(/v?(\d+\.\d+\.\d+)/);
149
+ const installedClean = versionMatch ? versionMatch[1] : installedRaw.trim();
150
+ const installedDisplay = versionMatch ? `v${installedClean}` : installedRaw.trim();
151
+ // Compare against the latest published @anthropic-ai/claude-code on npm.
152
+ // Replaces the old `claude doctor` delegate (which became a TUI in CC 2.1.x
153
+ // and could not be parsed from a non-TTY child). Freshness was the only
154
+ // user-actionable signal that delegate produced; the auto-updater state +
155
+ // .mcp.json server health it also covered are already verified by the
156
+ // existing daemon and `MCP Servers` checks.
157
+ let latest;
159
158
  try {
160
- await runCommand('claude --version', 3000);
159
+ latest = await fetchLatestNpmVersion('@anthropic-ai/claude-code');
161
160
  }
162
- catch {
163
- return {
164
- name: 'Claude Code Doctor',
165
- status: 'pass',
166
- message: 'Skipped (claude CLI not installed — see Claude Code CLI check)',
167
- };
168
- }
169
- // Capture both streams + exit code without throwing. `claude doctor` exits
170
- // non-zero on findings, so a try/catch over execAsync would lose the body.
171
- const result = await new Promise((resolve) => {
172
- const child = exec('claude doctor', {
173
- encoding: 'utf8',
174
- timeout: 30000,
175
- shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh',
176
- env: { ...process.env },
177
- windowsHide: true,
178
- }, (err, stdout, stderr) => {
179
- resolve({
180
- code: err && typeof err.code === 'number'
181
- ? (err.code)
182
- : (err ? 1 : 0),
183
- stdout: (stdout || '').toString().trim(),
184
- stderr: (stderr || '').toString().trim(),
185
- });
186
- });
187
- child.on('error', () => resolve({ code: 1, stdout: '', stderr: '' }));
188
- });
189
- // claude doctor not recognised → some Claude versions don't ship the
190
- // subcommand. Surface as a pass-skip rather than a failure so older Claude
191
- // installs aren't penalised.
192
- const combined = `${result.stdout}\n${result.stderr}`.toLowerCase();
193
- if (/unknown command|command not found|usage:.*claude/.test(combined) &&
194
- !combined.includes('check')) {
161
+ catch (e) {
195
162
  return {
196
- name: 'Claude Code Doctor',
163
+ name: 'Claude Code CLI',
197
164
  status: 'pass',
198
- message: 'Skipped (this Claude version does not expose `claude doctor`)',
165
+ message: `${installedDisplay} (registry unreachable: ${errorDetail(e, { firstLineOnly: true })})`,
199
166
  };
200
167
  }
201
- if (result.code === 0) {
202
- const firstLine = result.stdout.split(/\r?\n/).find((l) => l.trim()) || 'No issues reported';
203
- return { name: 'Claude Code Doctor', status: 'pass', message: firstLine.slice(0, 120) };
204
- }
205
- // Non-zero with zero output → `claude doctor` is interactive in current
206
- // Claude Code releases (verified on 2.1.132): it opens a TUI and produces
207
- // nothing on a non-TTY child stdout, then our exec timeout kills it. Treat
208
- // as a skip — the check can't observe the TUI from here, and warning would
209
- // fire on every machine running the same Claude version.
210
- if (!result.stdout && !result.stderr) {
168
+ if (versionMatch && isOutdated(parseVersion(installedClean), parseVersion(latest))) {
211
169
  return {
212
- name: 'Claude Code Doctor',
213
- status: 'pass',
214
- message: 'Skipped (claude doctor is interactive — run manually to see findings)',
170
+ name: 'Claude Code CLI',
171
+ status: 'warn',
172
+ message: `${installedDisplay} (latest: v${latest})`,
173
+ fix: 'npm install -g @anthropic-ai/claude-code@latest',
215
174
  };
216
175
  }
217
- // Non-zero — surface the tail so the user has a hint, and point to the
218
- // interactive command for the full report. Don't try to fix from here:
219
- // Claude-side fixes (re-auth, settings repair, IDE reload) need user gestures.
220
- const tailLines = result.stdout.split(/\r?\n/).filter((l) => l.trim()).slice(-3).join(' | ');
221
176
  return {
222
- name: 'Claude Code Doctor',
223
- status: 'warn',
224
- message: `claude doctor reported issues: ${tailLines.slice(0, 200)}`,
225
- fix: 'Run `claude doctor` interactively for full report and follow its instructions',
177
+ name: 'Claude Code CLI',
178
+ status: 'pass',
179
+ message: `${installedDisplay} (up to date)`,
226
180
  };
227
181
  }
228
182
  export async function installClaudeCode() {
@@ -5,7 +5,6 @@
5
5
  * shell-out where possible). Falls back to running the check's `fix` string
6
6
  * if it looks like an `npx`/`npm`/`claude` command.
7
7
  */
8
- import { execSync } from 'child_process';
9
8
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
10
9
  import { join } from 'path';
11
10
  import { output } from '../output.js';
@@ -175,27 +174,6 @@ export async function autoFixCheck(check) {
175
174
  'Claude Code CLI': async () => {
176
175
  return installClaudeCode();
177
176
  },
178
- // Pass-through to Claude Code's own diagnostic. We don't own its CLI surface
179
- // and most Claude-side findings (auth, IDE reload, settings drift) need
180
- // user gestures, so the "fix" here is just to re-run with inherited stdio
181
- // and let the user act on what they see.
182
- 'Claude Code Doctor': async () => {
183
- try {
184
- execSync('claude doctor', {
185
- encoding: 'utf8',
186
- stdio: 'inherit',
187
- windowsHide: true,
188
- timeout: 60000,
189
- });
190
- return true;
191
- }
192
- catch {
193
- // Non-zero exit is informational here — user has seen the output and
194
- // can act on it. Don't claim success, but don't claim failure of OUR
195
- // healer either; flag as "needs manual action".
196
- return false;
197
- }
198
- },
199
177
  'Zombie Processes': async () => {
200
178
  const result = await findZombieProcesses(true);
201
179
  return result.killed > 0 || result.details.length === 0;
@@ -8,7 +8,7 @@ import { checkSubagentHealth, checkSpellExecution, checkMcpToolInvocation, check
8
8
  import { checkEmbeddingHygiene } from './doctor-embedding-hygiene.js';
9
9
  import { checkSwarmFunctional, checkHiveMindFunctional, } from './doctor-checks-swarm.js';
10
10
  import { checkMemoryAccessFunctional } from './doctor-checks-memory-access.js';
11
- import { checkBuildTools, checkClaudeCode, checkClaudeCodeDoctor, checkDiskSpace, checkGit, checkGitRepo, checkNodeVersion, checkNpmVersion, } from './doctor-checks-runtime.js';
11
+ import { checkBuildTools, checkClaudeCode, checkDiskSpace, checkGit, checkGitRepo, checkNodeVersion, checkNpmVersion, } from './doctor-checks-runtime.js';
12
12
  import { checkConfigFile, checkDaemonStatus, checkMcpServers, checkMemoryDatabase, checkMofloYamlCompliance, checkStatusLine, checkTestDirs, } from './doctor-checks-config.js';
13
13
  import { checkSpellEngine, checkSandboxTier } from './doctor-checks-platform.js';
14
14
  import { checkEmbeddings, checkSemanticQuality, } from './doctor-checks-memory.js';
@@ -21,7 +21,6 @@ export const allChecks = [
21
21
  checkNodeVersion,
22
22
  checkNpmVersion,
23
23
  checkClaudeCode,
24
- checkClaudeCodeDoctor,
25
24
  checkGit,
26
25
  checkGitRepo,
27
26
  checkConfigFile,
@@ -64,8 +63,6 @@ export const componentMap = {
64
63
  'node': checkNodeVersion,
65
64
  'npm': checkNpmVersion,
66
65
  'claude': checkClaudeCode,
67
- 'claude-doctor': checkClaudeCodeDoctor,
68
- 'claude-code-doctor': checkClaudeCodeDoctor,
69
66
  'config': checkConfigFile,
70
67
  'yaml': checkMofloYamlCompliance,
71
68
  'moflo-yaml': checkMofloYamlCompliance,
@@ -66,11 +66,11 @@ function isOutdated(current, latest) {
66
66
  // Manual AbortController (NOT AbortSignal.timeout): the latter leaves
67
67
  // a libuv timer alive past process exit on Node 24 / Windows and trips
68
68
  // an `!(handle->flags & UV_HANDLE_CLOSING)` assertion in src/win/async.c.
69
- async function fetchLatestVersion() {
69
+ export async function fetchLatestNpmVersion(pkg) {
70
70
  const ac = new AbortController();
71
71
  const timer = setTimeout(() => ac.abort(), REGISTRY_FETCH_TIMEOUT_MS);
72
72
  try {
73
- const response = await fetch('https://registry.npmjs.org/moflo/latest', {
73
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`, {
74
74
  headers: { Accept: 'application/json' },
75
75
  signal: ac.signal,
76
76
  });
@@ -86,6 +86,10 @@ async function fetchLatestVersion() {
86
86
  clearTimeout(timer);
87
87
  }
88
88
  }
89
+ export { parseVersion, isOutdated };
90
+ async function fetchLatestVersion() {
91
+ return fetchLatestNpmVersion('moflo');
92
+ }
89
93
  export async function checkVersionFreshness() {
90
94
  try {
91
95
  const currentVersion = readCurrentVersion();
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.9.25';
5
+ export const VERSION = '4.9.26';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.9.25",
3
+ "version": "4.9.26",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "type": "module",
@@ -84,7 +84,7 @@
84
84
  "@typescript-eslint/eslint-plugin": "^7.18.0",
85
85
  "@typescript-eslint/parser": "^7.18.0",
86
86
  "eslint": "^8.0.0",
87
- "moflo": "^4.9.24",
87
+ "moflo": "^4.9.25",
88
88
  "tsx": "^4.21.0",
89
89
  "typescript": "^5.9.3",
90
90
  "vitest": "^4.0.0"