@geonosis/doctor 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @geonosis/doctor
2
2
 
3
+ ## 2.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - @geonosis/lint-parity@2.5.2
8
+
9
+ ## 2.5.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 7f24da6: The doctor's resolution is bounded to the root it was handed (#373, #386): `resolveFrom` used to
14
+ walk `node_modules` upward with no stop, so a tree nested under a directory that carries this kit's
15
+ own `node_modules` (the smoke's run copy, a monorepo of repos, a CI runner's shared cache dir) read
16
+ an ancestor's copy of `@geonosis/<x>` as installed here, at whatever version the ancestor has. The
17
+ walk now stops at the root; a hit above it is reported by the `loaded` and `group` checks as "not
18
+ installed here — found above the root at `<path>`, which this tree cannot see when deployed" — a
19
+ named FAIL rather than a silently wrong version.
20
+ - @geonosis/lint-parity@2.5.1
21
+
3
22
  ## 2.5.0
4
23
 
5
24
  ### Patch Changes
@@ -9,11 +9,19 @@ import { dirname, join } from 'node:path'
9
9
  import { fileURLToPath } from 'node:url'
10
10
 
11
11
  const here = dirname(fileURLToPath(import.meta.url))
12
- const newest = (dir) =>
12
+ // tsup never reads a test file, so one counting as source made every bin refuse a build that had
13
+ // not gone stale (#399).
14
+ const NOT_SOURCE = (name) =>
15
+ name.endsWith('.test.ts') ||
16
+ name.endsWith('.test.tsx') ||
17
+ name === '__tests__' ||
18
+ name === '__fixtures__'
19
+ const newest = (dir, skip) =>
13
20
  existsSync(dir)
14
21
  ? readdirSync(dir, { withFileTypes: true }).reduce((most, entry) => {
22
+ if (skip !== undefined && skip(entry.name)) return most
15
23
  const at = join(dir, entry.name)
16
- return Math.max(most, entry.isDirectory() ? newest(at) : statSync(at).mtimeMs)
24
+ return Math.max(most, entry.isDirectory() ? newest(at, skip) : statSync(at).mtimeMs)
17
25
  }, 0)
18
26
  : 0
19
27
  const src = join(here, '..', 'src')
@@ -30,7 +38,7 @@ if (existsSync(src)) {
30
38
  )
31
39
  process.exit(2)
32
40
  }
33
- if (newest(src) > built) {
41
+ if (newest(src, NOT_SOURCE) > built) {
34
42
  process.stderr.write(
35
43
  'geonosis-doctor: dist is older than src — run pnpm build before trusting this bin.\n',
36
44
  )
@@ -4,7 +4,13 @@ import { join, relative as relativeTo, sep } from "path";
4
4
  var CONFIG_FILE = ".oxlintrc.json";
5
5
  var MANIFEST_FILE = "package.json";
6
6
  var RATCHET_FILE = "geonosis.ratchet.json";
7
- var NEVER_WALKED = /* @__PURE__ */ new Set(["build", "coverage", "dist", "node_modules", "storybook-static"]);
7
+ var NEVER_WALKED = /* @__PURE__ */ new Set([
8
+ "build",
9
+ "coverage",
10
+ "dist",
11
+ "node_modules",
12
+ "storybook-static"
13
+ ]);
8
14
  var skipped = (name) => name.startsWith(".") || NEVER_WALKED.has(name);
9
15
  var relativePath = (root, path) => relativeTo(root, path).split(sep).join("/");
10
16
  var walk = (dir, onFile) => {
@@ -73,10 +79,11 @@ var discoverWorkspaces = (root) => {
73
79
  return found.toSorted((a, b) => a.relative.localeCompare(b.relative));
74
80
  };
75
81
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
76
- var testFilesUnder = (dir) => {
82
+ var testFilesUnder = (dir) => filesUnder(dir, (name) => TEST_FILE.test(name));
83
+ var filesUnder = (dir, match) => {
77
84
  const found = [];
78
85
  walk(dir, (path, name) => {
79
- if (TEST_FILE.test(name)) found.push(path);
86
+ if (match(name)) found.push(path);
80
87
  });
81
88
  return found;
82
89
  };
@@ -94,6 +101,33 @@ var readRatchet = (root) => {
94
101
  };
95
102
  };
96
103
 
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
+ ];
118
+ var findingMaker = (check) => (subject, verdict, message) => ({
119
+ check,
120
+ message,
121
+ subject,
122
+ verdict
123
+ });
124
+ var DoctorError = class extends Error {
125
+ constructor(message) {
126
+ super(message);
127
+ this.name = "DoctorError";
128
+ }
129
+ };
130
+
97
131
  // src/baseline.ts
98
132
  import { spawnSync } from "child_process";
99
133
  var DEFAULT_REF = "origin/main";
@@ -117,12 +151,7 @@ var numbersAt = (root, ref, path) => {
117
151
  }
118
152
  return numbers;
119
153
  };
120
- var finding = (subject, verdict, message) => ({
121
- check: "baseline",
122
- message,
123
- subject,
124
- verdict
125
- });
154
+ var finding = findingMaker("baseline");
126
155
  var checkBaseline = ({ ref, root }) => {
127
156
  const ratchet = readRatchet(root);
128
157
  if (ratchet === void 0) {
@@ -172,12 +201,8 @@ var DEPLOYED_FILE = ".geonosis/deployed.json";
172
201
  var NOT_WRITTEN = "no .geonosis/deployed.json \u2014 it is written by the pipeline after promote, and its absence is not a pass";
173
202
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
174
203
  var listOf = (found) => Array.isArray(found) ? found.filter((one) => typeof one === "string") : [];
175
- var finding2 = (verdict, subject, message) => ({
176
- check: "deployed",
177
- message,
178
- subject,
179
- verdict
180
- });
204
+ var make = findingMaker("deployed");
205
+ var finding2 = (verdict, subject, message) => make(subject, verdict, message);
181
206
  var parseJsonc = (source) => {
182
207
  let out = "";
183
208
  for (let index = 0; index < source.length; index += 1) {
@@ -339,27 +364,6 @@ var repoCorpusOf = (root) => {
339
364
  }
340
365
  };
341
366
 
342
- // src/types.ts
343
- var CHECKS = [
344
- "loaded",
345
- "group",
346
- "exercised",
347
- "baseline",
348
- "runner",
349
- "envelope",
350
- "drift",
351
- "observability",
352
- "deployed",
353
- "rails",
354
- "exams"
355
- ];
356
- var DoctorError = class extends Error {
357
- constructor(message) {
358
- super(message);
359
- this.name = "DoctorError";
360
- }
361
- };
362
-
363
367
  // src/resolve.ts
364
368
  import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
365
369
  import { createRequire } from "module";
@@ -369,22 +373,51 @@ var packageNameOf = (specifier) => {
369
373
  const parts2 = specifier.split("/");
370
374
  return specifier.startsWith("@") ? parts2.slice(0, 2).join("/") : parts2[0] ?? specifier;
371
375
  };
372
- var resolveFrom = (dir, specifier) => {
376
+ var AboveRootError = class extends DoctorError {
377
+ foundAt;
378
+ constructor(message, foundAt) {
379
+ super(message);
380
+ this.name = "AboveRootError";
381
+ this.foundAt = foundAt;
382
+ }
383
+ };
384
+ var aboveRootMessage = (specifier, foundAt) => `${specifier} is not installed here \u2014 found above the root at ${foundAt}, which this tree cannot see when deployed`;
385
+ var aboveRoot = (root, name) => {
386
+ let at = dirname(root);
387
+ for (; ; ) {
388
+ const dir = join4(at, "node_modules", name);
389
+ if (existsSync3(join4(dir, "package.json"))) return dir;
390
+ const parent = dirname(at);
391
+ if (parent === at) return void 0;
392
+ at = parent;
393
+ }
394
+ };
395
+ var resolveFrom = (dir, specifier, root) => {
373
396
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
374
397
  return createRequire(join4(dir, "noop.js")).resolve(specifier);
375
398
  }
376
399
  const name = packageNameOf(specifier);
377
400
  let at = dir;
401
+ let reachedRoot = false;
378
402
  for (; ; ) {
379
403
  if (existsSync3(join4(at, "node_modules", name, "package.json"))) {
380
404
  return createRequire(join4(at, "noop.js")).resolve(specifier);
381
405
  }
406
+ if (at === root) {
407
+ reachedRoot = true;
408
+ break;
409
+ }
382
410
  const parent = dirname(at);
383
411
  if (parent === at) break;
384
412
  at = parent;
385
413
  }
414
+ if (reachedRoot) {
415
+ const foundAt = aboveRoot(root, name);
416
+ if (foundAt !== void 0)
417
+ throw new AboveRootError(aboveRootMessage(specifier, foundAt), foundAt);
418
+ }
386
419
  throw new DoctorError(
387
- `${specifier} does not resolve from ${dir} \u2014 no node_modules on the way up carries ${name}`
420
+ `${specifier} does not resolve from ${dir} \u2014 no node_modules on the way up to ${root} carries ${name}`
388
421
  );
389
422
  };
390
423
  var packageDirOf = (entry, name) => {
@@ -460,7 +493,7 @@ var presumptionsOf = async (entry) => {
460
493
  )
461
494
  };
462
495
  };
463
- var corpusOfPlugin = (from, specifier) => join4(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
496
+ var corpusOfPlugin = (from, specifier, root) => join4(packageDirOf(resolveFrom(from, specifier, root), specifier), "corpus");
464
497
  var real = (path) => {
465
498
  try {
466
499
  return realpathSync(path);
@@ -558,12 +591,12 @@ var declaredGroupsOf = (root) => {
558
591
  });
559
592
  };
560
593
  var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
561
- var versionOf = (dir, name) => {
594
+ var versionOf = (dir, name, root) => {
562
595
  let entry;
563
596
  try {
564
- entry = resolveFrom(dir, name);
565
- } catch {
566
- return { kind: "absent" };
597
+ entry = resolveFrom(dir, name, root);
598
+ } catch (error) {
599
+ return error instanceof AboveRootError ? { foundAt: error.foundAt, kind: "aboveRoot" } : { kind: "absent" };
567
600
  }
568
601
  try {
569
602
  const manifest = JSON.parse(
@@ -574,12 +607,7 @@ var versionOf = (dir, name) => {
574
607
  return { kind: "unreadable", why: String(error.message).split("\n")[0] ?? "" };
575
608
  }
576
609
  };
577
- var finding3 = (subject, verdict, message) => ({
578
- check: "group",
579
- message,
580
- subject,
581
- verdict
582
- });
610
+ var finding3 = findingMaker("group");
583
611
  var said = (versions) => [...versions.entries()].map(([version, from]) => `${version} (${from.join(", ")})`).join("; ");
584
612
  var repoWide = (name, found) => {
585
613
  const versions = /* @__PURE__ */ new Map();
@@ -601,10 +629,10 @@ var perWorkspace = (group, at, found) => {
601
629
  );
602
630
  };
603
631
  var DOORS = ["@geonosis/cli", "geonosis"];
604
- var manifestOf = (dir, name) => {
632
+ var manifestOf = (root, name) => {
605
633
  try {
606
634
  return JSON.parse(
607
- readFileSync6(join6(packageDirOf(resolveFrom(dir, name), name), "package.json"), "utf8")
635
+ readFileSync6(join6(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
608
636
  );
609
637
  } catch {
610
638
  return void 0;
@@ -692,7 +720,7 @@ var checkGroup = ({
692
720
  const at = labelOf(workspace);
693
721
  const here = [];
694
722
  for (const name of group.packages) {
695
- const reading = versionOf(workspace.dir, name);
723
+ const reading = versionOf(workspace.dir, name, root);
696
724
  if (reading.kind === "read") here.push({ at, name, version: reading.version });
697
725
  if (reading.kind === "unreadable") {
698
726
  findings.push(
@@ -703,6 +731,15 @@ var checkGroup = ({
703
731
  )
704
732
  );
705
733
  }
734
+ if (reading.kind === "aboveRoot") {
735
+ findings.push(
736
+ finding3(
737
+ `${at} \u2192 ${name}`,
738
+ "FAIL",
739
+ `${name} is not installed here \u2014 found above the root at ${reading.foundAt}, which this tree cannot see when deployed`
740
+ )
741
+ );
742
+ }
706
743
  }
707
744
  const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
708
745
  if (split2 !== void 0) findings.push(split2);
@@ -816,33 +853,7 @@ var GEONOSIS = "geonosis.json";
816
853
  var LAW = "CLAUDE.md";
817
854
  var CEILING = 200;
818
855
  var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
819
- var finding4 = (subject, verdict, message) => ({
820
- check: "drift",
821
- message,
822
- subject,
823
- verdict
824
- });
825
- var NEVER_WALKED2 = /* @__PURE__ */ new Set(["build", "coverage", "dist", "node_modules", "storybook-static"]);
826
- var filesUnder = (dir, match) => {
827
- const found = [];
828
- const walk2 = (at) => {
829
- let entries;
830
- try {
831
- entries = readdirSync3(at, { withFileTypes: true });
832
- } catch {
833
- return;
834
- }
835
- for (const entry of entries) {
836
- if (entry.isDirectory()) {
837
- if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join7(at, entry.name));
838
- continue;
839
- }
840
- if (entry.isFile() && match(entry.name)) found.push(join7(at, entry.name));
841
- }
842
- };
843
- walk2(dir);
844
- return found;
845
- };
856
+ var finding4 = findingMaker("drift");
846
857
  var ci = (root) => {
847
858
  const dir = join7(root, WORKFLOWS);
848
859
  if (!existsSync6(dir)) {
@@ -1194,7 +1205,7 @@ var sourceDirsIn = (dir) => {
1194
1205
  return [];
1195
1206
  }
1196
1207
  return entries.filter(
1197
- (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name) && filesUnder(join7(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
1208
+ (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name) && filesUnder(join7(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
1198
1209
  ).map((entry) => entry.name);
1199
1210
  };
1200
1211
  var lintScopes = (root, workspaces) => {
@@ -1299,7 +1310,10 @@ var doorBringing = (root, name, declared) => DOORS.filter((door) => declared.has
1299
1310
  var dependenciesOf = (root, name) => {
1300
1311
  try {
1301
1312
  return JSON.parse(
1302
- readFileSync7(join7(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
1313
+ readFileSync7(
1314
+ join7(packageDirOf(resolveFrom(root, name, root), name), "package.json"),
1315
+ "utf8"
1316
+ )
1303
1317
  ).dependencies ?? {};
1304
1318
  } catch {
1305
1319
  return {};
@@ -1307,7 +1321,7 @@ var dependenciesOf = (root, name) => {
1307
1321
  };
1308
1322
  var resolves = (root, name) => {
1309
1323
  try {
1310
- resolveFrom(root, name);
1324
+ resolveFrom(root, name, root);
1311
1325
  return true;
1312
1326
  } catch {
1313
1327
  return false;
@@ -1563,16 +1577,15 @@ import {
1563
1577
  import { tmpdir } from "os";
1564
1578
  import { dirname as dirname2, join as join8, resolve } from "path";
1565
1579
  import { corpusOf, readManifest } from "@geonosis/lint-parity";
1580
+
1581
+ // src/rule-level.ts
1566
1582
  var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
1567
1583
  var severityOf = (level) => Array.isArray(level) ? level[0] : level;
1584
+
1585
+ // src/exercised.ts
1568
1586
  var enabledRulesOf = (rules, plugin) => Object.keys(rules).filter((id) => id.startsWith(`${plugin}/`) && !OFF.has(severityOf(rules[id]))).toSorted();
1569
1587
  var passes = (findings) => !findings.some((one) => one.verdict === "FAIL" || one.verdict === "UNJUDGED");
1570
- var finding5 = (subject, verdict, message) => ({
1571
- check: "exercised",
1572
- message,
1573
- subject,
1574
- verdict
1575
- });
1588
+ var finding5 = findingMaker("exercised");
1576
1589
  var LIMIT = 800;
1577
1590
  var refusal = (error) => {
1578
1591
  const said2 = String(error.message).trim();
@@ -1667,6 +1680,7 @@ var throughProbes = ({
1667
1680
  corpus,
1668
1681
  oxlint,
1669
1682
  probes,
1683
+ root,
1670
1684
  silent
1671
1685
  }) => {
1672
1686
  const refused = /* @__PURE__ */ new Map();
@@ -1725,7 +1739,7 @@ var throughProbes = ({
1725
1739
  const probeConfig = JSON.parse(readFileSync8(config.path, "utf8"));
1726
1740
  const configDir = dirname2(config.path);
1727
1741
  probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
1728
- (spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec), spec)
1742
+ (spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec, root), spec)
1729
1743
  );
1730
1744
  const at = join8(here, ".oxlintrc-geonosis-probe.json");
1731
1745
  writeFileSync(at, JSON.stringify(probeConfig, null, 2));
@@ -1822,7 +1836,7 @@ var checkExercised = async ({
1822
1836
  let probed = { fired: /* @__PURE__ */ new Set(), refused: /* @__PURE__ */ new Map() };
1823
1837
  if (declared.length > 0) {
1824
1838
  try {
1825
- probed = throughProbes({ config, corpus, oxlint, probes, silent: declared });
1839
+ probed = throughProbes({ config, corpus, oxlint, probes, root, silent: declared });
1826
1840
  } catch (error) {
1827
1841
  return said2("FAIL", `the declared probes could not be run \u2014 ${refusal(error)}`);
1828
1842
  }
@@ -1863,12 +1877,8 @@ var THIS_TOOL = "doctor";
1863
1877
  var OWN = `${THIS_TOOL}.json`;
1864
1878
  var NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one (this run\u2019s own doctor.json is not one of them: it is written after these checks, and balanced at write time)";
1865
1879
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1866
- var finding6 = (verdict, subject, message) => ({
1867
- check: "envelope",
1868
- message,
1869
- subject,
1870
- verdict
1871
- });
1880
+ var make2 = findingMaker("envelope");
1881
+ var finding6 = (verdict, subject, message) => make2(subject, verdict, message);
1872
1882
  var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
1873
1883
  var NEXT = "Next: re-run the tool that wrote it and read the numbers it prints \u2014 a run that has lost count of its own inputs is a bug in that tool, not in this tree";
1874
1884
  var judge = (subject, name, parsed) => {
@@ -1939,12 +1949,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
1939
1949
  import { dirname as dirname3, join as join10 } from "path";
1940
1950
  var SCOPE = "@geonosis/";
1941
1951
  var EXAM = /(?:^|[^$\w])([$\w]*Conformance)\b/g;
1942
- var finding7 = (subject, verdict, message) => ({
1943
- check: "exams",
1944
- message,
1945
- subject,
1946
- verdict
1947
- });
1952
+ var finding7 = findingMaker("exams");
1948
1953
  var declaredBy = (workspaces) => {
1949
1954
  const found = /* @__PURE__ */ new Map();
1950
1955
  const provided = new Set(
@@ -1984,18 +1989,19 @@ var declarationsOf = (dir) => {
1984
1989
  }
1985
1990
  }).join("\n");
1986
1991
  };
1987
- var installedAt = (from, name) => {
1992
+ var installedAt = (from, name, root) => {
1988
1993
  let at = from;
1989
1994
  for (; ; ) {
1990
1995
  const dir = join10(at, "node_modules", name);
1991
1996
  if (existsSync9(join10(dir, "package.json"))) return dir;
1997
+ if (at === root) return void 0;
1992
1998
  const up = dirname3(at);
1993
1999
  if (up === at) return void 0;
1994
2000
  at = up;
1995
2001
  }
1996
2002
  };
1997
- var examsShippedBy = (from, name) => {
1998
- const dir = installedAt(from, name);
2003
+ var examsShippedBy = (from, name, root) => {
2004
+ const dir = installedAt(from, name, root);
1999
2005
  if (dir === void 0) return [];
2000
2006
  return [...new Set([...declarationsOf(dir).matchAll(EXAM)].map((one) => one[1]))];
2001
2007
  };
@@ -2006,7 +2012,7 @@ var checkExams = ({
2006
2012
  const tests = testFilesUnder(root).map((path) => ({ path, source: readFileSync10(path, "utf8") }));
2007
2013
  const found = [];
2008
2014
  for (const { from, name } of declaredBy(workspaces)) {
2009
- const exams = examsShippedBy(from, name);
2015
+ const exams = examsShippedBy(from, name, root);
2010
2016
  if (exams.length === 0) continue;
2011
2017
  for (const exam of exams) {
2012
2018
  const importing = tests.find((one) => one.source.includes(name) && one.source.includes(exam));
@@ -2162,12 +2168,7 @@ var declaredFor = ({
2162
2168
  }
2163
2169
  return void 0;
2164
2170
  };
2165
- var finding8 = (subject, verdict, message) => ({
2166
- check: "loaded",
2167
- message,
2168
- subject,
2169
- verdict
2170
- });
2171
+ var finding8 = findingMaker("loaded");
2171
2172
  var firstLine = (error) => String(error.message).split("\n")[0] ?? "";
2172
2173
  var versionAt = async (entry, specifier, root) => ({
2173
2174
  at: relativeToRoot(root, packageDirOf(entry, specifier)),
@@ -2182,9 +2183,9 @@ var oneConfig = async ({
2182
2183
  const said2 = (verdict, message) => finding8(config.relative, verdict, `${specifier}: ${message}`);
2183
2184
  let loaded;
2184
2185
  try {
2185
- loaded = await versionAt(resolveFrom(config.dir, specifier), specifier, root);
2186
+ loaded = await versionAt(resolveFrom(config.dir, specifier, root), specifier, root);
2186
2187
  } catch (error) {
2187
- return said2(
2188
+ return error instanceof AboveRootError ? said2("FAIL", error.message) : said2(
2188
2189
  "FAIL",
2189
2190
  `could not resolve it from this config's directory, and oxlint resolves it from exactly there \u2014 ${firstLine(error)}`
2190
2191
  );
@@ -2218,7 +2219,7 @@ var copiesOf = async ({
2218
2219
  for (const workspace of workspaces) {
2219
2220
  let entry;
2220
2221
  try {
2221
- entry = resolveFrom(workspace.dir, specifier);
2222
+ entry = resolveFrom(workspace.dir, specifier, root);
2222
2223
  } catch {
2223
2224
  continue;
2224
2225
  }
@@ -2244,7 +2245,7 @@ var shipperOf = (root, bin) => {
2244
2245
  const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
2245
2246
  try {
2246
2247
  const manifest = JSON.parse(
2247
- readFileSync12(join12(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
2248
+ readFileSync12(join12(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
2248
2249
  );
2249
2250
  const declared = manifest.bin;
2250
2251
  return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
@@ -2342,7 +2343,7 @@ var checkLoaded = async ({
2342
2343
  var trainVersion = async (root, specifiers) => {
2343
2344
  for (const specifier of specifiers.toSorted()) {
2344
2345
  try {
2345
- return await pluginVersionOf(resolveFrom(root, specifier));
2346
+ return await pluginVersionOf(resolveFrom(root, specifier, root));
2346
2347
  } catch {
2347
2348
  continue;
2348
2349
  }
@@ -2357,12 +2358,8 @@ var GEONOSIS_FILE2 = "geonosis.json";
2357
2358
  var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
2358
2359
  var DEFAULT_MAX_AGE_SECONDS = 3600;
2359
2360
  var HEAD_TIMEOUT_MS = 3e3;
2360
- var finding9 = (verdict, subject, message) => ({
2361
- check: "observability",
2362
- message,
2363
- subject,
2364
- verdict
2365
- });
2361
+ var make3 = findingMaker("observability");
2362
+ var finding9 = (verdict, subject, message) => make3(subject, verdict, message);
2366
2363
  var readGeonosis2 = (root) => {
2367
2364
  let text;
2368
2365
  try {
@@ -2537,12 +2534,7 @@ var wrapperNamedBy = (script, dir) => {
2537
2534
  var asCommand = (source) => source.replaceAll(/['"`,[\]()]/gu, " ");
2538
2535
  var ASKS_FOR_A_REPORT = /--outputFile|--reporter[= ]\S*json/i;
2539
2536
  var PARSES_A_REPORT = /JSON\.parse|numFailedTests/;
2540
- var finding10 = (subject, verdict, message) => ({
2541
- check: "runner",
2542
- message,
2543
- subject,
2544
- verdict
2545
- });
2537
+ var finding10 = findingMaker("runner");
2546
2538
  var stringOf = (value) => typeof value === "string" ? value : "";
2547
2539
  var namesOf = (workspace) => [workspace.relative, stringOf(workspace.manifest.name)].filter((one) => one !== "");
2548
2540
  var covers = (command, workspace, workspaces) => {
@@ -2735,9 +2727,7 @@ var checkEngines = async ({
2735
2727
  // src/path-grants.ts
2736
2728
  import { readdirSync as readdirSync6 } from "fs";
2737
2729
  import { join as join15 } from "path";
2738
- var OFF2 = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
2739
- var severityOf2 = (level) => Array.isArray(level) ? level[0] : level;
2740
- var NEVER_WALKED3 = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
2730
+ var NEVER_WALKED2 = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
2741
2731
  var LINTABLE2 = /\.[cm]?[jt]sx?$/;
2742
2732
  var filesUnder2 = (root, at = "") => {
2743
2733
  let entries;
@@ -2749,7 +2739,7 @@ var filesUnder2 = (root, at = "") => {
2749
2739
  return entries.flatMap((entry) => {
2750
2740
  const here = at === "" ? entry.name : `${at}/${entry.name}`;
2751
2741
  if (entry.isDirectory()) {
2752
- return NEVER_WALKED3.has(entry.name) ? [] : filesUnder2(root, here);
2742
+ return NEVER_WALKED2.has(entry.name) ? [] : filesUnder2(root, here);
2753
2743
  }
2754
2744
  return LINTABLE2.test(entry.name) ? [here] : [];
2755
2745
  });
@@ -2779,7 +2769,7 @@ var checkPathGrants = ({
2779
2769
  const found = [];
2780
2770
  for (const [id, grant] of declared) {
2781
2771
  for (const layer of layersOf(config, id)) {
2782
- if (OFF2.has(severityOf2(layer.level))) continue;
2772
+ if (OFF.has(severityOf(layer.level))) continue;
2783
2773
  const options = bagOf(layer.level);
2784
2774
  const patterns = stringsOf2(options[grant.paths]);
2785
2775
  if (patterns.length === 0) continue;
@@ -2805,12 +2795,7 @@ var PROJECT = ".claude/settings.json";
2805
2795
  var LOCAL = ".claude/settings.local.json";
2806
2796
  var RUN_RECORD = ".geonosis/rails-run.json";
2807
2797
  var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
2808
- var finding11 = (subject, verdict, message) => ({
2809
- check: "rails",
2810
- message,
2811
- subject,
2812
- verdict
2813
- });
2798
+ var finding11 = findingMaker("rails");
2814
2799
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2815
2800
  var networkIn = (parsed) => {
2816
2801
  if (!isRecord3(parsed)) return void 0;
@@ -3009,8 +2994,8 @@ var exercisedOf = ({
3009
2994
  let corpus;
3010
2995
  let entry;
3011
2996
  try {
3012
- corpus = corpusOfPlugin(config.dir, specifier);
3013
- entry = resolveFrom(config.dir, specifier);
2997
+ corpus = corpusOfPlugin(config.dir, specifier, root);
2998
+ entry = resolveFrom(config.dir, specifier, root);
3014
2999
  } catch (error) {
3015
3000
  return [
3016
3001
  {
@@ -3040,13 +3025,13 @@ var exercisedOf = ({
3040
3025
  })
3041
3026
  )
3042
3027
  );
3043
- var requiredOptionsOf = async (configs) => {
3028
+ var requiredOptionsOf = async (configs, root) => {
3044
3029
  const found = await Promise.all(
3045
3030
  configs.flatMap(
3046
3031
  (config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE2)).map(async (specifier) => {
3047
3032
  let entry;
3048
3033
  try {
3049
- entry = resolveFrom(config.dir, specifier);
3034
+ entry = resolveFrom(config.dir, specifier, root);
3050
3035
  } catch {
3051
3036
  return [];
3052
3037
  }
@@ -3098,7 +3083,7 @@ var runDoctor = async ({
3098
3083
  "loaded",
3099
3084
  async () => [
3100
3085
  ...await checkLoaded({ configs, home, root, workspaces }),
3101
- ...await requiredOptionsOf(configs)
3086
+ ...await requiredOptionsOf(configs, root)
3102
3087
  ]
3103
3088
  ],
3104
3089
  [
@@ -3200,6 +3185,8 @@ export {
3200
3185
  discoverConfigs,
3201
3186
  discoverWorkspaces,
3202
3187
  readRatchet,
3188
+ CHECKS,
3189
+ DoctorError,
3203
3190
  defaultRef,
3204
3191
  checkBaseline,
3205
3192
  DEPLOYED_FILE,
@@ -3207,8 +3194,6 @@ export {
3207
3194
  checkDeployed,
3208
3195
  GEONOSIS_FILE,
3209
3196
  repoCorpusOf,
3210
- CHECKS,
3211
- DoctorError,
3212
3197
  resolveFrom,
3213
3198
  packageDirOf,
3214
3199
  pluginVersionOf,
@@ -4,7 +4,7 @@ import {
4
4
  formatDoctor,
5
5
  formatJson,
6
6
  runDoctor
7
- } from "./chunk-GXW7SYO2.js";
7
+ } from "./chunk-EZB5BG4L.js";
8
8
 
9
9
  // src/doctor-cli.ts
10
10
  import { fstatSync, statSync } from "fs";
package/dist/index.d.ts CHANGED
@@ -378,7 +378,12 @@ declare const repoCorpusOf: (root: string) => string | undefined;
378
378
  declare const formatDoctor: ({ counts, findings, ok, root }: DoctorReport) => string;
379
379
  declare const formatJson: (report: DoctorReport) => string;
380
380
 
381
- declare const resolveFrom: (dir: string, specifier: string) => string;
381
+ /**
382
+ * `root` is the directory the doctor was HANDED — the boundary a deployed tree actually has. A
383
+ * package on the way up from `dir` but past `root` is not this tree's to see; #373/#386 measured
384
+ * the kit's own workspace two directories above a consumer's checkout read exactly that way.
385
+ */
386
+ declare const resolveFrom: (dir: string, specifier: string, root: string) => string;
382
387
  /** The package a resolved entry point belongs to: up until a manifest claims the name. */
383
388
  declare const packageDirOf: (entry: string, name: string) => string;
384
389
  /**
@@ -388,7 +393,7 @@ declare const packageDirOf: (entry: string, name: string) => string;
388
393
  */
389
394
  declare const pluginVersionOf: (entry: string) => Promise<string>;
390
395
  /** Where a plugin keeps the corpus it is evidence by, if it ships one at all. */
391
- declare const corpusOfPlugin: (from: string, specifier: string) => string;
396
+ declare const corpusOfPlugin: (from: string, specifier: string, root: string) => string;
392
397
  /**
393
398
  * A resolved path, said relative to the root. Both sides are realpath'd first: a resolver returns
394
399
  * the real path, and on macOS a temp root under `/var/folders` is a symlink to `/private/var`, so
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  resolveFrom,
49
49
  runDoctor,
50
50
  satisfies
51
- } from "./chunk-GXW7SYO2.js";
51
+ } from "./chunk-EZB5BG4L.js";
52
52
  export {
53
53
  CHECKS,
54
54
  COMPOSITION_ROOT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/doctor",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "types": "./dist/index.d.ts",
5
5
  "description": "The adoption doctor — declared ≠ loaded, enabled ≠ exercised, a baseline that grew, a runner whose exit code is the only verdict.",
6
6
  "keywords": [
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@geonosis/lint-parity": "2.5.0"
39
+ "@geonosis/lint-parity": "2.5.2"
40
40
  },
41
41
  "devDependencies": {
42
- "@geonosis/ratchet": "2.5.0"
42
+ "@geonosis/ratchet": "2.5.2"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "oxlint": ">=1.77"