@consiliency/agent-board-cli 1.23.0 → 1.24.0

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.
package/dist/doctor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type IdentityHarness, type OperationSuccess, type OsKeychainExec, type SessionRecordEntry, type TrustOptions } from "@consiliency/agent-board-client";
2
- import { type RegistrationFs } from "./doctor_scopes.js";
2
+ import { type ClaudePluginSummary, type RegistrationFs, type RegQuery, type RegistrationVerdict } from "./doctor_scopes.js";
3
3
  export type DoctorStatus = "pass" | "fail" | "undetermined";
4
4
  export declare const DOCTOR_CHECKS: readonly ["identity_file", "keystore_key", "whoami", "board_membership", "registration", "runtime"];
5
5
  export type DoctorCheckName = (typeof DOCTOR_CHECKS)[number];
@@ -7,11 +7,40 @@ export interface DoctorCheck {
7
7
  status: DoctorStatus;
8
8
  reason: string;
9
9
  }
10
+ /**
11
+ * CCPLUGIN IF-0-CCPLUGIN-3 — the claude plugin report. Outside `DOCTOR_CHECKS`: it never changes
12
+ * the exit code. A version or ref prints only if it matches `REPORTABLE_TOKEN`, else
13
+ * `unrecognised`; `source` is authored. No other file string is echoed.
14
+ */
15
+ export interface PluginReport {
16
+ installed: boolean;
17
+ installed_version: string | null;
18
+ pinned_mcp_version: string | null;
19
+ source_ref: string | null;
20
+ auto_update: ClaudePluginSummary["autoUpdate"];
21
+ }
10
22
  export interface DoctorReport {
11
23
  harness: IdentityHarness;
12
24
  exit: 0 | 1 | 2;
13
25
  checks: Record<DoctorCheckName, DoctorCheck>;
26
+ /** claude only. */
27
+ plugin?: PluginReport;
28
+ /** claude only: wake hooks per required event in doctor's cwd, across settings and plugins. */
29
+ registration?: {
30
+ hook_counts: NonNullable<RegistrationVerdict["hookCounts"]>;
31
+ };
32
+ /**
33
+ * claude only; a report, never a check: which admin-policy sources doctor probed (authored
34
+ * names), which exist, and whether the platform has sources it checks by presence only.
35
+ */
36
+ managed_policy?: {
37
+ checked: string[];
38
+ evidence: string[];
39
+ unreadable_sources_possible: boolean;
40
+ platform: string;
41
+ };
14
42
  }
43
+ export declare const REPORTABLE_TOKEN: RegExp;
15
44
  /**
16
45
  * The two reads doctor makes, on a client whose credentials come from the identity file. Typed
17
46
  * as the client's own `OperationSuccess` envelope, so `defaultClient` below stops compiling if
@@ -52,8 +81,16 @@ export interface DoctorOptions {
52
81
  createClient?: (fileEnv: NodeJS.ProcessEnv) => DoctorClient;
53
82
  probeKeystore?: KeystoreProbe;
54
83
  registrationFs?: RegistrationFs;
55
- /** Claude Code's managed directory. Default `/etc/claude-code`. */
84
+ /** Claude Code's managed directory. Default: the binary's per-platform one (`/etc/claude-code` on Linux). */
56
85
  claudeManagedDir?: string;
86
+ /** The OS whose managed (MDM) sources are probed. Default `process.platform`. */
87
+ platform?: NodeJS.Platform;
88
+ /** The login name for the macOS per-user managed plist. Default: `lookupUserName()`. */
89
+ userName?: string;
90
+ /** Where the login name comes from; may throw. Default `os.userInfo().username`. */
91
+ lookupUserName?: () => string;
92
+ /** Registry probe (Windows/WSL policy keys). Default: `reg query … /v Settings`, 5 s. */
93
+ regQuery?: RegQuery;
57
94
  }
58
95
  /**
59
96
  * An error's `code`, only when this repository wrote it: a member of the derived
package/dist/doctor.js CHANGED
@@ -10,12 +10,50 @@
10
10
  // evidence. Output is names and codes only: no identity value, key or path content is printed,
11
11
  // and an error's `code` is printed only when this repository authored it (`authoredCode`).
12
12
  import { randomBytes } from "node:crypto";
13
- import { homedir } from "node:os";
13
+ import { homedir, userInfo } from "node:os";
14
+ import { resolve } from "node:path";
14
15
  import { AUTHORED_CAUSE_CODES, AgentSocketSelectionError, GENERIC_SUPABASE_FIELDS, IDENTITY_FILE_KEYS, IDENTITY_TRIGGER_FIELDS, KeystoreUnavailableError, assertIdentityHarness, codeProvenance, createAgentBoardClient, createOsKeychainBackend, expectedWakeTier, normalizeKeyReference, readIdentityFile, readSessionRecords, resolveCredentials, selectAgentSocketIdentity, } from "@consiliency/agent-board-client";
15
- import { judgeRegistration, nodeRegistrationFs } from "./doctor_scopes.js";
16
+ import { judgeRegistration, nodeRegistrationFs, } from "./doctor_scopes.js";
16
17
  export const DOCTOR_CHECKS = Object.freeze([
17
18
  "identity_file", "keystore_key", "whoami", "board_membership", "registration", "runtime",
18
19
  ]);
20
+ export const REPORTABLE_TOKEN = /^[A-Za-z0-9._/-]{1,64}$/;
21
+ const reportable = (value) => value === null ? null : REPORTABLE_TOKEN.test(value) ? value : "unrecognised";
22
+ /**
23
+ * The pin, over the live wake_producers whose cwd (realpath) is doctor's cwd, and only while our
24
+ * plugin's server is registered there. All at the pin (our plugin's version) → unchanged; NONE at
25
+ * it → `plugin_pin_mismatch`; a MIXED set → `plugin_pin_ambiguous` (which one is ours is unknown).
26
+ * A record elsewhere was launched by whatever registration applies THERE. An earlier reason is
27
+ * kept and this one appended.
28
+ */
29
+ function pinCheck(verdict, entries, cwd, fs) {
30
+ // Only while our plugin's server is registered here: one turned off by `disabledMcpServers`
31
+ // launched nothing whose version could be judged (panel S1).
32
+ const pin = verdict.plugin?.installed && verdict.plugin.serverActive ? verdict.plugin.version : null;
33
+ if (pin === null)
34
+ return verdict;
35
+ // Real paths on both sides (panel S2): a server records its physical cwd; doctor may be in a symlink.
36
+ const real = (path) => fs.realpath(path) ?? resolve(path);
37
+ const here = entries.flatMap((entry) => entry.status === "live" && entry.record.role === "wake_producer"
38
+ && real(entry.record.cwd) === real(cwd) ? [entry.record] : []);
39
+ const atPin = here.filter((record) => record.packageVersion === pin).length;
40
+ // All at the pin → nothing to say; none → a mismatch; a mixed set → which one is ours is unknown (panel B1).
41
+ if (atPin === here.length)
42
+ return verdict;
43
+ // An earlier reason is kept, never overwritten (panel S5).
44
+ const earlier = verdict.status === "pass" ? [] : [verdict.reason];
45
+ if (atPin === 0)
46
+ return { ...verdict, status: "fail", reason: [...earlier, "plugin_pin_mismatch"].join(",") };
47
+ return { ...verdict, status: verdict.status === "fail" ? "fail" : "undetermined", reason: [...earlier, "plugin_pin_ambiguous"].join(",") };
48
+ }
49
+ /** The binary's managed-settings directory per platform (`Zh()`, 2.1.283). */
50
+ function defaultClaudeManagedDir(platform) {
51
+ if (platform === "darwin")
52
+ return "/Library/Application Support/ClaudeCode";
53
+ if (platform === "win32")
54
+ return "C:\\Program Files\\ClaudeCode";
55
+ return "/etc/claude-code";
56
+ }
19
57
  const FINGERPRINT_KEY = "AGENT_BOARD_SIGNING_PUBLIC_KEY_FINGERPRINT";
20
58
  const HANDLE_KEY = "AGENT_BOARD_EXPECTED_AGENT_HANDLE";
21
59
  const KEY_REFERENCE_KEY = "AGENT_BOARD_AGENT_KEY_REFERENCE";
@@ -340,21 +378,76 @@ export async function runDoctor(options) {
340
378
  const cwds = [...new Set([cwd, ...entries.flatMap((entry) => entry.status === "live" ? [entry.record.cwd] : [])])];
341
379
  const fs = options.registrationFs ?? nodeRegistrationFs;
342
380
  const home = env.HOME && env.HOME.trim() !== "" ? env.HOME : homedir();
343
- checks.registration = worst(cwds.map((dir) => {
381
+ const platform = options.platform ?? process.platform;
382
+ let userName = options.userName;
383
+ if (userName === undefined) {
384
+ try {
385
+ userName = (options.lookupUserName ?? (() => userInfo().username))() || undefined;
386
+ }
387
+ catch {
388
+ userName = undefined;
389
+ }
390
+ }
391
+ const verdicts = cwds.map((dir) => {
344
392
  try {
345
- return judgeRegistration(harness, { cwd: dir, home, env, fs, claudeManagedDir: options.claudeManagedDir ?? "/etc/claude-code" });
393
+ return judgeRegistration(harness, { cwd: dir, home, env, fs, claudeManagedDir: options.claudeManagedDir ?? defaultClaudeManagedDir(platform),
394
+ platform, userName, regQuery: options.regQuery });
346
395
  }
347
396
  catch (error) {
348
397
  return check("undetermined", codeOf(error, "registration_unreadable"));
349
398
  }
350
- }));
399
+ });
400
+ // `cwds[0]` is doctor's own cwd: the pin and the report are about it.
401
+ verdicts[0] = pinCheck(verdicts[0], entries, cwd, fs);
402
+ const registration = worst(verdicts);
403
+ checks.registration = check(registration.status, registration.reason);
351
404
  const ordered = Object.fromEntries(DOCTOR_CHECKS.map((name) => [name, checks[name]]));
352
405
  const statuses = Object.values(ordered).map((item) => item.status);
353
406
  const exit = statuses.every((status) => status === "pass") ? 0 : statuses.includes("fail") ? 1 : 2;
354
- return { harness, exit, checks: ordered };
407
+ const report = { harness, exit, checks: ordered };
408
+ const here = verdicts[0];
409
+ if (harness === "claude") {
410
+ const plugin = here.plugin;
411
+ report.plugin = {
412
+ installed: plugin?.installed ?? false,
413
+ installed_version: reportable(plugin?.version ?? null),
414
+ pinned_mcp_version: reportable(plugin?.pinnedMcpVersion ?? null),
415
+ source_ref: reportable(plugin?.sourceRef ?? null),
416
+ auto_update: plugin ? { effective: plugin.autoUpdate.effective, source: plugin.autoUpdate.source } : { effective: false, source: "undetermined" },
417
+ };
418
+ if (here.hookCounts)
419
+ report.registration = { hook_counts: { SessionStart: here.hookCounts.SessionStart, FileChanged: here.hookCounts.FileChanged } };
420
+ if (here.managedPolicy) {
421
+ report.managed_policy = {
422
+ checked: [...here.managedPolicy.checked], evidence: [...here.managedPolicy.evidence],
423
+ unreadable_sources_possible: here.managedPolicy.unreadableSourcesPossible, platform: here.managedPolicy.platformLabel,
424
+ };
425
+ }
426
+ }
427
+ return report;
355
428
  }
356
429
  /** Human-readable form; `--json` prints the report object itself. */
357
430
  export function formatDoctorReport(report) {
358
431
  const lines = DOCTOR_CHECKS.map((name) => `${report.checks[name].status.padEnd(12)} ${name}: ${report.checks[name].reason}`);
432
+ if (report.plugin) {
433
+ const { plugin } = report;
434
+ const counts = report.registration?.hook_counts;
435
+ lines.push([
436
+ `${"plugin".padEnd(12)} installed=${plugin.installed ? "yes" : "no"}`,
437
+ `version=${plugin.installed_version ?? "none"}`,
438
+ `pinned_mcp=${plugin.pinned_mcp_version ?? "none"}`,
439
+ `ref=${plugin.source_ref ?? "none"}`,
440
+ `auto_update=${plugin.auto_update.effective ? "on" : "off"}(${plugin.auto_update.source})`,
441
+ counts ? `hooks SessionStart=${counts.SessionStart} FileChanged=${counts.FileChanged}` : "hooks undetermined",
442
+ ].join(" "));
443
+ }
444
+ if (report.managed_policy) {
445
+ const policy = report.managed_policy;
446
+ lines.push([
447
+ `${"admin policy".padEnd(12)} checked ${policy.checked.join(",") || "none"}`,
448
+ policy.evidence.length > 0 ? `; found ${policy.evidence.join(",")}` : "",
449
+ policy.unreadable_sources_possible ? `; ${policy.platform} MDM profiles not fully readable` : "",
450
+ ].join(""));
451
+ }
359
452
  return [`agent-board doctor --harness ${report.harness}`, ...lines, `exit ${report.exit}`].join("\n");
360
453
  }
@@ -5,7 +5,28 @@ export declare const OPENCODE_PLUGIN_PACKAGE = "@consiliency/agent-board-opencod
5
5
  export declare const PI_PACKAGE = "npm:@consiliency/agent-board-pi";
6
6
  /** Claude Code events that each need exactly one `wake-hook.sh` hook (`docs/local-wake-signal.md`). */
7
7
  export declare const CLAUDE_REQUIRED_HOOK_EVENTS: readonly ["SessionStart", "FileChanged"];
8
+ /** A wake hook is a command with a token whose BASENAME is this (see `namesWakeHook`). */
8
9
  export declare const WAKE_HOOK_MARKER = "wake-hook.sh";
10
+ export declare const CLAUDE_PLUGIN_ID = "agent-board@message-board";
11
+ export declare const CLAUDE_PLUGIN_MARKETPLACE = "message-board";
12
+ /** Compared lower-cased, as Claude Code compares repos. */
13
+ export declare const CLAUDE_PLUGIN_REPO = "consiliency/message-board";
14
+ export declare const CLAUDE_PLUGIN_SERVER = "agent-board";
15
+ export declare const CLAUDE_PLUGIN_MCP_PACKAGE = "@consiliency/agent-board-mcp";
16
+ /** The SL-0 outcome: `B` = the FileChanged hook carries `asyncRewake: true`. */
17
+ export declare const CLAUDE_WAKE_SHAPE: "A" | "B";
18
+ export declare const CLAUDE_PLUGIN_HOOK_COMMANDS: Readonly<Record<string, string>>;
19
+ /** The stanza a plugin of version `version` must carry: the pin IS the plugin version. */
20
+ export declare function claudePluginStanza(version: string): {
21
+ command: string;
22
+ args: string[];
23
+ };
24
+ /**
25
+ * Whether a hook command names the wake hook. The tokenizer is frozen: split on whitespace, strip
26
+ * quotes, no comment handling; a token matches when its basename is exactly `wake-hook.sh`, so
27
+ * `codex-wake-hook.sh` does not. Floor: a hook that never names the basename is invisible.
28
+ */
29
+ export declare function namesWakeHook(command: string): boolean;
9
30
  export interface ScopeContext {
10
31
  cwd: string;
11
32
  home: string;
@@ -13,6 +34,12 @@ export interface ScopeContext {
13
34
  /** Claude Code's managed directory (Linux `/etc/claude-code`); injectable for tests. */
14
35
  claudeManagedDir: string;
15
36
  fs: RegistrationFs;
37
+ /** The OS, for the managed (MDM) sources doctor probes. Absent → treated as Linux. */
38
+ platform?: NodeJS.Platform;
39
+ /** The login name the macOS per-user managed plist is keyed by. */
40
+ userName?: string;
41
+ /** Registry probe for the Windows policy keys; default `nodeRegQuery`. */
42
+ regQuery?: RegQuery;
16
43
  }
17
44
  /** The filesystem reads registration makes; everything is read-only. */
18
45
  export interface RegistrationFs {
@@ -26,6 +53,8 @@ export interface RegistrationFs {
26
53
  isFile: boolean;
27
54
  } | null;
28
55
  exists(path: string): boolean;
56
+ /** The canonical path (every symlink resolved), or null when it does not exist. */
57
+ realpath(path: string): string | null;
29
58
  getuid(): number;
30
59
  }
31
60
  export declare const nodeRegistrationFs: RegistrationFs;
@@ -58,10 +87,27 @@ export interface McpEntry {
58
87
  fields: Readonly<Record<string, unknown>>;
59
88
  shape: McpShape;
60
89
  }
90
+ export interface HookEntry {
91
+ /** The handler's `type` (`command`, `http`, `prompt`, `agent`, `mcp_tool`); a non-string type is "". */
92
+ type: string;
93
+ /** A command handler's command; null for every other handler (its url or prompt is never kept). */
94
+ command: string | null;
95
+ /** Claude Code's `asyncRewake`: only a literal `true` counts. */
96
+ asyncRewake: boolean;
97
+ /**
98
+ * The group has a matcher that filters (round-2 F029): anything but absent or "". A SessionStart
99
+ * matcher filters on the start source, a FileChanged one on basename(file_path) (binary). The
100
+ * matcher text itself is never kept.
101
+ */
102
+ hasMatcher: boolean;
103
+ /** The handler's and its group's own key NAMES (never values), for the exact freeze. */
104
+ handlerKeys: string[];
105
+ groupKeys: string[];
106
+ }
61
107
  export interface ScopeContribution {
62
108
  mcp: McpEntry[];
63
- /** Claude hooks: event → the command strings registered for it. */
64
- hooks: Record<string, string[]>;
109
+ /** Claude hooks: event → the hooks registered for it. */
110
+ hooks: Record<string, HookEntry[]>;
65
111
  /** opencode `plugin` / pi `packages` specs. */
66
112
  packages: string[];
67
113
  }
@@ -69,6 +115,8 @@ export interface ScopeFile {
69
115
  row: string;
70
116
  path: string;
71
117
  contribution: ScopeContribution;
118
+ /** A synthetic plugin file's plugin id (for the managed-plugin hook exemption); never echoed. */
119
+ pluginId?: string;
72
120
  }
73
121
  type Parser = "json" | "json_or_empty" | "jsonc" | "toml";
74
122
  export interface ScopeRow {
@@ -103,6 +151,110 @@ export declare const HARNESS_CONFIG_SCOPES: Readonly<Record<IdentityHarness, rea
103
151
  * their value is never used.
104
152
  */
105
153
  export declare const MCP_LAYER_MERGE: Readonly<Record<IdentityHarness, "replace" | "fields">>;
154
+ /** Synthetic row names: authored, so no plugin id, marketplace name or path reaches a reason. */
155
+ export declare const CLAUDE_PLUGIN_ROW = "claude_plugin";
156
+ export declare const CLAUDE_FOREIGN_PLUGIN_ROW = "claude_plugin_foreign";
157
+ export type AutoUpdateSource = "env_disabled" | "config_disabled" | "settings" | "known_marketplaces" | "default" | "undetermined";
158
+ /** What doctor learned about OUR plugin. Values are raw; the report sanitises them. */
159
+ export interface ClaudePluginSummary {
160
+ installed: boolean;
161
+ /** Our plugin's board server is registered in this cwd (not turned off by `disabledMcpServers`). */
162
+ serverActive: boolean;
163
+ version: string | null;
164
+ pinnedMcpVersion: string | null;
165
+ sourceRef: string | null;
166
+ autoUpdate: {
167
+ effective: boolean;
168
+ source: AutoUpdateSource;
169
+ };
170
+ }
171
+ export interface ClaudePluginRead {
172
+ /** One synthetic file per enabled plugin read, ranked above user settings. */
173
+ files: ScopeFile[];
174
+ unparseable: string[];
175
+ /** An installPath (or a file under it) resolved outside the cache: nothing beyond it was read. */
176
+ untrusted: boolean;
177
+ failures: string[];
178
+ summary: ClaudePluginSummary;
179
+ hookPolicy: ClaudeHookPolicy;
180
+ }
181
+ /**
182
+ * Which hooks Claude Code runs (round-2 F027/F028; see the header's `hook policy`): settings
183
+ * hooks from every scope, from managed settings only, or none; plugin hooks from every plugin,
184
+ * only from plugins managed settings enable, or none.
185
+ */
186
+ export interface ClaudeHookPolicy {
187
+ settings: "all" | "managed_only" | "none";
188
+ plugins: "all" | "managed_only" | "none";
189
+ /** Plugin ids managed settings enable (`enabledPlugins[id] === true`), for `managed_only`. */
190
+ managedPluginIds: ReadonlySet<string>;
191
+ /** The managed-plugin set comes from somewhere doctor cannot read (managed `prependPlugins`/`appendPlugins`). */
192
+ unjudgeable: boolean;
193
+ /**
194
+ * The managed tier may not be the file doctor reads (round-3 N1): a server-managed or MDM source
195
+ * replaces the file tier entirely ("first-wins"), and doctor reads neither. True when the probe
196
+ * found evidence of one, or could not complete.
197
+ */
198
+ managedTierUnreadable: boolean;
199
+ probe: ManagedPolicyProbe;
200
+ }
201
+ export declare const CLAUDE_REMOTE_SETTINGS_FILE = "remote-settings.json";
202
+ /**
203
+ * Storage-v5 (round 4): the binary writes the server-managed payload to its state store
204
+ * (`Re.state("remote-settings")`, a `state` namespace in the config home) and a signed-cache
205
+ * sidecar beside the json path. The store's exact on-disk name is not recoverable from strings,
206
+ * so every candidate is probed; any one present is evidence.
207
+ */
208
+ export declare const CLAUDE_REMOTE_SETTINGS_STATE_CANDIDATES: readonly string[];
209
+ export declare const CLAUDE_REMOTE_SETTINGS_SIDECARS: readonly string[];
210
+ export declare const MACOS_MANAGED_PREFERENCES = "/Library/Managed Preferences";
211
+ export declare const MACOS_POLICY_PLIST = "com.anthropic.claudecode.plist";
212
+ export declare const WINDOWS_POLICY_KEYS: Readonly<{
213
+ hklm: "HKLM\\SOFTWARE\\Policies\\ClaudeCode";
214
+ hkcu: "HKCU\\SOFTWARE\\Policies\\ClaudeCode";
215
+ }>;
216
+ export declare const WINDOWS_POLICY_VALUE = "Settings";
217
+ /** Where WSL's Windows interop puts reg.exe; the binary reads HKLM policy through this path. */
218
+ export declare const WSL_REG_EXE = "/mnt/c/Windows/System32/reg.exe";
219
+ export declare const WSL_WINDOWS_MANAGED_DIR = "/mnt/c/Program Files/ClaudeCode";
220
+ export type RegQueryOutcome = {
221
+ kind: "present";
222
+ } | {
223
+ kind: "absent";
224
+ } | {
225
+ kind: "error";
226
+ cause: "reg_unavailable" | "reg_timeout" | "reg_failed";
227
+ };
228
+ /** `reg query <key> /v Settings`, read-only. */
229
+ export type RegQuery = (regExe: string, key: string) => RegQueryOutcome;
230
+ /**
231
+ * The binary's own query: `reg query <key> /v Settings`, an argv array, no shell, 5 s. Exit 0 →
232
+ * the value is there; reg's "unable to find" → not there (the binary's own test); anything else —
233
+ * no reg.exe, a timeout, another failure — is an error, never "absent".
234
+ */
235
+ export declare function regQueryWith(timeoutMs: number): RegQuery;
236
+ export declare const nodeRegQuery: RegQuery;
237
+ /** What doctor looked at for admin policy: authored source names only. */
238
+ export interface ManagedPolicyProbe {
239
+ checked: string[];
240
+ /** Sources that exist: each means a tier doctor cannot read replaces the managed file. */
241
+ evidence: string[];
242
+ /** Authored causes of probes that could not finish (`reg_unavailable`, `reg_timeout`, `reg_failed`, `login_unavailable`). */
243
+ errors: string[];
244
+ /** The platform has admin sources doctor checks only by presence (macOS, Windows, WSL). */
245
+ unreadableSourcesPossible: boolean;
246
+ platformLabel: "Linux" | "macOS" | "Windows" | "WSL";
247
+ }
248
+ export declare function probeManagedPolicy(ctx: ScopeContext): ManagedPolicyProbe;
249
+ /** Evidence of (or an unfinished probe for) a tier that replaces the managed settings FILE. */
250
+ export declare const managedFileTierReplaced: (probe: ManagedPolicyProbe) => boolean;
251
+ /**
252
+ * IF-0-CCPLUGIN-3: every plugin enabled for `ctx.cwd`, followed from `enabledPlugins` to its
253
+ * installed row and read from the cache. Ours is the plugin id `agent-board@message-board` whose
254
+ * marketplace is declared, EVERYWHERE it is declared, as the github repository
255
+ * Consiliency/message-board; anything else contributing a board server or a wake hook is foreign.
256
+ */
257
+ export declare function readClaudePluginScopes(ctx: ScopeContext): ClaudePluginRead;
106
258
  /** A package spec's identity without its version: `@scope/name@1.2.3` → `@scope/name`. */
107
259
  export declare function packageIdentity(spec: string): string;
108
260
  interface BoardEntryVerdict {
@@ -114,6 +266,12 @@ export declare function judgeMcpEntry(entry: McpEntry, fs: RegistrationFs): Boar
114
266
  export interface RegistrationVerdict {
115
267
  status: "pass" | "fail" | "undetermined";
116
268
  reason: string;
269
+ /** claude only: what doctor learned about our plugin, for the report and the pin check. */
270
+ plugin?: ClaudePluginSummary;
271
+ /** claude only: wake hooks per required event, across settings and plugins. */
272
+ hookCounts?: Record<(typeof CLAUDE_REQUIRED_HOOK_EVENTS)[number], number>;
273
+ /** claude only: which admin-policy sources were probed, for the report. */
274
+ managedPolicy?: ManagedPolicyProbe;
117
275
  }
118
276
  /**
119
277
  * Read every scope row of `harness` for `cwd`. A file that fails to parse is reported, not guessed.
@@ -123,6 +281,8 @@ export declare function readScopes(harness: IdentityHarness, ctx: ScopeContext):
123
281
  files: ScopeFile[];
124
282
  unparseable: string[];
125
283
  };
284
+ /** The `.claude.json` `projects` key Claude Code uses for `cwd`: the git root of its realpath, else the realpath. */
285
+ export declare function claudeProjectKey(ctx: ScopeContext): string;
126
286
  /** IF-0-CREDPATH-4 registration, for one working directory. */
127
287
  export declare function judgeRegistration(harness: IdentityHarness, ctx: ScopeContext): RegistrationVerdict;
128
288
  export {};