@geonosis/doctor 2.6.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @geonosis/doctor
2
2
 
3
+ ## 2.8.0
4
+
5
+ ### Patch Changes
6
+
7
+ - @geonosis/lint-parity@2.8.0
8
+
9
+ ## 2.7.0
10
+
11
+ ### Minor Changes
12
+
13
+ - d90986b: A twelfth question: `seams`. Every glob `adoption.seams` declares has to name a file in the tree
14
+ (never one under node_modules); one that names nothing is a WARN per glob saying to drop it or
15
+ name the file that replaced it, no `adoption.seams` at all is a SKIP by name, and a full set is
16
+ one OK line with the count. `geonosis update` measures a floor's adoption by exactly those files,
17
+ and during.day carried three dead globs of eight for a month while every bump report read
18
+ `0 deleted, 0 added` over them. `--only seams` runs it alone.
19
+
20
+ ### Patch Changes
21
+
22
+ - 7ee2097: `FLOOR_PACKAGES` names `@geonosis/buketi-ui`, the first package on the `kits/*` floor, so the doctor's
23
+ floor lines ask about it the way they ask about every other publishable floor.
24
+ - c01ea60: The `exercised` check knows a retired rule. A plugin may keep a rule's id resolvable after retiring
25
+ it — `meta.retired` — so a config that still names it parses; judged as a live rule it read as "not
26
+ exported" or "never fires", a FAIL against a tree whose only fault is a block the kit made stale.
27
+ The id is now set aside before the judging, and every verdict names it with the way out: it reports
28
+ nothing, goes at the next major, remove it from this config. A verdict that would have been OK or
29
+ SKIP is a WARN while any such id is still named.
30
+ - Updated dependencies [c13ade8]
31
+ - @geonosis/lint-parity@2.7.0
32
+
3
33
  ## 2.6.1
4
34
 
5
35
  ### Patch Changes
package/README.md CHANGED
@@ -387,3 +387,16 @@ consumer wrote. It depends on `@geonosis/lint-parity` for the reach engine and o
387
387
  the kit's own `layer-walls` config holds every one of those walls.
388
388
 
389
389
  Apache-2.0.
390
+
391
+ ### `seams` — a declared seam that names nothing
392
+
393
+ `adoption.seams` in `geonosis.json` is what `geonosis update` measures a floor's adoption by:
394
+ lines deleted against lines added in the files a consumer says hold a floor's shape. A glob whose
395
+ file was deleted and never undeclared measures nothing, and every bump report reads `0 deleted,
396
+ 0 added` over it — during.day carried three of eight that way for a month.
397
+
398
+ | Seen | Line |
399
+ | ----------------------------------------------- | --------------------------------------------------- |
400
+ | no `adoption.seams` | **SKIP**, naming the key |
401
+ | every glob names a file (never in node_modules) | **OK**, with the count |
402
+ | a glob names no file | **WARN** per glob, saying to drop or replace it |
@@ -34,13 +34,13 @@ if (existsSync(src)) {
34
34
  const built = newest(dist)
35
35
  if (built === 0) {
36
36
  process.stderr.write(
37
- 'geonosis-doctor: this tree has never been built — run pnpm build. No dist here at all, which is what a fresh clone or a linked git worktree starts with; nothing is stale.\n',
37
+ 'geonosis-doctor: this tree has never been built — run pnpm build. No dist here at all, which is what a fresh clone or a linked git worktree starts with; nothing is stale.\n'
38
38
  )
39
39
  process.exit(2)
40
40
  }
41
41
  if (newest(src, NOT_SOURCE) > built) {
42
42
  process.stderr.write(
43
- 'geonosis-doctor: dist is older than src — run pnpm build before trusting this bin.\n',
43
+ 'geonosis-doctor: dist is older than src — run pnpm build before trusting this bin.\n'
44
44
  )
45
45
  process.exit(2)
46
46
  }
@@ -113,7 +113,8 @@ var CHECKS = [
113
113
  "observability",
114
114
  "deployed",
115
115
  "rails",
116
- "exams"
116
+ "exams",
117
+ "seams"
117
118
  ];
118
119
  var findingMaker = (check) => (subject, verdict, message) => ({
119
120
  check,
@@ -461,6 +462,12 @@ var probesOf = async (entry, plugin) => {
461
462
  })
462
463
  );
463
464
  };
465
+ var retiredOf = async (entry, plugin) => {
466
+ const loaded = await import(pathToFileURL(entry).href);
467
+ return new Set(
468
+ Object.entries(loaded.default?.rules ?? {}).filter(([, rule]) => typeof rule?.meta?.retired === "object" && rule.meta.retired !== null).map(([name]) => `${plugin}/${name}`)
469
+ );
470
+ };
464
471
  var grantsOf = async (entry) => {
465
472
  const loaded = await import(pathToFileURL(entry).href);
466
473
  const plugin = loaded.default?.meta?.name ?? "";
@@ -950,11 +957,7 @@ var linkedBins = (root, workspaces) => {
950
957
  );
951
958
  if (missing.length === 0) {
952
959
  return [
953
- finding4(
954
- "workspace bins",
955
- "OK",
956
- "every workspace bin a script names is linked under the root"
957
- )
960
+ finding4("workspace bins", "OK", "every workspace bin a script names is linked under the root")
958
961
  ];
959
962
  }
960
963
  return missing.map(
@@ -1245,6 +1248,7 @@ var OBSERVABILITY = "@geonosis/observability";
1245
1248
  var COMPOSITION_ROOT = "src/platform.ts";
1246
1249
  var FLOOR_PACKAGES = [
1247
1250
  "@geonosis/assistant",
1251
+ "@geonosis/buketi-ui",
1248
1252
  "@geonosis/conformance",
1249
1253
  "@geonosis/db",
1250
1254
  "@geonosis/document",
@@ -1629,6 +1633,7 @@ var reasonFrom = (config, oxlint) => {
1629
1633
  }
1630
1634
  };
1631
1635
  var named = (rules) => rules.join(", ");
1636
+ var retiredNote = (rules) => rules.length === 1 ? `${rules[0]} is retired by the plugin \u2014 it reports nothing and goes at the next major, so remove it from this config` : `${named(rules)} are retired by the plugin \u2014 they report nothing and go at the next major, so remove them from this config`;
1632
1637
  var countOf = (rules, verb) => rules.length === 1 ? `1 enabled rule ${verb}s` : `${rules.length} enabled rules ${verb}`;
1633
1638
  var ownReach = ({
1634
1639
  config,
@@ -1790,7 +1795,12 @@ var checkExercised = async ({
1790
1795
  repoCorpus,
1791
1796
  root
1792
1797
  }) => {
1793
- const said2 = (verdict, message) => finding5(config.relative, verdict, message);
1798
+ let retired = [];
1799
+ const said2 = (verdict, message) => retired.length === 0 ? finding5(config.relative, verdict, message) : finding5(
1800
+ config.relative,
1801
+ verdict === "OK" || verdict === "SKIP" ? "WARN" : verdict,
1802
+ `${message}; ${retiredNote(retired)}`
1803
+ );
1794
1804
  if (repoCorpus !== void 0 && !existsSync7(repoCorpus)) {
1795
1805
  return said2(
1796
1806
  "FAIL",
@@ -1809,8 +1819,13 @@ var checkExercised = async ({
1809
1819
  } catch (error) {
1810
1820
  return said2("SKIP", refusal(error));
1811
1821
  }
1812
- const enabled = enabledHere(config, manifest.plugin);
1813
- if (enabled.length === 0) return said2("SKIP", `no ${manifest.plugin} rule is enabled here`);
1822
+ const configured2 = enabledHere(config, manifest.plugin);
1823
+ const retiredIds = entry === void 0 ? /* @__PURE__ */ new Set() : await retiredOf(entry, manifest.plugin).catch(() => /* @__PURE__ */ new Set());
1824
+ retired = configured2.filter((rule) => retiredIds.has(rule));
1825
+ const enabled = configured2.filter((rule) => !retiredIds.has(rule));
1826
+ if (enabled.length === 0) {
1827
+ 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
+ }
1814
1829
  let reach;
1815
1830
  try {
1816
1831
  reach = corpusOf({ configA: config.path, configB: config.path, corpus, oxlint }).reach;
@@ -2040,11 +2055,7 @@ var checkExams = ({
2040
2055
  `${name} \u2192 ${exam}`,
2041
2056
  "WARN",
2042
2057
  `this tree declares ${name} and no test file imports ${exam}. A floor bump that broke it would read exactly like a green tier: the exam is the only thing that asks.`
2043
- ) : finding7(
2044
- `${name} \u2192 ${exam}`,
2045
- "OK",
2046
- `${relativePath(root, importing.path)} runs ${exam}`
2047
- )
2058
+ ) : finding7(`${name} \u2192 ${exam}`, "OK", `${relativePath(root, importing.path)} runs ${exam}`)
2048
2059
  );
2049
2060
  }
2050
2061
  }
@@ -2679,7 +2690,7 @@ var checkRunner = ({
2679
2690
 
2680
2691
  // src/doctor.ts
2681
2692
  import { homedir as homedir3 } from "os";
2682
- import { join as join17 } from "path";
2693
+ import { join as join18 } from "path";
2683
2694
  import { resolveOxlint } from "@geonosis/lint-parity";
2684
2695
 
2685
2696
  // src/engine.ts
@@ -3000,6 +3011,46 @@ var checkRequiredOptions = async ({
3000
3011
  ];
3001
3012
  };
3002
3013
 
3014
+ // 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;
3030
+ return Array.isArray(seams) ? seams.filter((one) => typeof one === "string") : void 0;
3031
+ };
3032
+ var namesAFile = (root, glob) => globSync(glob, {
3033
+ cwd: root,
3034
+ exclude: (name) => name === "node_modules" || name.startsWith("node_modules/")
3035
+ }).length > 0;
3036
+ var checkSeams = ({ root }) => {
3037
+ const seams = declaredSeams(root);
3038
+ if (seams === void 0 || seams.length === 0) return [finding12(CONFIG, "SKIP", NO_SEAMS)];
3039
+ const dead = seams.filter((glob) => !namesAFile(root, glob)).toSorted();
3040
+ if (dead.length === 0) {
3041
+ return [
3042
+ finding12(CONFIG, "OK", `adoption.seams: ${seams.length} seam glob(s), every one names a file`)
3043
+ ];
3044
+ }
3045
+ return dead.map(
3046
+ (glob) => finding12(
3047
+ glob,
3048
+ "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`
3050
+ )
3051
+ );
3052
+ };
3053
+
3003
3054
  // src/doctor.ts
3004
3055
  var exercisedOf = ({
3005
3056
  configs,
@@ -3135,8 +3186,9 @@ var runDoctor = async ({
3135
3186
  ["observability", () => checkObservability({ now: Date.now(), root })],
3136
3187
  ["drift", () => checkDrift({ root, workspaces })],
3137
3188
  ["deployed", () => checkDeployed({ root })],
3138
- ["rails", () => checkRails({ root, userSettings: join17(home, USER_SETTINGS) })],
3139
- ["exams", () => checkExams({ root, workspaces })]
3189
+ ["rails", () => checkRails({ root, userSettings: join18(home, USER_SETTINGS) })],
3190
+ ["exams", () => checkExams({ root, workspaces })],
3191
+ ["seams", () => checkSeams({ root })]
3140
3192
  ];
3141
3193
  const collected = [];
3142
3194
  const ran = [];
@@ -3163,6 +3215,7 @@ var ABOUT = {
3163
3215
  drift: "the gates that were set up and are no longer running",
3164
3216
  envelope: "every gate read as many things as it was handed",
3165
3217
  exams: "every adopted floor has a test file that runs its exam",
3218
+ seams: "the seam globs adoption.seams declares each name a file",
3166
3219
  exercised: "every enabled rule fires on at least one corpus file",
3167
3220
  group: "the packages published on one version resolve to one version",
3168
3221
  loaded: "the plugin oxlint would load is the one the manifest pins",
@@ -4,7 +4,7 @@ import {
4
4
  formatDoctor,
5
5
  formatJson,
6
6
  runDoctor
7
- } from "./chunk-6ERJOIZS.js";
7
+ } from "./chunk-2ZVCJG5R.js";
8
8
 
9
9
  // src/doctor-cli.ts
10
10
  import { fstatSync, statSync } from "fs";
@@ -135,6 +135,9 @@ var ABOUT = {
135
135
  installed floor's own declarations \u2014 every export ending in Conformance \u2014 never off a
136
136
  list held here. WARNs, because a floor adopted last week and one whose exam somebody
137
137
  deleted look the same from outside.`,
138
+ seams: `The seam globs adoption.seams declares each name a file. A glob whose file was deleted and
139
+ never undeclared measures nothing \u2014 every bump reads 0 deleted, 0 added over it \u2014 and a
140
+ floor's adoption is measured by exactly those files.`,
138
141
  exercised: `Every rule a config enables, against the corpus the loaded plugin ships. A rule at
139
142
  "error" that can never fire is indistinguishable from a clean tree. The line says what
140
143
  its number is evidence ABOUT \u2014 the corpus and the probes, never this tree \u2014 and a rule
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ type Overrides = {
11
11
  * question one layer out — whether what was reported is what happened; `rails` asks it of the
12
12
  * settings an unattended run is bounded by, where a rendered file is not a loaded setting.
13
13
  */
14
- declare const CHECKS: readonly ["loaded", "group", "exercised", "baseline", "runner", "envelope", "drift", "observability", "deployed", "rails", "exams"];
14
+ declare const CHECKS: readonly ["loaded", "group", "exercised", "baseline", "runner", "envelope", "drift", "observability", "deployed", "rails", "exams", "seams"];
15
15
  type CheckName = (typeof CHECKS)[number];
16
16
  /**
17
17
  * `SKIP` is a first-class answer and is printed like any other: a check whose line is missing reads
@@ -140,7 +140,7 @@ declare const discoverConfigs: (root: string) => DiscoveredConfig[];
140
140
  declare const discoverWorkspaces: (root: string) => Workspace[];
141
141
  declare const readRatchet: (root: string) => RatchetConfig | undefined;
142
142
 
143
- declare const runDoctor: ({ baseline, home, only, oxlint, root, strict, }: DoctorOptions) => Promise<DoctorReport>;
143
+ declare const runDoctor: ({ baseline, home, only, oxlint, root, strict }: DoctorOptions) => Promise<DoctorReport>;
144
144
 
145
145
  /** The file `create-geonosis --domains` writes, held against the scaffold's own name by a test. */
146
146
  declare const COMPOSITION_ROOT = "src/platform.ts";
@@ -160,7 +160,7 @@ declare const READERS: Record<string, string>;
160
160
  * block nothing reads. The other four checks ask whether a gate measures what it names; this one
161
161
  * asks whether it is still there at all.
162
162
  */
163
- declare const checkDrift: ({ readers, root, userSettings, workspaces, }: {
163
+ declare const checkDrift: ({ readers, root, userSettings, workspaces }: {
164
164
  /**
165
165
  * Which package reads which block. It is a parameter so a test can name one that can NEVER be
166
166
  * installed: a fixture that turns on whether some real package happens to be present on the base
@@ -211,7 +211,7 @@ declare const enabledRulesOf: (rules: Record<string, unknown>, plugin: string) =
211
211
  * place where the difference is visible, so the question is asked there, under THIS config's
212
212
  * options, because the options are usually where the reach went.
213
213
  */
214
- declare const checkExercised: ({ config, corpus, entry, oxlint, repoCorpus, root, }: {
214
+ declare const checkExercised: ({ config, corpus, entry, oxlint, repoCorpus, root }: {
215
215
  config: DiscoveredConfig;
216
216
  corpus: string;
217
217
  /** The plugin module oxlint loads from this config's directory — where the probes are read. */
@@ -261,7 +261,7 @@ declare const declaredGroupsOf: (root: string) => HeldTogether[] | undefined;
261
261
  * pnpm the store keeps every version ever installed, so a count of directories answers nonsense on
262
262
  * a correct tree.
263
263
  */
264
- declare const checkGroup: ({ groups, root, workspaces, }: {
264
+ declare const checkGroup: ({ groups, root, workspaces }: {
265
265
  groups?: readonly HeldTogether[];
266
266
  root: string;
267
267
  workspaces: Workspace[];
@@ -293,7 +293,7 @@ declare const installedPluginVersions: (home: string, name: string) => string[];
293
293
  * runs the hooks of the one before, and a refusal added this release cannot fire. That is the
294
294
  * declared-versus-loaded question one surface out, and it is this check's to ask.
295
295
  */
296
- declare const checkClaudePlugin: ({ candidate, home, name, train, }: {
296
+ declare const checkClaudePlugin: ({ candidate, home, name, train }: {
297
297
  /**
298
298
  * Whether the packages here are a RELEASE CANDIDATE the registry has never seen — installed from
299
299
  * tarballs or a `file:` override. `claude plugin update` reads the marketplace, so it cannot
@@ -320,7 +320,7 @@ declare const satisfies: (version: string, spec: string) => boolean | undefined;
320
320
  * config, that names the package in any dependency block — ending at the root's. A workspace that
321
321
  * declares nothing inherits the root's pin, which is what a hoisted install actually installed.
322
322
  */
323
- declare const declaredFor: ({ dir, specifier, workspaces, }: {
323
+ declare const declaredFor: ({ dir, specifier, workspaces }: {
324
324
  dir: string;
325
325
  specifier: string;
326
326
  workspaces: Workspace[];
@@ -328,7 +328,7 @@ declare const declaredFor: ({ dir, specifier, workspaces, }: {
328
328
  at: string;
329
329
  spec: string;
330
330
  } | undefined;
331
- declare const checkLoaded: ({ configs, home, root, workspaces, }: {
331
+ declare const checkLoaded: ({ configs, home, root, workspaces }: {
332
332
  configs: DiscoveredConfig[];
333
333
  home?: string;
334
334
  root: string;
@@ -354,7 +354,7 @@ type ObservabilityConfig = {
354
354
  /** What kind of sink this repo runs: `posthog`, `otlp`, `memory`, … */
355
355
  sink?: string;
356
356
  };
357
- declare const checkObservability: ({ now, root, }: {
357
+ declare const checkObservability: ({ now, root }: {
358
358
  now: number;
359
359
  root: string;
360
360
  }) => Promise<Finding[]>;
@@ -413,7 +413,7 @@ declare const relativeToRoot: (root: string, path: string) => string;
413
413
  * somebody deleted, and a FAIL on every fresh install is the shape that teaches a reader to skim
414
414
  * the section — the lesson the `runner` check already learned about warning the wrong runner.
415
415
  */
416
- declare const checkExams: ({ root, workspaces, }: {
416
+ declare const checkExams: ({ root, workspaces }: {
417
417
  root: string;
418
418
  workspaces: Workspace[];
419
419
  }) => Finding[];
@@ -429,7 +429,7 @@ declare const checkExams: ({ root, workspaces, }: {
429
429
  * a WARN that names the wrong runner's sin is a line a reader learns to skip, and the lines it
430
430
  * teaches them to skip are the true ones next to it.
431
431
  */
432
- declare const checkRunner: ({ ratchet, root, workspaces, }: {
432
+ declare const checkRunner: ({ ratchet, root, workspaces }: {
433
433
  ratchet?: RatchetConfig;
434
434
  root: string;
435
435
  workspaces: Workspace[];
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  resolveFrom,
49
49
  runDoctor,
50
50
  satisfies
51
- } from "./chunk-6ERJOIZS.js";
51
+ } from "./chunk-2ZVCJG5R.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.6.1",
3
+ "version": "2.8.0",
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.6.1"
39
+ "@geonosis/lint-parity": "2.8.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@geonosis/ratchet": "2.6.1"
42
+ "@geonosis/ratchet": "2.8.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "oxlint": ">=1.77"