@geonosis/doctor 2.1.0 → 2.2.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/dist/{chunk-R2R2OYH3.js → chunk-DR5P4QYC.js} +123 -32
- package/dist/doctor-cli.js +14 -5
- package/dist/index.d.ts +39 -3
- package/dist/index.js +9 -1
- package/package.json +3 -3
|
@@ -427,6 +427,14 @@ var presumptionsOf = async (entry) => {
|
|
|
427
427
|
const loaded = await import(pathToFileURL(entry).href);
|
|
428
428
|
const namespace = loaded.default?.meta?.name;
|
|
429
429
|
return {
|
|
430
|
+
// #290: a rule that names a pattern whose floor is not shipped is a requirement with nothing to
|
|
431
|
+
// install behind it — the second consumer hand-writes the floor to satisfy the lint.
|
|
432
|
+
floors: Object.fromEntries(
|
|
433
|
+
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
434
|
+
const floor = rule?.meta?.floor;
|
|
435
|
+
return typeof floor?.name === "string" && floor.pending === true ? [[name, { name: floor.name }]] : [];
|
|
436
|
+
})
|
|
437
|
+
),
|
|
430
438
|
namespace: typeof namespace === "string" ? namespace : "",
|
|
431
439
|
presumed: Object.fromEntries(
|
|
432
440
|
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
@@ -1217,6 +1225,10 @@ var COMPOSABLE = [
|
|
|
1217
1225
|
}))
|
|
1218
1226
|
];
|
|
1219
1227
|
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
1228
|
+
var QUOTE = "['\"`]";
|
|
1229
|
+
var imports = (body, name) => new RegExp(
|
|
1230
|
+
`(?:from|import|require)\\s*\\(?\\s*${QUOTE}${name.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)}(?:/[^'"\`]*)?${QUOTE}`
|
|
1231
|
+
).test(body);
|
|
1220
1232
|
var composedHere = (root, workspaces) => {
|
|
1221
1233
|
const declared = declaredAnywhere(workspaces);
|
|
1222
1234
|
const wanted = COMPOSABLE.filter((one) => declared.has(one.name));
|
|
@@ -1230,7 +1242,7 @@ var composedHere = (root, workspaces) => {
|
|
|
1230
1242
|
continue;
|
|
1231
1243
|
}
|
|
1232
1244
|
for (const one of wanted) {
|
|
1233
|
-
if (!importing.has(one.name) && body
|
|
1245
|
+
if (!importing.has(one.name) && imports(body, one.name)) importing.set(one.name, path);
|
|
1234
1246
|
}
|
|
1235
1247
|
if (importing.size === wanted.length) break;
|
|
1236
1248
|
}
|
|
@@ -1895,9 +1907,55 @@ var checkEnvelopes = ({ root }) => {
|
|
|
1895
1907
|
});
|
|
1896
1908
|
};
|
|
1897
1909
|
|
|
1910
|
+
// src/claude-plugin.ts
|
|
1911
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1912
|
+
import { homedir as homedir2 } from "os";
|
|
1913
|
+
import { join as join10 } from "path";
|
|
1914
|
+
var INSTALLED_PLUGINS = ".claude/plugins/installed_plugins.json";
|
|
1915
|
+
var PLUGIN_NAME = "geonosis";
|
|
1916
|
+
var installedPluginVersions = (home, name) => {
|
|
1917
|
+
const at = join10(home, INSTALLED_PLUGINS);
|
|
1918
|
+
if (!existsSync9(at)) return [];
|
|
1919
|
+
let record;
|
|
1920
|
+
try {
|
|
1921
|
+
record = JSON.parse(readFileSync10(at, "utf8"));
|
|
1922
|
+
} catch {
|
|
1923
|
+
return [];
|
|
1924
|
+
}
|
|
1925
|
+
return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap((one) => typeof one.version === "string" ? [one.version] : []);
|
|
1926
|
+
};
|
|
1927
|
+
var checkClaudePlugin = ({
|
|
1928
|
+
home = homedir2(),
|
|
1929
|
+
name = PLUGIN_NAME,
|
|
1930
|
+
train
|
|
1931
|
+
}) => {
|
|
1932
|
+
if (train === void 0) return [];
|
|
1933
|
+
const installed = installedPluginVersions(home, name);
|
|
1934
|
+
if (installed.length === 0) {
|
|
1935
|
+
return [
|
|
1936
|
+
{
|
|
1937
|
+
check: "loaded",
|
|
1938
|
+
message: `this machine has no "${name}" plugin installed, so none of this release's hooks, agents or skills is loaded in any session here \u2014 a bump moved the packages and nothing moved them. Install it, or say out loud that this repo runs without the plugin`,
|
|
1939
|
+
subject: `${name} (Claude plugin)`,
|
|
1940
|
+
verdict: "WARN"
|
|
1941
|
+
}
|
|
1942
|
+
];
|
|
1943
|
+
}
|
|
1944
|
+
const drifted = installed.filter((one) => one !== train);
|
|
1945
|
+
if (drifted.length === 0) return [];
|
|
1946
|
+
return [
|
|
1947
|
+
{
|
|
1948
|
+
check: "loaded",
|
|
1949
|
+
message: `the installed plugin is ${drifted.join(", ")} and the packages here are ${train} \u2014 the plugin carries the hooks, the agents and the skills, and it moves on \`claude plugin update\`, never on an install. Every session in this repo is running the older ones`,
|
|
1950
|
+
subject: `${name} (Claude plugin)`,
|
|
1951
|
+
verdict: "WARN"
|
|
1952
|
+
}
|
|
1953
|
+
];
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1898
1956
|
// src/loaded.ts
|
|
1899
|
-
import { readFileSync as
|
|
1900
|
-
import { join as
|
|
1957
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
1958
|
+
import { join as join11, sep as sep3 } from "path";
|
|
1901
1959
|
var SCOPE = "@geonosis/";
|
|
1902
1960
|
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.";
|
|
1903
1961
|
var BLOCKS = [
|
|
@@ -2050,7 +2108,7 @@ var shipperOf = (root, bin) => {
|
|
|
2050
2108
|
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2051
2109
|
try {
|
|
2052
2110
|
const manifest = JSON.parse(
|
|
2053
|
-
|
|
2111
|
+
readFileSync11(join11(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
2054
2112
|
);
|
|
2055
2113
|
const declared = manifest.bin;
|
|
2056
2114
|
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
@@ -2098,6 +2156,7 @@ var gitHooks = (root) => {
|
|
|
2098
2156
|
};
|
|
2099
2157
|
var checkLoaded = async ({
|
|
2100
2158
|
configs,
|
|
2159
|
+
home,
|
|
2101
2160
|
root,
|
|
2102
2161
|
workspaces
|
|
2103
2162
|
}) => {
|
|
@@ -2116,12 +2175,23 @@ var checkLoaded = async ({
|
|
|
2116
2175
|
for (const specifier of [...specifiers].toSorted()) {
|
|
2117
2176
|
findings.push(await copiesOf({ root, specifier, workspaces }));
|
|
2118
2177
|
}
|
|
2178
|
+
findings.push(...checkClaudePlugin({ home, train: await trainVersion(root, [...specifiers]) }));
|
|
2119
2179
|
return findings;
|
|
2120
2180
|
};
|
|
2181
|
+
var trainVersion = async (root, specifiers) => {
|
|
2182
|
+
for (const specifier of specifiers.toSorted()) {
|
|
2183
|
+
try {
|
|
2184
|
+
return await pluginVersionOf(resolveFrom(root, specifier));
|
|
2185
|
+
} catch {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
return void 0;
|
|
2190
|
+
};
|
|
2121
2191
|
|
|
2122
2192
|
// src/observability.ts
|
|
2123
|
-
import { readFileSync as
|
|
2124
|
-
import { join as
|
|
2193
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2194
|
+
import { join as join12 } from "path";
|
|
2125
2195
|
var GEONOSIS_FILE2 = "geonosis.json";
|
|
2126
2196
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
2127
2197
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
@@ -2135,7 +2205,7 @@ var finding8 = (verdict, subject, message) => ({
|
|
|
2135
2205
|
var readGeonosis2 = (root) => {
|
|
2136
2206
|
let text;
|
|
2137
2207
|
try {
|
|
2138
|
-
text =
|
|
2208
|
+
text = readFileSync12(join12(root, GEONOSIS_FILE2), "utf8");
|
|
2139
2209
|
} catch {
|
|
2140
2210
|
return { present: false };
|
|
2141
2211
|
}
|
|
@@ -2208,7 +2278,7 @@ var ageFinding = (config, root, now) => {
|
|
|
2208
2278
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
2209
2279
|
let record;
|
|
2210
2280
|
try {
|
|
2211
|
-
record = JSON.parse(
|
|
2281
|
+
record = JSON.parse(readFileSync12(join12(root, file), "utf8"));
|
|
2212
2282
|
} catch (error) {
|
|
2213
2283
|
return finding8(
|
|
2214
2284
|
"FAIL",
|
|
@@ -2285,8 +2355,8 @@ var checkObservability = async ({
|
|
|
2285
2355
|
};
|
|
2286
2356
|
|
|
2287
2357
|
// src/runner.ts
|
|
2288
|
-
import { existsSync as
|
|
2289
|
-
import { join as
|
|
2358
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
2359
|
+
import { join as join13 } from "path";
|
|
2290
2360
|
var TEST_FAILURES = "testFailures";
|
|
2291
2361
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
2292
2362
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
@@ -2312,17 +2382,17 @@ var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(ent
|
|
|
2312
2382
|
var PROVES = /--prove\b/;
|
|
2313
2383
|
var everythingRun2 = (root, workspaces) => {
|
|
2314
2384
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2315
|
-
const dir =
|
|
2316
|
-
const workflows = (
|
|
2385
|
+
const dir = join13(root, ".github/workflows");
|
|
2386
|
+
const workflows = (existsSync10(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
|
|
2317
2387
|
try {
|
|
2318
|
-
return
|
|
2388
|
+
return readFileSync13(join13(dir, name), "utf8");
|
|
2319
2389
|
} catch {
|
|
2320
2390
|
return "";
|
|
2321
2391
|
}
|
|
2322
2392
|
});
|
|
2323
2393
|
let tiers = {};
|
|
2324
2394
|
try {
|
|
2325
|
-
const config = JSON.parse(
|
|
2395
|
+
const config = JSON.parse(readFileSync13(join13(root, "geonosis.json"), "utf8"));
|
|
2326
2396
|
tiers = config.verify ?? {};
|
|
2327
2397
|
} catch {
|
|
2328
2398
|
}
|
|
@@ -2403,8 +2473,8 @@ var checkRunner = ({
|
|
|
2403
2473
|
};
|
|
2404
2474
|
|
|
2405
2475
|
// src/doctor.ts
|
|
2406
|
-
import { homedir as
|
|
2407
|
-
import { join as
|
|
2476
|
+
import { homedir as homedir3 } from "os";
|
|
2477
|
+
import { join as join15 } from "path";
|
|
2408
2478
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2409
2479
|
|
|
2410
2480
|
// src/engine.ts
|
|
@@ -2432,16 +2502,32 @@ var checkEngines = async ({
|
|
|
2432
2502
|
workspaces
|
|
2433
2503
|
}) => {
|
|
2434
2504
|
const plugin = await presumptionsOf(entry).catch(() => void 0);
|
|
2435
|
-
if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0) {
|
|
2505
|
+
if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0 && Object.keys(plugin.floors).length === 0) {
|
|
2436
2506
|
return [];
|
|
2437
2507
|
}
|
|
2508
|
+
const pending = (rule) => {
|
|
2509
|
+
const floor = plugin.floors[rule.slice(plugin.namespace.length + 1)];
|
|
2510
|
+
if (floor === void 0) return [];
|
|
2511
|
+
return [
|
|
2512
|
+
{
|
|
2513
|
+
check: "exercised",
|
|
2514
|
+
message: `it names a pattern the kit does not ship yet (${floor.name}) \u2014 the rule is the requirement and there is nothing to install behind it, so a repo enabling it hand-writes the floor to satisfy the lint. Wait for the floor, or keep the pattern of your own and know that is what it is`,
|
|
2515
|
+
subject: `${config.relative} \u2192 ${rule}`,
|
|
2516
|
+
verdict: "WARN"
|
|
2517
|
+
}
|
|
2518
|
+
];
|
|
2519
|
+
};
|
|
2438
2520
|
const governed = governedBy(config.dir, workspaces);
|
|
2439
2521
|
const declared = new Set(governed.flatMap((one) => [...declaredIn3(one.manifest)]));
|
|
2440
2522
|
const asked = governed.map(labelOf3).join(", ");
|
|
2441
2523
|
return enabledHere2(config, plugin.namespace).flatMap((rule) => {
|
|
2524
|
+
const floorless = pending(rule);
|
|
2442
2525
|
const presumes = plugin.presumed[rule.slice(plugin.namespace.length + 1)];
|
|
2443
|
-
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name)))
|
|
2526
|
+
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) {
|
|
2527
|
+
return floorless;
|
|
2528
|
+
}
|
|
2444
2529
|
return [
|
|
2530
|
+
...floorless,
|
|
2445
2531
|
{
|
|
2446
2532
|
check: "exercised",
|
|
2447
2533
|
message: `it presumes ${presumes.engine}, and none of the manifests this config governs (${asked}) declares ${presumes.packages.join(" or ")} \u2014 enabled here it has no possible finding, which reads exactly like a clean tree. Drop it from this config, or say which package brings the engine`,
|
|
@@ -2453,8 +2539,8 @@ var checkEngines = async ({
|
|
|
2453
2539
|
};
|
|
2454
2540
|
|
|
2455
2541
|
// src/rails.ts
|
|
2456
|
-
import { existsSync as
|
|
2457
|
-
import { join as
|
|
2542
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
2543
|
+
import { join as join14 } from "path";
|
|
2458
2544
|
var GEONOSIS2 = "geonosis.json";
|
|
2459
2545
|
var PROJECT = ".claude/settings.json";
|
|
2460
2546
|
var LOCAL = ".claude/settings.local.json";
|
|
@@ -2480,10 +2566,10 @@ var networkIn = (parsed) => {
|
|
|
2480
2566
|
};
|
|
2481
2567
|
};
|
|
2482
2568
|
var sourceAt = (path, label) => {
|
|
2483
|
-
if (!
|
|
2569
|
+
if (!existsSync11(path)) return { network: void 0, path: label, unreadable: false };
|
|
2484
2570
|
try {
|
|
2485
2571
|
return {
|
|
2486
|
-
network: networkIn(JSON.parse(
|
|
2572
|
+
network: networkIn(JSON.parse(readFileSync14(path, "utf8"))),
|
|
2487
2573
|
path: label,
|
|
2488
2574
|
unreadable: false
|
|
2489
2575
|
};
|
|
@@ -2492,11 +2578,11 @@ var sourceAt = (path, label) => {
|
|
|
2492
2578
|
}
|
|
2493
2579
|
};
|
|
2494
2580
|
var declaredEgress = (root) => {
|
|
2495
|
-
const at =
|
|
2496
|
-
if (!
|
|
2581
|
+
const at = join14(root, GEONOSIS2);
|
|
2582
|
+
if (!existsSync11(at)) return void 0;
|
|
2497
2583
|
let parsed;
|
|
2498
2584
|
try {
|
|
2499
|
-
parsed = JSON.parse(
|
|
2585
|
+
parsed = JSON.parse(readFileSync14(at, "utf8"));
|
|
2500
2586
|
} catch {
|
|
2501
2587
|
return void 0;
|
|
2502
2588
|
}
|
|
@@ -2508,8 +2594,8 @@ var declaredEgress = (root) => {
|
|
|
2508
2594
|
};
|
|
2509
2595
|
};
|
|
2510
2596
|
var deniedEgress = (root) => {
|
|
2511
|
-
const at =
|
|
2512
|
-
if (!
|
|
2597
|
+
const at = join14(root, RUN_RECORD);
|
|
2598
|
+
if (!existsSync11(at)) {
|
|
2513
2599
|
return [
|
|
2514
2600
|
finding10(
|
|
2515
2601
|
"deniedEgress",
|
|
@@ -2520,7 +2606,7 @@ var deniedEgress = (root) => {
|
|
|
2520
2606
|
}
|
|
2521
2607
|
let parsed;
|
|
2522
2608
|
try {
|
|
2523
|
-
parsed = JSON.parse(
|
|
2609
|
+
parsed = JSON.parse(readFileSync14(at, "utf8"));
|
|
2524
2610
|
} catch (error) {
|
|
2525
2611
|
return [
|
|
2526
2612
|
finding10(
|
|
@@ -2561,8 +2647,8 @@ var checkRails = ({
|
|
|
2561
2647
|
const sources = [
|
|
2562
2648
|
sourceAt(managedSettings, managedSettings),
|
|
2563
2649
|
sourceAt(userSettings, userSettings),
|
|
2564
|
-
sourceAt(
|
|
2565
|
-
sourceAt(
|
|
2650
|
+
sourceAt(join14(root, PROJECT), PROJECT),
|
|
2651
|
+
sourceAt(join14(root, LOCAL), LOCAL)
|
|
2566
2652
|
];
|
|
2567
2653
|
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2568
2654
|
(one) => finding10(
|
|
@@ -2733,6 +2819,7 @@ var countsOf = (findings) => findings.reduce((counts, one) => ({ ...counts, [one
|
|
|
2733
2819
|
});
|
|
2734
2820
|
var runDoctor = async ({
|
|
2735
2821
|
baseline,
|
|
2822
|
+
home = homedir3(),
|
|
2736
2823
|
only,
|
|
2737
2824
|
oxlint,
|
|
2738
2825
|
root,
|
|
@@ -2750,7 +2837,7 @@ var runDoctor = async ({
|
|
|
2750
2837
|
[
|
|
2751
2838
|
"loaded",
|
|
2752
2839
|
async () => [
|
|
2753
|
-
...await checkLoaded({ configs, root, workspaces }),
|
|
2840
|
+
...await checkLoaded({ configs, home, root, workspaces }),
|
|
2754
2841
|
...await requiredOptionsOf(configs)
|
|
2755
2842
|
]
|
|
2756
2843
|
],
|
|
@@ -2784,7 +2871,7 @@ var runDoctor = async ({
|
|
|
2784
2871
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
2785
2872
|
["drift", () => checkDrift({ root, workspaces })],
|
|
2786
2873
|
["deployed", () => checkDeployed({ root })],
|
|
2787
|
-
["rails", () => checkRails({ root, userSettings:
|
|
2874
|
+
["rails", () => checkRails({ root, userSettings: join15(home, USER_SETTINGS) })]
|
|
2788
2875
|
];
|
|
2789
2876
|
const collected = [];
|
|
2790
2877
|
const ran = [];
|
|
@@ -2878,6 +2965,10 @@ export {
|
|
|
2878
2965
|
ENVELOPES_DIR,
|
|
2879
2966
|
NO_ENVELOPES,
|
|
2880
2967
|
checkEnvelopes,
|
|
2968
|
+
INSTALLED_PLUGINS,
|
|
2969
|
+
PLUGIN_NAME,
|
|
2970
|
+
installedPluginVersions,
|
|
2971
|
+
checkClaudePlugin,
|
|
2881
2972
|
SCOPE,
|
|
2882
2973
|
satisfies,
|
|
2883
2974
|
declaredFor,
|
package/dist/doctor-cli.js
CHANGED
|
@@ -4,15 +4,15 @@ import {
|
|
|
4
4
|
formatDoctor,
|
|
5
5
|
formatJson,
|
|
6
6
|
runDoctor
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DR5P4QYC.js";
|
|
8
8
|
|
|
9
9
|
// src/doctor-cli.ts
|
|
10
10
|
import { fstatSync, statSync } from "fs";
|
|
11
|
-
import { resolve } from "path";
|
|
11
|
+
import { resolve as resolve2 } from "path";
|
|
12
12
|
|
|
13
13
|
// src/envelope-write.ts
|
|
14
14
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
|
-
import { dirname, join } from "path";
|
|
15
|
+
import { dirname, join, resolve } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
18
18
|
var UnbalancedEnvelope = class extends Error {
|
|
@@ -23,6 +23,14 @@ var UnbalancedEnvelope = class extends Error {
|
|
|
23
23
|
};
|
|
24
24
|
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
25
25
|
var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
|
|
26
|
+
var FORBIDDEN_ROOT = "GEONOSIS_ENVELOPES_FORBIDDEN_ROOT";
|
|
27
|
+
var refuseForbiddenRoot = (root) => {
|
|
28
|
+
const forbidden = process.env[FORBIDDEN_ROOT];
|
|
29
|
+
if (forbidden === void 0 || resolve(forbidden) !== resolve(root)) return;
|
|
30
|
+
throw new UnbalancedEnvelope(
|
|
31
|
+
`${root} is off limits to envelope writers in this process (${FORBIDDEN_ROOT}) \u2014 a run that writes one into a shared root races every other run reading it, and leaves a file the next one takes for real. Point this at a scratch root of its own: tooling/scratch-dir.ts.`
|
|
32
|
+
);
|
|
33
|
+
};
|
|
26
34
|
var writeEnvelope = ({
|
|
27
35
|
envelope,
|
|
28
36
|
next,
|
|
@@ -46,6 +54,7 @@ var writeEnvelope = ({
|
|
|
46
54
|
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
47
55
|
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
48
56
|
}
|
|
57
|
+
refuseForbiddenRoot(root);
|
|
49
58
|
const at = envelopePath(root, envelope.tool);
|
|
50
59
|
mkdirSync(dirname(at), { recursive: true });
|
|
51
60
|
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
@@ -225,8 +234,8 @@ var parseDoctorArgs = (argv, cwd) => {
|
|
|
225
234
|
...baseline === void 0 ? {} : { baseline },
|
|
226
235
|
json,
|
|
227
236
|
...only === void 0 || only.length === 0 ? {} : { only },
|
|
228
|
-
...read["--oxlint"] === void 0 ? {} : { oxlint:
|
|
229
|
-
root:
|
|
237
|
+
...read["--oxlint"] === void 0 ? {} : { oxlint: resolve2(cwd, read["--oxlint"]) },
|
|
238
|
+
root: resolve2(cwd, read["--root"] ?? cwd),
|
|
230
239
|
strict
|
|
231
240
|
};
|
|
232
241
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,8 @@ type RatchetConfig = {
|
|
|
66
66
|
counters: Record<string, unknown>[];
|
|
67
67
|
};
|
|
68
68
|
type DoctorOptions = {
|
|
69
|
+
/** Where `~/.claude` lives — the plugin registry and the user settings the doctor reads; the home directory unless a caller says otherwise. */
|
|
70
|
+
home?: string;
|
|
69
71
|
/**
|
|
70
72
|
* Present only when a baseline comparison was asked for, because making one is the one part of
|
|
71
73
|
* this tool that runs git — and the release check runs the doctor inside repos other sessions
|
|
@@ -138,7 +140,7 @@ declare const discoverConfigs: (root: string) => DiscoveredConfig[];
|
|
|
138
140
|
declare const discoverWorkspaces: (root: string) => Workspace[];
|
|
139
141
|
declare const readRatchet: (root: string) => RatchetConfig | undefined;
|
|
140
142
|
|
|
141
|
-
declare const runDoctor: ({ baseline, only, oxlint, root, strict, }: DoctorOptions) => Promise<DoctorReport>;
|
|
143
|
+
declare const runDoctor: ({ baseline, home, only, oxlint, root, strict, }: DoctorOptions) => Promise<DoctorReport>;
|
|
142
144
|
|
|
143
145
|
/** The file `create-geonosis --domains` writes, held against the scaffold's own name by a test. */
|
|
144
146
|
declare const COMPOSITION_ROOT = "src/platform.ts";
|
|
@@ -265,6 +267,39 @@ declare const checkGroup: ({ groups, root, workspaces, }: {
|
|
|
265
267
|
workspaces: Workspace[];
|
|
266
268
|
}) => Finding[];
|
|
267
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Where Claude Code records what it has installed.
|
|
272
|
+
*
|
|
273
|
+
* MEASURED 2026-09-02 (`~/.claude/plugins/installed_plugins.json`, `version: 2`): a map keyed
|
|
274
|
+
* `<plugin>@<marketplace>`, each value a list of installations carrying `version`, `installPath`
|
|
275
|
+
* and the commit it came from. This is the file `claude plugin list` answers from.
|
|
276
|
+
*/
|
|
277
|
+
declare const INSTALLED_PLUGINS = ".claude/plugins/installed_plugins.json";
|
|
278
|
+
/** The plugin this kit publishes. Its own name, the way `FIXED_GROUP` is its own group. */
|
|
279
|
+
declare const PLUGIN_NAME = "geonosis";
|
|
280
|
+
/**
|
|
281
|
+
* Every version of one plugin this machine has installed, in the order the record lists them.
|
|
282
|
+
*
|
|
283
|
+
* A name can be installed from more than one marketplace and at more than one scope, so this
|
|
284
|
+
* answers with a list rather than picking one: a session loads what it loads, and a doctor that
|
|
285
|
+
* chose for the reader would be reporting about an installation nobody has.
|
|
286
|
+
*/
|
|
287
|
+
declare const installedPluginVersions: (home: string, name: string) => string[];
|
|
288
|
+
/**
|
|
289
|
+
* #133: a consumer's npm bump does not move the CLAUDE PLUGIN.
|
|
290
|
+
*
|
|
291
|
+
* The hooks, the agents and the skills live in the plugin, and it updates on `claude plugin update`
|
|
292
|
+
* and never on an install — so a repo can be on this release's packages while every session in it
|
|
293
|
+
* runs the hooks of the one before, and a refusal added this release cannot fire. That is the
|
|
294
|
+
* declared-versus-loaded question one surface out, and it is this check's to ask.
|
|
295
|
+
*/
|
|
296
|
+
declare const checkClaudePlugin: ({ home, name, train, }: {
|
|
297
|
+
home?: string;
|
|
298
|
+
name?: string;
|
|
299
|
+
/** The version the package train is on here, or nothing when this tree declares none. */
|
|
300
|
+
train: string | undefined;
|
|
301
|
+
}) => Finding[];
|
|
302
|
+
|
|
268
303
|
/** The scope this tool asks about. A config's other plugins are somebody else's release to check. */
|
|
269
304
|
declare const SCOPE = "@geonosis/";
|
|
270
305
|
/**
|
|
@@ -286,8 +321,9 @@ declare const declaredFor: ({ dir, specifier, workspaces, }: {
|
|
|
286
321
|
at: string;
|
|
287
322
|
spec: string;
|
|
288
323
|
} | undefined;
|
|
289
|
-
declare const checkLoaded: ({ configs, root, workspaces, }: {
|
|
324
|
+
declare const checkLoaded: ({ configs, home, root, workspaces, }: {
|
|
290
325
|
configs: DiscoveredConfig[];
|
|
326
|
+
home?: string;
|
|
291
327
|
root: string;
|
|
292
328
|
workspaces: Workspace[];
|
|
293
329
|
}) => Promise<Finding[]>;
|
|
@@ -370,4 +406,4 @@ declare const checkRunner: ({ ratchet, root, workspaces, }: {
|
|
|
370
406
|
workspaces: Workspace[];
|
|
371
407
|
}) => Finding[];
|
|
372
408
|
|
|
373
|
-
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, KIT_GROUP, 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, checkGroup, checkLoaded, checkObservability, checkRunner, corpusOfPlugin, declaredFor, declaredGroupsOf, defaultRef, discoverConfigs, discoverWorkspaces, enabledRulesOf, formatDoctor, formatJson, packageDirOf, pluginVersionOf, readConfig, readRatchet, relativePath, relativeToRoot, repoCorpusOf, resolveFrom, runDoctor, satisfies };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -8,14 +8,17 @@ import {
|
|
|
8
8
|
FIXED_GROUP,
|
|
9
9
|
FLOOR_PACKAGES,
|
|
10
10
|
GEONOSIS_FILE,
|
|
11
|
+
INSTALLED_PLUGINS,
|
|
11
12
|
KIT_GROUP,
|
|
12
13
|
MANIFEST_FILE,
|
|
13
14
|
NOT_WRITTEN,
|
|
14
15
|
NO_ENVELOPES,
|
|
16
|
+
PLUGIN_NAME,
|
|
15
17
|
RATCHET_FILE,
|
|
16
18
|
READERS,
|
|
17
19
|
SCOPE,
|
|
18
20
|
checkBaseline,
|
|
21
|
+
checkClaudePlugin,
|
|
19
22
|
checkDeployed,
|
|
20
23
|
checkDrift,
|
|
21
24
|
checkEnvelopes,
|
|
@@ -33,6 +36,7 @@ import {
|
|
|
33
36
|
enabledRulesOf,
|
|
34
37
|
formatDoctor,
|
|
35
38
|
formatJson,
|
|
39
|
+
installedPluginVersions,
|
|
36
40
|
packageDirOf,
|
|
37
41
|
pluginVersionOf,
|
|
38
42
|
readConfig,
|
|
@@ -43,7 +47,7 @@ import {
|
|
|
43
47
|
resolveFrom,
|
|
44
48
|
runDoctor,
|
|
45
49
|
satisfies
|
|
46
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-DR5P4QYC.js";
|
|
47
51
|
export {
|
|
48
52
|
CHECKS,
|
|
49
53
|
COMPOSITION_ROOT,
|
|
@@ -54,14 +58,17 @@ export {
|
|
|
54
58
|
FIXED_GROUP,
|
|
55
59
|
FLOOR_PACKAGES,
|
|
56
60
|
GEONOSIS_FILE,
|
|
61
|
+
INSTALLED_PLUGINS,
|
|
57
62
|
KIT_GROUP,
|
|
58
63
|
MANIFEST_FILE,
|
|
59
64
|
NOT_WRITTEN,
|
|
60
65
|
NO_ENVELOPES,
|
|
66
|
+
PLUGIN_NAME,
|
|
61
67
|
RATCHET_FILE,
|
|
62
68
|
READERS,
|
|
63
69
|
SCOPE,
|
|
64
70
|
checkBaseline,
|
|
71
|
+
checkClaudePlugin,
|
|
65
72
|
checkDeployed,
|
|
66
73
|
checkDrift,
|
|
67
74
|
checkEnvelopes,
|
|
@@ -79,6 +86,7 @@ export {
|
|
|
79
86
|
enabledRulesOf,
|
|
80
87
|
formatDoctor,
|
|
81
88
|
formatJson,
|
|
89
|
+
installedPluginVersions,
|
|
82
90
|
packageDirOf,
|
|
83
91
|
pluginVersionOf,
|
|
84
92
|
readConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/doctor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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,10 +35,10 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@geonosis/lint-parity": "2.
|
|
38
|
+
"@geonosis/lint-parity": "2.2.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@geonosis/ratchet": "2.
|
|
41
|
+
"@geonosis/ratchet": "2.2.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"oxlint": ">=1.77"
|