@geonosis/doctor 2.2.0 → 2.3.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.
- package/README.md +25 -2
- package/dist/{chunk-DR5P4QYC.js → chunk-GWVSC7GZ.js} +307 -92
- package/dist/doctor-cli.js +8 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +3 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @geonosis/doctor
|
|
2
2
|
|
|
3
|
+
Through the front door: `geonosis doctor` — the metapackage pins this and every other kit tool at
|
|
4
|
+
ONE version, and passes the exit code through unchanged.
|
|
5
|
+
|
|
3
6
|
Seven questions a version bump is not finished until something has asked. The first four are ways
|
|
4
7
|
enforcement has reported **green while measuring nothing** — in real repos, for weeks at a time.
|
|
5
8
|
`drift` asks whether the gate is still there at all. The last two ask the same question one layer
|
|
@@ -24,9 +27,9 @@ geonosis-doctor [--root <dir>] [--json] [--strict] [--baseline-against [<ref>]]
|
|
|
24
27
|
|
|
25
28
|
## One check, asked alone
|
|
26
29
|
|
|
27
|
-
`geonosis-doctor --only loaded` runs that check and no other, in well under a second: a manifest read and a resolution per config. It exists because the doctor subsumed a consumer's own declared-vs-loaded script that lived in a 2.5 s local gate, and eleven seconds for
|
|
30
|
+
`geonosis-doctor --only loaded` runs that check and no other, in well under a second: a manifest read and a resolution per config. It exists because the doctor subsumed a consumer's own declared-vs-loaded script that lived in a 2.5 s local gate, and eleven seconds for every check cannot live there — so the question that catches a stale nested copy earliest had fallen out of the gate that runs most often (measured in a consumer, 2026-08-30: a per-workspace `bun add` left one workspace loading 1.0.0 against configs written for 1.1.0, and the script about to be deleted caught it). `--only loaded,runner` takes a list; a name the doctor does not have is refused with the ones it does. A `loaded` FAIL now says the remedy: remove the nested copies and reinstall.
|
|
28
31
|
|
|
29
|
-
## The
|
|
32
|
+
## The eleven checks
|
|
30
33
|
|
|
31
34
|
### `loaded` — declared ≠ loaded
|
|
32
35
|
|
|
@@ -326,6 +329,26 @@ environments inherit nothing per binding) and `release.secrets`.
|
|
|
326
329
|
It reads the file, and imports nothing of `@geonosis/release` — the same wall `observability` keeps,
|
|
327
330
|
held by the kit's own `layer-walls` config, so a repo can install either package alone.
|
|
328
331
|
|
|
332
|
+
### `exams` — a floor adopted and never examined
|
|
333
|
+
|
|
334
|
+
A floor bump that breaks search is invisible to a tier that never runs the search exam. A verify
|
|
335
|
+
tier declares COMMANDS, not globs, so "the exam runs in the fast tier" is not a claim anything can
|
|
336
|
+
check — but whether a test file importing the exam exists at all is.
|
|
337
|
+
|
|
338
|
+
For every `@geonosis/*` package a manifest in the tree declares, this reads the declarations that
|
|
339
|
+
package ships and takes every export ending in `Conformance` as its exam. The exams are read off
|
|
340
|
+
the installed floor, never off a list held here: a table in this package would be a second source
|
|
341
|
+
for a fact the floors already state, and wrong the day a floor adds one.
|
|
342
|
+
|
|
343
|
+
| | |
|
|
344
|
+
|---|---|
|
|
345
|
+
| no declared package ships an exam | **SKIP** — nothing here to ask about |
|
|
346
|
+
| a test file imports one of the floor's exams | **OK**, naming the file and the exams it runs |
|
|
347
|
+
| the floor is declared and no test file imports any of its exams | **WARN**, naming the floor and every exam it ships |
|
|
348
|
+
|
|
349
|
+
It WARNs rather than FAILs: the check cannot tell a floor adopted last week from one whose exam
|
|
350
|
+
somebody deleted, and a FAIL on every fresh install is the shape that teaches a reader to skim.
|
|
351
|
+
|
|
329
352
|
## What `--format=unix` does not tell you
|
|
330
353
|
|
|
331
354
|
Measured on oxlint 1.80: a JS plugin rule that throws from `create()` is reported **once per file**,
|
|
@@ -149,10 +149,14 @@ var checkBaseline = ({ ref, root }) => {
|
|
|
149
149
|
)
|
|
150
150
|
];
|
|
151
151
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
const regressed = Object.entries(there).flatMap(([key, was]) => {
|
|
153
|
+
const now = here[key];
|
|
154
|
+
if (now === void 0) {
|
|
155
|
+
return [finding(path, "FAIL", `${key} ${was} \u2192 gone (removed from ${path} against ${ref})`)];
|
|
156
|
+
}
|
|
157
|
+
return now > was ? [finding(path, "FAIL", `${key} ${was} \u2192 ${now} (grew against ${ref})`)] : [];
|
|
158
|
+
});
|
|
159
|
+
return regressed.length > 0 ? regressed : [
|
|
156
160
|
finding(
|
|
157
161
|
path,
|
|
158
162
|
"OK",
|
|
@@ -346,7 +350,8 @@ var CHECKS = [
|
|
|
346
350
|
"drift",
|
|
347
351
|
"observability",
|
|
348
352
|
"deployed",
|
|
349
|
-
"rails"
|
|
353
|
+
"rails",
|
|
354
|
+
"exams"
|
|
350
355
|
];
|
|
351
356
|
var DoctorError = class extends Error {
|
|
352
357
|
constructor(message) {
|
|
@@ -423,6 +428,16 @@ var probesOf = async (entry, plugin) => {
|
|
|
423
428
|
})
|
|
424
429
|
);
|
|
425
430
|
};
|
|
431
|
+
var grantsOf = async (entry) => {
|
|
432
|
+
const loaded = await import(pathToFileURL(entry).href);
|
|
433
|
+
const plugin = loaded.default?.meta?.name ?? "";
|
|
434
|
+
return Object.fromEntries(
|
|
435
|
+
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
436
|
+
const grants = rule?.meta?.grants;
|
|
437
|
+
return typeof grants?.modules === "string" && typeof grants.paths === "string" ? [[`${plugin}/${name}`, { modules: grants.modules, paths: grants.paths }]] : [];
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
};
|
|
426
441
|
var presumptionsOf = async (entry) => {
|
|
427
442
|
const loaded = await import(pathToFileURL(entry).href);
|
|
428
443
|
const namespace = loaded.default?.meta?.name;
|
|
@@ -1344,7 +1359,7 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
1344
1359
|
finding4(
|
|
1345
1360
|
name,
|
|
1346
1361
|
"WARN",
|
|
1347
|
-
`${name} is installed and geonosis.json has no "${block}" block \u2014 it runs on its defaults
|
|
1362
|
+
`${name} is installed and geonosis.json has no "${block}" block \u2014 it runs on its own defaults; \`geonosis explain ${name}\` prints them beside the files this repo has`
|
|
1348
1363
|
)
|
|
1349
1364
|
];
|
|
1350
1365
|
}
|
|
@@ -1907,18 +1922,116 @@ var checkEnvelopes = ({ root }) => {
|
|
|
1907
1922
|
});
|
|
1908
1923
|
};
|
|
1909
1924
|
|
|
1910
|
-
// src/
|
|
1925
|
+
// src/exams.ts
|
|
1911
1926
|
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1927
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
1928
|
+
var SCOPE = "@geonosis/";
|
|
1929
|
+
var EXAM = /(?:^|[^$\w])([$\w]*Conformance)\b/g;
|
|
1930
|
+
var finding7 = (subject, verdict, message) => ({
|
|
1931
|
+
check: "exams",
|
|
1932
|
+
message,
|
|
1933
|
+
subject,
|
|
1934
|
+
verdict
|
|
1935
|
+
});
|
|
1936
|
+
var declaredBy = (workspaces) => {
|
|
1937
|
+
const found = /* @__PURE__ */ new Map();
|
|
1938
|
+
const provided = new Set(
|
|
1939
|
+
workspaces.map((one) => one.manifest.name).filter((one) => typeof one === "string")
|
|
1940
|
+
);
|
|
1941
|
+
for (const workspace of workspaces) {
|
|
1942
|
+
const manifest = workspace.manifest;
|
|
1943
|
+
for (const block of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
1944
|
+
const declared = manifest[block];
|
|
1945
|
+
if (typeof declared !== "object" || declared === null) continue;
|
|
1946
|
+
for (const name of Object.keys(declared)) {
|
|
1947
|
+
if (name.startsWith(SCOPE) && !found.has(name) && !provided.has(name)) {
|
|
1948
|
+
found.set(name, workspace.dir);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return [...found].map(([name, from]) => ({ from, name })).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
1954
|
+
};
|
|
1955
|
+
var declarationsOf = (dir) => {
|
|
1956
|
+
const at = join10(dir, "package.json");
|
|
1957
|
+
if (!existsSync9(at)) return "";
|
|
1958
|
+
const manifest = JSON.parse(readFileSync10(at, "utf8"));
|
|
1959
|
+
const entries = [];
|
|
1960
|
+
const walk2 = (value) => {
|
|
1961
|
+
if (typeof value === "string" && value.endsWith(".d.ts")) entries.push(value);
|
|
1962
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1963
|
+
for (const one of Object.values(value)) walk2(one);
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
walk2({ exports: manifest["exports"], types: manifest["types"] });
|
|
1967
|
+
return [...new Set(entries)].map((entry) => {
|
|
1968
|
+
try {
|
|
1969
|
+
return readFileSync10(join10(dir, entry), "utf8");
|
|
1970
|
+
} catch {
|
|
1971
|
+
return "";
|
|
1972
|
+
}
|
|
1973
|
+
}).join("\n");
|
|
1974
|
+
};
|
|
1975
|
+
var installedAt = (from, name) => {
|
|
1976
|
+
let at = from;
|
|
1977
|
+
for (; ; ) {
|
|
1978
|
+
const dir = join10(at, "node_modules", name);
|
|
1979
|
+
if (existsSync9(join10(dir, "package.json"))) return dir;
|
|
1980
|
+
const up = dirname3(at);
|
|
1981
|
+
if (up === at) return void 0;
|
|
1982
|
+
at = up;
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
var examsShippedBy = (from, name) => {
|
|
1986
|
+
const dir = installedAt(from, name);
|
|
1987
|
+
if (dir === void 0) return [];
|
|
1988
|
+
return [...new Set([...declarationsOf(dir).matchAll(EXAM)].map((one) => one[1]))];
|
|
1989
|
+
};
|
|
1990
|
+
var checkExams = ({
|
|
1991
|
+
root,
|
|
1992
|
+
workspaces
|
|
1993
|
+
}) => {
|
|
1994
|
+
const tests = testFilesUnder(root).map((path) => ({ path, source: readFileSync10(path, "utf8") }));
|
|
1995
|
+
const found = [];
|
|
1996
|
+
for (const { from, name } of declaredBy(workspaces)) {
|
|
1997
|
+
const exams = examsShippedBy(from, name);
|
|
1998
|
+
if (exams.length === 0) continue;
|
|
1999
|
+
const importing = tests.find(
|
|
2000
|
+
(one) => one.source.includes(name) && exams.some((exam) => one.source.includes(exam))
|
|
2001
|
+
);
|
|
2002
|
+
found.push(
|
|
2003
|
+
importing === void 0 ? finding7(
|
|
2004
|
+
name,
|
|
2005
|
+
"WARN",
|
|
2006
|
+
`this tree declares ${name} and no test file imports its exam \u2014 ${exams.join(", ")}. A floor bump that broke it would read exactly like a green tier: the exam is the only thing that asks.`
|
|
2007
|
+
) : finding7(
|
|
2008
|
+
name,
|
|
2009
|
+
"OK",
|
|
2010
|
+
`${relativePath(root, importing.path)} runs its exam (${exams.filter((exam) => importing.source.includes(exam)).join(", ")})`
|
|
2011
|
+
)
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
return found.length > 0 ? found : [
|
|
2015
|
+
finding7(
|
|
2016
|
+
"package.json",
|
|
2017
|
+
"SKIP",
|
|
2018
|
+
`no floor with an exam is declared here \u2014 nothing to ask about, and nothing this can pass`
|
|
2019
|
+
)
|
|
2020
|
+
];
|
|
2021
|
+
};
|
|
2022
|
+
|
|
2023
|
+
// src/claude-plugin.ts
|
|
2024
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
1912
2025
|
import { homedir as homedir2 } from "os";
|
|
1913
|
-
import { join as
|
|
2026
|
+
import { join as join11 } from "path";
|
|
1914
2027
|
var INSTALLED_PLUGINS = ".claude/plugins/installed_plugins.json";
|
|
1915
2028
|
var PLUGIN_NAME = "geonosis";
|
|
1916
2029
|
var installedPluginVersions = (home, name) => {
|
|
1917
|
-
const at =
|
|
1918
|
-
if (!
|
|
2030
|
+
const at = join11(home, INSTALLED_PLUGINS);
|
|
2031
|
+
if (!existsSync10(at)) return [];
|
|
1919
2032
|
let record;
|
|
1920
2033
|
try {
|
|
1921
|
-
record = JSON.parse(
|
|
2034
|
+
record = JSON.parse(readFileSync11(at, "utf8"));
|
|
1922
2035
|
} catch {
|
|
1923
2036
|
return [];
|
|
1924
2037
|
}
|
|
@@ -1954,9 +2067,9 @@ var checkClaudePlugin = ({
|
|
|
1954
2067
|
};
|
|
1955
2068
|
|
|
1956
2069
|
// src/loaded.ts
|
|
1957
|
-
import { readFileSync as
|
|
1958
|
-
import { join as
|
|
1959
|
-
var
|
|
2070
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2071
|
+
import { join as join12, sep as sep3 } from "path";
|
|
2072
|
+
var SCOPE2 = "@geonosis/";
|
|
1960
2073
|
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.";
|
|
1961
2074
|
var BLOCKS = [
|
|
1962
2075
|
"dependencies",
|
|
@@ -2026,7 +2139,7 @@ var declaredFor = ({
|
|
|
2026
2139
|
}
|
|
2027
2140
|
return void 0;
|
|
2028
2141
|
};
|
|
2029
|
-
var
|
|
2142
|
+
var finding8 = (subject, verdict, message) => ({
|
|
2030
2143
|
check: "loaded",
|
|
2031
2144
|
message,
|
|
2032
2145
|
subject,
|
|
@@ -2043,7 +2156,7 @@ var oneConfig = async ({
|
|
|
2043
2156
|
specifier,
|
|
2044
2157
|
workspaces
|
|
2045
2158
|
}) => {
|
|
2046
|
-
const said2 = (verdict, message) =>
|
|
2159
|
+
const said2 = (verdict, message) => finding8(config.relative, verdict, `${specifier}: ${message}`);
|
|
2047
2160
|
let loaded;
|
|
2048
2161
|
try {
|
|
2049
2162
|
loaded = await versionAt(resolveFrom(config.dir, specifier), specifier, root);
|
|
@@ -2095,10 +2208,10 @@ var copiesOf = async ({
|
|
|
2095
2208
|
found.set(at, { from: [labelOf2(workspace)], version: await pluginVersionOf(entry) });
|
|
2096
2209
|
}
|
|
2097
2210
|
if (found.size === 0) {
|
|
2098
|
-
return
|
|
2211
|
+
return finding8(specifier, "FAIL", "no workspace in this tree can resolve it at all");
|
|
2099
2212
|
}
|
|
2100
2213
|
const listed = [...found.entries()].map(([at, one]) => `${at} ${one.version} (${one.from.join(", ")})`).join("; ");
|
|
2101
|
-
return found.size === 1 ?
|
|
2214
|
+
return found.size === 1 ? finding8(specifier, "OK", `1 copy \u2014 ${listed}`) : finding8(
|
|
2102
2215
|
specifier,
|
|
2103
2216
|
"WARN",
|
|
2104
2217
|
`${found.size} copies \u2014 ${listed}. Which one oxlint runs depends on which directory its config sits in.`
|
|
@@ -2108,7 +2221,7 @@ var shipperOf = (root, bin) => {
|
|
|
2108
2221
|
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2109
2222
|
try {
|
|
2110
2223
|
const manifest = JSON.parse(
|
|
2111
|
-
|
|
2224
|
+
readFileSync12(join12(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
2112
2225
|
);
|
|
2113
2226
|
const declared = manifest.bin;
|
|
2114
2227
|
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
@@ -2123,14 +2236,14 @@ var knipNote = (root, bin) => {
|
|
|
2123
2236
|
var gitHooks = (root) => {
|
|
2124
2237
|
const lines = hookLines(root);
|
|
2125
2238
|
if (lines.length === 0) {
|
|
2126
|
-
return [
|
|
2239
|
+
return [finding8("git hooks", "SKIP", "no lefthook, husky or .githooks file here to read")];
|
|
2127
2240
|
}
|
|
2128
2241
|
const files = [...new Set(lines.map((one) => one.at))].toSorted();
|
|
2129
2242
|
return files.flatMap((at) => {
|
|
2130
2243
|
const here = lines.filter((one) => one.at === at);
|
|
2131
2244
|
const named2 = here.filter((one) => NAMES_A_BIN.test(one.line));
|
|
2132
2245
|
if (named2.length === 0) {
|
|
2133
|
-
return [
|
|
2246
|
+
return [finding8(at, "SKIP", "it names no geonosis bin, so there is nothing here to start")];
|
|
2134
2247
|
}
|
|
2135
2248
|
const wrong = named2.flatMap((one) => {
|
|
2136
2249
|
const found = VERSION_MANAGED.exec(one.line)?.groups;
|
|
@@ -2138,7 +2251,7 @@ var gitHooks = (root) => {
|
|
|
2138
2251
|
});
|
|
2139
2252
|
if (wrong.length === 0) {
|
|
2140
2253
|
return [
|
|
2141
|
-
|
|
2254
|
+
finding8(
|
|
2142
2255
|
at,
|
|
2143
2256
|
"OK",
|
|
2144
2257
|
`${named2.length} geonosis bin(s) here, every one called directly rather than through a runner`
|
|
@@ -2146,7 +2259,7 @@ var gitHooks = (root) => {
|
|
|
2146
2259
|
];
|
|
2147
2260
|
}
|
|
2148
2261
|
return wrong.map(
|
|
2149
|
-
({ bin, runner }) =>
|
|
2262
|
+
({ bin, runner }) => finding8(
|
|
2150
2263
|
at,
|
|
2151
2264
|
"FAIL",
|
|
2152
2265
|
`it runs ${bin} through ${runner} \u2014 a git hook's PATH is not the shell's and carries no version-manager shim, so ${runner} fails to START and what the author reads is its message, not this gate's. Call it directly: node_modules/.bin/${bin}${knipNote(root, bin)}`
|
|
@@ -2164,10 +2277,10 @@ var checkLoaded = async ({
|
|
|
2164
2277
|
const specifiers = /* @__PURE__ */ new Set();
|
|
2165
2278
|
for (const config of configs) {
|
|
2166
2279
|
if (config.error !== void 0) {
|
|
2167
|
-
findings.push(
|
|
2280
|
+
findings.push(finding8(config.relative, "FAIL", config.error));
|
|
2168
2281
|
continue;
|
|
2169
2282
|
}
|
|
2170
|
-
for (const specifier of config.jsPlugins.filter((name) => name.startsWith(
|
|
2283
|
+
for (const specifier of config.jsPlugins.filter((name) => name.startsWith(SCOPE2))) {
|
|
2171
2284
|
specifiers.add(specifier);
|
|
2172
2285
|
findings.push(await oneConfig({ config, root, specifier, workspaces }));
|
|
2173
2286
|
}
|
|
@@ -2190,13 +2303,13 @@ var trainVersion = async (root, specifiers) => {
|
|
|
2190
2303
|
};
|
|
2191
2304
|
|
|
2192
2305
|
// src/observability.ts
|
|
2193
|
-
import { readFileSync as
|
|
2194
|
-
import { join as
|
|
2306
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
2307
|
+
import { join as join13 } from "path";
|
|
2195
2308
|
var GEONOSIS_FILE2 = "geonosis.json";
|
|
2196
2309
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
2197
2310
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
2198
2311
|
var HEAD_TIMEOUT_MS = 3e3;
|
|
2199
|
-
var
|
|
2312
|
+
var finding9 = (verdict, subject, message) => ({
|
|
2200
2313
|
check: "observability",
|
|
2201
2314
|
message,
|
|
2202
2315
|
subject,
|
|
@@ -2205,7 +2318,7 @@ var finding8 = (verdict, subject, message) => ({
|
|
|
2205
2318
|
var readGeonosis2 = (root) => {
|
|
2206
2319
|
let text;
|
|
2207
2320
|
try {
|
|
2208
|
-
text =
|
|
2321
|
+
text = readFileSync13(join13(root, GEONOSIS_FILE2), "utf8");
|
|
2209
2322
|
} catch {
|
|
2210
2323
|
return { present: false };
|
|
2211
2324
|
}
|
|
@@ -2223,25 +2336,25 @@ var readGeonosis2 = (root) => {
|
|
|
2223
2336
|
var exporterFinding = (config) => {
|
|
2224
2337
|
const sink = config.sink;
|
|
2225
2338
|
if (typeof sink !== "string" || sink.trim() === "") {
|
|
2226
|
-
return
|
|
2339
|
+
return finding9(
|
|
2227
2340
|
"FAIL",
|
|
2228
2341
|
GEONOSIS_FILE2,
|
|
2229
2342
|
"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"
|
|
2230
2343
|
);
|
|
2231
2344
|
}
|
|
2232
2345
|
if (REACHES_NOTHING.has(sink.toLowerCase())) {
|
|
2233
|
-
return
|
|
2346
|
+
return finding9(
|
|
2234
2347
|
"WARN",
|
|
2235
2348
|
GEONOSIS_FILE2,
|
|
2236
2349
|
`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.`
|
|
2237
2350
|
);
|
|
2238
2351
|
}
|
|
2239
|
-
return
|
|
2352
|
+
return finding9("OK", GEONOSIS_FILE2, `the configured sink is "${sink}"`);
|
|
2240
2353
|
};
|
|
2241
2354
|
var reachableFinding = async (config) => {
|
|
2242
2355
|
const endpoint = config.endpoint;
|
|
2243
2356
|
if (typeof endpoint !== "string" || endpoint.trim() === "") {
|
|
2244
|
-
return
|
|
2357
|
+
return finding9(
|
|
2245
2358
|
"SKIP",
|
|
2246
2359
|
GEONOSIS_FILE2,
|
|
2247
2360
|
"no observability.endpoint was named, so whether the exporter is reachable was not asked"
|
|
@@ -2251,13 +2364,13 @@ var reachableFinding = async (config) => {
|
|
|
2251
2364
|
const timer = setTimeout(() => controller.abort(), HEAD_TIMEOUT_MS);
|
|
2252
2365
|
try {
|
|
2253
2366
|
const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
|
|
2254
|
-
return
|
|
2367
|
+
return finding9(
|
|
2255
2368
|
"OK",
|
|
2256
2369
|
GEONOSIS_FILE2,
|
|
2257
2370
|
`${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
|
|
2258
2371
|
);
|
|
2259
2372
|
} catch (error) {
|
|
2260
|
-
return
|
|
2373
|
+
return finding9(
|
|
2261
2374
|
"FAIL",
|
|
2262
2375
|
GEONOSIS_FILE2,
|
|
2263
2376
|
`${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
|
|
@@ -2269,7 +2382,7 @@ var reachableFinding = async (config) => {
|
|
|
2269
2382
|
var ageFinding = (config, root, now) => {
|
|
2270
2383
|
const file = config.lastEventFile;
|
|
2271
2384
|
if (typeof file !== "string" || file.trim() === "") {
|
|
2272
|
-
return
|
|
2385
|
+
return finding9(
|
|
2273
2386
|
"SKIP",
|
|
2274
2387
|
GEONOSIS_FILE2,
|
|
2275
2388
|
"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."
|
|
@@ -2278,27 +2391,27 @@ var ageFinding = (config, root, now) => {
|
|
|
2278
2391
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
2279
2392
|
let record;
|
|
2280
2393
|
try {
|
|
2281
|
-
record = JSON.parse(
|
|
2394
|
+
record = JSON.parse(readFileSync13(join13(root, file), "utf8"));
|
|
2282
2395
|
} catch (error) {
|
|
2283
|
-
return
|
|
2396
|
+
return finding9(
|
|
2284
2397
|
"FAIL",
|
|
2285
2398
|
file,
|
|
2286
2399
|
`the last event file could not be read: ${error.message}. A sink that has never written one has never captured anything.`
|
|
2287
2400
|
);
|
|
2288
2401
|
}
|
|
2289
2402
|
if (typeof record.at !== "number" || !Number.isFinite(record.at)) {
|
|
2290
|
-
return
|
|
2403
|
+
return finding9(
|
|
2291
2404
|
"FAIL",
|
|
2292
2405
|
file,
|
|
2293
2406
|
'the last event record has no numeric "at", so its age cannot be read \u2014 and an age nobody can read is not an age inside the window'
|
|
2294
2407
|
);
|
|
2295
2408
|
}
|
|
2296
2409
|
const ageSeconds = Math.round((now - record.at) / 1e3);
|
|
2297
|
-
return ageSeconds > maxAgeSeconds ?
|
|
2410
|
+
return ageSeconds > maxAgeSeconds ? finding9(
|
|
2298
2411
|
"FAIL",
|
|
2299
2412
|
file,
|
|
2300
2413
|
`the last event arrived ${ageSeconds}s ago, past the ${maxAgeSeconds}s window. An exporter that stopped, a key that was rotated and a sink that has been dropping since Tuesday all look exactly like this, and all of them leave a green build.`
|
|
2301
|
-
) :
|
|
2414
|
+
) : finding9(
|
|
2302
2415
|
"OK",
|
|
2303
2416
|
file,
|
|
2304
2417
|
`the last event arrived ${ageSeconds}s ago, inside the ${maxAgeSeconds}s window`
|
|
@@ -2307,16 +2420,16 @@ var ageFinding = (config, root, now) => {
|
|
|
2307
2420
|
var probeFinding = (config) => {
|
|
2308
2421
|
const probe = config.probe;
|
|
2309
2422
|
if (typeof probe === "string" && probe.trim() !== "") {
|
|
2310
|
-
return
|
|
2423
|
+
return finding9("OK", GEONOSIS_FILE2, `the probe that proves this exporter is "${probe}"`);
|
|
2311
2424
|
}
|
|
2312
2425
|
if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
|
|
2313
|
-
return
|
|
2426
|
+
return finding9(
|
|
2314
2427
|
"OK",
|
|
2315
2428
|
GEONOSIS_FILE2,
|
|
2316
2429
|
"no probe command, but a last event file is read above, so something does look at this exporter"
|
|
2317
2430
|
);
|
|
2318
2431
|
}
|
|
2319
|
-
return
|
|
2432
|
+
return finding9(
|
|
2320
2433
|
"WARN",
|
|
2321
2434
|
GEONOSIS_FILE2,
|
|
2322
2435
|
"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."
|
|
@@ -2329,7 +2442,7 @@ var checkObservability = async ({
|
|
|
2329
2442
|
const read = readGeonosis2(root);
|
|
2330
2443
|
if (read.error !== void 0) {
|
|
2331
2444
|
return [
|
|
2332
|
-
|
|
2445
|
+
finding9(
|
|
2333
2446
|
"FAIL",
|
|
2334
2447
|
GEONOSIS_FILE2,
|
|
2335
2448
|
`${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.`
|
|
@@ -2338,7 +2451,7 @@ var checkObservability = async ({
|
|
|
2338
2451
|
}
|
|
2339
2452
|
if (!read.present || read.config === void 0) {
|
|
2340
2453
|
return [
|
|
2341
|
-
|
|
2454
|
+
finding9(
|
|
2342
2455
|
"SKIP",
|
|
2343
2456
|
GEONOSIS_FILE2,
|
|
2344
2457
|
`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.`
|
|
@@ -2355,13 +2468,28 @@ var checkObservability = async ({
|
|
|
2355
2468
|
};
|
|
2356
2469
|
|
|
2357
2470
|
// src/runner.ts
|
|
2358
|
-
import { existsSync as
|
|
2359
|
-
import { join as
|
|
2471
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
|
|
2472
|
+
import { join as join14, resolve as resolve2 } from "path";
|
|
2360
2473
|
var TEST_FAILURES = "testFailures";
|
|
2361
2474
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
2362
2475
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
2363
|
-
var
|
|
2364
|
-
var
|
|
2476
|
+
var OUTPUT_FILE = /--outputFile[= ](\S+)/i;
|
|
2477
|
+
var A_SCRIPT_FILE = /\.(?:[cm]?[jt]sx?|sh|bash)$/;
|
|
2478
|
+
var wrapperNamedBy = (script, dir) => {
|
|
2479
|
+
for (const token of script.split(/[\s'"]+/)) {
|
|
2480
|
+
if (!A_SCRIPT_FILE.test(token)) continue;
|
|
2481
|
+
try {
|
|
2482
|
+
return { source: readFileSync14(resolve2(dir, token), "utf8"), where: token };
|
|
2483
|
+
} catch {
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return void 0;
|
|
2488
|
+
};
|
|
2489
|
+
var asCommand = (source) => source.replaceAll(/['"`,[\]()]/gu, " ");
|
|
2490
|
+
var ASKS_FOR_A_REPORT = /--outputFile|--reporter[= ]\S*json/i;
|
|
2491
|
+
var PARSES_A_REPORT = /JSON\.parse|numFailedTests/;
|
|
2492
|
+
var finding10 = (subject, verdict, message) => ({
|
|
2365
2493
|
check: "runner",
|
|
2366
2494
|
message,
|
|
2367
2495
|
subject,
|
|
@@ -2379,20 +2507,21 @@ var reportingCounters = (ratchet) => (ratchet?.counters ?? []).filter(
|
|
|
2379
2507
|
(entry) => entry.counter === TEST_FAILURES && stringOf(entry.report) !== ""
|
|
2380
2508
|
);
|
|
2381
2509
|
var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(entry.key);
|
|
2510
|
+
var namesReport = (entry, path) => stringOf(entry.reportPath) === path || stringOf(entry.command).includes(path);
|
|
2382
2511
|
var PROVES = /--prove\b/;
|
|
2383
2512
|
var everythingRun2 = (root, workspaces) => {
|
|
2384
2513
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2385
|
-
const dir =
|
|
2386
|
-
const workflows = (
|
|
2514
|
+
const dir = join14(root, ".github/workflows");
|
|
2515
|
+
const workflows = (existsSync11(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
|
|
2387
2516
|
try {
|
|
2388
|
-
return
|
|
2517
|
+
return readFileSync14(join14(dir, name), "utf8");
|
|
2389
2518
|
} catch {
|
|
2390
2519
|
return "";
|
|
2391
2520
|
}
|
|
2392
2521
|
});
|
|
2393
2522
|
let tiers = {};
|
|
2394
2523
|
try {
|
|
2395
|
-
const config = JSON.parse(
|
|
2524
|
+
const config = JSON.parse(readFileSync14(join14(root, "geonosis.json"), "utf8"));
|
|
2396
2525
|
tiers = config.verify ?? {};
|
|
2397
2526
|
} catch {
|
|
2398
2527
|
}
|
|
@@ -2410,7 +2539,7 @@ var proveIsWired = ({
|
|
|
2410
2539
|
const { run, tiers } = everythingRun2(root, workspaces);
|
|
2411
2540
|
if (PROVES.test(run)) {
|
|
2412
2541
|
return [
|
|
2413
|
-
|
|
2542
|
+
finding10(
|
|
2414
2543
|
RATCHET_FILE,
|
|
2415
2544
|
"OK",
|
|
2416
2545
|
`${ratchet.counters.length} counter(s), and something here runs geonosis-ratchet --prove`
|
|
@@ -2419,7 +2548,7 @@ var proveIsWired = ({
|
|
|
2419
2548
|
}
|
|
2420
2549
|
const where = tiers.at(-1) ?? "full";
|
|
2421
2550
|
return [
|
|
2422
|
-
|
|
2551
|
+
finding10(
|
|
2423
2552
|
RATCHET_FILE,
|
|
2424
2553
|
"WARN",
|
|
2425
2554
|
`${ratchet.counters.length} counter(s) and nothing here ever runs geonosis-ratchet --prove \u2014 not a verify tier, not a script, not a workflow. A counter that has never been seen reading its own planted finding has not been shown to measure anything, and it reads exactly like a clean tree. Add \`geonosis-ratchet --prove\` to the "${where}" tier in geonosis.json, or to the workflow that runs it`
|
|
@@ -2437,36 +2566,53 @@ var checkRunner = ({
|
|
|
2437
2566
|
if (script === "") return [];
|
|
2438
2567
|
const subject = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
2439
2568
|
const said2 = (verdict, message) => [
|
|
2440
|
-
|
|
2569
|
+
finding10(subject, verdict, message)
|
|
2441
2570
|
];
|
|
2442
|
-
|
|
2571
|
+
const wrapper = RUNS_A_RUNNER.test(script) ? void 0 : wrapperNamedBy(script, workspace.dir);
|
|
2572
|
+
const judged = wrapper === void 0 ? script : asCommand(wrapper.source);
|
|
2573
|
+
const read = wrapper === void 0 ? `"${script}"` : `"${script}", read one level into ${wrapper.where},`;
|
|
2574
|
+
if (!RUNS_A_RUNNER.test(judged)) {
|
|
2443
2575
|
return said2(
|
|
2444
2576
|
"OK",
|
|
2445
|
-
|
|
2577
|
+
`${read} runs neither vitest nor bun test \u2014 this check has nothing to say about it`
|
|
2446
2578
|
);
|
|
2447
2579
|
}
|
|
2448
2580
|
if (testFilesUnder(workspace.dir).length === 0) {
|
|
2449
2581
|
return said2(
|
|
2450
2582
|
"SKIP",
|
|
2451
|
-
|
|
2583
|
+
`${read} runs a test runner and there is no test file under this workspace \u2014 nothing here for its exit code to be wrong about`
|
|
2452
2584
|
);
|
|
2453
2585
|
}
|
|
2454
|
-
if (
|
|
2455
|
-
return said2(
|
|
2586
|
+
if (wrapper !== void 0 && ASKS_FOR_A_REPORT.test(wrapper.source) && PARSES_A_REPORT.test(wrapper.source)) {
|
|
2587
|
+
return said2(
|
|
2588
|
+
"OK",
|
|
2589
|
+
`${wrapper.where} spawns the runner, asks it for a JSON report and parses that report \u2014 the exit code is not the verdict here`
|
|
2590
|
+
);
|
|
2456
2591
|
}
|
|
2592
|
+
const written = OUTPUT_FILE.exec(judged)?.[1];
|
|
2457
2593
|
const counter = reading.find((entry) => covers(stringOf(entry.command), workspace, workspaces));
|
|
2458
2594
|
if (counter !== void 0) {
|
|
2459
|
-
|
|
2595
|
+
const named2 = written !== void 0 && namesReport(counter, written);
|
|
2596
|
+
return said2(
|
|
2597
|
+
"OK",
|
|
2598
|
+
named2 ? `the script writes its JSON report to ${written} and the ratchet's "${keyOf(counter)}" counter reads that path` : `read by the ratchet's "${keyOf(counter)}" counter in report mode`
|
|
2599
|
+
);
|
|
2460
2600
|
}
|
|
2461
|
-
if (RUNS_BUN_TEST.test(
|
|
2601
|
+
if (RUNS_BUN_TEST.test(judged)) {
|
|
2462
2602
|
return said2(
|
|
2463
2603
|
"OK",
|
|
2464
|
-
|
|
2604
|
+
`${read} is judged by its exit code, and bun test's exit code is a verdict: a planted failure exits 1, measured on bun 1.4.0 (2026-08-30). Nothing further is required here.`
|
|
2605
|
+
);
|
|
2606
|
+
}
|
|
2607
|
+
if (written !== void 0) {
|
|
2608
|
+
return said2(
|
|
2609
|
+
"WARN",
|
|
2610
|
+
`${read} writes the runner's JSON report to ${written} and nothing reads it \u2014 written, read by nothing, which leaves the exit code as the only verdict again. Give the ratchet a ${TEST_FAILURES} counter in report mode whose "reportPath" or command names ${written}.`
|
|
2465
2611
|
);
|
|
2466
2612
|
}
|
|
2467
2613
|
return said2(
|
|
2468
2614
|
"WARN",
|
|
2469
|
-
|
|
2615
|
+
`${read} runs vitest, whose exit code is the only verdict here; @cloudflare/vitest-pool-workers exited 0 on failing tests for weeks in a consumer. Give the ratchet a ${TEST_FAILURES} counter in report mode covering this workspace, or have the script write a JSON report something reads.`
|
|
2470
2616
|
);
|
|
2471
2617
|
});
|
|
2472
2618
|
return [...proveIsWired({ ratchet, root, workspaces }), ...perWorkspace2];
|
|
@@ -2474,7 +2620,7 @@ var checkRunner = ({
|
|
|
2474
2620
|
|
|
2475
2621
|
// src/doctor.ts
|
|
2476
2622
|
import { homedir as homedir3 } from "os";
|
|
2477
|
-
import { join as
|
|
2623
|
+
import { join as join17 } from "path";
|
|
2478
2624
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2479
2625
|
|
|
2480
2626
|
// src/engine.ts
|
|
@@ -2538,15 +2684,80 @@ var checkEngines = async ({
|
|
|
2538
2684
|
});
|
|
2539
2685
|
};
|
|
2540
2686
|
|
|
2687
|
+
// src/path-grants.ts
|
|
2688
|
+
import { readdirSync as readdirSync6 } from "fs";
|
|
2689
|
+
import { join as join15 } from "path";
|
|
2690
|
+
var OFF2 = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
2691
|
+
var severityOf2 = (level) => Array.isArray(level) ? level[0] : level;
|
|
2692
|
+
var NEVER_WALKED3 = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
|
|
2693
|
+
var LINTABLE2 = /\.[cm]?[jt]sx?$/;
|
|
2694
|
+
var filesUnder2 = (root, at = "") => {
|
|
2695
|
+
let entries;
|
|
2696
|
+
try {
|
|
2697
|
+
entries = readdirSync6(join15(root, at), { withFileTypes: true });
|
|
2698
|
+
} catch {
|
|
2699
|
+
return [];
|
|
2700
|
+
}
|
|
2701
|
+
return entries.flatMap((entry) => {
|
|
2702
|
+
const here = at === "" ? entry.name : `${at}/${entry.name}`;
|
|
2703
|
+
if (entry.isDirectory()) {
|
|
2704
|
+
return NEVER_WALKED3.has(entry.name) ? [] : filesUnder2(root, here);
|
|
2705
|
+
}
|
|
2706
|
+
return LINTABLE2.test(entry.name) ? [here] : [];
|
|
2707
|
+
});
|
|
2708
|
+
};
|
|
2709
|
+
var stringsOf2 = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
2710
|
+
var bagOf = (level) => {
|
|
2711
|
+
if (!Array.isArray(level)) return {};
|
|
2712
|
+
const [, options] = level;
|
|
2713
|
+
return typeof options === "object" && options !== null && !Array.isArray(options) ? options : {};
|
|
2714
|
+
};
|
|
2715
|
+
var matches = (patterns, file) => patterns.some((pattern) => {
|
|
2716
|
+
if (file.includes(pattern)) return true;
|
|
2717
|
+
try {
|
|
2718
|
+
return new RegExp(pattern).test(file);
|
|
2719
|
+
} catch {
|
|
2720
|
+
return false;
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
var checkPathGrants = ({
|
|
2724
|
+
config,
|
|
2725
|
+
grants,
|
|
2726
|
+
root
|
|
2727
|
+
}) => {
|
|
2728
|
+
const declared = Object.entries(grants);
|
|
2729
|
+
if (declared.length === 0) return [];
|
|
2730
|
+
const files = filesUnder2(root);
|
|
2731
|
+
const found = [];
|
|
2732
|
+
for (const [id, grant] of declared) {
|
|
2733
|
+
for (const layer of layersOf(config, id)) {
|
|
2734
|
+
if (OFF2.has(severityOf2(layer.level))) continue;
|
|
2735
|
+
const options = bagOf(layer.level);
|
|
2736
|
+
const patterns = stringsOf2(options[grant.paths]);
|
|
2737
|
+
if (patterns.length === 0) continue;
|
|
2738
|
+
const enumerated = stringsOf2(options[grant.modules]);
|
|
2739
|
+
const exempted = files.filter((file) => matches(patterns, file));
|
|
2740
|
+
if (exempted.length <= enumerated.length) continue;
|
|
2741
|
+
found.push({
|
|
2742
|
+
check: "exercised",
|
|
2743
|
+
message: `${id} exempts ${exempted.length} file${exempted.length === 1 ? "" : "s"} in this tree by \`${grant.paths}\` (${patterns.join(", ")}) and enumerates ${enumerated.length} in \`${grant.modules}\`: a pattern grants its exemption to wherever it stops, so nothing counts what accumulates on the far side. Name them \u2014 ${exempted.slice(0, 5).join(", ")}${exempted.length > 5 ? ", \u2026" : ""} \u2014 in \`${grant.modules}\`.`,
|
|
2744
|
+
subject: config.relative,
|
|
2745
|
+
verdict: "WARN"
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
return found;
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2541
2752
|
// src/rails.ts
|
|
2542
|
-
import { existsSync as
|
|
2543
|
-
import { join as
|
|
2753
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
2754
|
+
import { join as join16 } from "path";
|
|
2544
2755
|
var GEONOSIS2 = "geonosis.json";
|
|
2545
2756
|
var PROJECT = ".claude/settings.json";
|
|
2546
2757
|
var LOCAL = ".claude/settings.local.json";
|
|
2547
2758
|
var RUN_RECORD = ".geonosis/rails-run.json";
|
|
2548
2759
|
var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
|
|
2549
|
-
var
|
|
2760
|
+
var finding11 = (subject, verdict, message) => ({
|
|
2550
2761
|
check: "rails",
|
|
2551
2762
|
message,
|
|
2552
2763
|
subject,
|
|
@@ -2566,10 +2777,10 @@ var networkIn = (parsed) => {
|
|
|
2566
2777
|
};
|
|
2567
2778
|
};
|
|
2568
2779
|
var sourceAt = (path, label) => {
|
|
2569
|
-
if (!
|
|
2780
|
+
if (!existsSync12(path)) return { network: void 0, path: label, unreadable: false };
|
|
2570
2781
|
try {
|
|
2571
2782
|
return {
|
|
2572
|
-
network: networkIn(JSON.parse(
|
|
2783
|
+
network: networkIn(JSON.parse(readFileSync15(path, "utf8"))),
|
|
2573
2784
|
path: label,
|
|
2574
2785
|
unreadable: false
|
|
2575
2786
|
};
|
|
@@ -2578,11 +2789,11 @@ var sourceAt = (path, label) => {
|
|
|
2578
2789
|
}
|
|
2579
2790
|
};
|
|
2580
2791
|
var declaredEgress = (root) => {
|
|
2581
|
-
const at =
|
|
2582
|
-
if (!
|
|
2792
|
+
const at = join16(root, GEONOSIS2);
|
|
2793
|
+
if (!existsSync12(at)) return void 0;
|
|
2583
2794
|
let parsed;
|
|
2584
2795
|
try {
|
|
2585
|
-
parsed = JSON.parse(
|
|
2796
|
+
parsed = JSON.parse(readFileSync15(at, "utf8"));
|
|
2586
2797
|
} catch {
|
|
2587
2798
|
return void 0;
|
|
2588
2799
|
}
|
|
@@ -2594,10 +2805,10 @@ var declaredEgress = (root) => {
|
|
|
2594
2805
|
};
|
|
2595
2806
|
};
|
|
2596
2807
|
var deniedEgress = (root) => {
|
|
2597
|
-
const at =
|
|
2598
|
-
if (!
|
|
2808
|
+
const at = join16(root, RUN_RECORD);
|
|
2809
|
+
if (!existsSync12(at)) {
|
|
2599
2810
|
return [
|
|
2600
|
-
|
|
2811
|
+
finding11(
|
|
2601
2812
|
"deniedEgress",
|
|
2602
2813
|
"SKIP",
|
|
2603
2814
|
`there is no ${RUN_RECORD} here, so no run has been recorded for this to be a gate over`
|
|
@@ -2606,10 +2817,10 @@ var deniedEgress = (root) => {
|
|
|
2606
2817
|
}
|
|
2607
2818
|
let parsed;
|
|
2608
2819
|
try {
|
|
2609
|
-
parsed = JSON.parse(
|
|
2820
|
+
parsed = JSON.parse(readFileSync15(at, "utf8"));
|
|
2610
2821
|
} catch (error) {
|
|
2611
2822
|
return [
|
|
2612
|
-
|
|
2823
|
+
finding11(
|
|
2613
2824
|
"deniedEgress",
|
|
2614
2825
|
"FAIL",
|
|
2615
2826
|
`${RUN_RECORD} is not readable JSON (${error.message}) \u2014 a record nobody can read is not a record of zero denied attempts`
|
|
@@ -2618,11 +2829,11 @@ var deniedEgress = (root) => {
|
|
|
2618
2829
|
}
|
|
2619
2830
|
const denied = isRecord3(parsed) && Array.isArray(parsed["deniedEgress"]) ? parsed["deniedEgress"] : [];
|
|
2620
2831
|
if (denied.length === 0) {
|
|
2621
|
-
return [
|
|
2832
|
+
return [finding11("deniedEgress", "OK", "no run recorded a denied egress attempt")];
|
|
2622
2833
|
}
|
|
2623
2834
|
const hosts = denied.map((one) => isRecord3(one) && typeof one["host"] === "string" ? one["host"] : "(unnamed)").join(", ");
|
|
2624
2835
|
return [
|
|
2625
|
-
|
|
2836
|
+
finding11(
|
|
2626
2837
|
"deniedEgress",
|
|
2627
2838
|
"FAIL",
|
|
2628
2839
|
`${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`
|
|
@@ -2637,7 +2848,7 @@ var checkRails = ({
|
|
|
2637
2848
|
const declared = declaredEgress(root);
|
|
2638
2849
|
if (declared === void 0 || declared.allow.length === 0) {
|
|
2639
2850
|
return [
|
|
2640
|
-
|
|
2851
|
+
finding11(
|
|
2641
2852
|
`${GEONOSIS2} \u2192 rails.egress`,
|
|
2642
2853
|
"SKIP",
|
|
2643
2854
|
"this repo declares no egress allowlist, so there is no rendered setting for this to read back"
|
|
@@ -2647,11 +2858,11 @@ var checkRails = ({
|
|
|
2647
2858
|
const sources = [
|
|
2648
2859
|
sourceAt(managedSettings, managedSettings),
|
|
2649
2860
|
sourceAt(userSettings, userSettings),
|
|
2650
|
-
sourceAt(
|
|
2651
|
-
sourceAt(
|
|
2861
|
+
sourceAt(join16(root, PROJECT), PROJECT),
|
|
2862
|
+
sourceAt(join16(root, LOCAL), LOCAL)
|
|
2652
2863
|
];
|
|
2653
2864
|
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2654
|
-
(one) =>
|
|
2865
|
+
(one) => finding11(
|
|
2655
2866
|
one.path,
|
|
2656
2867
|
"FAIL",
|
|
2657
2868
|
"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"
|
|
@@ -2666,7 +2877,7 @@ var checkRails = ({
|
|
|
2666
2877
|
return [
|
|
2667
2878
|
...unreadable,
|
|
2668
2879
|
...declared.allow.map(
|
|
2669
|
-
(domain) => effective.has(domain) ?
|
|
2880
|
+
(domain) => effective.has(domain) ? finding11(domain, "OK", "declared, and in the settings that actually load") : finding11(
|
|
2670
2881
|
domain,
|
|
2671
2882
|
"FAIL",
|
|
2672
2883
|
`declared in ${GEONOSIS2} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
|
|
@@ -2746,7 +2957,7 @@ var exercisedOf = ({
|
|
|
2746
2957
|
workspaces
|
|
2747
2958
|
}) => Promise.all(
|
|
2748
2959
|
configs.flatMap(
|
|
2749
|
-
(config) => config.jsPlugins.filter((name) => name.startsWith(
|
|
2960
|
+
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE2)).map(async (specifier) => {
|
|
2750
2961
|
let corpus;
|
|
2751
2962
|
let entry;
|
|
2752
2963
|
try {
|
|
@@ -2763,6 +2974,7 @@ var exercisedOf = ({
|
|
|
2763
2974
|
];
|
|
2764
2975
|
}
|
|
2765
2976
|
const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
|
|
2977
|
+
const grants = await grantsOf(entry).catch(() => ({}));
|
|
2766
2978
|
const [reach, engines] = await Promise.all([
|
|
2767
2979
|
checkExercised({
|
|
2768
2980
|
config,
|
|
@@ -2776,14 +2988,14 @@ var exercisedOf = ({
|
|
|
2776
2988
|
// inside it — one is about the corpus, the other about this repo's own manifests.
|
|
2777
2989
|
checkEngines({ config, entry, workspaces })
|
|
2778
2990
|
]);
|
|
2779
|
-
return [reach, ...engines];
|
|
2991
|
+
return [reach, ...engines, ...checkPathGrants({ config, grants, root })];
|
|
2780
2992
|
})
|
|
2781
2993
|
)
|
|
2782
2994
|
);
|
|
2783
2995
|
var requiredOptionsOf = async (configs) => {
|
|
2784
2996
|
const found = await Promise.all(
|
|
2785
2997
|
configs.flatMap(
|
|
2786
|
-
(config) => config.jsPlugins.filter((name) => name.startsWith(
|
|
2998
|
+
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE2)).map(async (specifier) => {
|
|
2787
2999
|
let entry;
|
|
2788
3000
|
try {
|
|
2789
3001
|
entry = resolveFrom(config.dir, specifier);
|
|
@@ -2871,7 +3083,8 @@ var runDoctor = async ({
|
|
|
2871
3083
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
2872
3084
|
["drift", () => checkDrift({ root, workspaces })],
|
|
2873
3085
|
["deployed", () => checkDeployed({ root })],
|
|
2874
|
-
["rails", () => checkRails({ root, userSettings:
|
|
3086
|
+
["rails", () => checkRails({ root, userSettings: join17(home, USER_SETTINGS) })],
|
|
3087
|
+
["exams", () => checkExams({ root, workspaces })]
|
|
2875
3088
|
];
|
|
2876
3089
|
const collected = [];
|
|
2877
3090
|
const ran = [];
|
|
@@ -2897,6 +3110,7 @@ var ABOUT = {
|
|
|
2897
3110
|
deployed: "what the pipeline reported deploying is what the tree declares",
|
|
2898
3111
|
drift: "the gates that were set up and are no longer running",
|
|
2899
3112
|
envelope: "every gate read as many things as it was handed",
|
|
3113
|
+
exams: "every adopted floor has a test file that runs its exam",
|
|
2900
3114
|
exercised: "every enabled rule fires on at least one corpus file",
|
|
2901
3115
|
group: "the packages published on one version resolve to one version",
|
|
2902
3116
|
loaded: "the plugin oxlint would load is the one the manifest pins",
|
|
@@ -2965,11 +3179,12 @@ export {
|
|
|
2965
3179
|
ENVELOPES_DIR,
|
|
2966
3180
|
NO_ENVELOPES,
|
|
2967
3181
|
checkEnvelopes,
|
|
3182
|
+
checkExams,
|
|
2968
3183
|
INSTALLED_PLUGINS,
|
|
2969
3184
|
PLUGIN_NAME,
|
|
2970
3185
|
installedPluginVersions,
|
|
2971
3186
|
checkClaudePlugin,
|
|
2972
|
-
SCOPE,
|
|
3187
|
+
SCOPE2 as SCOPE,
|
|
2973
3188
|
satisfies,
|
|
2974
3189
|
declaredFor,
|
|
2975
3190
|
checkLoaded,
|
package/dist/doctor-cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
formatDoctor,
|
|
5
5
|
formatJson,
|
|
6
6
|
runDoctor
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GWVSC7GZ.js";
|
|
8
8
|
|
|
9
9
|
// src/doctor-cli.ts
|
|
10
10
|
import { fstatSync, statSync } from "fs";
|
|
@@ -129,6 +129,12 @@ var ABOUT = {
|
|
|
129
129
|
parity run over a tree the second config ignored. Each published a numerator and no
|
|
130
130
|
denominator. Reads the files and imports nothing of the tools that wrote them; no
|
|
131
131
|
envelopes at all is a SKIP with the sentence.`,
|
|
132
|
+
exams: `Every @geonosis/* floor a manifest here declares, against the test files in this tree: a
|
|
133
|
+
floor whose exam nothing imports is a floor whose break the tier cannot see, and a
|
|
134
|
+
bump that broke it would read exactly like a green run. The exams are read off the
|
|
135
|
+
installed floor's own declarations \u2014 every export ending in Conformance \u2014 never off a
|
|
136
|
+
list held here. WARNs, because a floor adopted last week and one whose exam somebody
|
|
137
|
+
deleted look the same from outside.`,
|
|
132
138
|
exercised: `Every rule a config enables, against the corpus the loaded plugin ships. A rule at
|
|
133
139
|
"error" that can never fire is indistinguishable from a clean tree. The line says what
|
|
134
140
|
its number is evidence ABOUT \u2014 the corpus and the probes, never this tree \u2014 and a rule
|
|
@@ -190,7 +196,7 @@ plugin-route-namespaced on its package root, while the shipped corpus says acme
|
|
|
190
196
|
|
|
191
197
|
--print-config-shape every file this reads, and the keys it reads out of each
|
|
192
198
|
|
|
193
|
-
Every run writes .geonosis/envelopes/doctor.json: the
|
|
199
|
+
Every run writes .geonosis/envelopes/doctor.json: the eleven checks CONSIDERED against the ones this
|
|
194
200
|
run read, with the rest excused by name, so a --only run can never read as a verdict about the whole
|
|
195
201
|
tree. The envelope check SKIPs that one file \u2014 this run is the one writing it.
|
|
196
202
|
|
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"];
|
|
14
|
+
declare const CHECKS: readonly ["loaded", "group", "exercised", "baseline", "runner", "envelope", "drift", "observability", "deployed", "rails", "exams"];
|
|
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
|
|
@@ -389,6 +389,23 @@ declare const corpusOfPlugin: (from: string, specifier: string) => string;
|
|
|
389
389
|
*/
|
|
390
390
|
declare const relativeToRoot: (root: string, path: string) => string;
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Whether an adopted floor is exercised by a test file at all.
|
|
394
|
+
*
|
|
395
|
+
* A verify tier declares COMMANDS, not globs, so "the exam runs in the fast tier" is not a claim
|
|
396
|
+
* anything can check. What is checkable is whether a test file importing the exam exists: a floor a
|
|
397
|
+
* consumer adopted and never exams is a floor whose break their gate cannot see, and a floor bump
|
|
398
|
+
* that broke it would read exactly like a green tier (#293).
|
|
399
|
+
*
|
|
400
|
+
* It WARNs rather than FAILs. The check cannot tell a floor adopted last week from one whose exam
|
|
401
|
+
* somebody deleted, and a FAIL on every fresh install is the shape that teaches a reader to skim
|
|
402
|
+
* the section — the lesson the `runner` check already learned about warning the wrong runner.
|
|
403
|
+
*/
|
|
404
|
+
declare const checkExams: ({ root, workspaces, }: {
|
|
405
|
+
root: string;
|
|
406
|
+
workspaces: Workspace[];
|
|
407
|
+
}) => Finding[];
|
|
408
|
+
|
|
392
409
|
/**
|
|
393
410
|
* Whether a test runner's exit code is a verdict — asked of the runner the script actually names,
|
|
394
411
|
* not of runners in general.
|
|
@@ -406,4 +423,4 @@ declare const checkRunner: ({ ratchet, root, workspaces, }: {
|
|
|
406
423
|
workspaces: Workspace[];
|
|
407
424
|
}) => Finding[];
|
|
408
425
|
|
|
409
|
-
export { CHECKS, COMPOSITION_ROOT, CONFIG_FILE, type CheckName, DEPLOYED_FILE, type DiscoveredConfig, DoctorError, type DoctorOptions, type DoctorReport, ENVELOPES_DIR, FIXED_GROUP, FLOOR_PACKAGES, type Finding, GEONOSIS_FILE, type HeldTogether, INSTALLED_PLUGINS, KIT_GROUP, type LastEventRecord, MANIFEST_FILE, type Manifest, NOT_WRITTEN, NO_ENVELOPES, type ObservabilityConfig, PLUGIN_NAME, RATCHET_FILE, READERS, type RatchetConfig, SCOPE, type Verdict, type Workspace, checkBaseline, checkClaudePlugin, checkDeployed, checkDrift, checkEnvelopes, checkExercised, checkGroup, checkLoaded, checkObservability, checkRunner, corpusOfPlugin, declaredFor, declaredGroupsOf, defaultRef, discoverConfigs, discoverWorkspaces, enabledRulesOf, formatDoctor, formatJson, installedPluginVersions, packageDirOf, pluginVersionOf, readConfig, readRatchet, relativePath, relativeToRoot, repoCorpusOf, resolveFrom, runDoctor, satisfies };
|
|
426
|
+
export { CHECKS, COMPOSITION_ROOT, CONFIG_FILE, type CheckName, DEPLOYED_FILE, type DiscoveredConfig, DoctorError, type DoctorOptions, type DoctorReport, ENVELOPES_DIR, FIXED_GROUP, FLOOR_PACKAGES, type Finding, GEONOSIS_FILE, type HeldTogether, INSTALLED_PLUGINS, KIT_GROUP, type LastEventRecord, MANIFEST_FILE, type Manifest, NOT_WRITTEN, NO_ENVELOPES, type ObservabilityConfig, PLUGIN_NAME, RATCHET_FILE, READERS, type RatchetConfig, SCOPE, type Verdict, type Workspace, checkBaseline, checkClaudePlugin, checkDeployed, checkDrift, checkEnvelopes, checkExams, checkExercised, checkGroup, checkLoaded, checkObservability, checkRunner, corpusOfPlugin, declaredFor, declaredGroupsOf, defaultRef, discoverConfigs, discoverWorkspaces, enabledRulesOf, formatDoctor, formatJson, installedPluginVersions, packageDirOf, pluginVersionOf, readConfig, readRatchet, relativePath, relativeToRoot, repoCorpusOf, resolveFrom, runDoctor, satisfies };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
checkDeployed,
|
|
23
23
|
checkDrift,
|
|
24
24
|
checkEnvelopes,
|
|
25
|
+
checkExams,
|
|
25
26
|
checkExercised,
|
|
26
27
|
checkGroup,
|
|
27
28
|
checkLoaded,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
resolveFrom,
|
|
48
49
|
runDoctor,
|
|
49
50
|
satisfies
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-GWVSC7GZ.js";
|
|
51
52
|
export {
|
|
52
53
|
CHECKS,
|
|
53
54
|
COMPOSITION_ROOT,
|
|
@@ -72,6 +73,7 @@ export {
|
|
|
72
73
|
checkDeployed,
|
|
73
74
|
checkDrift,
|
|
74
75
|
checkEnvelopes,
|
|
76
|
+
checkExams,
|
|
75
77
|
checkExercised,
|
|
76
78
|
checkGroup,
|
|
77
79
|
checkLoaded,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/doctor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
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,10 +35,10 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@geonosis/lint-parity": "2.
|
|
38
|
+
"@geonosis/lint-parity": "2.3.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@geonosis/ratchet": "2.
|
|
41
|
+
"@geonosis/ratchet": "2.3.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"oxlint": ">=1.77"
|