From 809976638d04e0c70d7ebfeffa58382e2f91f72d Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sat, 22 Aug 2026 11:58:26 -0700 Subject: [PATCH] Share doctor boot finding policy Centralize the shared boot artifact checks so doctor adds only its deep checks while preserving the existing finding order and CLI contracts. AI-assisted: prepared by Codex under maintainer pbakaus scheduled-refactor authorization. --- skill/scripts/doctor.mjs | 37 +++++++++++++-------------------- skill/scripts/lib/staleness.mjs | 37 +++++++++++++++++++-------------- tests/doctor.test.mjs | 20 ++++++++++++++++++ 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/skill/scripts/doctor.mjs b/skill/scripts/doctor.mjs index ca3105809..b311f0366 100644 --- a/skill/scripts/doctor.mjs +++ b/skill/scripts/doctor.mjs @@ -33,13 +33,8 @@ import { stampProductSchema, } from './lib/artifact-schema.mjs'; import { - checkBuildPathUnset, - checkConfig, - checkDesignSidecar, + collectBootFindingGroups, checkNativePlatformEvidence, - checkProduct, - checkProjectRoots, - checkSurfaceBriefs, designSidecarCandidatesFor, } from './lib/staleness.mjs'; import { @@ -106,34 +101,30 @@ async function collect(cwd, targetOptions) { extractPlatform, readFile: safeRead, }); + const bootFindings = collectBootFindingGroups(ctx, { + absDesignPath, + sidecarCandidates, + projectRootPatterns: readProjectRootPatterns(ctx.repoRoot), + targetCandidates: workspaceCandidates, + }); const findings = [ - ...checkProduct(ctx.product, ctx.productPath || 'PRODUCT.md'), - ...(ctx.product - ? checkNativePlatformEvidence({ - projectRoot, - platform: ctx.platform, - product: ctx.product, - productPath: ctx.productPath, - }) - : []), - ...checkDesignSidecar({ designPath: absDesignPath, sidecarCandidates, projectRoot }), + ...bootFindings.product, + ...bootFindings.nativePlatform, + ...bootFindings.designSidecar, ...checkDesignDrift({ designPath: absDesignPath, projectRoot }), ...checkDesignCoverage({ design: ctx.design, designPath: ctx.designPath, parseDesignMd }), - ...checkConfig({ projectRoot, repoRoot: ctx.repoRoot }), - ...checkBuildPathUnset({ projectRoot, repoRoot: ctx.repoRoot, product: ctx.product }), + ...bootFindings.config, + ...bootFindings.buildPath, ...checkDetectorIgnores({ projectRoot, knownRuleIds }), - ...checkSurfaceBriefs({ candidates: ctx.surfaceBriefCandidates, projectRoot }), + ...bootFindings.surfaceBriefs, ...checkHookInstallation({ projectRoot, repoRoot: ctx.repoRoot, providerId: IMPECCABLE_PROVIDER_ID, }), ...checkLegacyLiveState({ projectRoot }), - ...checkProjectRoots({ - patterns: readProjectRootPatterns(ctx.repoRoot), - candidates: workspaceCandidates, - }), + ...bootFindings.projectRoots, ...workspaceResult.findings, ]; diff --git a/skill/scripts/lib/staleness.mjs b/skill/scripts/lib/staleness.mjs index 80599095b..dde3b2715 100644 --- a/skill/scripts/lib/staleness.mjs +++ b/skill/scripts/lib/staleness.mjs @@ -488,41 +488,46 @@ export function describeWorkspaceContext(candidates = []) { // ─── Tier 1 orchestration ────────────────────────────────────────────────── /** - * Everything a boot can afford. `ctx` is the loadContext result; `extras` - * carries values the caller already computed so nothing is recomputed here. + * Everything a boot can afford, grouped by artifact so deeper reports can + * interleave their own checks without rebuilding this policy. `ctx` is the + * loadContext result; `extras` carries values the caller already computed so + * nothing is recomputed here. */ -export function collectBootFindings(ctx, extras = {}) { - if (!ctx) return []; +export function collectBootFindingGroups(ctx, extras = {}) { + if (!ctx) return {}; const projectRoot = ctx.projectRoot || process.cwd(); - const absProductPath = extras.absProductPath || null; const absDesignPath = extras.absDesignPath || null; - return [ - ...checkProduct(ctx.product, ctx.productPath || 'PRODUCT.md'), + return { + product: checkProduct(ctx.product, ctx.productPath || 'PRODUCT.md'), // Only checked once a PRODUCT.md exists. Without one the boot already // emits NO_PRODUCT_MD and routes into init, which asks for the platform // directly; a second signal saying the same thing is noise. - ...(ctx.product + nativePlatform: ctx.product ? checkNativePlatformEvidence({ projectRoot, platform: ctx.platform, product: ctx.product, productPath: ctx.productPath, }) - : []), - ...checkDesignSidecar({ + : [], + designSidecar: checkDesignSidecar({ designPath: absDesignPath, sidecarCandidates: extras.sidecarCandidates || [], projectRoot, }), - ...checkConfig({ projectRoot, repoRoot: ctx.repoRoot }), - ...checkBuildPathUnset({ projectRoot, repoRoot: ctx.repoRoot, product: ctx.product }), - ...checkSurfaceBriefs({ candidates: ctx.surfaceBriefCandidates, projectRoot }), - ...(extras.projectRootPatterns + config: checkConfig({ projectRoot, repoRoot: ctx.repoRoot }), + buildPath: checkBuildPathUnset({ projectRoot, repoRoot: ctx.repoRoot, product: ctx.product }), + surfaceBriefs: checkSurfaceBriefs({ candidates: ctx.surfaceBriefCandidates, projectRoot }), + projectRoots: extras.projectRootPatterns ? checkProjectRoots({ patterns: extras.projectRootPatterns, candidates: extras.targetCandidates || [], }) - : []), - ]; + : [], + }; +} + +export function collectBootFindings(ctx, extras = {}) { + return Object.values(collectBootFindingGroups(ctx, extras)).flat(); } diff --git a/tests/doctor.test.mjs b/tests/doctor.test.mjs index 877c38b0e..a9d7ffb05 100644 --- a/tests/doctor.test.mjs +++ b/tests/doctor.test.mjs @@ -634,6 +634,26 @@ describe('doctor CLI', () => { assert.equal(report.ruleRegistryAvailable, true); }); + it('keeps boot and deep findings in their established artifact order', () => { + write('PRODUCT.md', '# Product\n\n## Register\n\nbrand\n\n## Users\nDesigners.\n'); + write('DESIGN.md', '---\nname: Example\n---\n\n# Design System: Example\n'); + write('.impeccable/design.json', JSON.stringify({ schemaVersion: 1 })); + write('.impeccable/config.json', JSON.stringify({ unknownSetting: true })); + + const res = run(['--json']); + assert.equal(res.status, 0, res.stderr); + assert.deepEqual( + JSON.parse(res.stdout).findings.map((entry) => entry.id), + [ + 'product-deprecated-register', + 'product-schema-legacy', + 'design-sidecar-schema-outdated', + 'design-md-coverage', + 'config-unknown-keys', + ], + ); + }); + it('applies only the automatic migrations under --fix', () => { write('PRODUCT.md', CURRENT_PRODUCT.replace('\n\n', '')); write('DESIGN.json', JSON.stringify({ schemaVersion: 2 }));