@@ -77,6 +77,19 @@ const mocks = vi.hoisted(() => ({
7777 noteWorkspaceStatus : vi . fn ( ) ,
7878 collectWorkspaceStatusHealthFindings : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
7979 collectDevicePairingHealthFindings : vi . fn ( async ( ) => [ ] ) ,
80+ scanConfiguredChannelPluginBlockers : vi . fn (
81+ ( ) : Array < { channelId : string ; pluginId : string ; reason : string } > => [ ] ,
82+ ) ,
83+ channelPluginBlockerHitToHealthFinding : vi . fn (
84+ ( hit : { channelId : string ; pluginId : string ; reason : string } ) => ( {
85+ checkId : "core/doctor/channel-plugin-blockers" ,
86+ severity : "warning" as const ,
87+ message : "channels." + hit . channelId + " blocked" ,
88+ path : "channels." + hit . channelId ,
89+ target : hit . pluginId ,
90+ requirement : hit . reason ,
91+ } ) ,
92+ ) ,
8093 applyWizardMetadata : vi . fn ( ( cfg : unknown ) => cfg ) ,
8194 logConfigUpdated : vi . fn ( ) ,
8295 isRecord : vi . fn (
@@ -278,6 +291,11 @@ vi.mock("../commands/doctor-device-pairing.js", () => ({
278291 noteDevicePairingHealth : vi . fn ( ) . mockResolvedValue ( undefined ) ,
279292} ) ) ;
280293
294+ vi . mock ( "../commands/doctor/shared/channel-plugin-blockers.js" , ( ) => ( {
295+ scanConfiguredChannelPluginBlockers : mocks . scanConfiguredChannelPluginBlockers ,
296+ channelPluginBlockerHitToHealthFinding : mocks . channelPluginBlockerHitToHealthFinding ,
297+ } ) ) ;
298+
281299vi . mock ( "../commands/onboard-helpers.js" , ( ) => ( {
282300 applyWizardMetadata : mocks . applyWizardMetadata ,
283301 randomToken : vi . fn ( ( ) => "generated-gateway-token" ) ,
@@ -452,6 +470,9 @@ describe("doctor health contributions", () => {
452470 mocks . collectWorkspaceStatusHealthFindings . mockResolvedValue ( [ ] ) ;
453471 mocks . collectDevicePairingHealthFindings . mockReset ( ) ;
454472 mocks . collectDevicePairingHealthFindings . mockResolvedValue ( [ ] ) ;
473+ mocks . scanConfiguredChannelPluginBlockers . mockReset ( ) ;
474+ mocks . scanConfiguredChannelPluginBlockers . mockReturnValue ( [ ] ) ;
475+ mocks . channelPluginBlockerHitToHealthFinding . mockClear ( ) ;
455476 } ) ;
456477
457478 afterEach ( ( ) => {
@@ -1169,6 +1190,7 @@ describe("doctor health contributions", () => {
11691190 expect ( contributionIds ) . toContain ( "core/doctor/plugin-registry" ) ;
11701191 expect ( contributionIds ) . toContain ( "core/doctor/configured-plugin-installs" ) ;
11711192 expect ( contributionIds ) . toContain ( "core/doctor/device-pairing" ) ;
1193+ expect ( contributionIds ) . toContain ( "core/doctor/channel-plugin-blockers" ) ;
11721194 expect ( contributionChecks . map ( ( check ) => check . id ) ) . toEqual ( contributionIds ) ;
11731195 } ) ;
11741196
@@ -1279,7 +1301,6 @@ describe("doctor health contributions", () => {
12791301 runtime : { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ,
12801302 } as const ;
12811303 const checks = [ devicePairingCheck ! ] ;
1282-
12831304 await expect ( runDoctorLintChecks ( ctx , { checks } ) ) . resolves . toMatchObject ( {
12841305 checksRun : 0 ,
12851306 checksSkipped : 1 ,
@@ -1298,6 +1319,46 @@ describe("doctor health contributions", () => {
12981319 } ) ;
12991320 } ) ;
13001321
1322+ it ( "keeps channel plugin blockers opt-in for default lint selection" , async ( ) => {
1323+ const contributionChecks = await resolveDoctorContributionHealthChecks ( ) ;
1324+ const blockerCheck = contributionChecks . find (
1325+ ( check ) => check . id === "core/doctor/channel-plugin-blockers" ,
1326+ ) ;
1327+ expect ( blockerCheck ) . toMatchObject ( { defaultEnabled : false } ) ;
1328+ expect ( blockerCheck ) . toBeDefined ( ) ;
1329+ mocks . scanConfiguredChannelPluginBlockers . mockReturnValue ( [
1330+ { channelId : "discord" , pluginId : "discord" , reason : "missing explicit enablement" } ,
1331+ ] ) ;
1332+
1333+ const ctx = {
1334+ cfg : { channels : { discord : { enabled : true } } } ,
1335+ mode : "lint" ,
1336+ runtime : { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ,
1337+ } as const ;
1338+ const checks = [ blockerCheck ! ] ;
1339+
1340+ await expect ( runDoctorLintChecks ( ctx , { checks } ) ) . resolves . toMatchObject ( {
1341+ checksRun : 0 ,
1342+ checksSkipped : 1 ,
1343+ } ) ;
1344+ expect ( mocks . scanConfiguredChannelPluginBlockers ) . not . toHaveBeenCalled ( ) ;
1345+
1346+ await expect (
1347+ runDoctorLintChecks ( ctx , { checks, onlyIds : [ "core/doctor/channel-plugin-blockers" ] } ) ,
1348+ ) . resolves . toMatchObject ( {
1349+ checksRun : 1 ,
1350+ checksSkipped : 0 ,
1351+ findings : [
1352+ expect . objectContaining ( {
1353+ checkId : "core/doctor/channel-plugin-blockers" ,
1354+ path : "channels.discord" ,
1355+ target : "discord" ,
1356+ } ) ,
1357+ ] ,
1358+ } ) ;
1359+ expect ( mocks . scanConfiguredChannelPluginBlockers ) . toHaveBeenCalledWith ( ctx . cfg , process . env ) ;
1360+ } ) ;
1361+
13011362 it ( "uses legacy run when a contribution also declares structured health" , async ( ) => {
13021363 const legacyRun = vi . fn ( ) ;
13031364 const healthChecks = {
0 commit comments