moflo 4.9.12 → 4.9.14

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 (33) hide show
  1. package/.claude/helpers/gate.cjs +21 -5
  2. package/.claude/skills/eldar/SKILL.md +305 -0
  3. package/.claude/skills/fl/phases.md +18 -2
  4. package/.claude/skills/simplify/SKILL.md +35 -48
  5. package/README.md +25 -0
  6. package/bin/gate.cjs +21 -5
  7. package/bin/hooks.mjs +2 -2
  8. package/bin/index-guidance.mjs +14 -24
  9. package/bin/index-patterns.mjs +13 -10
  10. package/bin/session-start-launcher.mjs +64 -10
  11. package/bin/simplify-classify.cjs +211 -0
  12. package/dist/src/cli/commands/doctor-checks-config.js +246 -0
  13. package/dist/src/cli/commands/doctor-checks-deep.js +14 -0
  14. package/dist/src/cli/commands/doctor-checks-intelligence.js +197 -0
  15. package/dist/src/cli/commands/doctor-checks-memory.js +207 -0
  16. package/dist/src/cli/commands/doctor-checks-platform.js +138 -0
  17. package/dist/src/cli/commands/doctor-checks-runtime.js +170 -0
  18. package/dist/src/cli/commands/doctor-fixes.js +165 -0
  19. package/dist/src/cli/commands/doctor-registry.js +109 -0
  20. package/dist/src/cli/commands/doctor-render.js +203 -0
  21. package/dist/src/cli/commands/doctor-types.js +9 -0
  22. package/dist/src/cli/commands/doctor-version.js +134 -0
  23. package/dist/src/cli/commands/doctor-zombies.js +201 -0
  24. package/dist/src/cli/commands/doctor.js +35 -1657
  25. package/dist/src/cli/init/helpers-generator.js +21 -5
  26. package/dist/src/cli/init/moflo-init.js +20 -268
  27. package/dist/src/cli/init/moflo-yaml-template.js +370 -0
  28. package/dist/src/cli/mcp-tools/hooks-tools.js +3 -1
  29. package/dist/src/cli/movector/model-router.js +66 -20
  30. package/dist/src/cli/services/hook-block-hash.js +23 -2
  31. package/dist/src/cli/version.js +1 -1
  32. package/package.json +2 -2
  33. package/scripts/post-install-bootstrap.mjs +1 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Intelligence-layer functional checks for `flo doctor`:
3
+ * SONA, ReasoningBank, PatternLearner, MicroLoRA + EWC++, RL algorithms.
4
+ *
5
+ * Each component is exercised with a lightweight functional test rather than
6
+ * just checking "loaded".
7
+ */
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ // Memory-backed pattern fallback (populated by pretrain) when an in-process
11
+ // neural component returns no data. Uses the pattern-search handler that
12
+ // pretrain writes to.
13
+ async function checkMemoryPatterns(_namespace) {
14
+ try {
15
+ const hooksMod = await import('../mcp-tools/hooks-tools.js');
16
+ if (hooksMod.hooksPatternSearch) {
17
+ const result = await hooksMod.hooksPatternSearch.handler({
18
+ query: 'pretrain',
19
+ topK: 1,
20
+ minConfidence: 0.1,
21
+ });
22
+ const matches = result?.results;
23
+ if (Array.isArray(matches))
24
+ return matches.length;
25
+ }
26
+ }
27
+ catch {
28
+ // hooks module not available
29
+ }
30
+ // Secondary fallback: check the memory DB file exists
31
+ const dbPath = join(process.cwd(), '.claude', 'memory.db');
32
+ if (existsSync(dbPath))
33
+ return 1;
34
+ return 0;
35
+ }
36
+ export async function checkIntelligence() {
37
+ try {
38
+ const neural = await import('../neural/index.js');
39
+ const results = [];
40
+ const failures = [];
41
+ // 1. SONA — create manager, run trajectory lifecycle
42
+ try {
43
+ const sona = neural.createSONAManager('balanced');
44
+ await sona.initialize();
45
+ const tid = sona.beginTrajectory('doctor-check', 'general');
46
+ const embedding = new Float32Array(64).fill(0.1);
47
+ sona.recordStep(tid, 'test-action', 0.8, embedding);
48
+ const traj = sona.completeTrajectory(tid, 0.9);
49
+ if (traj && traj.steps.length > 0) {
50
+ results.push('SONA');
51
+ }
52
+ else {
53
+ failures.push('SONA (no trajectory output)');
54
+ }
55
+ await sona.cleanup();
56
+ }
57
+ catch (e) {
58
+ failures.push(`SONA (${e instanceof Error ? e.message : 'error'})`);
59
+ }
60
+ // 2. ReasoningBank — verify instantiation and trajectory store/distill lifecycle
61
+ try {
62
+ const rb = neural.createReasoningBank();
63
+ const stateAfter = new Float32Array(64).fill(0.2);
64
+ const trajectory = {
65
+ trajectoryId: 'doctor-test',
66
+ context: 'health check',
67
+ domain: 'general',
68
+ steps: [{ stepId: 's1', action: 'test', reward: 1, stateBefore: stateAfter, stateAfter, timestamp: Date.now() }],
69
+ startTime: Date.now(),
70
+ endTime: Date.now(),
71
+ qualityScore: 0.9,
72
+ isComplete: true,
73
+ verdict: {
74
+ success: true,
75
+ confidence: 0.9,
76
+ strengths: ['health check passed'],
77
+ weaknesses: [],
78
+ improvements: [],
79
+ relevanceScore: 0.9,
80
+ },
81
+ };
82
+ rb.storeTrajectory(trajectory);
83
+ // distill() populates memories (storeTrajectory alone does not)
84
+ const distilled = await rb.distill(trajectory);
85
+ if (distilled || rb.getTrajectories().length > 0) {
86
+ results.push('ReasoningBank');
87
+ }
88
+ else {
89
+ const memoryPatterns = await checkMemoryPatterns('patterns');
90
+ if (memoryPatterns > 0) {
91
+ results.push('ReasoningBank(memory)');
92
+ }
93
+ else {
94
+ failures.push('ReasoningBank (distill returned no data)');
95
+ }
96
+ }
97
+ }
98
+ catch (e) {
99
+ failures.push(`ReasoningBank (${e instanceof Error ? e.message : 'error'})`);
100
+ }
101
+ // 3. PatternLearner — extract + match
102
+ try {
103
+ const pl = neural.createPatternLearner();
104
+ const embedding = new Float32Array(64).fill(0.3);
105
+ const now = Date.now();
106
+ pl.extractPattern({
107
+ trajectoryId: 'doctor-pl', context: 'test', domain: 'general',
108
+ steps: [{ stepId: 's1', action: 'test', reward: 1, stateBefore: embedding, stateAfter: embedding, timestamp: now }],
109
+ startTime: now, endTime: now, qualityScore: 1, isComplete: true,
110
+ }, { memoryId: 'doctor-pl-mem', trajectoryId: 'doctor-pl', strategy: 'health-check', keyLearnings: ['test'], embedding, quality: 1, usageCount: 0, lastUsed: now });
111
+ const matches = pl.findMatches(embedding, 1);
112
+ if (matches.length > 0) {
113
+ results.push('PatternLearner');
114
+ }
115
+ else {
116
+ const memoryPatterns = await checkMemoryPatterns('patterns');
117
+ if (memoryPatterns > 0) {
118
+ results.push('PatternLearner(memory)');
119
+ }
120
+ else {
121
+ failures.push('PatternLearner (no matches)');
122
+ }
123
+ }
124
+ }
125
+ catch (e) {
126
+ failures.push(`PatternLearner (${e instanceof Error ? e.message : 'error'})`);
127
+ }
128
+ // 4. SONALearningEngine (MicroLoRA + EWC++)
129
+ try {
130
+ const engine = neural.createSONALearningEngine();
131
+ const ctx = { domain: 'general', queryEmbedding: new Float32Array(768).fill(0.1) };
132
+ const adapted = await engine.adapt(ctx);
133
+ const components = [];
134
+ if (adapted && adapted.transformedQuery)
135
+ components.push('LoRA');
136
+ if (adapted && adapted.patterns !== undefined)
137
+ components.push('EWC++');
138
+ if (components.length > 0) {
139
+ results.push(...components);
140
+ }
141
+ else {
142
+ failures.push('LoRA/EWC++ (adapt returned no data)');
143
+ }
144
+ }
145
+ catch (e) {
146
+ // Gracefully handle cold/uninitialized state
147
+ const msg = e instanceof Error ? e.message : 'error';
148
+ if (msg.includes('undefined') || msg.includes('not initialized')) {
149
+ results.push('LoRA/EWC++(cold)');
150
+ }
151
+ else {
152
+ failures.push(`LoRA/EWC++ (${msg})`);
153
+ }
154
+ }
155
+ // 5. RL Algorithms — quick instantiation check
156
+ try {
157
+ const algNames = [];
158
+ const ppo = neural.createPPO();
159
+ if (ppo)
160
+ algNames.push('PPO');
161
+ const dqn = neural.createDQN();
162
+ if (dqn)
163
+ algNames.push('DQN');
164
+ const ql = neural.createQLearning();
165
+ if (ql)
166
+ algNames.push('Q-Learn');
167
+ if (algNames.length > 0) {
168
+ results.push(`RL(${algNames.join('+')})`);
169
+ }
170
+ }
171
+ catch (e) {
172
+ failures.push(`RL (${e instanceof Error ? e.message : 'error'})`);
173
+ }
174
+ if (failures.length > 0) {
175
+ return {
176
+ name: 'Intelligence',
177
+ status: results.length > 0 ? 'warn' : 'fail',
178
+ message: `${results.join(', ')} OK; FAILED: ${failures.join(', ')}`,
179
+ fix: 'Check neural module imports and dependencies',
180
+ };
181
+ }
182
+ return {
183
+ name: 'Intelligence',
184
+ status: 'pass',
185
+ message: results.join(', '),
186
+ };
187
+ }
188
+ catch (e) {
189
+ return {
190
+ name: 'Intelligence',
191
+ status: 'warn',
192
+ message: `Module unavailable: ${e instanceof Error ? e.message.split(/\r?\n/)[0] : 'import failed'}`,
193
+ fix: 'Ensure moflo is built (npm run build)',
194
+ };
195
+ }
196
+ }
197
+ //# sourceMappingURL=doctor-checks-intelligence.js.map
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Memory + embeddings + semantic-search checks for `flo doctor`.
3
+ *
4
+ * `checkEmbeddings` is exported for the #639 stale-cache regression test
5
+ * (src/cli/__tests__/commands/doctor-stale-vector-stats.test.ts).
6
+ */
7
+ import { existsSync, readFileSync, statSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { memoryDbCandidatePaths } from '../services/moflo-paths.js';
10
+ import { errorDetail } from '../shared/utils/error-detail.js';
11
+ /** Skew (cached / live count delta) above which the cache is treated as stale. */
12
+ const VECTOR_STATS_SKEW_WARN_THRESHOLD = 0.2;
13
+ /**
14
+ * Open `dbPath` via moflo's bundled sql.js and return the count of memory_entries
15
+ * rows that have an embedding. Returns null if sql.js can't be loaded, the file
16
+ * isn't a v3 schema, or the query fails — every error is treated as "unknown
17
+ * truth", letting the caller fall back to the cached stats rather than masking
18
+ * a healthy DB as broken.
19
+ */
20
+ async function countEmbeddedRowsFromDb(dbPath) {
21
+ try {
22
+ const { mofloImport } = await import('../services/moflo-require.js');
23
+ const initSqlJs = (await mofloImport('sql.js'))?.default;
24
+ if (!initSqlJs)
25
+ return null;
26
+ const SQL = await initSqlJs();
27
+ const buffer = readFileSync(dbPath);
28
+ const db = new SQL.Database(buffer);
29
+ try {
30
+ const res = db.exec("SELECT COUNT(*) FROM memory_entries WHERE embedding IS NOT NULL AND embedding != ''");
31
+ const cell = res?.[0]?.values?.[0]?.[0];
32
+ return typeof cell === 'number' ? cell : Number(cell ?? 0);
33
+ }
34
+ finally {
35
+ db.close();
36
+ }
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ export async function checkEmbeddings() {
43
+ const liveDbPath = memoryDbCandidatePaths(process.cwd()).find((p) => existsSync(p));
44
+ // 1. Fast path: read cached vector-stats.json if available
45
+ const statsPath = join(process.cwd(), '.moflo', 'vector-stats.json');
46
+ try {
47
+ if (existsSync(statsPath)) {
48
+ const stats = JSON.parse(readFileSync(statsPath, 'utf8'));
49
+ const count = stats.vectorCount ?? 0;
50
+ const updatedAt = typeof stats.updatedAt === 'number' ? stats.updatedAt : 0;
51
+ const hasHnsw = stats.hasHnsw ?? false;
52
+ const dbSizeKB = stats.dbSizeKB ?? 0;
53
+ // Skew check (#639): cross-check the cached vectorCount against the actual
54
+ // DB; if they differ by more than VECTOR_STATS_SKEW_WARN_THRESHOLD, surface
55
+ // a stale-cache warning rather than displaying a wrong number on the
56
+ // statusline. Cheap signals first — opening memory.db via sql.js loads the
57
+ // whole file. Skip the open when the cache was clearly written after the
58
+ // last DB mutation (mtime check) AND the cached count is non-zero. The
59
+ // count===0 case keeps the open because that's the observed #639 failure
60
+ // mode (cache silently clobbered to zero).
61
+ let dbMtimeMs = 0;
62
+ if (liveDbPath) {
63
+ try {
64
+ dbMtimeMs = statSync(liveDbPath).mtimeMs;
65
+ }
66
+ catch { /* missing — handled below */ }
67
+ }
68
+ const cacheNewerThanDb = updatedAt > 0 && dbMtimeMs > 0 && updatedAt >= dbMtimeMs;
69
+ if (liveDbPath && (count === 0 || !cacheNewerThanDb)) {
70
+ const liveCount = await countEmbeddedRowsFromDb(liveDbPath);
71
+ if (liveCount !== null) {
72
+ const denom = Math.max(liveCount, 1);
73
+ const skew = Math.abs(liveCount - count) / denom;
74
+ if (skew > VECTOR_STATS_SKEW_WARN_THRESHOLD) {
75
+ return {
76
+ name: 'Embeddings',
77
+ status: 'warn',
78
+ message: `vector-stats cache is stale (cached ${count}, DB has ${liveCount} embedded rows — ${Math.round(skew * 100)}% skew)`,
79
+ fix: 'node node_modules/moflo/bin/build-embeddings.mjs',
80
+ };
81
+ }
82
+ }
83
+ }
84
+ if (count === 0) {
85
+ return {
86
+ name: 'Embeddings',
87
+ status: 'warn',
88
+ message: `Memory DB exists (${dbSizeKB} KB) but 0 vectors indexed — documents not embedded`,
89
+ fix: 'npx moflo memory init --force && npx moflo embeddings init',
90
+ };
91
+ }
92
+ const hnswLabel = hasHnsw ? ', HNSW' : '';
93
+ return {
94
+ name: 'Embeddings',
95
+ status: 'pass',
96
+ message: `${count} vectors indexed (${dbSizeKB} KB${hnswLabel})`,
97
+ };
98
+ }
99
+ }
100
+ catch {
101
+ // Stats file unreadable — fall through to DB check
102
+ }
103
+ // 2. Check if memory DB file exists at all (reuse liveDbPath from above)
104
+ const foundDbPath = liveDbPath ?? null;
105
+ if (!foundDbPath) {
106
+ return {
107
+ name: 'Embeddings',
108
+ status: 'warn',
109
+ message: 'No memory database — embeddings not initialized',
110
+ fix: 'npx moflo memory init --force',
111
+ };
112
+ }
113
+ // 3. DB exists but no stats cache — try querying the DB for entry count
114
+ try {
115
+ const { checkMemoryInitialization } = await import('../memory/memory-initializer.js');
116
+ const info = await checkMemoryInitialization(foundDbPath);
117
+ if (!info.initialized) {
118
+ return {
119
+ name: 'Embeddings',
120
+ status: 'warn',
121
+ message: 'Memory DB exists but not properly initialized',
122
+ fix: 'npx moflo memory init --force',
123
+ };
124
+ }
125
+ const hasVectors = info.features?.vectorEmbeddings ?? false;
126
+ if (!hasVectors) {
127
+ return {
128
+ name: 'Embeddings',
129
+ status: 'warn',
130
+ message: `Memory DB initialized (v${info.version}) but no vector_indexes table`,
131
+ fix: 'npx moflo memory init --force && npx moflo embeddings init',
132
+ };
133
+ }
134
+ return {
135
+ name: 'Embeddings',
136
+ status: 'pass',
137
+ message: `Memory DB initialized (v${info.version}, vectors enabled)`,
138
+ };
139
+ }
140
+ catch (sqlJsError) {
141
+ // sql.js not available — fall back to file-size heuristic
142
+ const sqlDetail = errorDetail(sqlJsError);
143
+ try {
144
+ const stats = statSync(foundDbPath);
145
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
146
+ return {
147
+ name: 'Embeddings',
148
+ status: 'warn',
149
+ message: `Memory DB exists (${sizeMB} MB) — cannot verify vectors (sql.js not available: ${sqlDetail})`,
150
+ fix: 'npm install sql.js && npx moflo embeddings init',
151
+ };
152
+ }
153
+ catch (statError) {
154
+ return { name: 'Embeddings', status: 'warn', message: `Unable to check: sql.js failed (${sqlDetail}), stat failed (${errorDetail(statError)})` };
155
+ }
156
+ }
157
+ }
158
+ export async function checkSemanticQuality() {
159
+ try {
160
+ const { searchEntries } = await import('../memory/memory-initializer.js');
161
+ const result = await searchEntries({
162
+ query: 'test infrastructure health check',
163
+ namespace: 'patterns',
164
+ limit: 5,
165
+ threshold: 0.1,
166
+ });
167
+ if (!result.success || result.results.length === 0) {
168
+ return {
169
+ name: 'Semantic Quality',
170
+ status: 'warn',
171
+ message: 'No search results (empty database or no patterns namespace)',
172
+ };
173
+ }
174
+ const scores = result.results.map((r) => r.score);
175
+ const allSame = scores.every((s) => s === scores[0]);
176
+ const hasFallback = scores.some((s) => s === 0.5);
177
+ if (hasFallback) {
178
+ return {
179
+ name: 'Semantic Quality',
180
+ status: 'fail',
181
+ message: `${scores.length} results, scores include 0.500 fallback (keyword-only, no embeddings)`,
182
+ fix: 'Re-index with: npx moflo embeddings build --force',
183
+ };
184
+ }
185
+ if (allSame && scores.length > 1) {
186
+ return {
187
+ name: 'Semantic Quality',
188
+ status: 'warn',
189
+ message: `${scores.length} results, all scores identical (${scores[0].toFixed(3)}) — degraded search`,
190
+ };
191
+ }
192
+ const topScore = Math.max(...scores);
193
+ return {
194
+ name: 'Semantic Quality',
195
+ status: topScore >= 0.3 ? 'pass' : 'warn',
196
+ message: `${scores.length} results, top ${topScore.toFixed(3)}, varied (semantic search active)`,
197
+ };
198
+ }
199
+ catch (e) {
200
+ return {
201
+ name: 'Semantic Quality',
202
+ status: 'warn',
203
+ message: `Check failed: ${e instanceof Error ? e.message.split(/\r?\n/)[0] : 'error'}`,
204
+ };
205
+ }
206
+ }
207
+ //# sourceMappingURL=doctor-checks-memory.js.map
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Platform-level checks for `flo doctor`:
3
+ * spell engine integrity and OS sandbox tier.
4
+ */
5
+ import { existsSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { errorDetail } from '../shared/utils/error-detail.js';
8
+ import { getMofloRoot } from './doctor-checks-deep.js';
9
+ // Validates core modules, built output, and step commands for the spell engine.
10
+ export async function checkSpellEngine() {
11
+ try {
12
+ // Resolve relative to the moflo package root (works in both dev and consumer)
13
+ const mofloRoot = getMofloRoot();
14
+ if (!mofloRoot) {
15
+ return { name: 'Spell Engine', status: 'warn', message: 'Could not locate moflo package root', fix: 'npm run build' };
16
+ }
17
+ // Post-#586 workspace collapse: spell engine lives at src/cli/spells/
18
+ // (source) and dist/src/cli/spells/ (compiled). The legacy
19
+ // src/modules/spells/{src,dist}/ tree was deleted.
20
+ const distDir = join(mofloRoot, 'dist', 'src', 'cli', 'spells');
21
+ const srcDir = join(mofloRoot, 'src', 'cli', 'spells');
22
+ const hasDistDir = existsSync(distDir);
23
+ const hasSrcDir = existsSync(srcDir);
24
+ if (!hasDistDir && !hasSrcDir) {
25
+ return { name: 'Spell Engine', status: 'warn', message: 'Spell engine not found', fix: 'npm run build' };
26
+ }
27
+ const coreModules = [
28
+ 'core/runner',
29
+ 'core/step-executor',
30
+ 'core/step-command-registry',
31
+ 'core/interpolation',
32
+ 'core/credential-masker',
33
+ 'registry/spell-registry',
34
+ 'factory/runner-factory',
35
+ 'schema',
36
+ 'types',
37
+ 'credentials',
38
+ 'scheduler',
39
+ ];
40
+ const baseDir = hasDistDir ? distDir : srcDir;
41
+ const ext = hasDistDir ? '.js' : '.ts';
42
+ const dirModules = ['schema', 'types', 'credentials', 'scheduler'];
43
+ const missing = coreModules.filter(m => dirModules.includes(m)
44
+ ? !existsSync(join(baseDir, m))
45
+ : !existsSync(join(baseDir, m + ext)));
46
+ if (missing.length > 0) {
47
+ return {
48
+ name: 'Spell Engine',
49
+ status: 'warn',
50
+ message: `Missing modules: ${missing.join(', ')}`,
51
+ fix: 'npm run build',
52
+ };
53
+ }
54
+ const commandsDir = join(baseDir, 'commands');
55
+ const hasCommands = existsSync(commandsDir);
56
+ const loadersDir = join(baseDir, 'loaders');
57
+ const hasLoaders = existsSync(loadersDir);
58
+ const hasIndex = existsSync(join(baseDir, 'index' + ext));
59
+ const parts = [];
60
+ parts.push(`${coreModules.length} core modules`);
61
+ if (hasCommands)
62
+ parts.push('step commands');
63
+ if (hasLoaders)
64
+ parts.push('loaders');
65
+ if (hasIndex)
66
+ parts.push('index');
67
+ return {
68
+ name: 'Spell Engine',
69
+ status: 'pass',
70
+ message: parts.join(', '),
71
+ };
72
+ }
73
+ catch (e) {
74
+ return { name: 'Spell Engine', status: 'warn', message: `Unable to check spell engine: ${errorDetail(e)}` };
75
+ }
76
+ }
77
+ // Reports OS sandbox capability AND, if the project has `sandbox.enabled: true`,
78
+ // whether the effective sandbox would actually start (e.g. Windows Docker image
79
+ // pulled and configured).
80
+ export async function checkSandboxTier() {
81
+ try {
82
+ const { detectSandboxCapability, loadSandboxConfigFromProject, resolveEffectiveSandbox, } = await import('../spells/index.js');
83
+ const cap = await detectSandboxCapability();
84
+ const config = await loadSandboxConfigFromProject(process.cwd());
85
+ if (!config.enabled) {
86
+ if (cap.available) {
87
+ return {
88
+ name: 'Sandbox Tier',
89
+ status: 'pass',
90
+ message: `${cap.tool} available (${cap.platform}) — sandboxing off in moflo.yaml`,
91
+ };
92
+ }
93
+ const offHint = {
94
+ win32: 'Install Docker Desktop and set sandbox.dockerImage in moflo.yaml to enable sandboxing',
95
+ linux: 'Install bubblewrap: sudo apt install bubblewrap',
96
+ darwin: 'sandbox-exec should be available on macOS — check /usr/bin/sandbox-exec',
97
+ };
98
+ return {
99
+ name: 'Sandbox Tier',
100
+ status: 'pass',
101
+ message: `sandboxing off (${cap.platform}, denylist active)`,
102
+ fix: offHint[cap.platform],
103
+ };
104
+ }
105
+ try {
106
+ const effective = await resolveEffectiveSandbox(config);
107
+ if (effective.useOsSandbox) {
108
+ const imageHint = effective.config.dockerImage ? `, ${effective.config.dockerImage}` : '';
109
+ return {
110
+ name: 'Sandbox Tier',
111
+ status: 'pass',
112
+ message: `${cap.tool} ready (${cap.platform}${imageHint})`,
113
+ };
114
+ }
115
+ return {
116
+ name: 'Sandbox Tier',
117
+ status: 'warn',
118
+ message: `denylist only (${cap.platform})`,
119
+ };
120
+ }
121
+ catch (err) {
122
+ return {
123
+ name: 'Sandbox Tier',
124
+ status: 'warn',
125
+ message: `sandboxing enabled but not ready (${cap.platform})`,
126
+ fix: errorDetail(err),
127
+ };
128
+ }
129
+ }
130
+ catch (err) {
131
+ return {
132
+ name: 'Sandbox Tier',
133
+ status: 'warn',
134
+ message: `Unable to detect: ${err instanceof Error ? err.message : 'unknown error'}`,
135
+ };
136
+ }
137
+ }
138
+ //# sourceMappingURL=doctor-checks-platform.js.map