| 294 | const raw = fs.readFileSync(absolute, 'utf8'); |
| 295 | if (!raw.trim()) return source(relativePath, 'empty', 'source exists and is empty', 0); |
| 296 | if (options.json) { |
| 297 | const value = JSON.parse(raw); |
| 298 | if (options.schema && value.schema !== options.schema) throw new Error(`expected schema ${options.schema}`); |
| 299 | } |
| 300 | return source(relativePath, 'healthy', 'source is readable', 1); |
| 301 | } catch (error) { |
| 302 | return source(relativePath, 'unreadable', error.message, null, [relativePath]); |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | function inspectSources(projectRoot, data) { |
| 307 | const planningExists = fs.existsSync(path.join(projectRoot, '.planning')); |
| 308 | if (!planningExists) { |
| 309 | const missing = (name, pathname) => [name, source(pathname, 'unknown', '.planning is absent')]; |
| 310 | return Object.fromEntries([ |
| 311 | missing('campaigns', '.planning/campaigns'), missing('fleet', '.planning/fleet'), |
| 312 | missing('forks', '.planning/operation-forks'), |
| 313 | missing('loops', '.planning/loops'), missing('hooks', '.planning/telemetry'), |
| 314 | missing('handoffs', '.planning/handoffs'), missing('cost', '.planning/telemetry'), |
| 315 | missing('activation', '.planning/product-proof/activation-report.json'), |
| 316 | ]); |
| 317 | } |
| 318 | |
| 319 | const campaignState = inspectDirectory(projectRoot, '.planning/campaigns', { |
| 320 | filter: (name) => name.endsWith('.md'), midRun: Boolean(data.snapshot && data.snapshot.campaigns && data.snapshot.campaigns.length), |
| 321 | }); |
| 322 | if (data.snapshot && data.snapshot.skippedCampaigns && data.snapshot.skippedCampaigns.length) { |
| 323 | campaignState.status = 'unreadable'; |
| 324 | campaignState.detail = `${data.snapshot.skippedCampaigns.length} campaign file(s) could not be parsed`; |
| 325 | campaignState.unreadable = data.snapshot.skippedCampaigns.map((item) => path.relative(projectRoot, item.filePath).replace(/\\/g, '/')); |
| 326 | } |
| 327 | const fleetState = inspectDirectory(projectRoot, '.planning/fleet', { |