@geonosis/doctor 1.2.0 → 1.4.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.
@@ -3,58 +3,72 @@ import {
3
3
  formatDoctor,
4
4
  formatJson,
5
5
  runDoctor
6
- } from "./chunk-ZJCUTDQW.js";
6
+ } from "./chunk-R4OLFNI6.js";
7
7
 
8
8
  // src/doctor-cli.ts
9
9
  import { resolve } from "path";
10
- var USAGE = `geonosis-doctor [--root <dir>] [--only <check,\u2026>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
11
-
12
- Five questions a version bump is not finished until something has asked. The first four are ways
13
- enforcement has reported green while measuring nothing; the fifth asks whether it is still there.
14
-
15
- loaded The plugin oxlint would LOAD from each config's directory, against the version that
10
+ var ABOUT = {
11
+ baseline: `The ratchet's numbers at HEAD against another ref. A ratchet lowers its own baseline
12
+ when a number shrinks, so nothing inside one checkout can see a branch raise one back
13
+ up. OFF unless asked for: it is the only check that runs git, and this tool is run
14
+ over repos other people are working in.`,
15
+ deployed: `What the pipeline REPORTED deploying, in .geonosis/deployed.json, against what the
16
+ tree declares in the wrangler configs geonosis.json names and release.secrets.
17
+ "versions upload" applies no triggers, so a cron edited in a config is silently
18
+ ignored in production for ever with a green pipeline; a secret can be put outside the
19
+ gate entirely. SKIPs with the sentence when the file is absent \u2014 its absence is not a
20
+ pass \u2014 and reads the file rather than importing @geonosis/release.`,
21
+ drift: `The gates that were set up and are no longer running: a CI job switched off by a
22
+ condition that can never be true, a test file under a workspace with no test script, a
23
+ script handing bun/node/tsx a file that is not on disk, a workspace bin nothing links,
24
+ an integration directory the registry never names, a geonosis bin a git hook calls
25
+ through pnpm/npx/bunx and therefore cannot start, a law over the ceiling its own
26
+ geonosis.json declared, nothing enabling the plugin in either scope, and a
27
+ geonosis.json block no manifest declares a package for (or the reverse).`,
28
+ envelope: `The arithmetic under every other line: for each .geonosis/envelopes/<tool>.json a gate
29
+ wrote, considered === read + refused + excused. Four denominator bugs landed in one
30
+ day and every one of them was GREEN \u2014 a migrations run that reported on three of four
31
+ files, a plan check that printed "PASS \u2014 0 plan(s)" over a directory of twenty-one, a
32
+ parity run over a tree the second config ignored. Each published a numerator and no
33
+ denominator. Reads the files and imports nothing of the tools that wrote them; no
34
+ envelopes at all is a SKIP with the sentence.`,
35
+ exercised: `Every rule a config enables, against the corpus the loaded plugin ships. A rule at
36
+ "error" that can never fire is indistinguishable from a clean tree.`,
37
+ loaded: `The plugin oxlint would LOAD from each config's directory, against the version that
16
38
  config's workspace DECLARES. oxlint resolves a jsPlugins specifier from the CONFIG
17
39
  FILE's directory, so a nested copy left behind by a per-workspace install runs while
18
40
  every manifest and the lockfile say otherwise \u2014 one consumer measured a whole bug
19
41
  report against 0.3.0 on a repo pinned to 0.4.0. Every copy is found by RESOLUTION,
20
- never by find: on pnpm the store keeps every version ever installed.
21
-
22
- exercised Every rule a config enables, against the corpus the loaded plugin ships. A rule at
23
- "error" that can never fire is indistinguishable from a clean tree.
24
-
25
- baseline The ratchet's numbers at HEAD against another ref. A ratchet lowers its own baseline
26
- when a number shrinks, so nothing inside one checkout can see a branch raise one back
27
- up. OFF unless asked for: it is the only check that runs git, and this tool is run
28
- over repos other people are working in.
29
-
30
- runner Workspaces whose test script's exit code is the only verdict. A pool exited 0 over
31
- suites it had just reported as failing, for weeks, in a consumer.
32
-
33
- observability
34
- The exporter named by the "observability" block in geonosis.json: is a sink
42
+ never by find: on pnpm the store keeps every version ever installed.`,
43
+ observability: `The exporter named by the "observability" block in geonosis.json: is a sink
35
44
  configured, is its endpoint reachable (a HEAD with a short timeout), and did an event
36
45
  arrive inside maxAgeSeconds \u2014 read from the lastEventFile the sink writes, never from
37
46
  this tool importing the library it is checking. A deploy job reported success having
38
47
  deployed nothing and a /health answered ok over a dead database on the same
39
- afternoon; both left a green build and a silent project.
40
- drift The gates that were set up and are no longer running: a CI job switched off by a
41
- condition that can never be true, a test file under a workspace with no test script,
42
- an integration directory the registry never names, a law over the ceiling its own
43
- geonosis.json set, no .claude/settings.json installing the plugin, and a
44
- geonosis.json block whose package is not installed (or the reverse).
48
+ afternoon; both left a green build and a silent project.`,
49
+ runner: `Workspaces whose test script's exit code is the only verdict. A pool exited 0 over
50
+ suites it had just reported as failing, for weeks, in a consumer. A script with no
51
+ test file under it is a SKIP \u2014 there is no result for an exit code to be wrong about.`
52
+ };
53
+ var USAGE = `geonosis-doctor [--root <dir>] [--only <check,\u2026>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
45
54
 
46
- deployed What the pipeline REPORTED deploying, in .geonosis/deployed.json, against what the
47
- tree declares in the wrangler configs geonosis.json names and release.secrets.
48
- "versions upload" applies no triggers, so a cron edited in a config is silently
49
- ignored in production for ever with a green pipeline; a secret can be put outside the
50
- gate entirely. SKIPs with the sentence when the file is absent \u2014 its absence is not a
51
- pass \u2014 and reads the file rather than importing @geonosis/release.
55
+ ${CHECKS.length} questions a version bump is not finished until something has asked. Most are ways
56
+ enforcement has reported green while measuring nothing; drift asks whether the gate is still there
57
+ at all, and the last two ask whether what was reported is what happened.
58
+
59
+ With no arguments it runs every one of them over the working directory. A tree holding no
60
+ package.json and no .oxlintrc.json is REFUSED with exit 2 \u2014 there is nothing there to examine, and a
61
+ page of SKIPs is not a pass.
62
+
63
+ ${CHECKS.map((check) => ` ${check.padEnd(11)} ${ABOUT[check]}`).join("\n\n")}
52
64
 
53
65
  exercised reads the corpus the loaded plugin ships, and \u2014 when geonosis.json names one under
54
66
  doctor.corpus \u2014 the corpus THIS repo ships for its own options. Three rules cannot be answered any
55
67
  other way: layer-walls fires on a repo's own layer names, no-brand-names on its brand, and
56
68
  plugin-route-namespaced on its package root, while the shipped corpus says acme and layers/core.
57
69
 
70
+ --print-config-shape every file this reads, and the keys it reads out of each
71
+
58
72
  Exits 1 when any line is a FAIL or an UNJUDGED \u2014 a question this could not ask is not a pass \u2014 and
59
73
  2 when the run could not be made at all. --strict promotes every WARN to a FAIL. --json prints the
60
74
  whole report for a CI step to read.`;
@@ -100,10 +114,43 @@ var parseDoctorArgs = (argv, cwd) => {
100
114
  strict
101
115
  };
102
116
  };
117
+ var CONFIG_SHAPE = `geonosis-doctor reads geonosis.json, geonosis.ratchet.json, .oxlintrc.json,
118
+ pnpm-workspace.yaml and .claude/settings.json
119
+
120
+ geonosis.json \u2192 "law"
121
+ file string? the law file to measure (default "CLAUDE.md")
122
+ maxLines number? the ceiling. Undeclared means UNJUDGED, never a default it fires on
123
+ geonosis.json \u2192 "observability"
124
+ sink string where this repo sends its errors
125
+ endpoint string the URL that must be reachable
126
+ lastEventFile string the file the sink writes on arrival
127
+ probe string? a command that answers instead of lastEventFile
128
+ maxAgeSeconds number how old the last event may be
129
+ geonosis.json \u2192 "release"
130
+ wrangler string[] the wrangler configs a deployment carries
131
+ wranglerEnv string? the named environment block to read
132
+ secrets string[] the secret names it carries
133
+ .oxlintrc.json
134
+ jsPlugins string[] the plugin specifiers oxlint would load
135
+ rules object the base rules, and the options each was given
136
+ overrides object[] files + rules; an entry REPLACES a rule's options for the files it
137
+ claims, last match winning \u2014 the same resolution oxlint does
138
+ geonosis.ratchet.json
139
+ the ratchet's own \u2014 run \`geonosis-ratchet --print-config-shape\`
140
+ pnpm-workspace.yaml
141
+ publicHoistPattern string[] the packages this repo claims are linked at the root, read as
142
+ text; a "*" is the only wildcard and it spans "/"
143
+ .claude/settings.json
144
+ enabledPlugins object what installs the kit's plugin in THIS repo`;
103
145
  var main = async () => {
104
146
  const argv = process.argv.slice(2);
105
147
  if (argv.includes("--help") || argv.includes("-h")) {
106
148
  process.stdout.write(`${USAGE}
149
+ `);
150
+ return 0;
151
+ }
152
+ if (argv.includes("--print-config-shape")) {
153
+ process.stdout.write(`${CONFIG_SHAPE}
107
154
  `);
108
155
  return 0;
109
156
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,16 @@
1
+ type Overrides = {
2
+ files: readonly string[];
3
+ rules: Record<string, unknown>;
4
+ }[];
5
+
1
6
  /**
2
7
  * The questions a bump is not finished until something has asked, in the order a run asks them.
3
- * The first four are ways enforcement has reported green while measuring nothing; `drift` asks
4
- * whether the gate is still there at all; `observability` and `deployed` ask the same question one
5
- * layer out — whether what was reported is what happened.
8
+ * The first four are ways enforcement has reported green while measuring nothing; `envelope` asks
9
+ * the arithmetic underneath all of them — whether a gate read as many things as it was handed;
10
+ * `drift` asks whether the gate is still there at all; `observability` and `deployed` ask the same
11
+ * question one layer out — whether what was reported is what happened.
6
12
  */
7
- declare const CHECKS: readonly ["loaded", "exercised", "baseline", "runner", "drift", "observability", "deployed"];
13
+ declare const CHECKS: readonly ["loaded", "exercised", "baseline", "runner", "envelope", "drift", "observability", "deployed"];
8
14
  type CheckName = (typeof CHECKS)[number];
9
15
  /**
10
16
  * `SKIP` is a first-class answer and is printed like any other: a check whose line is missing reads
@@ -29,6 +35,7 @@ declare class DoctorError extends Error {
29
35
  constructor(message: string);
30
36
  }
31
37
  type Manifest = {
38
+ bin?: Record<string, string> | string;
32
39
  dependencies?: Record<string, string>;
33
40
  devDependencies?: Record<string, string>;
34
41
  name?: string;
@@ -47,6 +54,8 @@ type DiscoveredConfig = {
47
54
  /** Why this config could not be read. A config nobody can parse is a finding, never a skip. */
48
55
  error?: string;
49
56
  jsPlugins: string[];
57
+ /** Layers that REPLACE a rule's options for the files they claim — never merged into the base. */
58
+ overrides: Overrides;
50
59
  path: string;
51
60
  relative: string;
52
61
  rules: Record<string, unknown>;
@@ -138,7 +147,7 @@ declare const READERS: Record<string, string>;
138
147
  * block nothing reads. The other four checks ask whether a gate measures what it names; this one
139
148
  * asks whether it is still there at all.
140
149
  */
141
- declare const checkDrift: ({ readers, root, workspaces, }: {
150
+ declare const checkDrift: ({ readers, root, userSettings, workspaces, }: {
142
151
  /**
143
152
  * Which package reads which block. It is a parameter so a test can name one that can NEVER be
144
153
  * installed: a fixture that turns on whether some real package happens to be present on the base
@@ -147,9 +156,35 @@ declare const checkDrift: ({ readers, root, workspaces, }: {
147
156
  */
148
157
  readers?: Record<string, string>;
149
158
  root: string;
159
+ /**
160
+ * The machine-wide settings, a parameter so a test never asks about the home directory it happens
161
+ * to run in — the same reason `readers` is one.
162
+ */
163
+ userSettings?: string;
150
164
  workspaces: Workspace[];
151
165
  }) => Finding[];
152
166
 
167
+ /**
168
+ * The one line that catches a denominator bug: for every envelope a tool wrote,
169
+ * `considered === read + refused + excused`.
170
+ *
171
+ * Four of them landed in one day, all green — a migrations run that reported on three of four
172
+ * files, a plan check that printed `PASS — 0 plan(s)` over a directory of twenty-one, a parity run
173
+ * over a tree the second config had ignored, a validator that walked a list it had already
174
+ * filtered. Every one published a numerator and no denominator, so nothing could be wrong.
175
+ *
176
+ * This READS the files and imports nothing of the tools that wrote them — the same discipline
177
+ * `deployed` and `observability` follow. A check that needed the package it checks cannot run in
178
+ * the tree where that package is missing, which is the first case it exists to find; and this one
179
+ * has to work over a consumer's `.geonosis/` with no kit installed at all. The arithmetic is
180
+ * duplicated here on purpose: it is one comparison over three numbers, and it is the check.
181
+ */
182
+ declare const ENVELOPES_DIR = ".geonosis/envelopes";
183
+ declare const 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";
184
+ declare const checkEnvelopes: ({ root }: {
185
+ root: string;
186
+ }) => Finding[];
187
+
153
188
  /**
154
189
  * The rules of one plugin a config actually turns on. A rule NAMED in a config is not a rule
155
190
  * enabled by it — `"off"` is how a config records a rule it decided against, and counting those as
@@ -244,15 +279,6 @@ declare const repoCorpusOf: (root: string) => string | undefined;
244
279
  declare const formatDoctor: ({ counts, findings, ok, root }: DoctorReport) => string;
245
280
  declare const formatJson: (report: DoctorReport) => string;
246
281
 
247
- /**
248
- * What oxlint would load, asked the only way that answers on every package manager.
249
- *
250
- * oxlint resolves a `jsPlugins` specifier from the CONFIG FILE's directory, not the working
251
- * directory, so the question is asked from a directory and never from the process's cwd. And it is
252
- * asked of the RESOLVER: counting copies with `find` is a bun/npm check only — on pnpm the entries
253
- * under `node_modules/@scope/` are symlinks into `.pnpm/`, which keeps every version ever
254
- * installed, so the same count answers 7 on a correct tree and 0 with the store excluded.
255
- */
256
282
  declare const resolveFrom: (dir: string, specifier: string) => string;
257
283
  /** The package a resolved entry point belongs to: up until a manifest claims the name. */
258
284
  declare const packageDirOf: (entry: string, name: string) => string;
@@ -287,4 +313,4 @@ declare const checkRunner: ({ ratchet, workspaces, }: {
287
313
  workspaces: Workspace[];
288
314
  }) => Finding[];
289
315
 
290
- export { CHECKS, CONFIG_FILE, type CheckName, DEPLOYED_FILE, type DiscoveredConfig, DoctorError, type DoctorOptions, type DoctorReport, type Finding, GEONOSIS_FILE, type LastEventRecord, MANIFEST_FILE, type Manifest, NOT_WRITTEN, type ObservabilityConfig, RATCHET_FILE, READERS, type RatchetConfig, SCOPE, type Verdict, type Workspace, checkBaseline, checkDeployed, checkDrift, checkExercised, checkLoaded, checkObservability, checkRunner, corpusOfPlugin, declaredFor, defaultRef, discoverConfigs, discoverWorkspaces, enabledRulesOf, formatDoctor, formatJson, packageDirOf, pluginVersionOf, readConfig, readRatchet, relativePath, relativeToRoot, repoCorpusOf, resolveFrom, runDoctor, satisfies };
316
+ export { CHECKS, CONFIG_FILE, type CheckName, DEPLOYED_FILE, type DiscoveredConfig, DoctorError, type DoctorOptions, type DoctorReport, ENVELOPES_DIR, type Finding, GEONOSIS_FILE, type LastEventRecord, MANIFEST_FILE, type Manifest, NOT_WRITTEN, NO_ENVELOPES, type ObservabilityConfig, RATCHET_FILE, READERS, type RatchetConfig, SCOPE, type Verdict, type Workspace, checkBaseline, checkDeployed, checkDrift, checkEnvelopes, checkExercised, checkLoaded, checkObservability, checkRunner, corpusOfPlugin, declaredFor, defaultRef, discoverConfigs, discoverWorkspaces, enabledRulesOf, formatDoctor, formatJson, packageDirOf, pluginVersionOf, readConfig, readRatchet, relativePath, relativeToRoot, repoCorpusOf, resolveFrom, runDoctor, satisfies };
package/dist/index.js CHANGED
@@ -3,15 +3,18 @@ import {
3
3
  CONFIG_FILE,
4
4
  DEPLOYED_FILE,
5
5
  DoctorError,
6
+ ENVELOPES_DIR,
6
7
  GEONOSIS_FILE,
7
8
  MANIFEST_FILE,
8
9
  NOT_WRITTEN,
10
+ NO_ENVELOPES,
9
11
  RATCHET_FILE,
10
12
  READERS,
11
13
  SCOPE,
12
14
  checkBaseline,
13
15
  checkDeployed,
14
16
  checkDrift,
17
+ checkEnvelopes,
15
18
  checkExercised,
16
19
  checkLoaded,
17
20
  checkObservability,
@@ -34,21 +37,24 @@ import {
34
37
  resolveFrom,
35
38
  runDoctor,
36
39
  satisfies
37
- } from "./chunk-ZJCUTDQW.js";
40
+ } from "./chunk-R4OLFNI6.js";
38
41
  export {
39
42
  CHECKS,
40
43
  CONFIG_FILE,
41
44
  DEPLOYED_FILE,
42
45
  DoctorError,
46
+ ENVELOPES_DIR,
43
47
  GEONOSIS_FILE,
44
48
  MANIFEST_FILE,
45
49
  NOT_WRITTEN,
50
+ NO_ENVELOPES,
46
51
  RATCHET_FILE,
47
52
  READERS,
48
53
  SCOPE,
49
54
  checkBaseline,
50
55
  checkDeployed,
51
56
  checkDrift,
57
+ checkEnvelopes,
52
58
  checkExercised,
53
59
  checkLoaded,
54
60
  checkObservability,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/doctor",
3
- "version": "1.2.0",
3
+ "version": "1.4.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": [
@@ -35,7 +35,7 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@geonosis/lint-parity": "1.2.0"
38
+ "@geonosis/lint-parity": "1.4.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "oxlint": ">=1.77"