@geonosis/doctor 2.10.0 → 2.11.1

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.
@@ -1,6 +1,91 @@
1
+ // src/types.ts
2
+ var CHECKS = [
3
+ "loaded",
4
+ "group",
5
+ "exercised",
6
+ "baseline",
7
+ "runner",
8
+ "envelope",
9
+ "drift",
10
+ "observability",
11
+ "deployed",
12
+ "rails",
13
+ "exams",
14
+ "seams",
15
+ "formatter"
16
+ ];
17
+ var findingMaker = (check) => (subject, verdict, message) => ({
18
+ check,
19
+ message,
20
+ subject,
21
+ verdict
22
+ });
23
+ var DoctorError = class extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = "DoctorError";
27
+ }
28
+ };
29
+ var dependencyNamesOf = (manifest) => new Set(
30
+ [
31
+ manifest.dependencies,
32
+ manifest.devDependencies,
33
+ manifest.optionalDependencies,
34
+ manifest.peerDependencies
35
+ ].flatMap((block) => Object.keys(block ?? {}))
36
+ );
37
+
38
+ // src/geonosis-file.ts
39
+ import { existsSync, readFileSync } from "fs";
40
+ import { join } from "path";
41
+ var GEONOSIS_FILE = "geonosis.json";
42
+ var readGeonosisFile = (root) => {
43
+ const path = join(root, GEONOSIS_FILE);
44
+ if (!existsSync(path)) return { kind: "absent" };
45
+ try {
46
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
47
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
48
+ return { error: "the top level is not an object", kind: "unreadable" };
49
+ }
50
+ return { config: parsed, kind: "present" };
51
+ } catch (error) {
52
+ return { error: error.message, kind: "unreadable" };
53
+ }
54
+ };
55
+ var unreadableGeonosis = (check, error) => findingMaker(check)(
56
+ GEONOSIS_FILE,
57
+ "FAIL",
58
+ `${GEONOSIS_FILE} could not be parsed: ${error}. A config nobody can read has not been read, and every question this check would answer is answered from a default nobody chose.`
59
+ );
60
+
61
+ // src/apparatus.ts
62
+ import { realpathSync } from "fs";
63
+ import { join as join2, sep } from "path";
64
+ var APPARATUS_KEY = "apparatus";
65
+ var real = (path) => {
66
+ try {
67
+ return realpathSync(path);
68
+ } catch {
69
+ return path;
70
+ }
71
+ };
72
+ var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep}`);
73
+ var declaredApparatusOf = (root) => {
74
+ const read = readGeonosisFile(root);
75
+ if (read.kind !== "present") return [];
76
+ const doctor = read.config.doctor;
77
+ if (typeof doctor !== "object" || doctor === null) return [];
78
+ const declared = doctor[APPARATUS_KEY];
79
+ return Array.isArray(declared) ? declared.filter((one) => typeof one === "string" && one !== "").map((one) => join2(root, one)) : [];
80
+ };
81
+ var evidenceHolding = (config, evidence) => {
82
+ const dir = real(config.dir);
83
+ return evidence.find((one) => holds(real(one), dir));
84
+ };
85
+
1
86
  // src/discover.ts
2
- import { readdirSync, readFileSync, statSync } from "fs";
3
- import { join, relative as relativeTo, sep } from "path";
87
+ import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
88
+ import { join as join3, relative as relativeTo, sep as sep2 } from "path";
4
89
  var CONFIG_FILE = ".oxlintrc.json";
5
90
  var MANIFEST_FILE = "package.json";
6
91
  var RATCHET_FILE = "geonosis.ratchet.json";
@@ -12,7 +97,7 @@ var NEVER_WALKED = /* @__PURE__ */ new Set([
12
97
  "storybook-static"
13
98
  ]);
14
99
  var skipped = (name) => name.startsWith(".") || NEVER_WALKED.has(name);
15
- var relativePath = (root, path) => relativeTo(root, path).split(sep).join("/");
100
+ var relativePath = (root, path) => relativeTo(root, path).split(sep2).join("/");
16
101
  var walk = (dir, onFile) => {
17
102
  let entries;
18
103
  try {
@@ -22,18 +107,18 @@ var walk = (dir, onFile) => {
22
107
  }
23
108
  for (const entry of entries) {
24
109
  if (entry.isDirectory()) {
25
- if (!skipped(entry.name)) walk(join(dir, entry.name), onFile);
110
+ if (!skipped(entry.name)) walk(join3(dir, entry.name), onFile);
26
111
  continue;
27
112
  }
28
- if (entry.isFile()) onFile(join(dir, entry.name), entry.name);
113
+ if (entry.isFile()) onFile(join3(dir, entry.name), entry.name);
29
114
  }
30
115
  };
31
- var parse = (path) => JSON.parse(readFileSync(path, "utf8"));
116
+ var parse = (path) => JSON.parse(readFileSync2(path, "utf8"));
32
117
  var stringsOf = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
33
118
  var rulesOf = (value) => typeof value === "object" && value !== null ? value : {};
34
119
  var overridesOf = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "object" && one !== null).map((one) => ({ files: stringsOf(one.files), rules: rulesOf(one.rules) })) : [];
35
120
  var readConfig = (path, root) => {
36
- const dir = join(path, "..");
121
+ const dir = join3(path, "..");
37
122
  const relative = relativePath(root, path);
38
123
  try {
39
124
  const config = parse(path);
@@ -62,7 +147,7 @@ var discoverConfigs = (root) => {
62
147
  walk(root, (path, name) => {
63
148
  if (name === CONFIG_FILE) found.push(readConfig(path, root));
64
149
  });
65
- return found.toSorted((a, b) => a.relative.localeCompare(b.relative));
150
+ return found.toSorted((a, b) => a.relative.localeCompare(b.relative, "en-US"));
66
151
  };
67
152
  var discoverWorkspaces = (root) => {
68
153
  const found = [];
@@ -74,9 +159,9 @@ var discoverWorkspaces = (root) => {
74
159
  } catch {
75
160
  return;
76
161
  }
77
- found.push({ dir: join(path, ".."), manifest, relative: relativePath(root, join(path, "..")) });
162
+ found.push({ dir: join3(path, ".."), manifest, relative: relativePath(root, join3(path, "..")) });
78
163
  });
79
- return found.toSorted((a, b) => a.relative.localeCompare(b.relative));
164
+ return found.toSorted((a, b) => a.relative.localeCompare(b.relative, "en-US"));
80
165
  };
81
166
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
82
167
  var testFilesUnder = (dir) => filesUnder(dir, (name) => TEST_FILE.test(name));
@@ -88,7 +173,7 @@ var filesUnder = (dir, match) => {
88
173
  return found;
89
174
  };
90
175
  var readRatchet = (root) => {
91
- const path = join(root, RATCHET_FILE);
176
+ const path = join3(root, RATCHET_FILE);
92
177
  try {
93
178
  statSync(path);
94
179
  } catch {
@@ -101,40 +186,12 @@ var readRatchet = (root) => {
101
186
  };
102
187
  };
103
188
 
104
- // src/types.ts
105
- var CHECKS = [
106
- "loaded",
107
- "group",
108
- "exercised",
109
- "baseline",
110
- "runner",
111
- "envelope",
112
- "drift",
113
- "observability",
114
- "deployed",
115
- "rails",
116
- "exams",
117
- "seams"
118
- ];
119
- var findingMaker = (check) => (subject, verdict, message) => ({
120
- check,
121
- message,
122
- subject,
123
- verdict
124
- });
125
- var DoctorError = class extends Error {
126
- constructor(message) {
127
- super(message);
128
- this.name = "DoctorError";
129
- }
130
- };
131
-
132
189
  // src/baseline.ts
133
190
  import { spawnSync } from "child_process";
134
191
  var DEFAULT_REF = "origin/main";
135
192
  var git = (root, args) => {
136
- const run = spawnSync("git", args, { cwd: root, encoding: "utf8" });
137
- return { code: run.status ?? -1, output: `${run.stdout ?? ""}${run.stderr ?? ""}` };
193
+ const run = spawnSync("git", args, { maxBuffer: Infinity, cwd: root, encoding: "utf8" });
194
+ return { code: run.status ?? -1, stdout: run.stdout ?? "" };
138
195
  };
139
196
  var defaultRef = (root) => git(root, ["rev-parse", "--verify", "--quiet", DEFAULT_REF]).code === 0 ? DEFAULT_REF : void 0;
140
197
  var numbersAt = (root, ref, path) => {
@@ -142,7 +199,7 @@ var numbersAt = (root, ref, path) => {
142
199
  if (shown.code !== 0) return void 0;
143
200
  let parsed;
144
201
  try {
145
- parsed = JSON.parse(shown.output);
202
+ parsed = JSON.parse(shown.stdout);
146
203
  } catch {
147
204
  return void 0;
148
205
  }
@@ -196,8 +253,8 @@ var checkBaseline = ({ ref, root }) => {
196
253
  };
197
254
 
198
255
  // src/deployed.ts
199
- import { existsSync, readFileSync as readFileSync2 } from "fs";
200
- import { join as join2 } from "path";
256
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
257
+ import { join as join4 } from "path";
201
258
  var DEPLOYED_FILE = ".geonosis/deployed.json";
202
259
  var NOT_WRITTEN = "no .geonosis/deployed.json \u2014 it is written by the pipeline after promote, and its absence is not a pass";
203
260
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -283,19 +340,11 @@ var differences = (kind, declared, deployed) => {
283
340
  ...extra.length === 0 ? [] : [`deployed but not declared ${kind}: ${extra.join(", ")}`]
284
341
  ];
285
342
  };
286
- var releaseOf = (root) => {
287
- const path = join2(root, "geonosis.json");
288
- if (!existsSync(path)) return void 0;
289
- try {
290
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
291
- return isRecord(parsed) && isRecord(parsed["release"]) ? parsed["release"] : {};
292
- } catch {
293
- return {};
294
- }
295
- };
296
343
  var checkDeployed = ({ root }) => {
297
- const release = releaseOf(root);
298
- if (release === void 0) return [];
344
+ const read = readGeonosisFile(root);
345
+ if (read.kind === "absent") return [];
346
+ if (read.kind === "unreadable") return [unreadableGeonosis("deployed", read.error)];
347
+ const release = isRecord(read.config["release"]) ? read.config["release"] : {};
299
348
  const configs = listOf(release["wrangler"]);
300
349
  const secrets = listOf(release["secrets"]);
301
350
  if (configs.length === 0 && secrets.length === 0) {
@@ -312,7 +361,7 @@ var checkDeployed = ({ root }) => {
312
361
  for (const relative of configs) {
313
362
  let parsed;
314
363
  try {
315
- parsed = parseJsonc(readFileSync2(join2(root, relative), "utf8"));
364
+ parsed = parseJsonc(readFileSync3(join4(root, relative), "utf8"));
316
365
  } catch (error) {
317
366
  return [finding2("FAIL", relative, `could not be read: ${error.message}`)];
318
367
  }
@@ -324,11 +373,11 @@ var checkDeployed = ({ root }) => {
324
373
  declared.crons.push(...one.crons);
325
374
  declared.routes.push(...one.routes);
326
375
  }
327
- const path = join2(root, DEPLOYED_FILE);
328
- if (!existsSync(path)) return [finding2("SKIP", DEPLOYED_FILE, NOT_WRITTEN)];
376
+ const path = join4(root, DEPLOYED_FILE);
377
+ if (!existsSync2(path)) return [finding2("SKIP", DEPLOYED_FILE, NOT_WRITTEN)];
329
378
  let reported;
330
379
  try {
331
- reported = JSON.parse(readFileSync2(path, "utf8"));
380
+ reported = JSON.parse(readFileSync3(path, "utf8"));
332
381
  } catch (error) {
333
382
  return [finding2("FAIL", DEPLOYED_FILE, `is not readable JSON: ${error.message}`)];
334
383
  }
@@ -349,26 +398,10 @@ var checkDeployed = ({ root }) => {
349
398
  ];
350
399
  };
351
400
 
352
- // src/repo-corpus.ts
353
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
354
- import { join as join3 } from "path";
355
- var GEONOSIS_FILE = "geonosis.json";
356
- var repoCorpusOf = (root) => {
357
- const path = join3(root, GEONOSIS_FILE);
358
- if (!existsSync2(path)) return void 0;
359
- try {
360
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
361
- const declared = parsed.doctor?.corpus;
362
- return typeof declared === "string" && declared !== "" ? join3(root, declared) : void 0;
363
- } catch {
364
- return void 0;
365
- }
366
- };
367
-
368
401
  // src/resolve.ts
369
- import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
402
+ import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "fs";
370
403
  import { createRequire } from "module";
371
- import { dirname, join as join4 } from "path";
404
+ import { dirname, join as join5 } from "path";
372
405
  import { pathToFileURL } from "url";
373
406
  var packageNameOf = (specifier) => {
374
407
  const parts2 = specifier.split("/");
@@ -386,8 +419,8 @@ var aboveRootMessage = (specifier, foundAt) => `${specifier} is not installed he
386
419
  var aboveRoot = (root, name) => {
387
420
  let at = dirname(root);
388
421
  for (; ; ) {
389
- const dir = join4(at, "node_modules", name);
390
- if (existsSync3(join4(dir, "package.json"))) return dir;
422
+ const dir = join5(at, "node_modules", name);
423
+ if (existsSync3(join5(dir, "package.json"))) return dir;
391
424
  const parent = dirname(at);
392
425
  if (parent === at) return void 0;
393
426
  at = parent;
@@ -395,14 +428,14 @@ var aboveRoot = (root, name) => {
395
428
  };
396
429
  var resolveFrom = (dir, specifier, root) => {
397
430
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
398
- return createRequire(join4(dir, "noop.js")).resolve(specifier);
431
+ return createRequire(join5(dir, "noop.js")).resolve(specifier);
399
432
  }
400
433
  const name = packageNameOf(specifier);
401
434
  let at = dir;
402
435
  let reachedRoot = false;
403
436
  for (; ; ) {
404
- if (existsSync3(join4(at, "node_modules", name, "package.json"))) {
405
- return createRequire(join4(at, "noop.js")).resolve(specifier);
437
+ if (existsSync3(join5(at, "node_modules", name, "package.json"))) {
438
+ return createRequire(join5(at, "noop.js")).resolve(specifier);
406
439
  }
407
440
  if (at === root) {
408
441
  reachedRoot = true;
@@ -424,7 +457,7 @@ var resolveFrom = (dir, specifier, root) => {
424
457
  var packageDirOf = (entry, name) => {
425
458
  let dir = dirname(entry);
426
459
  for (; ; ) {
427
- const manifest = join4(dir, "package.json");
460
+ const manifest = join5(dir, "package.json");
428
461
  if (existsSync3(manifest)) {
429
462
  try {
430
463
  const parsed = JSON.parse(readFileSync4(manifest, "utf8"));
@@ -500,23 +533,92 @@ var presumptionsOf = async (entry) => {
500
533
  )
501
534
  };
502
535
  };
503
- var corpusOfPlugin = (from, specifier, root) => join4(packageDirOf(resolveFrom(from, specifier, root), specifier), "corpus");
504
- var real = (path) => {
536
+ var corpusOfPlugin = (from, specifier, root) => join5(packageDirOf(resolveFrom(from, specifier, root), specifier), "corpus");
537
+ var real2 = (path) => {
505
538
  try {
506
- return realpathSync(path);
539
+ return realpathSync2(path);
507
540
  } catch {
508
541
  return path;
509
542
  }
510
543
  };
511
- var relativeToRoot = (root, path) => relativePath(real(root), real(path));
544
+ var relativeToRoot = (root, path) => relativePath(real2(root), real2(path));
512
545
 
513
546
  // src/group.ts
514
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
515
- import { join as join6 } from "path";
547
+ import { readFileSync as readFileSync7 } from "fs";
548
+ import { join as join8 } from "path";
516
549
 
517
- // src/hooks.ts
550
+ // src/callers.ts
518
551
  import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
519
- import { join as join5 } from "path";
552
+ import { join as join6, resolve } from "path";
553
+ var WORKFLOWS = ".github/workflows";
554
+ var A_WORKFLOW = (name) => name.endsWith(".yml") || name.endsWith(".yaml");
555
+ var readOr = (path) => {
556
+ try {
557
+ return readFileSync5(path, "utf8");
558
+ } catch {
559
+ return "";
560
+ }
561
+ };
562
+ var workflowSources = (root) => {
563
+ const dir = join6(root, WORKFLOWS);
564
+ if (!existsSync4(dir)) return [];
565
+ return readdirSync2(dir).filter(A_WORKFLOW).map((name) => readOr(join6(dir, name)));
566
+ };
567
+ var commandsOf = (step) => {
568
+ if (typeof step === "string") return [step];
569
+ if (typeof step !== "object" || step === null) return [];
570
+ const shape = step;
571
+ if (typeof shape.command === "string") return [shape.command];
572
+ if (Array.isArray(shape.parallel)) return shape.parallel.flatMap(commandsOf);
573
+ return [];
574
+ };
575
+ var declaredTiers = (root) => {
576
+ const read = readGeonosisFile(root);
577
+ if (read.kind !== "present") return { commands: [], names: [] };
578
+ const verify = read.config.verify;
579
+ if (typeof verify !== "object" || verify === null || Array.isArray(verify)) {
580
+ return { commands: [], names: [] };
581
+ }
582
+ const tiers = verify;
583
+ return {
584
+ commands: Object.values(tiers).flatMap(
585
+ (steps) => Array.isArray(steps) ? steps.flatMap(commandsOf) : []
586
+ ),
587
+ names: Object.keys(tiers)
588
+ };
589
+ };
590
+ var everythingRun = (root, workspaces) => {
591
+ const tiers = declaredTiers(root);
592
+ return {
593
+ run: [
594
+ ...tiers.commands,
595
+ ...workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {})),
596
+ ...(readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? "")),
597
+ ...workflowSources(root)
598
+ ].join("\n"),
599
+ tiers: tiers.names
600
+ };
601
+ };
602
+ var A_SCRIPT_FILE = /\.(?:[cm]?[jt]sx?|sh|bash)$/;
603
+ var scriptFilesNamedBy = (root, lines) => {
604
+ const seen = /* @__PURE__ */ new Set();
605
+ const found = [];
606
+ for (const line of lines) {
607
+ for (const token of line.split(/[\s'"`]+/)) {
608
+ if (!A_SCRIPT_FILE.test(token) || seen.has(token)) continue;
609
+ seen.add(token);
610
+ const path = resolve(root, token);
611
+ if (!path.startsWith(root) || !existsSync4(path)) continue;
612
+ const source = readOr(path);
613
+ if (source !== "") found.push({ at: token, source });
614
+ }
615
+ }
616
+ return found;
617
+ };
618
+
619
+ // src/hooks.ts
620
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
621
+ import { join as join7 } from "path";
520
622
  var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
521
623
  var HOOK_DIRS = [".husky", ".githooks"];
522
624
  var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
@@ -525,20 +627,20 @@ var hookLines = (root) => {
525
627
  const found = [];
526
628
  const read = (path) => {
527
629
  const at = relativePath(root, path);
528
- for (const line of readFileSync5(path, "utf8").split("\n")) {
630
+ for (const line of readFileSync6(path, "utf8").split("\n")) {
529
631
  if (line.trim() !== "") found.push({ at, line });
530
632
  }
531
633
  };
532
634
  for (const name of HOOK_FILES) {
533
- const path = join5(root, name);
534
- if (existsSync4(path)) read(path);
635
+ const path = join7(root, name);
636
+ if (existsSync5(path)) read(path);
535
637
  }
536
638
  for (const name of HOOK_DIRS) {
537
- const dir = join5(root, name);
538
- if (!existsSync4(dir)) continue;
539
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
639
+ const dir = join7(root, name);
640
+ if (!existsSync5(dir)) continue;
641
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
540
642
  if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
541
- read(join5(dir, entry.name));
643
+ read(join7(dir, entry.name));
542
644
  }
543
645
  }
544
646
  }
@@ -571,16 +673,8 @@ var FIXED_GROUP = [
571
673
  "geonosis"
572
674
  ];
573
675
  var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
574
- var declaredGroupsOf = (root) => {
575
- const path = join6(root, GEONOSIS_FILE);
576
- if (!existsSync5(path)) return void 0;
577
- let parsed;
578
- try {
579
- parsed = JSON.parse(readFileSync6(path, "utf8"));
580
- } catch {
581
- return void 0;
582
- }
583
- const declared = parsed.doctor?.groups;
676
+ var groupsFrom = (config) => {
677
+ const declared = config.doctor?.groups;
584
678
  if (declared === void 0) return void 0;
585
679
  if (!Array.isArray(declared) || declared.length === 0) {
586
680
  throw new DoctorError(
@@ -597,6 +691,13 @@ var declaredGroupsOf = (root) => {
597
691
  return { name: group.name, packages: group.packages };
598
692
  });
599
693
  };
694
+ var declaredGroupsOf = (root) => {
695
+ const read = readGeonosisFile(root);
696
+ if (read.kind === "unreadable") {
697
+ throw new DoctorError(`${GEONOSIS_FILE} could not be parsed: ${read.error}`);
698
+ }
699
+ return read.kind === "present" ? groupsFrom(read.config) : void 0;
700
+ };
600
701
  var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
601
702
  var versionOf = (dir, name, root) => {
602
703
  let entry;
@@ -607,7 +708,7 @@ var versionOf = (dir, name, root) => {
607
708
  }
608
709
  try {
609
710
  const manifest = JSON.parse(
610
- readFileSync6(join6(packageDirOf(entry, name), "package.json"), "utf8")
711
+ readFileSync7(join8(packageDirOf(entry, name), "package.json"), "utf8")
611
712
  );
612
713
  return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
613
714
  } catch (error) {
@@ -639,7 +740,7 @@ var DOORS = ["@geonosis/cli", "geonosis"];
639
740
  var manifestOf = (root, name) => {
640
741
  try {
641
742
  return JSON.parse(
642
- readFileSync6(join6(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
743
+ readFileSync7(join8(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
643
744
  );
644
745
  } catch {
645
746
  return void 0;
@@ -649,17 +750,8 @@ var binNamesOf = (manifest, name) => {
649
750
  const declared = manifest?.bin;
650
751
  return typeof declared === "string" ? [name.replace(/^@[^/]+\//, "")] : Object.keys(declared ?? {});
651
752
  };
652
- var declaredIn2 = (manifest) => [
653
- ...new Set(
654
- [
655
- manifest.dependencies,
656
- manifest.devDependencies,
657
- manifest.optionalDependencies,
658
- manifest.peerDependencies
659
- ].flatMap((block) => Object.keys(block ?? {}))
660
- )
661
- ];
662
- var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(lines);
753
+ var declaredIn2 = (manifest) => [...dependencyNamesOf(manifest)];
754
+ var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/'"\`])${bin}(?:[\\s;&|)'"\`,\\]]|$)`).test(lines);
663
755
  var workspaceCaller = (workspaces, bins) => {
664
756
  for (const workspace of workspaces) {
665
757
  if (workspace.relative === "") continue;
@@ -681,20 +773,25 @@ var rootDeclarations = (root, workspaces) => {
681
773
  return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
682
774
  })
683
775
  );
684
- const callers = [
776
+ const lines = [
685
777
  ...Object.values(here.manifest.scripts ?? {}),
686
- ...hookLines(root).map((one) => one.line)
687
- ].join("\n");
688
- const loadedHere = new Set(readConfig(join6(root, CONFIG_FILE), root).jsPlugins);
689
- const spare = declared.filter(
690
- (name) => !DOORS.includes(name) && owned.has(name) && !loadedHere.has(name) && !binNamesOf(manifestOf(root, name), name).some((bin) => calls(callers, bin))
691
- );
778
+ ...hookLines(root).map((one) => one.line),
779
+ ...workflowSources(root),
780
+ ...declaredTiers(root).commands
781
+ ];
782
+ const callers = [...lines, ...scriptFilesNamedBy(root, lines).map((one) => one.source)].join("\n");
783
+ const loadedHere = new Set(readConfig(join8(root, CONFIG_FILE), root).jsPlugins);
784
+ const spare = declared.filter((name) => {
785
+ if (DOORS.includes(name) || !owned.has(name) || loadedHere.has(name)) return false;
786
+ const bins = binNamesOf(manifestOf(root, name), name);
787
+ return bins.length > 0 && !bins.some((bin) => calls(callers, bin));
788
+ });
692
789
  if (spare.length === 0) {
693
790
  return [
694
791
  finding3(
695
792
  "package.json",
696
793
  "OK",
697
- `every @geonosis package this root declares is ${door} or a bin its own scripts call by name`
794
+ `every @geonosis package this root declares is ${door} or a bin something here calls by name \u2014 a script, a hook, a script file they run, a workflow or a tier`
698
795
  )
699
796
  ];
700
797
  }
@@ -711,7 +808,7 @@ var rootDeclarations = (root, workspaces) => {
711
808
  return finding3(
712
809
  name,
713
810
  "WARN",
714
- `the root declares it and no script in this repo calls its bin \u2014 ${door} already brings it, so this second declaration is a version that can disagree with the door's, and every tool that asks what is used here reads it as unused. Drop it from the root, or declare it in the workspace whose code imports it`
811
+ `the root declares it and nothing in this repo calls its bin \u2014 no root or workspace script, no committed hook, no script file those name, no CI workflow, no geonosis.json tier. ${door} already brings it, so this second declaration is a version that can disagree with the door's, and every tool that asks what is used here reads it as unused. Drop it from the root, or declare it in the workspace whose code imports it \u2014 and if something this check cannot see does call the bin, keep the declaration: under an isolated linker it is what links the bin into node_modules/.bin`
715
812
  );
716
813
  });
717
814
  };
@@ -721,8 +818,15 @@ var checkGroup = ({
721
818
  workspaces
722
819
  }) => {
723
820
  const findings = [...rootDeclarations(root, workspaces)];
724
- for (const group of groups ?? [KIT_GROUP]) {
821
+ let resolved = groups;
822
+ if (resolved === void 0) {
823
+ const read = readGeonosisFile(root);
824
+ if (read.kind === "unreadable") return [...findings, unreadableGeonosis("group", read.error)];
825
+ resolved = read.kind === "present" ? declaredGroupsOf(root) : void 0;
826
+ }
827
+ for (const group of resolved ?? [KIT_GROUP]) {
725
828
  const found = [];
829
+ let halfway = false;
726
830
  for (const workspace of workspaces) {
727
831
  const at = labelOf(workspace);
728
832
  const here = [];
@@ -749,7 +853,10 @@ var checkGroup = ({
749
853
  }
750
854
  }
751
855
  const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
752
- if (split2 !== void 0) findings.push(split2);
856
+ if (split2 !== void 0) {
857
+ findings.push(split2);
858
+ halfway = true;
859
+ }
753
860
  found.push(...here);
754
861
  }
755
862
  if (found.length === 0) {
@@ -771,7 +878,7 @@ var checkGroup = ({
771
878
  return line === void 0 ? [] : [line];
772
879
  });
773
880
  findings.push(...split);
774
- if (split.length === 0) {
881
+ if (split.length === 0 && !halfway) {
775
882
  const versions = [...new Set(found.map((one) => one.version))];
776
883
  findings.push(
777
884
  finding3(
@@ -786,14 +893,15 @@ var checkGroup = ({
786
893
  };
787
894
 
788
895
  // src/drift.ts
789
- import { closeSync, existsSync as existsSync6, openSync, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync } from "fs";
896
+ import { closeSync, existsSync as existsSync6, openSync, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync } from "fs";
790
897
  import { homedir } from "os";
791
- import { join as join7, sep as sep2 } from "path";
898
+ import { join as join9, sep as sep3 } from "path";
792
899
 
793
900
  // src/overrides.ts
794
901
  var SPECIAL = /* @__PURE__ */ new Set(["$", "(", ")", "+", ".", "/", "@", "\\", "^", "|"]);
795
902
  var globToRegExp = (glob) => {
796
903
  let source = "";
904
+ let braces = 0;
797
905
  for (let at = 0; at < glob.length; at += 1) {
798
906
  const character = glob[at] ?? "";
799
907
  if (character === "*") {
@@ -816,13 +924,15 @@ var globToRegExp = (glob) => {
816
924
  }
817
925
  if (character === "{") {
818
926
  source += "(?:";
927
+ braces += 1;
819
928
  continue;
820
929
  }
821
- if (character === "}") {
930
+ if (character === "}" && braces > 0) {
822
931
  source += ")";
932
+ braces -= 1;
823
933
  continue;
824
934
  }
825
- if (character === ",") {
935
+ if (character === "," && braces > 0) {
826
936
  source += "|";
827
937
  continue;
828
938
  }
@@ -854,29 +964,27 @@ var governing = (layers, path) => {
854
964
  };
855
965
 
856
966
  // src/drift.ts
857
- var WORKFLOWS = ".github/workflows";
858
967
  var SETTINGS = ".claude/settings.json";
859
- var GEONOSIS = "geonosis.json";
860
968
  var LAW = "CLAUDE.md";
861
969
  var CEILING = 200;
862
970
  var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
863
971
  var finding4 = findingMaker("drift");
864
972
  var ci = (root) => {
865
- const dir = join7(root, WORKFLOWS);
973
+ const dir = join9(root, WORKFLOWS);
866
974
  if (!existsSync6(dir)) {
867
975
  return [finding4(WORKFLOWS, "SKIP", "there are no workflows here to read")];
868
976
  }
869
977
  return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
870
978
  const at = relativePath(root, path);
871
- return SWITCHED_OFF.test(readFileSync7(path, "utf8")) ? finding4(
979
+ return SWITCHED_OFF.test(readFileSync8(path, "utf8")) ? finding4(
872
980
  at,
873
981
  "FAIL",
874
982
  "a job or step here is switched off by a condition that can never be true \u2014 every gate downstream of it reports green having run nothing"
875
983
  ) : finding4(at, "OK", "nothing in it is switched off");
876
984
  });
877
985
  };
878
- var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep2}`);
879
- var ownersOf = (path, workspaces) => workspaces.filter((one) => holds(one.dir, path)).toSorted((a, b) => b.dir.length - a.dir.length);
986
+ var holds2 = (parent, child) => child === parent || child.startsWith(`${parent}${sep3}`);
987
+ var ownersOf = (path, workspaces) => workspaces.filter((one) => holds2(one.dir, path)).toSorted((a, b) => b.dir.length - a.dir.length);
880
988
  var orphanTests = (root, workspaces) => {
881
989
  const orphaned = /* @__PURE__ */ new Map();
882
990
  for (const path of testFilesUnder(root)) {
@@ -916,7 +1024,7 @@ var pathsRunByScript = (script) => script.split(BETWEEN_COMMANDS).map(pathRunBy)
916
1024
  var scriptPaths = (workspaces) => {
917
1025
  const missing = workspaces.flatMap(
918
1026
  (one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
919
- ([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(join7(one.dir, path))).map((path) => ({ name, path, workspace: one }))
1027
+ ([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(join9(one.dir, path))).map((path) => ({ name, path, workspace: one }))
920
1028
  )
921
1029
  );
922
1030
  if (missing.length === 0) {
@@ -952,7 +1060,9 @@ var linkedBins = (root, workspaces) => {
952
1060
  }
953
1061
  const missing = workspaces.flatMap(
954
1062
  (one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
955
- ([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !existsSync6(join7(root, "node_modules/.bin", name))).map((name) => ({ name, script, workspace: one }))
1063
+ ([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter(
1064
+ (name) => !existsSync6(join9(one.dir, "node_modules/.bin", name)) && !existsSync6(join9(root, "node_modules/.bin", name))
1065
+ ).map((name) => ({ name, script, workspace: one }))
956
1066
  )
957
1067
  );
958
1068
  if (missing.length === 0) {
@@ -1018,21 +1128,6 @@ var markerIn = (path) => {
1018
1128
  closeSync(handle);
1019
1129
  }
1020
1130
  };
1021
- var everythingRun = (root, workspaces) => {
1022
- const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
1023
- const counters = (readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? ""));
1024
- const workflows = filesUnder(
1025
- join7(root, WORKFLOWS),
1026
- (name) => name.endsWith(".yml") || name.endsWith(".yaml")
1027
- ).map((path) => {
1028
- try {
1029
- return readFileSync7(path, "utf8");
1030
- } catch {
1031
- return "";
1032
- }
1033
- });
1034
- return [...scripts, ...counters, ...workflows].join("\n");
1035
- };
1036
1131
  var generatedFiles = (root, workspaces) => {
1037
1132
  const marked = filesUnder(root, (name) => CAN_CARRY_A_MARKER.test(name)).map((path) => ({ marker: markerIn(path), path })).filter((one) => one.marker !== void 0);
1038
1133
  if (marked.length === 0) {
@@ -1044,7 +1139,7 @@ var generatedFiles = (root, workspaces) => {
1044
1139
  )
1045
1140
  ];
1046
1141
  }
1047
- const run = everythingRun(root, workspaces);
1142
+ const { run } = everythingRun(root, workspaces);
1048
1143
  return marked.map(({ marker, path }) => {
1049
1144
  const at = relativePath(root, path);
1050
1145
  if ("writes" in marker) {
@@ -1064,27 +1159,18 @@ var generatedFiles = (root, workspaces) => {
1064
1159
  return run.includes(marker.gate) ? finding4(at, "OK", `generated, and \`${marker.gate}\` reads it back`) : finding4(
1065
1160
  at,
1066
1161
  "FAIL",
1067
- `generated, and nothing here runs \`${marker.gate}\` \u2014 the gate it names. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Add \`${marker.gate}\` to a script, a counter or a workflow`
1162
+ `generated, and nothing here runs \`${marker.gate}\` \u2014 the gate it names. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Add \`${marker.gate}\` to a script, a verify tier, a counter or a workflow`
1068
1163
  );
1069
1164
  });
1070
1165
  };
1071
- var readGeonosis = (root) => {
1072
- const path = join7(root, GEONOSIS);
1073
- if (!existsSync6(path)) return void 0;
1074
- try {
1075
- return JSON.parse(readFileSync7(path, "utf8"));
1076
- } catch {
1077
- return void 0;
1078
- }
1079
- };
1080
1166
  var law = (root, config) => {
1081
1167
  const declared = config?.law ?? {};
1082
1168
  const file = typeof declared.file === "string" ? declared.file : LAW;
1083
- const path = join7(root, file);
1169
+ const path = join9(root, file);
1084
1170
  if (!existsSync6(path)) {
1085
1171
  return [finding4(file, "SKIP", "there is no law file here to measure")];
1086
1172
  }
1087
- const source = readFileSync7(path, "utf8");
1173
+ const source = readFileSync8(path, "utf8");
1088
1174
  const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
1089
1175
  if (typeof declared.maxLines !== "number") {
1090
1176
  return [
@@ -1108,7 +1194,7 @@ var KIT_PLUGIN = "geonosis";
1108
1194
  var enablesKit = (path) => {
1109
1195
  if (!existsSync6(path)) return false;
1110
1196
  try {
1111
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
1197
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
1112
1198
  const enabled = parsed.enabledPlugins;
1113
1199
  if (typeof enabled !== "object" || enabled === null) return false;
1114
1200
  return Object.entries(enabled).some(
@@ -1119,7 +1205,7 @@ var enablesKit = (path) => {
1119
1205
  }
1120
1206
  };
1121
1207
  var hooks = (root, userSettings) => {
1122
- const path = join7(root, SETTINGS);
1208
+ const path = join9(root, SETTINGS);
1123
1209
  if (enablesKit(path)) {
1124
1210
  return [finding4(SETTINGS, "OK", `\`enabledPlugins\` here enables the kit\u2019s plugin`)];
1125
1211
  }
@@ -1142,9 +1228,9 @@ var hooks = (root, userSettings) => {
1142
1228
  };
1143
1229
  var ALLOW_BUILDS_KEY = "allowBuilds";
1144
1230
  var allowedBuilds = (root) => {
1145
- const path = join7(root, WORKSPACE_YAML);
1231
+ const path = join9(root, WORKSPACE_YAML);
1146
1232
  if (!existsSync6(path)) return void 0;
1147
- const lines = readFileSync7(path, "utf8").split("\n");
1233
+ const lines = readFileSync8(path, "utf8").split("\n");
1148
1234
  const at = lines.findIndex((line) => new RegExp(`^${ALLOW_BUILDS_KEY}\\s*:`).test(line));
1149
1235
  if (at < 0) return void 0;
1150
1236
  const names = [];
@@ -1158,7 +1244,10 @@ var allowedBuilds = (root) => {
1158
1244
  return names;
1159
1245
  };
1160
1246
  var PLATFORM = /(?:^|[-/])(?:darwin|linux|win32|windows|freebsd|openbsd|android|sunos)(?:[-.]|$)|(?:^|[-/])(?:arm64|x64|ia32|ppc64|s390x|riscv64)(?:[-.]|$)/;
1161
- var familyOf = (name) => name.split("/").slice(0, -1).join("/") || name;
1247
+ var familyOf = (name) => {
1248
+ const at = name.search(PLATFORM);
1249
+ return at > 0 ? name.slice(0, at) : name;
1250
+ };
1162
1251
  var platformSplitBuilds = (root) => {
1163
1252
  const names = allowedBuilds(root);
1164
1253
  if (names === void 0 || names.length === 0) return [];
@@ -1195,7 +1284,7 @@ var pathsLintedBy = (script, dir) => {
1195
1284
  if (word.startsWith("-")) continue;
1196
1285
  const before = words[at - 1] ?? "";
1197
1286
  if (TAKES_A_VALUE.has(before)) continue;
1198
- if (existsSync6(join7(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
1287
+ if (existsSync6(join9(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
1199
1288
  }
1200
1289
  return named2;
1201
1290
  };
@@ -1203,12 +1292,12 @@ var LINTABLE = /\.[cm]?[jt]sx?$/;
1203
1292
  var sourceDirsIn = (dir) => {
1204
1293
  let entries;
1205
1294
  try {
1206
- entries = readdirSync3(dir, { withFileTypes: true });
1295
+ entries = readdirSync4(dir, { withFileTypes: true });
1207
1296
  } catch {
1208
1297
  return [];
1209
1298
  }
1210
1299
  return entries.filter(
1211
- (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name) && filesUnder(join7(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
1300
+ (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name) && filesUnder(join9(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
1212
1301
  ).map((entry) => entry.name);
1213
1302
  };
1214
1303
  var lintScopes = (root, workspaces) => {
@@ -1248,7 +1337,6 @@ var OBSERVABILITY = "@geonosis/observability";
1248
1337
  var COMPOSITION_ROOT = "src/platform.ts";
1249
1338
  var FLOOR_PACKAGES = [
1250
1339
  "@geonosis/assistant",
1251
- "@geonosis/buketi-ui",
1252
1340
  "@geonosis/conformance",
1253
1341
  "@geonosis/db",
1254
1342
  "@geonosis/document",
@@ -1262,6 +1350,7 @@ var FLOOR_PACKAGES = [
1262
1350
  "@geonosis/plans",
1263
1351
  "@geonosis/recurring",
1264
1352
  "@geonosis/search",
1353
+ "@geonosis/storefront-ui",
1265
1354
  "@geonosis/ui",
1266
1355
  "@geonosis/workflows"
1267
1356
  ];
@@ -1298,7 +1387,7 @@ var composedHere = (root, workspaces) => {
1298
1387
  for (const path of filesUnder(root, (name) => SOURCE_FILE.test(name))) {
1299
1388
  let body;
1300
1389
  try {
1301
- body = readFileSync7(path, "utf8");
1390
+ body = readFileSync8(path, "utf8");
1302
1391
  } catch {
1303
1392
  continue;
1304
1393
  }
@@ -1333,8 +1422,8 @@ var doorBringing = (root, name, declared) => DOORS.filter((door) => declared.has
1333
1422
  var dependenciesOf = (root, name) => {
1334
1423
  try {
1335
1424
  return JSON.parse(
1336
- readFileSync7(
1337
- join7(packageDirOf(resolveFrom(root, name, root), name), "package.json"),
1425
+ readFileSync8(
1426
+ join9(packageDirOf(resolveFrom(root, name, root), name), "package.json"),
1338
1427
  "utf8"
1339
1428
  )
1340
1429
  ).dependencies ?? {};
@@ -1350,21 +1439,12 @@ var resolves = (root, name) => {
1350
1439
  return false;
1351
1440
  }
1352
1441
  };
1353
- var declaredAnywhere = (workspaces) => new Set(
1354
- workspaces.flatMap(
1355
- (one) => [
1356
- one.manifest.dependencies,
1357
- one.manifest.devDependencies,
1358
- one.manifest.optionalDependencies,
1359
- one.manifest.peerDependencies
1360
- ].flatMap((field) => Object.keys(field ?? {}))
1361
- )
1362
- );
1442
+ var declaredAnywhere = (workspaces) => new Set(workspaces.flatMap((one) => [...dependencyNamesOf(one.manifest)]));
1363
1443
  var blocks = (root, config, readers, workspaces) => {
1364
1444
  if (config === void 0) {
1365
1445
  return [
1366
1446
  finding4(
1367
- GEONOSIS,
1447
+ GEONOSIS_FILE,
1368
1448
  "SKIP",
1369
1449
  "there is no geonosis.json here, so no block says what this repo asks the kit to do"
1370
1450
  )
@@ -1437,7 +1517,7 @@ var pluginDirsOf = (level) => {
1437
1517
  return { manifests: options.manifests ?? ["index.ts"], registry, roots: options.roots };
1438
1518
  };
1439
1519
  var pluginDirLayers = (root) => {
1440
- const path = join7(root, ".oxlintrc.json");
1520
+ const path = join9(root, ".oxlintrc.json");
1441
1521
  if (!existsSync6(path)) return [];
1442
1522
  const layers = [];
1443
1523
  for (const layer of layersOf(readConfig(path, root), PLUGIN_DIR_RULE)) {
@@ -1447,8 +1527,8 @@ var pluginDirLayers = (root) => {
1447
1527
  return layers;
1448
1528
  };
1449
1529
  var layerOver = (layers, root, relative) => {
1450
- const at = join7(root, relative);
1451
- const inside = readdirSync3(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
1530
+ const at = join9(root, relative);
1531
+ const inside = readdirSync4(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
1452
1532
  const paths = inside.length === 0 ? [relative] : inside;
1453
1533
  const wrapped = layers.map((layer) => ({ files: layer.files, level: layer.dirs }));
1454
1534
  for (const path of paths) {
@@ -1469,7 +1549,7 @@ var pluginDirs = (root) => {
1469
1549
  ];
1470
1550
  }
1471
1551
  const missing = [...new Set(layers.flatMap((layer) => layer.dirs.registry))].filter(
1472
- (registry) => !existsSync6(join7(root, registry))
1552
+ (registry) => !existsSync6(join9(root, registry))
1473
1553
  );
1474
1554
  if (missing.length > 0) {
1475
1555
  return missing.map(
@@ -1479,17 +1559,17 @@ var pluginDirs = (root) => {
1479
1559
  const source = /* @__PURE__ */ new Map();
1480
1560
  const unreachable = /* @__PURE__ */ new Map();
1481
1561
  for (const rootDir of new Set(layers.flatMap((layer) => layer.dirs.roots))) {
1482
- const at = join7(root, rootDir);
1562
+ const at = join9(root, rootDir);
1483
1563
  if (!existsSync6(at)) continue;
1484
- for (const entry of readdirSync3(at, { withFileTypes: true })) {
1564
+ for (const entry of readdirSync4(at, { withFileTypes: true })) {
1485
1565
  if (!entry.isDirectory()) continue;
1486
1566
  const relative = `${rootDir}/${entry.name}`;
1487
1567
  const governs = layerOver(layers, root, relative);
1488
1568
  if (governs === void 0) continue;
1489
1569
  const key = governs.registry.join(", ");
1490
- const registry = source.get(key) ?? governs.registry.map((half) => readFileSync7(join7(root, half), "utf8")).join("\n");
1570
+ const registry = source.get(key) ?? governs.registry.map((half) => readFileSync8(join9(root, half), "utf8")).join("\n");
1491
1571
  source.set(key, registry);
1492
- const hasManifest = governs.manifests.some((name) => existsSync6(join7(at, entry.name, name)));
1572
+ const hasManifest = governs.manifests.some((name) => existsSync6(join9(at, entry.name, name)));
1493
1573
  if (!hasManifest || registry.includes(entry.name)) continue;
1494
1574
  unreachable.set(key, [...unreachable.get(key) ?? [], relative]);
1495
1575
  }
@@ -1505,11 +1585,57 @@ var pluginDirs = (root) => {
1505
1585
  };
1506
1586
  var WORKSPACE_YAML = "pnpm-workspace.yaml";
1507
1587
  var HOIST_KEY = "publicHoistPattern";
1588
+ var pnpmMajorOf = (manifest) => {
1589
+ const packageManager = manifest["packageManager"];
1590
+ const pinned = typeof packageManager === "string" ? /^pnpm@(\d+)[\d.]*/.exec(packageManager) : null;
1591
+ if (pinned?.[1] !== void 0) return { major: Number(pinned[1]), said: packageManager };
1592
+ const engine = manifest["engines"]?.["pnpm"];
1593
+ const ranged = typeof engine === "string" ? /(\d+)/.exec(engine) : null;
1594
+ if (ranged?.[1] !== void 0) return { major: Number(ranged[1]), said: `engines.pnpm ${engine}` };
1595
+ return void 0;
1596
+ };
1597
+ var pnpmBlock = (root) => {
1598
+ let manifest;
1599
+ try {
1600
+ manifest = JSON.parse(readFileSync8(join9(root, "package.json"), "utf8"));
1601
+ } catch {
1602
+ return [];
1603
+ }
1604
+ const block = manifest["pnpm"];
1605
+ if (typeof block !== "object" || block === null || Array.isArray(block)) return [];
1606
+ const keys = Object.keys(block).map((key) => `pnpm.${key}`);
1607
+ if (keys.length === 0) return [];
1608
+ const pinned = pnpmMajorOf(manifest);
1609
+ const named2 = keys.join(", ");
1610
+ const move = `Move each key to ${WORKSPACE_YAML} (\`overrides:\` at the top level), then ${HOIST_REPAIR} \u2014 a plain install answers "Already up to date" over the old tree.`;
1611
+ if (pinned === void 0) {
1612
+ return [
1613
+ finding4(
1614
+ "package.json",
1615
+ "WARN",
1616
+ `it has a pnpm block (${named2}) and nothing here pins pnpm \u2014 pnpm \u226511 does not read that block at all, so whether these settings apply depends on whichever pnpm is on the machine. ${move}`
1617
+ )
1618
+ ];
1619
+ }
1620
+ return pinned.major >= 11 ? [
1621
+ finding4(
1622
+ "package.json",
1623
+ "FAIL",
1624
+ `it has a pnpm block (${named2}) that pnpm \u226511 does not read, and this repo pins ${pinned.said}: every key in it is silently ignored, and the install still says "Already up to date". ${move}`
1625
+ )
1626
+ ] : [
1627
+ finding4(
1628
+ "package.json",
1629
+ "OK",
1630
+ `its pnpm block (${named2}) is read by the ${pinned.said} pinned here; pnpm 11 stops reading it, so move it to ${WORKSPACE_YAML} before that bump`
1631
+ )
1632
+ ];
1633
+ };
1508
1634
  var HOIST_REPAIR = "rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install";
1509
1635
  var hoistPatterns = (root) => {
1510
- const path = join7(root, WORKSPACE_YAML);
1636
+ const path = join9(root, WORKSPACE_YAML);
1511
1637
  if (!existsSync6(path)) return void 0;
1512
- const lines = readFileSync7(path, "utf8").split("\n");
1638
+ const lines = readFileSync8(path, "utf8").split("\n");
1513
1639
  const at = lines.findIndex((line) => new RegExp(`^${HOIST_KEY}\\s*:`).test(line));
1514
1640
  if (at < 0) return void 0;
1515
1641
  const patterns = [];
@@ -1546,7 +1672,7 @@ var publicHoists = (root, workspaces) => {
1546
1672
  `this ${HOIST_KEY} matches no workspace package here, so what it should have linked at the root is a question this cannot answer`
1547
1673
  );
1548
1674
  }
1549
- const pruned = hoisted.filter((name) => !existsSync6(join7(root, "node_modules", name)));
1675
+ const pruned = hoisted.filter((name) => !existsSync6(join9(root, "node_modules", name)));
1550
1676
  if (pruned.length === 0) {
1551
1677
  return finding4(
1552
1678
  pattern,
@@ -1564,11 +1690,13 @@ var publicHoists = (root, workspaces) => {
1564
1690
  var checkDrift = ({
1565
1691
  readers = READERS,
1566
1692
  root,
1567
- userSettings = join7(homedir(), SETTINGS),
1693
+ userSettings = join9(homedir(), SETTINGS),
1568
1694
  workspaces
1569
1695
  }) => {
1570
- const config = readGeonosis(root);
1696
+ const read = readGeonosisFile(root);
1697
+ const config = read.kind === "present" ? read.config : void 0;
1571
1698
  return [
1699
+ ...read.kind === "unreadable" ? [unreadableGeonosis("drift", read.error)] : [],
1572
1700
  ...ci(root),
1573
1701
  ...orphanTests(root, workspaces),
1574
1702
  ...scriptPaths(workspaces),
@@ -1577,15 +1705,53 @@ var checkDrift = ({
1577
1705
  ...generatedFiles(root, workspaces),
1578
1706
  ...pluginDirs(root),
1579
1707
  ...publicHoists(root, workspaces),
1708
+ ...pnpmBlock(root),
1580
1709
  ...composedHere(root, workspaces),
1581
1710
  ...lintScopes(root, workspaces),
1582
1711
  ...platformSplitBuilds(root),
1583
1712
  ...law(root, config),
1584
1713
  ...hooks(root, userSettings),
1585
- ...blocks(root, config, readers, workspaces)
1714
+ ...read.kind === "unreadable" ? [] : blocks(root, config, readers, workspaces)
1586
1715
  ];
1587
1716
  };
1588
1717
 
1718
+ // src/missing-option.ts
1719
+ import { pathToFileURL as pathToFileURL2 } from "url";
1720
+ var HEADER = /requires option `([^`]+)`/;
1721
+ var missingOption = (said2) => {
1722
+ const named2 = said2.option;
1723
+ if (typeof named2 === "string") return named2;
1724
+ const message = said2.message ?? said2.message;
1725
+ return HEADER.exec(String(message))?.[1];
1726
+ };
1727
+ var PROGRAM = {
1728
+ body: [],
1729
+ loc: { end: { column: 0, line: 1 }, start: { column: 0, line: 1 } },
1730
+ range: [0, 0],
1731
+ type: "Program"
1732
+ };
1733
+ var missingOptionOf = (rule, options) => {
1734
+ const reported = [];
1735
+ let visitor;
1736
+ try {
1737
+ visitor = rule.create({ options, report: (said2) => reported.push(said2) });
1738
+ } catch (error) {
1739
+ return missingOption(error);
1740
+ }
1741
+ const program = visitor?.Program;
1742
+ if (typeof program !== "function") return void 0;
1743
+ try {
1744
+ program(PROGRAM);
1745
+ } catch {
1746
+ return void 0;
1747
+ }
1748
+ return reported.map(missingOption).find((option) => option !== void 0);
1749
+ };
1750
+ var constructibleRulesOf = async (entry) => {
1751
+ const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
1752
+ return loaded?.default?.rules;
1753
+ };
1754
+
1589
1755
  // src/exercised.ts
1590
1756
  import { spawnSync as spawnSync2 } from "child_process";
1591
1757
  import {
@@ -1593,12 +1759,12 @@ import {
1593
1759
  existsSync as existsSync7,
1594
1760
  mkdirSync,
1595
1761
  mkdtempSync,
1596
- readFileSync as readFileSync8,
1762
+ readFileSync as readFileSync9,
1597
1763
  rmSync,
1598
1764
  writeFileSync
1599
1765
  } from "fs";
1600
1766
  import { tmpdir } from "os";
1601
- import { dirname as dirname2, join as join8, resolve } from "path";
1767
+ import { dirname as dirname2, join as join10, resolve as resolve2 } from "path";
1602
1768
  import { corpusOf, readManifest } from "@geonosis/lint-parity";
1603
1769
 
1604
1770
  // src/rule-level.ts
@@ -1615,9 +1781,9 @@ var refusal = (error) => {
1615
1781
  return said2.length > LIMIT ? `${said2.slice(0, LIMIT)}\u2026` : said2;
1616
1782
  };
1617
1783
  var reasonFrom = (config, oxlint) => {
1618
- const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-why-"));
1784
+ const dir = mkdtempSync(join10(tmpdir(), "geonosis-doctor-why-"));
1619
1785
  try {
1620
- const probe = join8(dir, "probe.tsx");
1786
+ const probe = join10(dir, "probe.tsx");
1621
1787
  writeFileSync(probe, "export const probe = 1\n");
1622
1788
  const run = spawnSync2(
1623
1789
  oxlint,
@@ -1679,6 +1845,18 @@ var enablingLayersOf = (config, rule) => {
1679
1845
  return layers;
1680
1846
  };
1681
1847
  var under = (config, rule) => `${rule} under ${optionSetsOf(config, rule).map((options) => JSON.stringify(options)).join(" / ")}`;
1848
+ var unconfiguredOf = async (config, entry, enabled) => {
1849
+ const rules = await constructibleRulesOf(entry);
1850
+ if (rules === void 0) return [];
1851
+ const found = [];
1852
+ for (const id of enabled) {
1853
+ const rule = rules[id.slice(id.indexOf("/") + 1)];
1854
+ if (rule === void 0) continue;
1855
+ const option = enablingLayersOf(config, id).map((layer) => missingOptionOf(rule, [...layer.options])).find((one) => one !== void 0);
1856
+ if (option !== void 0) found.push([id, option]);
1857
+ }
1858
+ return found;
1859
+ };
1682
1860
  var enabledHere = (config, plugin) => [
1683
1861
  .../* @__PURE__ */ new Set([
1684
1862
  ...enabledRulesOf(config.rules, plugin),
@@ -1709,8 +1887,8 @@ var throughProbes = ({
1709
1887
  }) => {
1710
1888
  const refused = /* @__PURE__ */ new Map();
1711
1889
  const placed = /* @__PURE__ */ new Set();
1712
- const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-probe-"));
1713
- const here = join8(dir, "corpus");
1890
+ const dir = mkdtempSync(join10(tmpdir(), "geonosis-doctor-probe-"));
1891
+ const here = join10(dir, "corpus");
1714
1892
  try {
1715
1893
  cpSync(corpus, here, { recursive: true });
1716
1894
  for (const rule of silent) {
@@ -1747,7 +1925,7 @@ var throughProbes = ({
1747
1925
  mounted = rehomed;
1748
1926
  }
1749
1927
  for (const file of mounted) {
1750
- const at2 = join8(here, file.path);
1928
+ const at2 = join10(here, file.path);
1751
1929
  if (placed.has(at2)) continue;
1752
1930
  placed.add(at2);
1753
1931
  mkdirSync(dirname2(at2), { recursive: true });
@@ -1760,26 +1938,32 @@ var throughProbes = ({
1760
1938
  refused.set(rule, String(error.message));
1761
1939
  }
1762
1940
  }
1763
- const probeConfig = JSON.parse(readFileSync8(config.path, "utf8"));
1941
+ const probeConfig = JSON.parse(readFileSync9(config.path, "utf8"));
1764
1942
  const configDir = dirname2(config.path);
1765
1943
  probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
1766
- (spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec, root), spec)
1944
+ (spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve2(configDir, spec) : packageDirOf(resolveFrom(configDir, spec, root), spec)
1767
1945
  );
1768
- const at = join8(here, ".oxlintrc-geonosis-probe.json");
1946
+ const at = join10(here, ".oxlintrc-geonosis-probe.json");
1769
1947
  writeFileSync(at, JSON.stringify(probeConfig, null, 2));
1770
1948
  const run = spawnSync2(
1771
1949
  oxlint,
1772
1950
  ["-c", at, "--format", "json", "--no-ignore", "--disable-nested-config", here],
1773
1951
  { cwd: dir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
1774
1952
  );
1775
- const fired = /* @__PURE__ */ new Set();
1953
+ if (run.error !== void 0) throw new Error(`oxlint could not start: ${run.error.message}`);
1954
+ const said2 = `${run.stdout ?? ""}${run.stderr ?? ""}`.trim().slice(0, LIMIT);
1955
+ let parsed;
1776
1956
  try {
1777
- const parsed = JSON.parse(run.stdout || "{}");
1778
- for (const one of parsed.diagnostics ?? []) {
1779
- const renamed = /^([\w@./-]+)\((.+)\)$/.exec(one.code ?? "");
1780
- if (renamed !== null) fired.add(`${renamed[1]}/${renamed[2]}`);
1781
- }
1957
+ parsed = JSON.parse(run.stdout);
1782
1958
  } catch {
1959
+ throw new Error(
1960
+ `oxlint exited ${run.status ?? -1} over the probes, printing ${said2 || "nothing"}`
1961
+ );
1962
+ }
1963
+ const fired = /* @__PURE__ */ new Set();
1964
+ for (const one of parsed.diagnostics ?? []) {
1965
+ const renamed = /^([\w@./-]+)\((.+)\)$/.exec(one.code ?? "");
1966
+ if (renamed !== null) fired.add(`${renamed[1]}/${renamed[2]}`);
1783
1967
  }
1784
1968
  return { fired, refused };
1785
1969
  } finally {
@@ -1826,6 +2010,16 @@ var checkExercised = async ({
1826
2010
  if (enabled.length === 0) {
1827
2011
  return retired.length === 0 ? said2("SKIP", `no ${manifest.plugin} rule is enabled here`) : said2("WARN", `nothing enabled here is a live ${manifest.plugin} rule`);
1828
2012
  }
2013
+ const unconfigured = entry === void 0 ? [] : await unconfiguredOf(config, entry, enabled);
2014
+ if (unconfigured.length > 0) {
2015
+ return said2(
2016
+ "FAIL",
2017
+ `${countOf(
2018
+ unconfigured.map(([rule]) => rule),
2019
+ "judge"
2020
+ )} nothing \u2014 enabled without the option each requires, so its only finding is the refusal, which is not reach: ${unconfigured.map(([rule, option]) => `${rule} (\`${option}\`)`).join(", ")}`
2021
+ );
2022
+ }
1829
2023
  let reach;
1830
2024
  try {
1831
2025
  reach = corpusOf({ configA: config.path, configB: config.path, corpus, oxlint }).reach;
@@ -1904,8 +2098,8 @@ var checkExercised = async ({
1904
2098
  };
1905
2099
 
1906
2100
  // src/envelope.ts
1907
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
1908
- import { join as join9 } from "path";
2101
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
2102
+ import { join as join11 } from "path";
1909
2103
  var ENVELOPES_DIR = ".geonosis/envelopes";
1910
2104
  var THIS_TOOL = "doctor";
1911
2105
  var OWN = `${THIS_TOOL}.json`;
@@ -1963,14 +2157,14 @@ var judge = (subject, name, parsed) => {
1963
2157
  );
1964
2158
  };
1965
2159
  var checkEnvelopes = ({ root }) => {
1966
- const dir = join9(root, ENVELOPES_DIR);
1967
- const files = existsSync8(dir) ? readdirSync4(dir).filter((name) => name.endsWith(".json") && name !== OWN).toSorted() : [];
2160
+ const dir = join11(root, ENVELOPES_DIR);
2161
+ const files = existsSync8(dir) ? readdirSync5(dir).filter((name) => name.endsWith(".json") && name !== OWN).toSorted() : [];
1968
2162
  if (files.length === 0) return [finding6("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
1969
2163
  return files.map((name) => {
1970
2164
  const subject = `${ENVELOPES_DIR}/${name}`;
1971
2165
  let parsed;
1972
2166
  try {
1973
- parsed = JSON.parse(readFileSync9(join9(dir, name), "utf8"));
2167
+ parsed = JSON.parse(readFileSync10(join11(dir, name), "utf8"));
1974
2168
  } catch (error) {
1975
2169
  return finding6("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
1976
2170
  }
@@ -1979,8 +2173,8 @@ var checkEnvelopes = ({ root }) => {
1979
2173
  };
1980
2174
 
1981
2175
  // src/exams.ts
1982
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
1983
- import { dirname as dirname3, join as join10 } from "path";
2176
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
2177
+ import { dirname as dirname3, join as join12 } from "path";
1984
2178
  var SCOPE = "@geonosis/";
1985
2179
  var EXAM = /(?:^|[^$\w])([$\w]*Conformance)\b/g;
1986
2180
  var finding7 = findingMaker("exams");
@@ -2001,12 +2195,12 @@ var declaredBy = (workspaces) => {
2001
2195
  }
2002
2196
  }
2003
2197
  }
2004
- return [...found].map(([name, from]) => ({ from, name })).toSorted((a, b) => a.name.localeCompare(b.name));
2198
+ return [...found].map(([name, from]) => ({ from, name })).toSorted((a, b) => a.name.localeCompare(b.name, "en-US"));
2005
2199
  };
2006
2200
  var declarationsOf = (dir) => {
2007
- const at = join10(dir, "package.json");
2201
+ const at = join12(dir, "package.json");
2008
2202
  if (!existsSync9(at)) return "";
2009
- const manifest = JSON.parse(readFileSync10(at, "utf8"));
2203
+ const manifest = JSON.parse(readFileSync11(at, "utf8"));
2010
2204
  const entries = [];
2011
2205
  const walk2 = (value) => {
2012
2206
  if (typeof value === "string" && value.endsWith(".d.ts")) entries.push(value);
@@ -2017,7 +2211,7 @@ var declarationsOf = (dir) => {
2017
2211
  walk2({ exports: manifest["exports"], types: manifest["types"] });
2018
2212
  return [...new Set(entries)].map((entry) => {
2019
2213
  try {
2020
- return readFileSync10(join10(dir, entry), "utf8");
2214
+ return readFileSync11(join12(dir, entry), "utf8");
2021
2215
  } catch {
2022
2216
  return "";
2023
2217
  }
@@ -2026,8 +2220,8 @@ var declarationsOf = (dir) => {
2026
2220
  var installedAt = (from, name, root) => {
2027
2221
  let at = from;
2028
2222
  for (; ; ) {
2029
- const dir = join10(at, "node_modules", name);
2030
- if (existsSync9(join10(dir, "package.json"))) return dir;
2223
+ const dir = join12(at, "node_modules", name);
2224
+ if (existsSync9(join12(dir, "package.json"))) return dir;
2031
2225
  if (at === root) return void 0;
2032
2226
  const up = dirname3(at);
2033
2227
  if (up === at) return void 0;
@@ -2043,7 +2237,7 @@ var checkExams = ({
2043
2237
  root,
2044
2238
  workspaces
2045
2239
  }) => {
2046
- const tests = testFilesUnder(root).map((path) => ({ path, source: readFileSync10(path, "utf8") }));
2240
+ const tests = testFilesUnder(root).map((path) => ({ path, source: readFileSync11(path, "utf8") }));
2047
2241
  const found = [];
2048
2242
  for (const { from, name } of declaredBy(workspaces)) {
2049
2243
  const exams = examsShippedBy(from, name, root);
@@ -2069,65 +2263,125 @@ var checkExams = ({
2069
2263
  };
2070
2264
 
2071
2265
  // src/claude-plugin.ts
2072
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
2266
+ import { existsSync as existsSync10, readFileSync as readFileSync12, realpathSync as realpathSync3 } from "fs";
2073
2267
  import { homedir as homedir2 } from "os";
2074
- import { join as join11 } from "path";
2268
+ import { join as join13, resolve as resolve3 } from "path";
2075
2269
  var INSTALLED_PLUGINS = ".claude/plugins/installed_plugins.json";
2076
2270
  var PLUGIN_NAME = "geonosis";
2077
- var installedPluginVersions = (home, name) => {
2078
- const at = join11(home, INSTALLED_PLUGINS);
2271
+ var USER_SCOPE = "user";
2272
+ var installedPlugins = (home, name) => {
2273
+ const at = join13(home, INSTALLED_PLUGINS);
2079
2274
  if (!existsSync10(at)) return [];
2080
2275
  let record;
2081
2276
  try {
2082
- record = JSON.parse(readFileSync11(at, "utf8"));
2277
+ record = JSON.parse(readFileSync12(at, "utf8"));
2083
2278
  } catch {
2084
2279
  return [];
2085
2280
  }
2086
- return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap((one) => typeof one.version === "string" ? [one.version] : []);
2281
+ return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap(
2282
+ (one) => typeof one.version === "string" ? [
2283
+ {
2284
+ ...typeof one.projectPath === "string" ? { projectPath: one.projectPath } : {},
2285
+ ...typeof one.scope === "string" ? { scope: one.scope } : {},
2286
+ version: one.version
2287
+ }
2288
+ ] : []
2289
+ );
2290
+ };
2291
+ var installedPluginVersions = (home, name) => installedPlugins(home, name).map((one) => one.version);
2292
+ var real3 = (path) => {
2293
+ try {
2294
+ return realpathSync3(path);
2295
+ } catch {
2296
+ return resolve3(path);
2297
+ }
2298
+ };
2299
+ var RELEASE = /^v?(\d+)\.(\d+)\.(\d+)/;
2300
+ var isAhead = (installed, train) => {
2301
+ const mine = RELEASE.exec(installed);
2302
+ const theirs = RELEASE.exec(train);
2303
+ if (mine === null || theirs === null) return false;
2304
+ for (let at = 1; at <= 3; at += 1) {
2305
+ const step = Number(mine[at]) - Number(theirs[at]);
2306
+ if (step !== 0) return step > 0;
2307
+ }
2308
+ return false;
2309
+ };
2310
+ var isUserScope = (one) => one.scope === void 0 || one.scope === USER_SCOPE;
2311
+ var loadedAt = (installs, root) => {
2312
+ const here = real3(root);
2313
+ const scopedHere = installs.filter(
2314
+ (one) => !isUserScope(one) && one.projectPath !== void 0 && real3(one.projectPath) === here
2315
+ );
2316
+ return scopedHere.length > 0 ? scopedHere : installs.filter(isUserScope);
2087
2317
  };
2088
2318
  var checkClaudePlugin = ({
2089
2319
  candidate = false,
2090
2320
  home = homedir2(),
2091
2321
  name = PLUGIN_NAME,
2322
+ root,
2092
2323
  train
2093
2324
  }) => {
2094
2325
  if (train === void 0) return [];
2095
- const installed = installedPluginVersions(home, name);
2326
+ const subject = `${name} (Claude plugin)`;
2327
+ const installed = installedPlugins(home, name);
2096
2328
  if (installed.length === 0) {
2097
2329
  return [
2098
2330
  {
2099
2331
  check: "loaded",
2100
2332
  message: `this machine has no "${name}" plugin installed, so none of this release's hooks, agents or skills is loaded in any session here \u2014 a bump moved the packages and nothing moved them. Install it, or say out loud that this repo runs without the plugin`,
2101
- subject: `${name} (Claude plugin)`,
2333
+ subject,
2102
2334
  verdict: "WARN"
2103
2335
  }
2104
2336
  ];
2105
2337
  }
2106
- const drifted = installed.filter((one) => one !== train);
2338
+ const loaded = root === void 0 ? installed.filter(isUserScope) : loadedAt(installed, root);
2339
+ if (loaded.length === 0) {
2340
+ const elsewhere = installed.map((one) => `${one.version} for ${one.projectPath ?? "an unnamed project"}`).join(", ");
2341
+ return [
2342
+ {
2343
+ check: "loaded",
2344
+ message: `the "${name}" plugin is installed only at project scope for other paths (${elsewhere}), so no session opened here loads it \u2014 none of this release's hooks, agents or skills runs in this repo. Install it at user scope, or for this project`,
2345
+ subject,
2346
+ verdict: "WARN"
2347
+ }
2348
+ ];
2349
+ }
2350
+ const drifted = loaded.filter((one) => one.version !== train).map((one) => one.version);
2107
2351
  if (drifted.length === 0) return [];
2108
2352
  if (candidate) {
2109
2353
  return [
2110
2354
  {
2111
2355
  check: "loaded",
2112
2356
  message: `the packages here are ${train}, a candidate the registry does not have, and the plugin is ${drifted.join(", ")} \u2014 the marketplace is where \`claude plugin update\` looks, so the plugin CANNOT follow a candidate and this is not drift. It becomes a real question the moment ${train} publishes`,
2113
- subject: `${name} (Claude plugin)`,
2357
+ subject,
2114
2358
  verdict: "OK"
2115
2359
  }
2116
2360
  ];
2117
2361
  }
2362
+ if (drifted.every((one) => isAhead(one, train))) {
2363
+ return [
2364
+ {
2365
+ check: "loaded",
2366
+ message: `the installed plugin is ${drifted.join(", ")} and the packages here are ${train} \u2014 the plugin is ahead, so its hooks, agents and skills can name commands and flags these packages do not have yet. Bump the packages to it, or install the plugin at ${train}`,
2367
+ subject,
2368
+ verdict: "WARN"
2369
+ }
2370
+ ];
2371
+ }
2118
2372
  return [
2119
2373
  {
2120
2374
  check: "loaded",
2121
2375
  message: `the installed plugin is ${drifted.join(", ")} and the packages here are ${train} \u2014 the plugin carries the hooks, the agents and the skills, and it moves on \`claude plugin update\`, never on an install. Every session in this repo is running the older ones`,
2122
- subject: `${name} (Claude plugin)`,
2376
+ subject,
2123
2377
  verdict: "WARN"
2124
2378
  }
2125
2379
  ];
2126
2380
  };
2127
2381
 
2128
2382
  // src/loaded.ts
2129
- import { readFileSync as readFileSync12 } from "fs";
2130
- import { join as join12, sep as sep3 } from "path";
2383
+ import { readFileSync as readFileSync13 } from "fs";
2384
+ import { join as join14, sep as sep4 } from "path";
2131
2385
  var SCOPE2 = "@geonosis/";
2132
2386
  var NOT_THE_SCOPE_DIRECTORY = "Deleting only the scope directory inside that node_modules is a repair no pnpm install undoes \u2014 the tree is then missing the package and every install stays green.";
2133
2387
  var BLOCKS = [
@@ -2136,7 +2390,7 @@ var BLOCKS = [
2136
2390
  "optionalDependencies",
2137
2391
  "peerDependencies"
2138
2392
  ];
2139
- var RELEASE = /^v?(\d+)\.(\d+)\.(\d+)$/;
2393
+ var RELEASE2 = /^v?(\d+)\.(\d+)\.(\d+)$/;
2140
2394
  var PINNED = /^[=v]?(\d+\.\d+\.\d+(?:[-+][\w.-]+)?)$/;
2141
2395
  var RANGE = /^(\^|~|>=)\s*v?(\d+)\.(\d+)\.(\d+)$/;
2142
2396
  var LINKED = /^(?:file|link|portal|workspace):/;
@@ -2162,7 +2416,7 @@ var satisfies = (version, spec) => {
2162
2416
  const pinned = PINNED.exec(wanted);
2163
2417
  return pinned === null ? void 0 : pinned[1] === version.replace(/^v/, "");
2164
2418
  }
2165
- const here = RELEASE.exec(version);
2419
+ const here = RELEASE2.exec(version);
2166
2420
  if (here === null) return void 0;
2167
2421
  const at = parts(here);
2168
2422
  const bound = [
@@ -2184,14 +2438,14 @@ var specIn = (manifest, specifier) => {
2184
2438
  }
2185
2439
  return void 0;
2186
2440
  };
2187
- var holds2 = (parent, child) => child === parent || child.startsWith(`${parent}${sep3}`);
2441
+ var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
2188
2442
  var manifestNameOf = (workspace) => workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
2189
2443
  var declaredFor = ({
2190
2444
  dir,
2191
2445
  specifier,
2192
2446
  workspaces
2193
2447
  }) => {
2194
- const upwards = workspaces.filter((one) => holds2(one.dir, dir)).toSorted((a, b) => b.dir.length - a.dir.length);
2448
+ const upwards = workspaces.filter((one) => holds3(one.dir, dir)).toSorted((a, b) => b.dir.length - a.dir.length);
2195
2449
  for (const workspace of upwards) {
2196
2450
  const spec = specIn(workspace.manifest, specifier);
2197
2451
  if (spec !== void 0) return { at: manifestNameOf(workspace), spec };
@@ -2236,7 +2490,7 @@ var oneConfig = async ({
2236
2490
  }
2237
2491
  return held ? said2("OK", `loaded ${loaded.version} = declared ${declared.spec} (${declared.at})`) : said2(
2238
2492
  "FAIL",
2239
- `loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Run \`geonosis update --to ${declared.spec}\`: it removes the WHOLE node_modules of every non-root workspace carrying one, never the root's, and reinstalls. ${NOT_THE_SCOPE_DIRECTORY} The linter is not running what the tree declares until then`
2493
+ `loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Run \`geonosis update --to ${declared.spec.replace(/^(?:\^|~|>=|=)\s*v?/, "")}\`: it removes the WHOLE node_modules of every non-root workspace carrying one, never the root's, and reinstalls. ${NOT_THE_SCOPE_DIRECTORY} The linter is not running what the tree declares until then`
2240
2494
  );
2241
2495
  };
2242
2496
  var labelOf2 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
@@ -2275,7 +2529,7 @@ var shipperOf = (root, bin) => {
2275
2529
  const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
2276
2530
  try {
2277
2531
  const manifest = JSON.parse(
2278
- readFileSync12(join12(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
2532
+ readFileSync13(join14(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
2279
2533
  );
2280
2534
  const declared = manifest.bin;
2281
2535
  return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
@@ -2365,6 +2619,7 @@ var checkLoaded = async ({
2365
2619
  ...checkClaudePlugin({
2366
2620
  candidate: installedFromCandidate(workspaces),
2367
2621
  home,
2622
+ root,
2368
2623
  train: await trainVersion(root, [...specifiers])
2369
2624
  })
2370
2625
  );
@@ -2382,56 +2637,47 @@ var trainVersion = async (root, specifiers) => {
2382
2637
  };
2383
2638
 
2384
2639
  // src/observability.ts
2385
- import { readFileSync as readFileSync13 } from "fs";
2386
- import { join as join13 } from "path";
2387
- var GEONOSIS_FILE2 = "geonosis.json";
2640
+ import { readFileSync as readFileSync14 } from "fs";
2641
+ import { join as join15 } from "path";
2388
2642
  var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
2389
2643
  var DEFAULT_MAX_AGE_SECONDS = 3600;
2390
2644
  var HEAD_TIMEOUT_MS = 3e3;
2391
2645
  var make3 = findingMaker("observability");
2392
2646
  var finding9 = (verdict, subject, message) => make3(subject, verdict, message);
2393
- var readGeonosis2 = (root) => {
2394
- let text;
2395
- try {
2396
- text = readFileSync13(join13(root, GEONOSIS_FILE2), "utf8");
2397
- } catch {
2647
+ var readGeonosis = (root) => {
2648
+ const read = readGeonosisFile(root);
2649
+ if (read.kind === "unreadable") return { error: read.error, present: false };
2650
+ if (read.kind === "absent") return { present: false };
2651
+ const block = read.config.observability;
2652
+ if (typeof block !== "object" || block === null || Array.isArray(block)) {
2398
2653
  return { present: false };
2399
2654
  }
2400
- try {
2401
- const parsed = JSON.parse(text);
2402
- const block = parsed.observability;
2403
- if (typeof block !== "object" || block === null || Array.isArray(block)) {
2404
- return { present: false };
2405
- }
2406
- return { config: block, present: true };
2407
- } catch (error) {
2408
- return { error: error.message, present: false };
2409
- }
2655
+ return { config: block, present: true };
2410
2656
  };
2411
2657
  var exporterFinding = (config) => {
2412
2658
  const sink = config.sink;
2413
2659
  if (typeof sink !== "string" || sink.trim() === "") {
2414
2660
  return finding9(
2415
2661
  "FAIL",
2416
- GEONOSIS_FILE2,
2662
+ GEONOSIS_FILE,
2417
2663
  "observability.sink is not set, so nothing here says where errors are supposed to go \u2014 and a repo that cannot name its exporter has not got one"
2418
2664
  );
2419
2665
  }
2420
2666
  if (REACHES_NOTHING.has(sink.toLowerCase())) {
2421
2667
  return finding9(
2422
2668
  "WARN",
2423
- GEONOSIS_FILE2,
2669
+ GEONOSIS_FILE,
2424
2670
  `the configured sink is "${sink}", which answers ok and reaches nothing. Correct in a dev tree; in a deployed one it is the instrument that cannot fail.`
2425
2671
  );
2426
2672
  }
2427
- return finding9("OK", GEONOSIS_FILE2, `the configured sink is "${sink}"`);
2673
+ return finding9("OK", GEONOSIS_FILE, `the configured sink is "${sink}"`);
2428
2674
  };
2429
2675
  var reachableFinding = async (config) => {
2430
2676
  const endpoint = config.endpoint;
2431
2677
  if (typeof endpoint !== "string" || endpoint.trim() === "") {
2432
2678
  return finding9(
2433
2679
  "SKIP",
2434
- GEONOSIS_FILE2,
2680
+ GEONOSIS_FILE,
2435
2681
  "no observability.endpoint was named, so whether the exporter is reachable was not asked"
2436
2682
  );
2437
2683
  }
@@ -2441,32 +2687,41 @@ var reachableFinding = async (config) => {
2441
2687
  const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
2442
2688
  return finding9(
2443
2689
  "OK",
2444
- GEONOSIS_FILE2,
2690
+ GEONOSIS_FILE,
2445
2691
  `${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
2446
2692
  );
2447
2693
  } catch (error) {
2448
2694
  return finding9(
2449
2695
  "FAIL",
2450
- GEONOSIS_FILE2,
2696
+ GEONOSIS_FILE,
2451
2697
  `${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
2452
2698
  );
2453
2699
  } finally {
2454
2700
  clearTimeout(timer);
2455
2701
  }
2456
2702
  };
2703
+ var reachesNothing = (config) => typeof config.sink === "string" && REACHES_NOTHING.has(config.sink.toLowerCase()) ? config.sink : void 0;
2457
2704
  var ageFinding = (config, root, now) => {
2458
2705
  const file = config.lastEventFile;
2706
+ const dead = reachesNothing(config);
2707
+ if (dead !== void 0) {
2708
+ return finding9(
2709
+ "SKIP",
2710
+ GEONOSIS_FILE,
2711
+ `the sink is "${dead}", which reaches nothing, so the age of its last event says when this checkout last ran, not whether anything was delivered \u2014 not judged. Point a real sink here and it will be.`
2712
+ );
2713
+ }
2459
2714
  if (typeof file !== "string" || file.trim() === "") {
2460
2715
  return finding9(
2461
2716
  "SKIP",
2462
- GEONOSIS_FILE2,
2717
+ GEONOSIS_FILE,
2463
2718
  "no observability.lastEventFile was configured, so when the last event arrived is not a question anything here can answer. Have the sink write { at, id, sink } on every capture and name the file."
2464
2719
  );
2465
2720
  }
2466
2721
  const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
2467
2722
  let record;
2468
2723
  try {
2469
- record = JSON.parse(readFileSync13(join13(root, file), "utf8"));
2724
+ record = JSON.parse(readFileSync14(join15(root, file), "utf8"));
2470
2725
  } catch (error) {
2471
2726
  return finding9(
2472
2727
  "FAIL",
@@ -2494,19 +2749,27 @@ var ageFinding = (config, root, now) => {
2494
2749
  };
2495
2750
  var probeFinding = (config) => {
2496
2751
  const probe = config.probe;
2752
+ const dead = reachesNothing(config);
2753
+ if (dead !== void 0) {
2754
+ return finding9(
2755
+ "SKIP",
2756
+ GEONOSIS_FILE,
2757
+ `the sink is "${dead}", which reaches nothing, so no probe can prove a report reached it \u2014 not asked`
2758
+ );
2759
+ }
2497
2760
  if (typeof probe === "string" && probe.trim() !== "") {
2498
- return finding9("OK", GEONOSIS_FILE2, `the probe that proves this exporter is "${probe}"`);
2761
+ return finding9("OK", GEONOSIS_FILE, `the probe that proves this exporter is "${probe}"`);
2499
2762
  }
2500
2763
  if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
2501
2764
  return finding9(
2502
2765
  "OK",
2503
- GEONOSIS_FILE2,
2766
+ GEONOSIS_FILE,
2504
2767
  "no probe command, but a last event file is read above, so something does look at this exporter"
2505
2768
  );
2506
2769
  }
2507
2770
  return finding9(
2508
2771
  "WARN",
2509
- GEONOSIS_FILE2,
2772
+ GEONOSIS_FILE,
2510
2773
  "neither observability.probe nor observability.lastEventFile is configured, so nothing in this repo has ever established that a report reaches the sink. Name a probe command \u2014 the doctor reports it, your gate runs it."
2511
2774
  );
2512
2775
  };
@@ -2514,13 +2777,13 @@ var checkObservability = async ({
2514
2777
  now,
2515
2778
  root
2516
2779
  }) => {
2517
- const read = readGeonosis2(root);
2780
+ const read = readGeonosis(root);
2518
2781
  if (read.error !== void 0) {
2519
2782
  return [
2520
2783
  finding9(
2521
2784
  "FAIL",
2522
- GEONOSIS_FILE2,
2523
- `${GEONOSIS_FILE2} could not be parsed: ${read.error}. A config nobody can read has not been read, and every question below would have been answered from a default nobody chose.`
2785
+ GEONOSIS_FILE,
2786
+ `${GEONOSIS_FILE} could not be parsed: ${read.error}. A config nobody can read has not been read, and every question below would have been answered from a default nobody chose.`
2524
2787
  )
2525
2788
  ];
2526
2789
  }
@@ -2528,8 +2791,8 @@ var checkObservability = async ({
2528
2791
  return [
2529
2792
  finding9(
2530
2793
  "SKIP",
2531
- GEONOSIS_FILE2,
2532
- `no observability block in ${GEONOSIS_FILE2}, so nothing here knows where this repo sends its errors. Add { sink, endpoint, lastEventFile | probe, maxAgeSeconds } to have this asked.`
2794
+ GEONOSIS_FILE,
2795
+ `no observability block in ${GEONOSIS_FILE}, so nothing here knows where this repo sends its errors. Add { sink, endpoint, lastEventFile | probe, maxAgeSeconds } to have this asked.`
2533
2796
  )
2534
2797
  ];
2535
2798
  }
@@ -2542,19 +2805,77 @@ var checkObservability = async ({
2542
2805
  ];
2543
2806
  };
2544
2807
 
2808
+ // src/repo-corpus.ts
2809
+ import { join as join16 } from "path";
2810
+ var corpusFrom = (config, root) => {
2811
+ const declared = config.doctor?.corpus;
2812
+ return typeof declared === "string" && declared !== "" ? join16(root, declared) : void 0;
2813
+ };
2814
+ var repoCorpusOf = (root) => {
2815
+ const read = readGeonosisFile(root);
2816
+ if (read.kind === "unreadable") {
2817
+ throw new DoctorError(`${GEONOSIS_FILE} could not be parsed: ${read.error}`);
2818
+ }
2819
+ return read.kind === "present" ? corpusFrom(read.config, root) : void 0;
2820
+ };
2821
+
2822
+ // src/required-options.ts
2823
+ import { pathToFileURL as pathToFileURL3 } from "url";
2824
+ var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
2825
+ var refusesBare = (rule) => missingOptionOf(rule, []) !== void 0;
2826
+ var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
2827
+ (rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
2828
+ );
2829
+ var checkRequiredOptions = async ({
2830
+ config,
2831
+ entry
2832
+ }) => {
2833
+ const loaded = await import(pathToFileURL3(entry).href).catch(() => void 0);
2834
+ const namespace = loaded?.default?.meta?.name;
2835
+ const rules = loaded?.default?.rules;
2836
+ if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
2837
+ const enabled = configured(config, namespace);
2838
+ if (enabled.length === 0) return [];
2839
+ const missing = [];
2840
+ const said2 = /* @__PURE__ */ new Set();
2841
+ const gated = /* @__PURE__ */ new Set();
2842
+ for (const [id, options] of enabled) {
2843
+ const rule = rules[id.slice(namespace.length + 1)];
2844
+ if (rule === void 0) continue;
2845
+ if (refusesBare(rule)) gated.add(id);
2846
+ const option = missingOptionOf(rule, options);
2847
+ if (option === void 0 || said2.has(`${id} ${option}`)) continue;
2848
+ said2.add(`${id} ${option}`);
2849
+ missing.push({
2850
+ check: "loaded",
2851
+ message: `it is enabled here with no \`${option}\`, and the rule cannot judge a file without one \u2014 every run reports only that it is missing, and nothing about the tree. Configure \`${option}\`, or turn the rule off`,
2852
+ subject: `${config.relative} \u2192 ${id}`,
2853
+ verdict: "FAIL"
2854
+ });
2855
+ }
2856
+ if (missing.length > 0) return missing;
2857
+ return [
2858
+ {
2859
+ check: "loaded",
2860
+ message: gated.size === 1 ? "1 rule requires options, 1 carries them" : `${gated.size} rules require options, ${gated.size} carry them`,
2861
+ subject: config.relative,
2862
+ verdict: "OK"
2863
+ }
2864
+ ];
2865
+ };
2866
+
2545
2867
  // src/runner.ts
2546
- import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
2547
- import { join as join14, resolve as resolve2 } from "path";
2868
+ import { readFileSync as readFileSync15 } from "fs";
2869
+ import { resolve as resolve4 } from "path";
2548
2870
  var TEST_FAILURES = "testFailures";
2549
2871
  var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
2550
2872
  var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
2551
2873
  var OUTPUT_FILE = /--outputFile[= ](\S+)/i;
2552
- var A_SCRIPT_FILE = /\.(?:[cm]?[jt]sx?|sh|bash)$/;
2553
2874
  var wrapperNamedBy = (script, dir) => {
2554
2875
  for (const token of script.split(/[\s'"]+/)) {
2555
2876
  if (!A_SCRIPT_FILE.test(token)) continue;
2556
2877
  try {
2557
- return { source: readFileSync14(resolve2(dir, token), "utf8"), where: token };
2878
+ return { source: readFileSync15(resolve4(dir, token), "utf8"), where: token };
2558
2879
  } catch {
2559
2880
  continue;
2560
2881
  }
@@ -2579,34 +2900,13 @@ var reportingCounters = (ratchet) => (ratchet?.counters ?? []).filter(
2579
2900
  var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(entry.key);
2580
2901
  var namesReport = (entry, path) => stringOf(entry.reportPath) === path || stringOf(entry.command).includes(path);
2581
2902
  var PROVES = /--prove\b/;
2582
- var everythingRun2 = (root, workspaces) => {
2583
- const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
2584
- const dir = join14(root, ".github/workflows");
2585
- const workflows = (existsSync11(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
2586
- try {
2587
- return readFileSync14(join14(dir, name), "utf8");
2588
- } catch {
2589
- return "";
2590
- }
2591
- });
2592
- let tiers = {};
2593
- try {
2594
- const config = JSON.parse(readFileSync14(join14(root, "geonosis.json"), "utf8"));
2595
- tiers = config.verify ?? {};
2596
- } catch {
2597
- }
2598
- return {
2599
- run: [...Object.values(tiers).flat(), ...scripts, ...workflows].join("\n"),
2600
- tiers: Object.keys(tiers)
2601
- };
2602
- };
2603
2903
  var proveIsWired = ({
2604
2904
  ratchet,
2605
2905
  root,
2606
2906
  workspaces
2607
2907
  }) => {
2608
2908
  if (ratchet === void 0 || ratchet.counters.length === 0) return [];
2609
- const { run, tiers } = everythingRun2(root, workspaces);
2909
+ const { run, tiers } = everythingRun(root, workspaces);
2610
2910
  if (PROVES.test(run)) {
2611
2911
  return [
2612
2912
  finding10(
@@ -2690,33 +2990,31 @@ var checkRunner = ({
2690
2990
 
2691
2991
  // src/doctor.ts
2692
2992
  import { homedir as homedir3 } from "os";
2693
- import { join as join18 } from "path";
2993
+ import { join as join19 } from "path";
2694
2994
  import { resolveOxlint } from "@geonosis/lint-parity";
2695
2995
 
2696
2996
  // src/engine.ts
2697
- import { sep as sep4 } from "path";
2698
- var declaredIn3 = (manifest) => new Set(
2699
- [
2700
- manifest.dependencies,
2701
- manifest.devDependencies,
2702
- manifest.optionalDependencies,
2703
- manifest.peerDependencies
2704
- ].flatMap((block) => Object.keys(block ?? {}))
2705
- );
2706
- var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
2997
+ import { sep as sep5 } from "path";
2998
+ var holds4 = (parent, child) => child === parent || child.startsWith(`${parent}${sep5}`);
2707
2999
  var labelOf3 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
2708
- var governedBy = (dir, workspaces) => workspaces.filter((one) => holds3(one.dir, dir) || holds3(dir, one.dir));
2709
- var enabledHere2 = (config, plugin) => [
2710
- .../* @__PURE__ */ new Set([
2711
- ...enabledRulesOf(config.rules, plugin),
2712
- ...config.overrides.flatMap((one) => enabledRulesOf(one.rules, plugin))
2713
- ])
2714
- ];
3000
+ var governedBy = (dir, workspaces) => workspaces.filter((one) => holds4(one.dir, dir) || holds4(dir, one.dir));
2715
3001
  var checkEngines = async ({
2716
3002
  config,
2717
3003
  entry,
3004
+ evidence = [],
2718
3005
  workspaces
2719
3006
  }) => {
3007
+ const holding = evidenceHolding(config, evidence);
3008
+ if (holding !== void 0) {
3009
+ return [
3010
+ {
3011
+ check: "exercised",
3012
+ message: "evidence, not a product config \u2014 its directory is one this doctor reads as a corpus of planted files, so it is not asked which engine its manifests presume",
3013
+ subject: config.relative,
3014
+ verdict: "SKIP"
3015
+ }
3016
+ ];
3017
+ }
2720
3018
  const plugin = await presumptionsOf(entry).catch(() => void 0);
2721
3019
  if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0 && Object.keys(plugin.floors).length === 0) {
2722
3020
  return [];
@@ -2734,9 +3032,9 @@ var checkEngines = async ({
2734
3032
  ];
2735
3033
  };
2736
3034
  const governed = governedBy(config.dir, workspaces);
2737
- const declared = new Set(governed.flatMap((one) => [...declaredIn3(one.manifest)]));
3035
+ const declared = new Set(governed.flatMap((one) => [...dependencyNamesOf(one.manifest)]));
2738
3036
  const asked = governed.map(labelOf3).join(", ");
2739
- return enabledHere2(config, plugin.namespace).flatMap((rule) => {
3037
+ return enabledHere(config, plugin.namespace).flatMap((rule) => {
2740
3038
  const floorless = pending(rule);
2741
3039
  const presumes = plugin.presumed[rule.slice(plugin.namespace.length + 1)];
2742
3040
  if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) {
@@ -2754,26 +3052,53 @@ var checkEngines = async ({
2754
3052
  });
2755
3053
  };
2756
3054
 
2757
- // src/path-grants.ts
2758
- import { readdirSync as readdirSync6 } from "fs";
2759
- import { join as join15 } from "path";
2760
- var NEVER_WALKED2 = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
2761
- var LINTABLE2 = /\.[cm]?[jt]sx?$/;
2762
- var filesUnder2 = (root, at = "") => {
2763
- let entries;
2764
- try {
2765
- entries = readdirSync6(join15(root, at), { withFileTypes: true });
2766
- } catch {
2767
- return [];
3055
+ // src/formatter.ts
3056
+ import { spawnSync as spawnSync3 } from "child_process";
3057
+ import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
3058
+ import { join as join17 } from "path";
3059
+ var KIT_RULES = ".claude/rules/geonosis";
3060
+ var finding11 = findingMaker("formatter");
3061
+ var ALL_PATHS_IGNORED = 2;
3062
+ var checkFormatter = ({ root }) => {
3063
+ if (!existsSync11(join17(root, KIT_RULES))) {
3064
+ return [
3065
+ finding11(KIT_RULES, "SKIP", "no kit rules are installed here for a formatter to rewrite")
3066
+ ];
2768
3067
  }
2769
- return entries.flatMap((entry) => {
2770
- const here = at === "" ? entry.name : `${at}/${entry.name}`;
2771
- if (entry.isDirectory()) {
2772
- return NEVER_WALKED2.has(entry.name) ? [] : filesUnder2(root, here);
2773
- }
2774
- return LINTABLE2.test(entry.name) ? [here] : [];
3068
+ const oxfmt = join17(root, "node_modules/.bin/oxfmt");
3069
+ if (!existsSync11(oxfmt)) {
3070
+ return [finding11(KIT_RULES, "SKIP", "this repo has no oxfmt, the formatter the kit speaks for")];
3071
+ }
3072
+ const files = readdirSync6(join17(root, KIT_RULES)).filter((name) => name.endsWith(".md")).map((name) => join17(KIT_RULES, name));
3073
+ const run = spawnSync3(oxfmt, ["--list-different", ...files], {
3074
+ maxBuffer: Infinity,
3075
+ cwd: root,
3076
+ encoding: "utf8"
2775
3077
  });
3078
+ const named2 = run.stdout.split("\n").filter((line) => files.includes(line.trim()));
3079
+ if (run.status === ALL_PATHS_IGNORED && run.stderr.includes("excluded by ignore rules")) {
3080
+ return [finding11(KIT_RULES, "OK", "oxfmt leaves the kit\u2019s rule files alone")];
3081
+ }
3082
+ if (run.status === 0 || run.status === 1 && named2.length > 0) {
3083
+ return [
3084
+ finding11(
3085
+ KIT_RULES,
3086
+ "WARN",
3087
+ `oxfmt would format the kit's rule files, and the next \`format\` rewrites them \u2014 tiers.md is generated. Add "${KIT_RULES}" to the ignorePatterns in .oxfmtrc.json`
3088
+ )
3089
+ ];
3090
+ }
3091
+ return [
3092
+ finding11(
3093
+ KIT_RULES,
3094
+ "UNJUDGED",
3095
+ `oxfmt could not be asked (exit ${String(run.status)}): ${run.stderr.trim() || run.stdout.trim()}`
3096
+ )
3097
+ ];
2776
3098
  };
3099
+
3100
+ // src/path-grants.ts
3101
+ var LINTABLE2 = /\.[cm]?[jt]sx?$/;
2777
3102
  var stringsOf2 = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
2778
3103
  var bagOf = (level) => {
2779
3104
  if (!Array.isArray(level)) return {};
@@ -2795,7 +3120,9 @@ var checkPathGrants = ({
2795
3120
  }) => {
2796
3121
  const declared = Object.entries(grants);
2797
3122
  if (declared.length === 0) return [];
2798
- const files = filesUnder2(root);
3123
+ const files = filesUnder(root, (name) => LINTABLE2.test(name)).map(
3124
+ (path) => relativePath(root, path)
3125
+ );
2799
3126
  const found = [];
2800
3127
  for (const [id, grant] of declared) {
2801
3128
  for (const layer of layersOf(config, id)) {
@@ -2818,14 +3145,13 @@ var checkPathGrants = ({
2818
3145
  };
2819
3146
 
2820
3147
  // src/rails.ts
2821
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
2822
- import { join as join16 } from "path";
2823
- var GEONOSIS2 = "geonosis.json";
3148
+ import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
3149
+ import { join as join18 } from "path";
2824
3150
  var PROJECT = ".claude/settings.json";
2825
3151
  var LOCAL = ".claude/settings.local.json";
2826
3152
  var RUN_RECORD = ".geonosis/rails-run.json";
2827
3153
  var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
2828
- var finding11 = findingMaker("rails");
3154
+ var finding12 = findingMaker("rails");
2829
3155
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2830
3156
  var networkIn = (parsed) => {
2831
3157
  if (!isRecord3(parsed)) return void 0;
@@ -2843,7 +3169,7 @@ var sourceAt = (path, label) => {
2843
3169
  if (!existsSync12(path)) return { network: void 0, path: label, unreadable: false };
2844
3170
  try {
2845
3171
  return {
2846
- network: networkIn(JSON.parse(readFileSync15(path, "utf8"))),
3172
+ network: networkIn(JSON.parse(readFileSync16(path, "utf8"))),
2847
3173
  path: label,
2848
3174
  unreadable: false
2849
3175
  };
@@ -2851,16 +3177,8 @@ var sourceAt = (path, label) => {
2851
3177
  return { network: void 0, path: label, unreadable: true };
2852
3178
  }
2853
3179
  };
2854
- var declaredEgress = (root) => {
2855
- const at = join16(root, GEONOSIS2);
2856
- if (!existsSync12(at)) return void 0;
2857
- let parsed;
2858
- try {
2859
- parsed = JSON.parse(readFileSync15(at, "utf8"));
2860
- } catch {
2861
- return void 0;
2862
- }
2863
- const egress = parsed?.rails?.egress;
3180
+ var declaredEgress = (config) => {
3181
+ const egress = config.rails?.egress;
2864
3182
  if (!isRecord3(egress)) return void 0;
2865
3183
  const allow = egress["allow"];
2866
3184
  return {
@@ -2868,10 +3186,10 @@ var declaredEgress = (root) => {
2868
3186
  };
2869
3187
  };
2870
3188
  var deniedEgress = (root) => {
2871
- const at = join16(root, RUN_RECORD);
3189
+ const at = join18(root, RUN_RECORD);
2872
3190
  if (!existsSync12(at)) {
2873
3191
  return [
2874
- finding11(
3192
+ finding12(
2875
3193
  "deniedEgress",
2876
3194
  "SKIP",
2877
3195
  `there is no ${RUN_RECORD} here, so no run has been recorded for this to be a gate over`
@@ -2880,23 +3198,32 @@ var deniedEgress = (root) => {
2880
3198
  }
2881
3199
  let parsed;
2882
3200
  try {
2883
- parsed = JSON.parse(readFileSync15(at, "utf8"));
3201
+ parsed = JSON.parse(readFileSync16(at, "utf8"));
2884
3202
  } catch (error) {
2885
3203
  return [
2886
- finding11(
3204
+ finding12(
2887
3205
  "deniedEgress",
2888
3206
  "FAIL",
2889
3207
  `${RUN_RECORD} is not readable JSON (${error.message}) \u2014 a record nobody can read is not a record of zero denied attempts`
2890
3208
  )
2891
3209
  ];
2892
3210
  }
2893
- const denied = isRecord3(parsed) && Array.isArray(parsed["deniedEgress"]) ? parsed["deniedEgress"] : [];
3211
+ if (!isRecord3(parsed) || !Array.isArray(parsed["deniedEgress"])) {
3212
+ return [
3213
+ finding12(
3214
+ "deniedEgress",
3215
+ "FAIL",
3216
+ `${RUN_RECORD} lists no deniedEgress \u2014 a record that does not say what it records is not a record of zero denied attempts`
3217
+ )
3218
+ ];
3219
+ }
3220
+ const denied = parsed["deniedEgress"];
2894
3221
  if (denied.length === 0) {
2895
- return [finding11("deniedEgress", "OK", "no run recorded a denied egress attempt")];
3222
+ return [finding12("deniedEgress", "OK", "no run recorded a denied egress attempt")];
2896
3223
  }
2897
3224
  const hosts = denied.map((one) => isRecord3(one) && typeof one["host"] === "string" ? one["host"] : "(unnamed)").join(", ");
2898
3225
  return [
2899
- finding11(
3226
+ finding12(
2900
3227
  "deniedEgress",
2901
3228
  "FAIL",
2902
3229
  `${denied.length} denied egress attempt(s) in this run's record \u2014 ${hosts}. This is a gate at ZERO and never a counter to hold flat: a run that reached for a host this repo does not allow is a run somebody reads, whatever the number was yesterday`
@@ -2908,24 +3235,27 @@ var checkRails = ({
2908
3235
  root,
2909
3236
  userSettings
2910
3237
  }) => {
2911
- const declared = declaredEgress(root);
3238
+ const read = readGeonosisFile(root);
3239
+ if (read.kind === "unreadable") return [unreadableGeonosis("rails", read.error)];
3240
+ const declared = read.kind === "absent" ? void 0 : declaredEgress(read.config);
2912
3241
  if (declared === void 0 || declared.allow.length === 0) {
2913
3242
  return [
2914
- finding11(
2915
- `${GEONOSIS2} \u2192 rails.egress`,
3243
+ finding12(
3244
+ `${GEONOSIS_FILE} \u2192 rails.egress`,
2916
3245
  "SKIP",
2917
3246
  "this repo declares no egress allowlist, so there is no rendered setting for this to read back"
2918
- )
3247
+ ),
3248
+ ...deniedEgress(root)
2919
3249
  ];
2920
3250
  }
2921
3251
  const sources = [
2922
3252
  sourceAt(managedSettings, managedSettings),
2923
3253
  sourceAt(userSettings, userSettings),
2924
- sourceAt(join16(root, PROJECT), PROJECT),
2925
- sourceAt(join16(root, LOCAL), LOCAL)
3254
+ sourceAt(join18(root, PROJECT), PROJECT),
3255
+ sourceAt(join18(root, LOCAL), LOCAL)
2926
3256
  ];
2927
3257
  const unreadable = sources.filter((one) => one.unreadable).map(
2928
- (one) => finding11(
3258
+ (one) => finding12(
2929
3259
  one.path,
2930
3260
  "FAIL",
2931
3261
  "is not readable JSON, and Claude Code SILENTLY ignores a settings file that fails validation \u2014 every setting rendered into this file loads as nothing, with no error anywhere. Fix the file, then render the allowlist again"
@@ -2940,93 +3270,22 @@ var checkRails = ({
2940
3270
  return [
2941
3271
  ...unreadable,
2942
3272
  ...declared.allow.map(
2943
- (domain) => effective.has(domain) ? finding11(domain, "OK", "declared, and in the settings that actually load") : finding11(
3273
+ (domain) => effective.has(domain) ? finding12(domain, "OK", "declared, and in the settings that actually load") : finding12(
2944
3274
  domain,
2945
3275
  "FAIL",
2946
- `declared in ${GEONOSIS2} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
3276
+ `declared in ${GEONOSIS_FILE} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
2947
3277
  )
2948
3278
  ),
2949
3279
  ...deniedEgress(root)
2950
3280
  ];
2951
3281
  };
2952
3282
 
2953
- // src/required-options.ts
2954
- import { pathToFileURL as pathToFileURL2 } from "url";
2955
- var HEADER = /requires option `([^`]+)`/;
2956
- var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
2957
- var missingOption = (error) => {
2958
- const named2 = error.option;
2959
- return typeof named2 === "string" ? named2 : HEADER.exec(String(error.message))?.[1];
2960
- };
2961
- var refusesBare = (rule) => {
2962
- try {
2963
- rule.create({ options: [], report: () => {
2964
- } });
2965
- return false;
2966
- } catch (error) {
2967
- return missingOption(error) !== void 0;
2968
- }
2969
- };
2970
- var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
2971
- (rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
2972
- );
2973
- var checkRequiredOptions = async ({
2974
- config,
2975
- entry
2976
- }) => {
2977
- const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
2978
- const namespace = loaded?.default?.meta?.name;
2979
- const rules = loaded?.default?.rules;
2980
- if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
2981
- const enabled = configured(config, namespace);
2982
- if (enabled.length === 0) return [];
2983
- const missing = [];
2984
- let gated = 0;
2985
- for (const [id, options] of enabled) {
2986
- const rule = rules[id.slice(namespace.length + 1)];
2987
- if (rule === void 0) continue;
2988
- if (refusesBare(rule)) gated += 1;
2989
- try {
2990
- rule.create({ options, report: () => {
2991
- } });
2992
- } catch (error) {
2993
- const option = missingOption(error);
2994
- if (option === void 0) continue;
2995
- missing.push({
2996
- check: "loaded",
2997
- message: `it is enabled here with no \`${option}\`, and the rule refuses to be constructed without one \u2014 oxlint stops the whole run at load, linting nothing. Configure \`${option}\`, or turn the rule off`,
2998
- subject: `${config.relative} \u2192 ${id}`,
2999
- verdict: "FAIL"
3000
- });
3001
- }
3002
- }
3003
- if (missing.length > 0) return missing;
3004
- return [
3005
- {
3006
- check: "loaded",
3007
- message: `${gated} rules require options, ${gated} carry them`,
3008
- subject: config.relative,
3009
- verdict: "OK"
3010
- }
3011
- ];
3012
- };
3013
-
3014
3283
  // src/seams.ts
3015
- import { existsSync as existsSync13, globSync, readFileSync as readFileSync16 } from "fs";
3016
- import { join as join17 } from "path";
3017
- var CONFIG = "geonosis.json";
3018
- var finding12 = findingMaker("seams");
3019
- var NO_SEAMS = `${CONFIG} declares no adoption.seams \u2014 nothing here says which files hold a floor's shape, so no bump can measure what it deleted; declare them when a floor is adopted`;
3020
- var declaredSeams = (root) => {
3021
- const at = join17(root, CONFIG);
3022
- if (!existsSync13(at)) return void 0;
3023
- let parsed;
3024
- try {
3025
- parsed = JSON.parse(readFileSync16(at, "utf8"));
3026
- } catch {
3027
- return void 0;
3028
- }
3029
- const seams = parsed.adoption?.seams;
3284
+ import { globSync } from "fs";
3285
+ var finding13 = findingMaker("seams");
3286
+ var NO_SEAMS = `${GEONOSIS_FILE} declares no adoption.seams \u2014 nothing here says which files hold a floor's shape, so no bump can measure what it deleted; declare them when a floor is adopted`;
3287
+ var declaredSeams = (config) => {
3288
+ const seams = config.adoption?.seams;
3030
3289
  return Array.isArray(seams) ? seams.filter((one) => typeof one === "string") : void 0;
3031
3290
  };
3032
3291
  var namesAFile = (root, glob) => globSync(glob, {
@@ -3034,25 +3293,32 @@ var namesAFile = (root, glob) => globSync(glob, {
3034
3293
  exclude: (name) => name === "node_modules" || name.startsWith("node_modules/")
3035
3294
  }).length > 0;
3036
3295
  var checkSeams = ({ root }) => {
3037
- const seams = declaredSeams(root);
3038
- if (seams === void 0 || seams.length === 0) return [finding12(CONFIG, "SKIP", NO_SEAMS)];
3296
+ const read = readGeonosisFile(root);
3297
+ if (read.kind === "unreadable") return [unreadableGeonosis("seams", read.error)];
3298
+ const seams = read.kind === "absent" ? void 0 : declaredSeams(read.config);
3299
+ if (seams === void 0 || seams.length === 0) return [finding13(GEONOSIS_FILE, "SKIP", NO_SEAMS)];
3039
3300
  const dead = seams.filter((glob) => !namesAFile(root, glob)).toSorted();
3040
3301
  if (dead.length === 0) {
3041
3302
  return [
3042
- finding12(CONFIG, "OK", `adoption.seams: ${seams.length} seam glob(s), every one names a file`)
3303
+ finding13(
3304
+ GEONOSIS_FILE,
3305
+ "OK",
3306
+ `adoption.seams: ${seams.length} seam glob(s), every one names a file`
3307
+ )
3043
3308
  ];
3044
3309
  }
3045
3310
  return dead.map(
3046
- (glob) => finding12(
3311
+ (glob) => finding13(
3047
3312
  glob,
3048
3313
  "WARN",
3049
- `names no file in this tree, so the seam measures nothing and every bump reads 0 deleted, 0 added over it. Next: take it out of adoption.seams in ${CONFIG}, or name the file that replaced it`
3314
+ `names no file in this tree, so the seam measures nothing and every bump reads 0 deleted, 0 added over it. Next: take it out of adoption.seams in ${GEONOSIS_FILE}, or name the file that replaced it`
3050
3315
  )
3051
3316
  );
3052
3317
  };
3053
3318
 
3054
3319
  // src/doctor.ts
3055
3320
  var exercisedOf = ({
3321
+ apparatus,
3056
3322
  configs,
3057
3323
  oxlint,
3058
3324
  repoCorpus,
@@ -3088,8 +3354,14 @@ var exercisedOf = ({
3088
3354
  root
3089
3355
  }),
3090
3356
  // #159: the rules whose engine this tree has not got. Beside the reach line rather than
3091
- // inside it — one is about the corpus, the other about this repo's own manifests.
3092
- checkEngines({ config, entry, workspaces })
3357
+ // inside it — one is about the corpus, the other about this repo's own manifests. A
3358
+ // config inside a corpus is evidence, not a product, and is not asked (row 585).
3359
+ checkEngines({
3360
+ config,
3361
+ entry,
3362
+ evidence: [corpus, ...repoCorpus === void 0 ? [] : [repoCorpus], ...apparatus],
3363
+ workspaces
3364
+ })
3093
3365
  ]);
3094
3366
  return [reach, ...engines, ...checkPathGrants({ config, grants, root })];
3095
3367
  })
@@ -3156,22 +3428,15 @@ var runDoctor = async ({
3156
3428
  ...await requiredOptionsOf(configs, root)
3157
3429
  ]
3158
3430
  ],
3159
- [
3160
- "group",
3161
- () => {
3162
- const declared = declaredGroupsOf(root);
3163
- return checkGroup({
3164
- ...declared === void 0 ? {} : { groups: declared },
3165
- root,
3166
- workspaces
3167
- });
3168
- }
3169
- ],
3431
+ ["group", () => checkGroup({ root, workspaces })],
3170
3432
  [
3171
3433
  "exercised",
3172
3434
  () => {
3435
+ const read = readGeonosisFile(root);
3436
+ if (read.kind === "unreadable") return [unreadableGeonosis("exercised", read.error)];
3173
3437
  const repoCorpus = repoCorpusOf(root);
3174
3438
  return exercisedOf({
3439
+ apparatus: declaredApparatusOf(root),
3175
3440
  configs,
3176
3441
  oxlint: oxlint ?? resolveOxlint(root),
3177
3442
  ...repoCorpus === void 0 ? {} : { repoCorpus },
@@ -3186,9 +3451,10 @@ var runDoctor = async ({
3186
3451
  ["observability", () => checkObservability({ now: Date.now(), root })],
3187
3452
  ["drift", () => checkDrift({ root, workspaces })],
3188
3453
  ["deployed", () => checkDeployed({ root })],
3189
- ["rails", () => checkRails({ root, userSettings: join18(home, USER_SETTINGS) })],
3454
+ ["rails", () => checkRails({ root, userSettings: join19(home, USER_SETTINGS) })],
3190
3455
  ["exams", () => checkExams({ root, workspaces })],
3191
- ["seams", () => checkSeams({ root })]
3456
+ ["seams", () => checkSeams({ root })],
3457
+ ["formatter", () => checkFormatter({ root })]
3192
3458
  ];
3193
3459
  const collected = [];
3194
3460
  const ran = [];
@@ -3217,6 +3483,7 @@ var ABOUT = {
3217
3483
  exams: "every adopted floor has a test file that runs its exam",
3218
3484
  seams: "the seam globs adoption.seams declares each name a file",
3219
3485
  exercised: "every enabled rule fires on at least one corpus file",
3486
+ formatter: "the formatter leaves the kit\u2019s installed rule files alone",
3220
3487
  group: "the packages published on one version resolve to one version",
3221
3488
  loaded: "the plugin oxlint would load is the one the manifest pins",
3222
3489
  observability: "an exporter is configured, reachable, and something arrived through it lately",
@@ -3249,6 +3516,12 @@ var formatJson = (report) => `${JSON.stringify(report, null, 2)}
3249
3516
  var ranOf = (findings) => new Set(findings.map((one) => one.check)).size;
3250
3517
 
3251
3518
  export {
3519
+ CHECKS,
3520
+ DoctorError,
3521
+ GEONOSIS_FILE,
3522
+ APPARATUS_KEY,
3523
+ declaredApparatusOf,
3524
+ evidenceHolding,
3252
3525
  CONFIG_FILE,
3253
3526
  MANIFEST_FILE,
3254
3527
  RATCHET_FILE,
@@ -3257,15 +3530,11 @@ export {
3257
3530
  discoverConfigs,
3258
3531
  discoverWorkspaces,
3259
3532
  readRatchet,
3260
- CHECKS,
3261
- DoctorError,
3262
3533
  defaultRef,
3263
3534
  checkBaseline,
3264
3535
  DEPLOYED_FILE,
3265
3536
  NOT_WRITTEN,
3266
3537
  checkDeployed,
3267
- GEONOSIS_FILE,
3268
- repoCorpusOf,
3269
3538
  resolveFrom,
3270
3539
  packageDirOf,
3271
3540
  pluginVersionOf,
@@ -3279,6 +3548,7 @@ export {
3279
3548
  FLOOR_PACKAGES,
3280
3549
  READERS,
3281
3550
  checkDrift,
3551
+ missingOptionOf,
3282
3552
  enabledRulesOf,
3283
3553
  checkExercised,
3284
3554
  ENVELOPES_DIR,
@@ -3287,13 +3557,17 @@ export {
3287
3557
  checkExams,
3288
3558
  INSTALLED_PLUGINS,
3289
3559
  PLUGIN_NAME,
3560
+ installedPlugins,
3290
3561
  installedPluginVersions,
3562
+ loadedAt,
3291
3563
  checkClaudePlugin,
3292
3564
  SCOPE2 as SCOPE,
3293
3565
  satisfies,
3294
3566
  declaredFor,
3295
3567
  checkLoaded,
3296
3568
  checkObservability,
3569
+ repoCorpusOf,
3570
+ checkRequiredOptions,
3297
3571
  checkRunner,
3298
3572
  runDoctor,
3299
3573
  formatDoctor,