@geonosis/doctor 1.1.0 → 1.3.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.
@@ -1,63 +1,71 @@
1
1
  import {
2
+ CHECKS,
2
3
  formatDoctor,
3
4
  formatJson,
4
5
  runDoctor
5
- } from "./chunk-CG2LN4VW.js";
6
+ } from "./chunk-7BRGHYQR.js";
6
7
 
7
8
  // src/doctor-cli.ts
8
9
  import { resolve } from "path";
9
- var USAGE = `geonosis-doctor [--root <dir>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
10
-
11
- Five questions a version bump is not finished until something has asked. The first four are ways
12
- enforcement has reported green while measuring nothing; the fifth asks whether it is still there.
13
-
14
- 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
+ exercised: `Every rule a config enables, against the corpus the loaded plugin ships. A rule at
29
+ "error" that can never fire is indistinguishable from a clean tree.`,
30
+ loaded: `The plugin oxlint would LOAD from each config's directory, against the version that
15
31
  config's workspace DECLARES. oxlint resolves a jsPlugins specifier from the CONFIG
16
32
  FILE's directory, so a nested copy left behind by a per-workspace install runs while
17
33
  every manifest and the lockfile say otherwise \u2014 one consumer measured a whole bug
18
34
  report against 0.3.0 on a repo pinned to 0.4.0. Every copy is found by RESOLUTION,
19
- never by find: on pnpm the store keeps every version ever installed.
20
-
21
- exercised Every rule a config enables, against the corpus the loaded plugin ships. A rule at
22
- "error" that can never fire is indistinguishable from a clean tree.
23
-
24
- baseline The ratchet's numbers at HEAD against another ref. A ratchet lowers its own baseline
25
- when a number shrinks, so nothing inside one checkout can see a branch raise one back
26
- up. OFF unless asked for: it is the only check that runs git, and this tool is run
27
- over repos other people are working in.
28
-
29
- runner Workspaces whose test script's exit code is the only verdict. A pool exited 0 over
30
- suites it had just reported as failing, for weeks, in a consumer.
31
-
32
- observability
33
- The exporter named by the "observability" block in geonosis.json: is a sink
35
+ never by find: on pnpm the store keeps every version ever installed.`,
36
+ observability: `The exporter named by the "observability" block in geonosis.json: is a sink
34
37
  configured, is its endpoint reachable (a HEAD with a short timeout), and did an event
35
38
  arrive inside maxAgeSeconds \u2014 read from the lastEventFile the sink writes, never from
36
39
  this tool importing the library it is checking. A deploy job reported success having
37
40
  deployed nothing and a /health answered ok over a dead database on the same
38
- afternoon; both left a green build and a silent project.
39
- drift The gates that were set up and are no longer running: a CI job switched off by a
40
- condition that can never be true, a test file under a workspace with no test script,
41
- an integration directory the registry never names, a law over the ceiling its own
42
- geonosis.json set, no .claude/settings.json installing the plugin, and a
43
- geonosis.json block whose package is not installed (or the reverse).
41
+ afternoon; both left a green build and a silent project.`,
42
+ runner: `Workspaces whose test script's exit code is the only verdict. A pool exited 0 over
43
+ suites it had just reported as failing, for weeks, in a consumer. A script with no
44
+ test file under it is a SKIP \u2014 there is no result for an exit code to be wrong about.`
45
+ };
46
+ var USAGE = `geonosis-doctor [--root <dir>] [--only <check,\u2026>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
44
47
 
45
- deployed What the pipeline REPORTED deploying, in .geonosis/deployed.json, against what the
46
- tree declares in the wrangler configs geonosis.json names and release.secrets.
47
- "versions upload" applies no triggers, so a cron edited in a config is silently
48
- ignored in production for ever with a green pipeline; a secret can be put outside the
49
- gate entirely. SKIPs with the sentence when the file is absent \u2014 its absence is not a
50
- pass \u2014 and reads the file rather than importing @geonosis/release.
48
+ ${CHECKS.length} questions a version bump is not finished until something has asked. Most are ways
49
+ enforcement has reported green while measuring nothing; drift asks whether the gate is still there
50
+ at all, and the last two ask whether what was reported is what happened.
51
+
52
+ With no arguments it runs every one of them over the working directory. A tree holding no
53
+ package.json and no .oxlintrc.json is REFUSED with exit 2 \u2014 there is nothing there to examine, and a
54
+ page of SKIPs is not a pass.
55
+
56
+ ${CHECKS.map((check) => ` ${check.padEnd(11)} ${ABOUT[check]}`).join("\n\n")}
51
57
 
52
58
  exercised reads the corpus the loaded plugin ships, and \u2014 when geonosis.json names one under
53
59
  doctor.corpus \u2014 the corpus THIS repo ships for its own options. Three rules cannot be answered any
54
60
  other way: layer-walls fires on a repo's own layer names, no-brand-names on its brand, and
55
61
  plugin-route-namespaced on its package root, while the shipped corpus says acme and layers/core.
56
62
 
63
+ --print-config-shape every file this reads, and the keys it reads out of each
64
+
57
65
  Exits 1 when any line is a FAIL or an UNJUDGED \u2014 a question this could not ask is not a pass \u2014 and
58
66
  2 when the run could not be made at all. --strict promotes every WARN to a FAIL. --json prints the
59
67
  whole report for a CI step to read.`;
60
- var VALUED = /* @__PURE__ */ new Set(["--oxlint", "--root"]);
68
+ var VALUED = /* @__PURE__ */ new Set(["--only", "--oxlint", "--root"]);
61
69
  var parseDoctorArgs = (argv, cwd) => {
62
70
  const read = {};
63
71
  let baseline;
@@ -85,18 +93,57 @@ var parseDoctorArgs = (argv, cwd) => {
85
93
  read[flag] = value;
86
94
  index += 1;
87
95
  }
96
+ const only = read["--only"]?.split(",").map((one) => one.trim()).filter((one) => one !== "");
97
+ const unknown = (only ?? []).filter((one) => !CHECKS.includes(one));
98
+ if (unknown.length > 0) {
99
+ throw new Error(`no check called ${unknown.join(", ")} \u2014 the checks are ${CHECKS.join(", ")}`);
100
+ }
88
101
  return {
89
102
  ...baseline === void 0 ? {} : { baseline },
90
103
  json,
104
+ ...only === void 0 || only.length === 0 ? {} : { only },
91
105
  ...read["--oxlint"] === void 0 ? {} : { oxlint: resolve(cwd, read["--oxlint"]) },
92
106
  root: resolve(cwd, read["--root"] ?? cwd),
93
107
  strict
94
108
  };
95
109
  };
110
+ var CONFIG_SHAPE = `geonosis-doctor reads geonosis.json, geonosis.ratchet.json, .oxlintrc.json,
111
+ pnpm-workspace.yaml and .claude/settings.json
112
+
113
+ geonosis.json \u2192 "law"
114
+ file string? the law file to measure (default "CLAUDE.md")
115
+ maxLines number? the ceiling. Undeclared means UNJUDGED, never a default it fires on
116
+ geonosis.json \u2192 "observability"
117
+ sink string where this repo sends its errors
118
+ endpoint string the URL that must be reachable
119
+ lastEventFile string the file the sink writes on arrival
120
+ probe string? a command that answers instead of lastEventFile
121
+ maxAgeSeconds number how old the last event may be
122
+ geonosis.json \u2192 "release"
123
+ wrangler string[] the wrangler configs a deployment carries
124
+ wranglerEnv string? the named environment block to read
125
+ secrets string[] the secret names it carries
126
+ .oxlintrc.json
127
+ jsPlugins string[] the plugin specifiers oxlint would load
128
+ rules object the base rules, and the options each was given
129
+ overrides object[] files + rules; an entry REPLACES a rule's options for the files it
130
+ claims, last match winning \u2014 the same resolution oxlint does
131
+ geonosis.ratchet.json
132
+ the ratchet's own \u2014 run \`geonosis-ratchet --print-config-shape\`
133
+ pnpm-workspace.yaml
134
+ publicHoistPattern string[] the packages this repo claims are linked at the root, read as
135
+ text; a "*" is the only wildcard and it spans "/"
136
+ .claude/settings.json
137
+ enabledPlugins object what installs the kit's plugin in THIS repo`;
96
138
  var main = async () => {
97
139
  const argv = process.argv.slice(2);
98
140
  if (argv.includes("--help") || argv.includes("-h")) {
99
141
  process.stdout.write(`${USAGE}
142
+ `);
143
+ return 0;
144
+ }
145
+ if (argv.includes("--print-config-shape")) {
146
+ process.stdout.write(`${CONFIG_SHAPE}
100
147
  `);
101
148
  return 0;
102
149
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,8 @@
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
8
  * The first four are ways enforcement has reported green while measuring nothing; `drift` asks
@@ -29,6 +34,7 @@ declare class DoctorError extends Error {
29
34
  constructor(message: string);
30
35
  }
31
36
  type Manifest = {
37
+ bin?: Record<string, string> | string;
32
38
  dependencies?: Record<string, string>;
33
39
  devDependencies?: Record<string, string>;
34
40
  name?: string;
@@ -47,6 +53,8 @@ type DiscoveredConfig = {
47
53
  /** Why this config could not be read. A config nobody can parse is a finding, never a skip. */
48
54
  error?: string;
49
55
  jsPlugins: string[];
56
+ /** Layers that REPLACE a rule's options for the files they claim — never merged into the base. */
57
+ overrides: Overrides;
50
58
  path: string;
51
59
  relative: string;
52
60
  rules: Record<string, unknown>;
@@ -65,6 +73,8 @@ type DoctorOptions = {
65
73
  ref?: string;
66
74
  };
67
75
  oxlint?: string;
76
+ /** Run these checks and no other — one question, asked fast, belongs in a sub-second gate. */
77
+ only?: CheckName[];
68
78
  root: string;
69
79
  strict?: boolean;
70
80
  };
@@ -124,7 +134,7 @@ declare const discoverConfigs: (root: string) => DiscoveredConfig[];
124
134
  declare const discoverWorkspaces: (root: string) => Workspace[];
125
135
  declare const readRatchet: (root: string) => RatchetConfig | undefined;
126
136
 
127
- declare const runDoctor: ({ baseline, oxlint, root, strict, }: DoctorOptions) => Promise<DoctorReport>;
137
+ declare const runDoctor: ({ baseline, only, oxlint, root, strict, }: DoctorOptions) => Promise<DoctorReport>;
128
138
 
129
139
  /** Which kit package reads which block of `geonosis.json`. */
130
140
  declare const READERS: Record<string, string>;
@@ -136,7 +146,7 @@ declare const READERS: Record<string, string>;
136
146
  * block nothing reads. The other four checks ask whether a gate measures what it names; this one
137
147
  * asks whether it is still there at all.
138
148
  */
139
- declare const checkDrift: ({ readers, root, workspaces, }: {
149
+ declare const checkDrift: ({ readers, root, userSettings, workspaces, }: {
140
150
  /**
141
151
  * Which package reads which block. It is a parameter so a test can name one that can NEVER be
142
152
  * installed: a fixture that turns on whether some real package happens to be present on the base
@@ -145,6 +155,11 @@ declare const checkDrift: ({ readers, root, workspaces, }: {
145
155
  */
146
156
  readers?: Record<string, string>;
147
157
  root: string;
158
+ /**
159
+ * The machine-wide settings, a parameter so a test never asks about the home directory it happens
160
+ * to run in — the same reason `readers` is one.
161
+ */
162
+ userSettings?: string;
148
163
  workspaces: Workspace[];
149
164
  }) => Finding[];
150
165
 
@@ -242,15 +257,6 @@ declare const repoCorpusOf: (root: string) => string | undefined;
242
257
  declare const formatDoctor: ({ counts, findings, ok, root }: DoctorReport) => string;
243
258
  declare const formatJson: (report: DoctorReport) => string;
244
259
 
245
- /**
246
- * What oxlint would load, asked the only way that answers on every package manager.
247
- *
248
- * oxlint resolves a `jsPlugins` specifier from the CONFIG FILE's directory, not the working
249
- * directory, so the question is asked from a directory and never from the process's cwd. And it is
250
- * asked of the RESOLVER: counting copies with `find` is a bun/npm check only — on pnpm the entries
251
- * under `node_modules/@scope/` are symlinks into `.pnpm/`, which keeps every version ever
252
- * installed, so the same count answers 7 on a correct tree and 0 with the store excluded.
253
- */
254
260
  declare const resolveFrom: (dir: string, specifier: string) => string;
255
261
  /** The package a resolved entry point belongs to: up until a manifest claims the name. */
256
262
  declare const packageDirOf: (entry: string, name: string) => string;
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  resolveFrom,
35
35
  runDoctor,
36
36
  satisfies
37
- } from "./chunk-CG2LN4VW.js";
37
+ } from "./chunk-7BRGHYQR.js";
38
38
  export {
39
39
  CHECKS,
40
40
  CONFIG_FILE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/doctor",
3
- "version": "1.1.0",
3
+ "version": "1.3.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.1.0"
38
+ "@geonosis/lint-parity": "1.3.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "oxlint": ">=1.77"