@geonosis/doctor 2.0.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/README.md +9 -7
- package/dist/{chunk-SMCRXSH5.js → chunk-DR5P4QYC.js} +672 -490
- package/dist/doctor-cli.js +127 -4
- package/dist/index.d.ts +42 -4
- package/dist/index.js +9 -1
- package/package.json +5 -2
|
@@ -319,6 +319,22 @@ var checkDeployed = ({ root }) => {
|
|
|
319
319
|
];
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
+
// src/repo-corpus.ts
|
|
323
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
324
|
+
import { join as join3 } from "path";
|
|
325
|
+
var GEONOSIS_FILE = "geonosis.json";
|
|
326
|
+
var repoCorpusOf = (root) => {
|
|
327
|
+
const path = join3(root, GEONOSIS_FILE);
|
|
328
|
+
if (!existsSync2(path)) return void 0;
|
|
329
|
+
try {
|
|
330
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
331
|
+
const declared = parsed.doctor?.corpus;
|
|
332
|
+
return typeof declared === "string" && declared !== "" ? join3(root, declared) : void 0;
|
|
333
|
+
} catch {
|
|
334
|
+
return void 0;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
322
338
|
// src/types.ts
|
|
323
339
|
var CHECKS = [
|
|
324
340
|
"loaded",
|
|
@@ -340,9 +356,9 @@ var DoctorError = class extends Error {
|
|
|
340
356
|
};
|
|
341
357
|
|
|
342
358
|
// src/resolve.ts
|
|
343
|
-
import { existsSync as
|
|
359
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
344
360
|
import { createRequire } from "module";
|
|
345
|
-
import { dirname, join as
|
|
361
|
+
import { dirname, join as join4 } from "path";
|
|
346
362
|
import { pathToFileURL } from "url";
|
|
347
363
|
var packageNameOf = (specifier) => {
|
|
348
364
|
const parts2 = specifier.split("/");
|
|
@@ -350,13 +366,13 @@ var packageNameOf = (specifier) => {
|
|
|
350
366
|
};
|
|
351
367
|
var resolveFrom = (dir, specifier) => {
|
|
352
368
|
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
353
|
-
return createRequire(
|
|
369
|
+
return createRequire(join4(dir, "noop.js")).resolve(specifier);
|
|
354
370
|
}
|
|
355
371
|
const name = packageNameOf(specifier);
|
|
356
372
|
let at = dir;
|
|
357
373
|
for (; ; ) {
|
|
358
|
-
if (
|
|
359
|
-
return createRequire(
|
|
374
|
+
if (existsSync3(join4(at, "node_modules", name, "package.json"))) {
|
|
375
|
+
return createRequire(join4(at, "noop.js")).resolve(specifier);
|
|
360
376
|
}
|
|
361
377
|
const parent = dirname(at);
|
|
362
378
|
if (parent === at) break;
|
|
@@ -369,10 +385,10 @@ var resolveFrom = (dir, specifier) => {
|
|
|
369
385
|
var packageDirOf = (entry, name) => {
|
|
370
386
|
let dir = dirname(entry);
|
|
371
387
|
for (; ; ) {
|
|
372
|
-
const manifest =
|
|
373
|
-
if (
|
|
388
|
+
const manifest = join4(dir, "package.json");
|
|
389
|
+
if (existsSync3(manifest)) {
|
|
374
390
|
try {
|
|
375
|
-
const parsed = JSON.parse(
|
|
391
|
+
const parsed = JSON.parse(readFileSync4(manifest, "utf8"));
|
|
376
392
|
if (parsed.name === name) return dir;
|
|
377
393
|
} catch {
|
|
378
394
|
}
|
|
@@ -411,6 +427,14 @@ var presumptionsOf = async (entry) => {
|
|
|
411
427
|
const loaded = await import(pathToFileURL(entry).href);
|
|
412
428
|
const namespace = loaded.default?.meta?.name;
|
|
413
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
|
+
),
|
|
414
438
|
namespace: typeof namespace === "string" ? namespace : "",
|
|
415
439
|
presumed: Object.fromEntries(
|
|
416
440
|
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
@@ -421,7 +445,7 @@ var presumptionsOf = async (entry) => {
|
|
|
421
445
|
)
|
|
422
446
|
};
|
|
423
447
|
};
|
|
424
|
-
var corpusOfPlugin = (from, specifier) =>
|
|
448
|
+
var corpusOfPlugin = (from, specifier) => join4(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
|
|
425
449
|
var real = (path) => {
|
|
426
450
|
try {
|
|
427
451
|
return realpathSync(path);
|
|
@@ -431,14 +455,13 @@ var real = (path) => {
|
|
|
431
455
|
};
|
|
432
456
|
var relativeToRoot = (root, path) => relativePath(real(root), real(path));
|
|
433
457
|
|
|
434
|
-
// src/
|
|
435
|
-
import {
|
|
436
|
-
import {
|
|
437
|
-
import { join as join5, sep as sep2 } from "path";
|
|
458
|
+
// src/group.ts
|
|
459
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
460
|
+
import { join as join6 } from "path";
|
|
438
461
|
|
|
439
462
|
// src/hooks.ts
|
|
440
|
-
import { existsSync as
|
|
441
|
-
import { join as
|
|
463
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
464
|
+
import { join as join5 } from "path";
|
|
442
465
|
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
443
466
|
var HOOK_DIRS = [".husky", ".githooks"];
|
|
444
467
|
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
@@ -447,26 +470,267 @@ var hookLines = (root) => {
|
|
|
447
470
|
const found = [];
|
|
448
471
|
const read = (path) => {
|
|
449
472
|
const at = relativePath(root, path);
|
|
450
|
-
for (const line of
|
|
473
|
+
for (const line of readFileSync5(path, "utf8").split("\n")) {
|
|
451
474
|
if (line.trim() !== "") found.push({ at, line });
|
|
452
475
|
}
|
|
453
476
|
};
|
|
454
477
|
for (const name of HOOK_FILES) {
|
|
455
|
-
const path =
|
|
456
|
-
if (
|
|
478
|
+
const path = join5(root, name);
|
|
479
|
+
if (existsSync4(path)) read(path);
|
|
457
480
|
}
|
|
458
481
|
for (const name of HOOK_DIRS) {
|
|
459
|
-
const dir =
|
|
460
|
-
if (!
|
|
482
|
+
const dir = join5(root, name);
|
|
483
|
+
if (!existsSync4(dir)) continue;
|
|
461
484
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
462
485
|
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
463
|
-
read(
|
|
486
|
+
read(join5(dir, entry.name));
|
|
464
487
|
}
|
|
465
488
|
}
|
|
466
489
|
}
|
|
467
490
|
return found;
|
|
468
491
|
};
|
|
469
492
|
|
|
493
|
+
// src/group.ts
|
|
494
|
+
var FIXED_GROUP = [
|
|
495
|
+
"@geonosis/cli",
|
|
496
|
+
"@geonosis/doctor",
|
|
497
|
+
"@geonosis/evals",
|
|
498
|
+
"@geonosis/integrations",
|
|
499
|
+
"@geonosis/ledger",
|
|
500
|
+
"@geonosis/lint-parity",
|
|
501
|
+
"@geonosis/mcp",
|
|
502
|
+
"@geonosis/observability",
|
|
503
|
+
"@geonosis/oxlint-plugin-biological-architecture",
|
|
504
|
+
"@geonosis/policy",
|
|
505
|
+
"@geonosis/rails",
|
|
506
|
+
"@geonosis/ratchet",
|
|
507
|
+
"@geonosis/release",
|
|
508
|
+
"@geonosis/review",
|
|
509
|
+
"@geonosis/testbed",
|
|
510
|
+
"@geonosis/themekit",
|
|
511
|
+
"@geonosis/verify",
|
|
512
|
+
"@geonosis/verify-arch",
|
|
513
|
+
"@geonosis/visual-diff",
|
|
514
|
+
"@geonosis/walk",
|
|
515
|
+
"create-geonosis",
|
|
516
|
+
"geonosis"
|
|
517
|
+
];
|
|
518
|
+
var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
|
|
519
|
+
var declaredGroupsOf = (root) => {
|
|
520
|
+
const path = join6(root, GEONOSIS_FILE);
|
|
521
|
+
if (!existsSync5(path)) return void 0;
|
|
522
|
+
let parsed;
|
|
523
|
+
try {
|
|
524
|
+
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
525
|
+
} catch {
|
|
526
|
+
return void 0;
|
|
527
|
+
}
|
|
528
|
+
const declared = parsed.doctor?.groups;
|
|
529
|
+
if (declared === void 0) return void 0;
|
|
530
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
531
|
+
throw new DoctorError(
|
|
532
|
+
`${GEONOSIS_FILE} declares doctor.groups and names no group in it \u2014 say which packages must move together, or remove the key and get the kit's own group`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return declared.map((one, at) => {
|
|
536
|
+
const group = one;
|
|
537
|
+
if (typeof group.name !== "string" || !Array.isArray(group.packages) || group.packages.length < 2) {
|
|
538
|
+
throw new DoctorError(
|
|
539
|
+
`${GEONOSIS_FILE} doctor.groups[${at}] needs a name and at least two packages \u2014 one package cannot disagree with itself`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return { name: group.name, packages: group.packages };
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
546
|
+
var versionOf = (dir, name) => {
|
|
547
|
+
let entry;
|
|
548
|
+
try {
|
|
549
|
+
entry = resolveFrom(dir, name);
|
|
550
|
+
} catch {
|
|
551
|
+
return { kind: "absent" };
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const manifest = JSON.parse(
|
|
555
|
+
readFileSync6(join6(packageDirOf(entry, name), "package.json"), "utf8")
|
|
556
|
+
);
|
|
557
|
+
return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
|
|
558
|
+
} catch (error) {
|
|
559
|
+
return { kind: "unreadable", why: String(error.message).split("\n")[0] ?? "" };
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
var finding3 = (subject, verdict, message) => ({
|
|
563
|
+
check: "group",
|
|
564
|
+
message,
|
|
565
|
+
subject,
|
|
566
|
+
verdict
|
|
567
|
+
});
|
|
568
|
+
var said = (versions) => [...versions.entries()].map(([version, from]) => `${version} (${from.join(", ")})`).join("; ");
|
|
569
|
+
var repoWide = (name, found) => {
|
|
570
|
+
const versions = /* @__PURE__ */ new Map();
|
|
571
|
+
for (const one of found) versions.set(one.version, [...versions.get(one.version) ?? [], one.at]);
|
|
572
|
+
if (versions.size === 1) return void 0;
|
|
573
|
+
return finding3(
|
|
574
|
+
name,
|
|
575
|
+
"FAIL",
|
|
576
|
+
`${versions.size} versions in one tree \u2014 ${said(versions)}. They are published as one number, so a tree holding two is a tree where one workspace is running code another has already replaced; remove the nested copies and reinstall`
|
|
577
|
+
);
|
|
578
|
+
};
|
|
579
|
+
var perWorkspace = (group, at, found) => {
|
|
580
|
+
if (new Set(found.map((one) => one.version)).size === 1) return void 0;
|
|
581
|
+
const listed = found.map((one) => `${one.name} ${one.version}`).join(", ");
|
|
582
|
+
return finding3(
|
|
583
|
+
at,
|
|
584
|
+
"FAIL",
|
|
585
|
+
`${group.name} does not hold together here \u2014 ${listed}. Every member is cut on one version, so a workspace resolving two of them is a bump that landed halfway`
|
|
586
|
+
);
|
|
587
|
+
};
|
|
588
|
+
var DOORS = ["@geonosis/cli", "geonosis"];
|
|
589
|
+
var manifestOf = (dir, name) => {
|
|
590
|
+
try {
|
|
591
|
+
return JSON.parse(
|
|
592
|
+
readFileSync6(join6(packageDirOf(resolveFrom(dir, name), name), "package.json"), "utf8")
|
|
593
|
+
);
|
|
594
|
+
} catch {
|
|
595
|
+
return void 0;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var binNamesOf = (manifest, name) => {
|
|
599
|
+
const declared = manifest?.bin;
|
|
600
|
+
return typeof declared === "string" ? [name.replace(/^@[^/]+\//, "")] : Object.keys(declared ?? {});
|
|
601
|
+
};
|
|
602
|
+
var declaredIn2 = (manifest) => [
|
|
603
|
+
...new Set(
|
|
604
|
+
[
|
|
605
|
+
manifest.dependencies,
|
|
606
|
+
manifest.devDependencies,
|
|
607
|
+
manifest.optionalDependencies,
|
|
608
|
+
manifest.peerDependencies
|
|
609
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
610
|
+
)
|
|
611
|
+
];
|
|
612
|
+
var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(lines);
|
|
613
|
+
var workspaceCaller = (workspaces, bins) => {
|
|
614
|
+
for (const workspace of workspaces) {
|
|
615
|
+
if (workspace.relative === "") continue;
|
|
616
|
+
for (const [script, line] of Object.entries(workspace.manifest.scripts ?? {})) {
|
|
617
|
+
if (bins.some((bin) => calls(line, bin))) return { at: workspace.relative, script };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return void 0;
|
|
621
|
+
};
|
|
622
|
+
var rootDeclarations = (root, workspaces) => {
|
|
623
|
+
const here = workspaces.find((one) => one.relative === "");
|
|
624
|
+
if (here === void 0) return [];
|
|
625
|
+
const declared = declaredIn2(here.manifest);
|
|
626
|
+
const door = DOORS.find((name) => declared.includes(name));
|
|
627
|
+
if (door === void 0) return [];
|
|
628
|
+
const owned = new Set(
|
|
629
|
+
[door, ...DOORS].flatMap((name) => {
|
|
630
|
+
const manifest = manifestOf(root, name);
|
|
631
|
+
return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
const callers = [
|
|
635
|
+
...Object.values(here.manifest.scripts ?? {}),
|
|
636
|
+
...hookLines(root).map((one) => one.line)
|
|
637
|
+
].join("\n");
|
|
638
|
+
const loadedHere = new Set(readConfig(join6(root, CONFIG_FILE), root).jsPlugins);
|
|
639
|
+
const spare = declared.filter(
|
|
640
|
+
(name) => !DOORS.includes(name) && owned.has(name) && !loadedHere.has(name) && !binNamesOf(manifestOf(root, name), name).some((bin) => calls(callers, bin))
|
|
641
|
+
);
|
|
642
|
+
if (spare.length === 0) {
|
|
643
|
+
return [
|
|
644
|
+
finding3(
|
|
645
|
+
"package.json",
|
|
646
|
+
"OK",
|
|
647
|
+
`every @geonosis package this root declares is ${door} or a bin its own scripts call by name`
|
|
648
|
+
)
|
|
649
|
+
];
|
|
650
|
+
}
|
|
651
|
+
return spare.map((name) => {
|
|
652
|
+
const bins = binNamesOf(manifestOf(root, name), name);
|
|
653
|
+
const inWorkspace = workspaceCaller(workspaces, bins);
|
|
654
|
+
if (inWorkspace !== void 0) {
|
|
655
|
+
return finding3(
|
|
656
|
+
name,
|
|
657
|
+
"OK",
|
|
658
|
+
`no root script calls its bin, and ${inWorkspace.at}'s "${inWorkspace.script}" script does \u2014 under a hoisted linker the ROOT declaration is what makes that bin resolvable there, so this is the declaration doing its job`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return finding3(
|
|
662
|
+
name,
|
|
663
|
+
"WARN",
|
|
664
|
+
`the root declares it and no script in this repo calls its bin \u2014 ${door} already brings it, so this second declaration is a version that can disagree with the door's, and every tool that asks what is used here reads it as unused. Drop it from the root, or declare it in the workspace whose code imports it`
|
|
665
|
+
);
|
|
666
|
+
});
|
|
667
|
+
};
|
|
668
|
+
var checkGroup = ({
|
|
669
|
+
groups,
|
|
670
|
+
root,
|
|
671
|
+
workspaces
|
|
672
|
+
}) => {
|
|
673
|
+
const findings = [...rootDeclarations(root, workspaces)];
|
|
674
|
+
for (const group of groups ?? [KIT_GROUP]) {
|
|
675
|
+
const found = [];
|
|
676
|
+
for (const workspace of workspaces) {
|
|
677
|
+
const at = labelOf(workspace);
|
|
678
|
+
const here = [];
|
|
679
|
+
for (const name of group.packages) {
|
|
680
|
+
const reading = versionOf(workspace.dir, name);
|
|
681
|
+
if (reading.kind === "read") here.push({ at, name, version: reading.version });
|
|
682
|
+
if (reading.kind === "unreadable") {
|
|
683
|
+
findings.push(
|
|
684
|
+
finding3(
|
|
685
|
+
`${at} \u2192 ${name}`,
|
|
686
|
+
"UNJUDGED",
|
|
687
|
+
`it resolves from here and ${reading.why} \u2014 this copy could be any version, so the group cannot be said to hold or not to hold`
|
|
688
|
+
)
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
|
|
693
|
+
if (split2 !== void 0) findings.push(split2);
|
|
694
|
+
found.push(...here);
|
|
695
|
+
}
|
|
696
|
+
if (found.length === 0) {
|
|
697
|
+
findings.push(
|
|
698
|
+
finding3(
|
|
699
|
+
group.name,
|
|
700
|
+
"SKIP",
|
|
701
|
+
`no workspace under ${root} resolves any of ${group.packages.join(", ")}, so nothing here versions together`
|
|
702
|
+
)
|
|
703
|
+
);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const names = [...new Set(found.map((one) => one.name))].toSorted();
|
|
707
|
+
const split = names.flatMap((name) => {
|
|
708
|
+
const line = repoWide(
|
|
709
|
+
name,
|
|
710
|
+
found.filter((one) => one.name === name)
|
|
711
|
+
);
|
|
712
|
+
return line === void 0 ? [] : [line];
|
|
713
|
+
});
|
|
714
|
+
findings.push(...split);
|
|
715
|
+
if (split.length === 0) {
|
|
716
|
+
const versions = [...new Set(found.map((one) => one.version))];
|
|
717
|
+
findings.push(
|
|
718
|
+
finding3(
|
|
719
|
+
group.name,
|
|
720
|
+
"OK",
|
|
721
|
+
`${names.length} of ${group.packages.length} installed, at ${versions.join(", ")}, one copy each across ${new Set(found.map((one) => one.at)).size} workspaces`
|
|
722
|
+
)
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return findings;
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/drift.ts
|
|
730
|
+
import { closeSync, existsSync as existsSync6, openSync, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync } from "fs";
|
|
731
|
+
import { homedir } from "os";
|
|
732
|
+
import { join as join7, sep as sep2 } from "path";
|
|
733
|
+
|
|
470
734
|
// src/overrides.ts
|
|
471
735
|
var SPECIAL = /* @__PURE__ */ new Set(["$", "(", ")", "+", ".", "/", "@", "\\", "^", "|"]);
|
|
472
736
|
var globToRegExp = (glob) => {
|
|
@@ -537,7 +801,7 @@ var GEONOSIS = "geonosis.json";
|
|
|
537
801
|
var LAW = "CLAUDE.md";
|
|
538
802
|
var CEILING = 200;
|
|
539
803
|
var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
|
|
540
|
-
var
|
|
804
|
+
var finding4 = (subject, verdict, message) => ({
|
|
541
805
|
check: "drift",
|
|
542
806
|
message,
|
|
543
807
|
subject,
|
|
@@ -555,27 +819,27 @@ var filesUnder = (dir, match) => {
|
|
|
555
819
|
}
|
|
556
820
|
for (const entry of entries) {
|
|
557
821
|
if (entry.isDirectory()) {
|
|
558
|
-
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(
|
|
822
|
+
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join7(at, entry.name));
|
|
559
823
|
continue;
|
|
560
824
|
}
|
|
561
|
-
if (entry.isFile() && match(entry.name)) found.push(
|
|
825
|
+
if (entry.isFile() && match(entry.name)) found.push(join7(at, entry.name));
|
|
562
826
|
}
|
|
563
827
|
};
|
|
564
828
|
walk2(dir);
|
|
565
829
|
return found;
|
|
566
830
|
};
|
|
567
831
|
var ci = (root) => {
|
|
568
|
-
const dir =
|
|
569
|
-
if (!
|
|
570
|
-
return [
|
|
832
|
+
const dir = join7(root, WORKFLOWS);
|
|
833
|
+
if (!existsSync6(dir)) {
|
|
834
|
+
return [finding4(WORKFLOWS, "SKIP", "there are no workflows here to read")];
|
|
571
835
|
}
|
|
572
836
|
return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
|
|
573
837
|
const at = relativePath(root, path);
|
|
574
|
-
return SWITCHED_OFF.test(
|
|
838
|
+
return SWITCHED_OFF.test(readFileSync7(path, "utf8")) ? finding4(
|
|
575
839
|
at,
|
|
576
840
|
"FAIL",
|
|
577
841
|
"a job or step here is switched off by a condition that can never be true \u2014 every gate downstream of it reports green having run nothing"
|
|
578
|
-
) :
|
|
842
|
+
) : finding4(at, "OK", "nothing in it is switched off");
|
|
579
843
|
});
|
|
580
844
|
};
|
|
581
845
|
var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep2}`);
|
|
@@ -591,10 +855,10 @@ var orphanTests = (root, workspaces) => {
|
|
|
591
855
|
orphaned.set(at, [...orphaned.get(at) ?? [], relativePath(root, path)]);
|
|
592
856
|
}
|
|
593
857
|
if (orphaned.size === 0) {
|
|
594
|
-
return [
|
|
858
|
+
return [finding4("test files", "OK", "every test file sits under a workspace that runs tests")];
|
|
595
859
|
}
|
|
596
860
|
return [...orphaned.entries()].map(
|
|
597
|
-
([at, files]) =>
|
|
861
|
+
([at, files]) => finding4(
|
|
598
862
|
at,
|
|
599
863
|
"FAIL",
|
|
600
864
|
`${files.length} test file(s) here and no test script to run them \u2014 ${files.slice(0, 3).join(", ")}`
|
|
@@ -619,16 +883,16 @@ var pathsRunByScript = (script) => script.split(BETWEEN_COMMANDS).map(pathRunBy)
|
|
|
619
883
|
var scriptPaths = (workspaces) => {
|
|
620
884
|
const missing = workspaces.flatMap(
|
|
621
885
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
622
|
-
([name, script]) => pathsRunByScript(script).filter((path) => !
|
|
886
|
+
([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(join7(one.dir, path))).map((path) => ({ name, path, workspace: one }))
|
|
623
887
|
)
|
|
624
888
|
);
|
|
625
889
|
if (missing.length === 0) {
|
|
626
890
|
return [
|
|
627
|
-
|
|
891
|
+
finding4("script paths", "OK", "every file a script hands to bun, node or tsx is on disk")
|
|
628
892
|
];
|
|
629
893
|
}
|
|
630
894
|
return missing.map(
|
|
631
|
-
({ name, path, workspace }) =>
|
|
895
|
+
({ name, path, workspace }) => finding4(
|
|
632
896
|
workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`,
|
|
633
897
|
"FAIL",
|
|
634
898
|
`the "${name}" script runs ${path} and there is no such file \u2014 this script fails at that command, and every gate that calls it has been running nothing since the file went`
|
|
@@ -651,16 +915,16 @@ var workspaceBins = (workspaces) => {
|
|
|
651
915
|
var linkedBins = (root, workspaces) => {
|
|
652
916
|
const bins = workspaceBins(workspaces);
|
|
653
917
|
if (bins.size === 0) {
|
|
654
|
-
return [
|
|
918
|
+
return [finding4("workspace bins", "SKIP", "no workspace here declares a bin")];
|
|
655
919
|
}
|
|
656
920
|
const missing = workspaces.flatMap(
|
|
657
921
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
658
|
-
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !
|
|
922
|
+
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !existsSync6(join7(root, "node_modules/.bin", name))).map((name) => ({ name, script, workspace: one }))
|
|
659
923
|
)
|
|
660
924
|
);
|
|
661
925
|
if (missing.length === 0) {
|
|
662
926
|
return [
|
|
663
|
-
|
|
927
|
+
finding4(
|
|
664
928
|
"workspace bins",
|
|
665
929
|
"OK",
|
|
666
930
|
"every workspace bin a script names is linked under the root"
|
|
@@ -668,7 +932,7 @@ var linkedBins = (root, workspaces) => {
|
|
|
668
932
|
];
|
|
669
933
|
}
|
|
670
934
|
return missing.map(
|
|
671
|
-
({ name, script, workspace }) =>
|
|
935
|
+
({ name, script, workspace }) => finding4(
|
|
672
936
|
name,
|
|
673
937
|
"FAIL",
|
|
674
938
|
`the "${script}" script in ${workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`} calls it, ${bins.get(name)?.manifest.name ?? "a workspace"} declares it, and node_modules/.bin/${name} is not there \u2014 add that package as a dependency of the one whose script calls it, so the package manager links the bin`
|
|
@@ -690,7 +954,7 @@ var preloadAroundPnpm = (root, workspaces) => {
|
|
|
690
954
|
const some = scripts.slice(0, 3).join(", ");
|
|
691
955
|
const rest = scripts.length > 3 ? ` and ${scripts.length - 3} more` : "";
|
|
692
956
|
return [
|
|
693
|
-
|
|
957
|
+
finding4(
|
|
694
958
|
"NODE_OPTIONS",
|
|
695
959
|
"WARN",
|
|
696
960
|
`${scripts.length} script(s) preload something through NODE_OPTIONS (${some}${rest}), and the "${named2}" counter(s) shell out to pnpm \u2014 a nested pnpm INHERITS the option and dies on it (a preloaded resolver patch sends it looking for a .pnpmfile.mjs that is not there). Run the ratchet outside those scripts, or unset NODE_OPTIONS for the nested call: env -u NODE_OPTIONS pnpm \u2026`
|
|
@@ -726,11 +990,11 @@ var everythingRun = (root, workspaces) => {
|
|
|
726
990
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
727
991
|
const counters = (readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? ""));
|
|
728
992
|
const workflows = filesUnder(
|
|
729
|
-
|
|
993
|
+
join7(root, WORKFLOWS),
|
|
730
994
|
(name) => name.endsWith(".yml") || name.endsWith(".yaml")
|
|
731
995
|
).map((path) => {
|
|
732
996
|
try {
|
|
733
|
-
return
|
|
997
|
+
return readFileSync7(path, "utf8");
|
|
734
998
|
} catch {
|
|
735
999
|
return "";
|
|
736
1000
|
}
|
|
@@ -741,7 +1005,7 @@ var generatedFiles = (root, workspaces) => {
|
|
|
741
1005
|
const marked = filesUnder(root, (name) => CAN_CARRY_A_MARKER.test(name)).map((path) => ({ marker: markerIn(path), path })).filter((one) => one.marker !== void 0);
|
|
742
1006
|
if (marked.length === 0) {
|
|
743
1007
|
return [
|
|
744
|
-
|
|
1008
|
+
finding4(
|
|
745
1009
|
"generated files",
|
|
746
1010
|
"SKIP",
|
|
747
1011
|
"nothing here says it was generated, so there is no write half here to look for a read half of"
|
|
@@ -752,13 +1016,13 @@ var generatedFiles = (root, workspaces) => {
|
|
|
752
1016
|
return marked.map(({ marker, path }) => {
|
|
753
1017
|
const at = relativePath(root, path);
|
|
754
1018
|
if (!("gate" in marker)) {
|
|
755
|
-
return
|
|
1019
|
+
return finding4(
|
|
756
1020
|
at,
|
|
757
1021
|
"FAIL",
|
|
758
1022
|
`generated by \`${marker.writer}\` and it names no gate that reads it back. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Re-run \`${marker.writer}\` on a version that writes the \`geonosis:gated-by:\` marker, and run the gate it names`
|
|
759
1023
|
);
|
|
760
1024
|
}
|
|
761
|
-
return run.includes(marker.gate) ?
|
|
1025
|
+
return run.includes(marker.gate) ? finding4(at, "OK", `generated, and \`${marker.gate}\` reads it back`) : finding4(
|
|
762
1026
|
at,
|
|
763
1027
|
"FAIL",
|
|
764
1028
|
`generated, and nothing here runs \`${marker.gate}\` \u2014 the gate it names. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Add \`${marker.gate}\` to a script, a counter or a workflow`
|
|
@@ -766,10 +1030,10 @@ var generatedFiles = (root, workspaces) => {
|
|
|
766
1030
|
});
|
|
767
1031
|
};
|
|
768
1032
|
var readGeonosis = (root) => {
|
|
769
|
-
const path =
|
|
770
|
-
if (!
|
|
1033
|
+
const path = join7(root, GEONOSIS);
|
|
1034
|
+
if (!existsSync6(path)) return void 0;
|
|
771
1035
|
try {
|
|
772
|
-
return JSON.parse(
|
|
1036
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
773
1037
|
} catch {
|
|
774
1038
|
return void 0;
|
|
775
1039
|
}
|
|
@@ -777,15 +1041,15 @@ var readGeonosis = (root) => {
|
|
|
777
1041
|
var law = (root, config) => {
|
|
778
1042
|
const declared = config?.law ?? {};
|
|
779
1043
|
const file = typeof declared.file === "string" ? declared.file : LAW;
|
|
780
|
-
const path =
|
|
781
|
-
if (!
|
|
782
|
-
return [
|
|
1044
|
+
const path = join7(root, file);
|
|
1045
|
+
if (!existsSync6(path)) {
|
|
1046
|
+
return [finding4(file, "SKIP", "there is no law file here to measure")];
|
|
783
1047
|
}
|
|
784
|
-
const source =
|
|
1048
|
+
const source = readFileSync7(path, "utf8");
|
|
785
1049
|
const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
786
1050
|
if (typeof declared.maxLines !== "number") {
|
|
787
1051
|
return [
|
|
788
|
-
|
|
1052
|
+
finding4(
|
|
789
1053
|
file,
|
|
790
1054
|
"SKIP",
|
|
791
1055
|
`${lines} lines, and no ceiling to measure them against \u2014 set law.maxLines in geonosis.json to have this asked (${CEILING} is what both source repos converged on)`
|
|
@@ -794,18 +1058,18 @@ var law = (root, config) => {
|
|
|
794
1058
|
}
|
|
795
1059
|
const ceiling = declared.maxLines;
|
|
796
1060
|
return [
|
|
797
|
-
lines > ceiling ?
|
|
1061
|
+
lines > ceiling ? finding4(
|
|
798
1062
|
file,
|
|
799
1063
|
"WARN",
|
|
800
1064
|
`${lines} lines against a ceiling of ${ceiling} \u2014 depth belongs in the skills and in the rules, where it is read`
|
|
801
|
-
) :
|
|
1065
|
+
) : finding4(file, "OK", `${lines} lines, under the ceiling of ${ceiling}`)
|
|
802
1066
|
];
|
|
803
1067
|
};
|
|
804
1068
|
var KIT_PLUGIN = "geonosis";
|
|
805
1069
|
var enablesKit = (path) => {
|
|
806
|
-
if (!
|
|
1070
|
+
if (!existsSync6(path)) return false;
|
|
807
1071
|
try {
|
|
808
|
-
const parsed = JSON.parse(
|
|
1072
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
809
1073
|
const enabled = parsed.enabledPlugins;
|
|
810
1074
|
if (typeof enabled !== "object" || enabled === null) return false;
|
|
811
1075
|
return Object.entries(enabled).some(
|
|
@@ -816,13 +1080,13 @@ var enablesKit = (path) => {
|
|
|
816
1080
|
}
|
|
817
1081
|
};
|
|
818
1082
|
var hooks = (root, userSettings) => {
|
|
819
|
-
const path =
|
|
1083
|
+
const path = join7(root, SETTINGS);
|
|
820
1084
|
if (enablesKit(path)) {
|
|
821
|
-
return [
|
|
1085
|
+
return [finding4(SETTINGS, "OK", `\`enabledPlugins\` here enables the kit\u2019s plugin`)];
|
|
822
1086
|
}
|
|
823
1087
|
if (enablesKit(userSettings)) {
|
|
824
1088
|
return [
|
|
825
|
-
|
|
1089
|
+
finding4(
|
|
826
1090
|
SETTINGS,
|
|
827
1091
|
"OK",
|
|
828
1092
|
`installed elsewhere \u2014 not this repo\u2019s to declare: \`enabledPlugins\` in ${userSettings} enables it for every repo on this machine`
|
|
@@ -830,7 +1094,7 @@ var hooks = (root, userSettings) => {
|
|
|
830
1094
|
];
|
|
831
1095
|
}
|
|
832
1096
|
return [
|
|
833
|
-
|
|
1097
|
+
finding4(
|
|
834
1098
|
SETTINGS,
|
|
835
1099
|
"WARN",
|
|
836
1100
|
`nothing enables the kit\u2019s plugin, so none of the hooks, agents or skills reach this repo \u2014 the gates run, the method does not. What satisfies this: a "geonosis@<marketplace>" entry under \`enabledPlugins\` in ${SETTINGS} here, or the same in the user-scope ${userSettings}`
|
|
@@ -839,9 +1103,9 @@ var hooks = (root, userSettings) => {
|
|
|
839
1103
|
};
|
|
840
1104
|
var ALLOW_BUILDS_KEY = "allowBuilds";
|
|
841
1105
|
var allowedBuilds = (root) => {
|
|
842
|
-
const path =
|
|
843
|
-
if (!
|
|
844
|
-
const lines =
|
|
1106
|
+
const path = join7(root, WORKSPACE_YAML);
|
|
1107
|
+
if (!existsSync6(path)) return void 0;
|
|
1108
|
+
const lines = readFileSync7(path, "utf8").split("\n");
|
|
845
1109
|
const at = lines.findIndex((line) => new RegExp(`^${ALLOW_BUILDS_KEY}\\s*:`).test(line));
|
|
846
1110
|
if (at < 0) return void 0;
|
|
847
1111
|
const names = [];
|
|
@@ -866,7 +1130,7 @@ var platformSplitBuilds = (root) => {
|
|
|
866
1130
|
}
|
|
867
1131
|
return [...families.entries()].flatMap(
|
|
868
1132
|
([family, allowed]) => allowed.length > 1 ? [] : [
|
|
869
|
-
|
|
1133
|
+
finding4(
|
|
870
1134
|
allowed[0] ?? family,
|
|
871
1135
|
"WARN",
|
|
872
1136
|
`this ${ALLOW_BUILDS_KEY} entry names one platform of ${family}, which publishes one package per platform \u2014 pnpm writes the entry for the machine that ran the install, so an install on any other platform refuses by the name that is missing, on somebody else's machine. List every platform ${family} publishes, or none of them`
|
|
@@ -892,7 +1156,7 @@ var pathsLintedBy = (script, dir) => {
|
|
|
892
1156
|
if (word.startsWith("-")) continue;
|
|
893
1157
|
const before = words[at - 1] ?? "";
|
|
894
1158
|
if (TAKES_A_VALUE.has(before)) continue;
|
|
895
|
-
if (
|
|
1159
|
+
if (existsSync6(join7(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
896
1160
|
}
|
|
897
1161
|
return named2;
|
|
898
1162
|
};
|
|
@@ -905,13 +1169,13 @@ var sourceDirsIn = (dir) => {
|
|
|
905
1169
|
return [];
|
|
906
1170
|
}
|
|
907
1171
|
return entries.filter(
|
|
908
|
-
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name) && filesUnder(
|
|
1172
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name) && filesUnder(join7(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
|
|
909
1173
|
).map((entry) => entry.name);
|
|
910
1174
|
};
|
|
911
1175
|
var lintScopes = (root, workspaces) => {
|
|
912
1176
|
if (!hookLines(root).some((one) => LINTS_STAGED.test(one.line))) {
|
|
913
1177
|
return [
|
|
914
|
-
|
|
1178
|
+
finding4(
|
|
915
1179
|
"lint scope",
|
|
916
1180
|
"SKIP",
|
|
917
1181
|
"no committed hook lints staged files here, so there is no second file set for a lint script to be narrower than"
|
|
@@ -924,16 +1188,16 @@ var lintScopes = (root, workspaces) => {
|
|
|
924
1188
|
const at = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
925
1189
|
const covered = pathsLintedBy(script, workspace.dir);
|
|
926
1190
|
if (covered.length === 0 || covered.includes(".")) {
|
|
927
|
-
return [
|
|
1191
|
+
return [finding4(at, "OK", `"${script}" covers everything the hook could stage here`)];
|
|
928
1192
|
}
|
|
929
1193
|
const uncovered = sourceDirsIn(workspace.dir).filter(
|
|
930
1194
|
(name) => !covered.some((path) => name === path || path.startsWith(`${name}/`))
|
|
931
1195
|
);
|
|
932
1196
|
if (uncovered.length === 0) {
|
|
933
|
-
return [
|
|
1197
|
+
return [finding4(at, "OK", `"${script}" covers every source directory under this workspace`)];
|
|
934
1198
|
}
|
|
935
1199
|
return [
|
|
936
|
-
|
|
1200
|
+
finding4(
|
|
937
1201
|
at,
|
|
938
1202
|
"WARN",
|
|
939
1203
|
`the "lint" script covers ${covered.join(", ")} and the pre-commit hook lints whatever is staged, so ${uncovered.map((name) => `${name}/**`).join(", ")} is linted by the hook alone \u2014 the package gate has never read those files, and the drift stays invisible until one moves across it. Widen the script, or say in it which globs are deliberately outside`
|
|
@@ -961,6 +1225,10 @@ var COMPOSABLE = [
|
|
|
961
1225
|
}))
|
|
962
1226
|
];
|
|
963
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);
|
|
964
1232
|
var composedHere = (root, workspaces) => {
|
|
965
1233
|
const declared = declaredAnywhere(workspaces);
|
|
966
1234
|
const wanted = COMPOSABLE.filter((one) => declared.has(one.name));
|
|
@@ -969,22 +1237,22 @@ var composedHere = (root, workspaces) => {
|
|
|
969
1237
|
for (const path of filesUnder(root, (name) => SOURCE_FILE.test(name))) {
|
|
970
1238
|
let body;
|
|
971
1239
|
try {
|
|
972
|
-
body =
|
|
1240
|
+
body = readFileSync7(path, "utf8");
|
|
973
1241
|
} catch {
|
|
974
1242
|
continue;
|
|
975
1243
|
}
|
|
976
1244
|
for (const one of wanted) {
|
|
977
|
-
if (!importing.has(one.name) && body
|
|
1245
|
+
if (!importing.has(one.name) && imports(body, one.name)) importing.set(one.name, path);
|
|
978
1246
|
}
|
|
979
1247
|
if (importing.size === wanted.length) break;
|
|
980
1248
|
}
|
|
981
1249
|
return wanted.map((one) => {
|
|
982
1250
|
const at = importing.get(one.name);
|
|
983
|
-
return at === void 0 ?
|
|
1251
|
+
return at === void 0 ? finding4(
|
|
984
1252
|
one.name,
|
|
985
1253
|
"WARN",
|
|
986
1254
|
`it is declared here and no source file imports it \u2014 ${one.uncomposed}`
|
|
987
|
-
) :
|
|
1255
|
+
) : finding4(
|
|
988
1256
|
one.name,
|
|
989
1257
|
"OK",
|
|
990
1258
|
`composed in ${relativePath(root, at)} \u2014 something here builds what it reports through`
|
|
@@ -998,6 +1266,18 @@ var READERS = {
|
|
|
998
1266
|
verify: "@geonosis/verify",
|
|
999
1267
|
walk: "@geonosis/walk"
|
|
1000
1268
|
};
|
|
1269
|
+
var doorBringing = (root, name, declared) => DOORS.filter((door) => declared.has(door)).find(
|
|
1270
|
+
(door) => Object.hasOwn(dependenciesOf(root, door), name)
|
|
1271
|
+
);
|
|
1272
|
+
var dependenciesOf = (root, name) => {
|
|
1273
|
+
try {
|
|
1274
|
+
return JSON.parse(
|
|
1275
|
+
readFileSync7(join7(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
1276
|
+
).dependencies ?? {};
|
|
1277
|
+
} catch {
|
|
1278
|
+
return {};
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1001
1281
|
var resolves = (root, name) => {
|
|
1002
1282
|
try {
|
|
1003
1283
|
resolveFrom(root, name);
|
|
@@ -1019,7 +1299,7 @@ var declaredAnywhere = (workspaces) => new Set(
|
|
|
1019
1299
|
var blocks = (root, config, readers, workspaces) => {
|
|
1020
1300
|
if (config === void 0) {
|
|
1021
1301
|
return [
|
|
1022
|
-
|
|
1302
|
+
finding4(
|
|
1023
1303
|
GEONOSIS,
|
|
1024
1304
|
"SKIP",
|
|
1025
1305
|
"there is no geonosis.json here, so no block says what this repo asks the kit to do"
|
|
@@ -1029,11 +1309,21 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
1029
1309
|
const declaredPackages = declaredAnywhere(workspaces);
|
|
1030
1310
|
return Object.entries(readers).flatMap(([block, name]) => {
|
|
1031
1311
|
const declared = config[block] !== void 0;
|
|
1312
|
+
const door = doorBringing(root, name, declaredPackages);
|
|
1032
1313
|
const chosen = declaredPackages.has(name);
|
|
1033
1314
|
const installed = resolves(root, name);
|
|
1315
|
+
if (declared && !chosen && door !== void 0) {
|
|
1316
|
+
return [
|
|
1317
|
+
finding4(
|
|
1318
|
+
name,
|
|
1319
|
+
"OK",
|
|
1320
|
+
`a "${block}" block, and ${door} brings ${name} to read it \u2014 #177: the root declares the door, not what the door owns`
|
|
1321
|
+
)
|
|
1322
|
+
];
|
|
1323
|
+
}
|
|
1034
1324
|
if (declared && !chosen) {
|
|
1035
1325
|
return [
|
|
1036
|
-
|
|
1326
|
+
finding4(
|
|
1037
1327
|
name,
|
|
1038
1328
|
"WARN",
|
|
1039
1329
|
`geonosis.json has a "${block}" block and no manifest here declares ${name} \u2014 nothing reads it${installed ? ", and the copy still under node_modules is what a stale install left behind" : ""}`
|
|
@@ -1042,7 +1332,7 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
1042
1332
|
}
|
|
1043
1333
|
if (declared && !installed) {
|
|
1044
1334
|
return [
|
|
1045
|
-
|
|
1335
|
+
finding4(
|
|
1046
1336
|
name,
|
|
1047
1337
|
"WARN",
|
|
1048
1338
|
`geonosis.json has a "${block}" block, a manifest declares ${name}, and it does not resolve here \u2014 run the install`
|
|
@@ -1051,7 +1341,7 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
1051
1341
|
}
|
|
1052
1342
|
if (!declared && chosen && installed) {
|
|
1053
1343
|
return [
|
|
1054
|
-
|
|
1344
|
+
finding4(
|
|
1055
1345
|
name,
|
|
1056
1346
|
"WARN",
|
|
1057
1347
|
`${name} is installed and geonosis.json has no "${block}" block \u2014 it runs on its defaults, whatever they are`
|
|
@@ -1060,14 +1350,14 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
1060
1350
|
}
|
|
1061
1351
|
if (!declared && installed) {
|
|
1062
1352
|
return [
|
|
1063
|
-
|
|
1353
|
+
finding4(
|
|
1064
1354
|
name,
|
|
1065
1355
|
"OK",
|
|
1066
1356
|
`${name} is here but no manifest declares it \u2014 a transitive dependency this repo did not choose, so it wants no "${block}" block`
|
|
1067
1357
|
)
|
|
1068
1358
|
];
|
|
1069
1359
|
}
|
|
1070
|
-
return declared ? [
|
|
1360
|
+
return declared ? [finding4(name, "OK", `a "${block}" block, and ${name} to read it`)] : [];
|
|
1071
1361
|
});
|
|
1072
1362
|
};
|
|
1073
1363
|
var PLUGIN_DIR_RULE = "biological-architecture/no-unregistered-plugin-dir";
|
|
@@ -1083,8 +1373,8 @@ var pluginDirsOf = (level) => {
|
|
|
1083
1373
|
return { manifests: options.manifests ?? ["index.ts"], registry, roots: options.roots };
|
|
1084
1374
|
};
|
|
1085
1375
|
var pluginDirLayers = (root) => {
|
|
1086
|
-
const path =
|
|
1087
|
-
if (!
|
|
1376
|
+
const path = join7(root, ".oxlintrc.json");
|
|
1377
|
+
if (!existsSync6(path)) return [];
|
|
1088
1378
|
const layers = [];
|
|
1089
1379
|
for (const layer of layersOf(readConfig(path, root), PLUGIN_DIR_RULE)) {
|
|
1090
1380
|
const dirs = pluginDirsOf(layer.level);
|
|
@@ -1093,7 +1383,7 @@ var pluginDirLayers = (root) => {
|
|
|
1093
1383
|
return layers;
|
|
1094
1384
|
};
|
|
1095
1385
|
var layerOver = (layers, root, relative) => {
|
|
1096
|
-
const at =
|
|
1386
|
+
const at = join7(root, relative);
|
|
1097
1387
|
const inside = readdirSync3(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
|
|
1098
1388
|
const paths = inside.length === 0 ? [relative] : inside;
|
|
1099
1389
|
const wrapped = layers.map((layer) => ({ files: layer.files, level: layer.dirs }));
|
|
@@ -1107,7 +1397,7 @@ var pluginDirs = (root) => {
|
|
|
1107
1397
|
const layers = pluginDirLayers(root);
|
|
1108
1398
|
if (layers.length === 0) {
|
|
1109
1399
|
return [
|
|
1110
|
-
|
|
1400
|
+
finding4(
|
|
1111
1401
|
"plugin directories",
|
|
1112
1402
|
"SKIP",
|
|
1113
1403
|
"no-unregistered-plugin-dir is not configured here, so this repo has not said where its integrations live"
|
|
@@ -1115,34 +1405,34 @@ var pluginDirs = (root) => {
|
|
|
1115
1405
|
];
|
|
1116
1406
|
}
|
|
1117
1407
|
const missing = [...new Set(layers.flatMap((layer) => layer.dirs.registry))].filter(
|
|
1118
|
-
(registry) => !
|
|
1408
|
+
(registry) => !existsSync6(join7(root, registry))
|
|
1119
1409
|
);
|
|
1120
1410
|
if (missing.length > 0) {
|
|
1121
1411
|
return missing.map(
|
|
1122
|
-
(registry) =>
|
|
1412
|
+
(registry) => finding4(registry, "FAIL", "the registry the rule names is not there")
|
|
1123
1413
|
);
|
|
1124
1414
|
}
|
|
1125
1415
|
const source = /* @__PURE__ */ new Map();
|
|
1126
1416
|
const unreachable = /* @__PURE__ */ new Map();
|
|
1127
1417
|
for (const rootDir of new Set(layers.flatMap((layer) => layer.dirs.roots))) {
|
|
1128
|
-
const at =
|
|
1129
|
-
if (!
|
|
1418
|
+
const at = join7(root, rootDir);
|
|
1419
|
+
if (!existsSync6(at)) continue;
|
|
1130
1420
|
for (const entry of readdirSync3(at, { withFileTypes: true })) {
|
|
1131
1421
|
if (!entry.isDirectory()) continue;
|
|
1132
1422
|
const relative = `${rootDir}/${entry.name}`;
|
|
1133
1423
|
const governs = layerOver(layers, root, relative);
|
|
1134
1424
|
if (governs === void 0) continue;
|
|
1135
1425
|
const key = governs.registry.join(", ");
|
|
1136
|
-
const registry = source.get(key) ?? governs.registry.map((half) =>
|
|
1426
|
+
const registry = source.get(key) ?? governs.registry.map((half) => readFileSync7(join7(root, half), "utf8")).join("\n");
|
|
1137
1427
|
source.set(key, registry);
|
|
1138
|
-
const hasManifest = governs.manifests.some((name) =>
|
|
1428
|
+
const hasManifest = governs.manifests.some((name) => existsSync6(join7(at, entry.name, name)));
|
|
1139
1429
|
if (!hasManifest || registry.includes(entry.name)) continue;
|
|
1140
1430
|
unreachable.set(key, [...unreachable.get(key) ?? [], relative]);
|
|
1141
1431
|
}
|
|
1142
1432
|
}
|
|
1143
1433
|
return [...source.keys()].toSorted().map((registry) => {
|
|
1144
1434
|
const named2 = unreachable.get(registry) ?? [];
|
|
1145
|
-
return named2.length === 0 ?
|
|
1435
|
+
return named2.length === 0 ? finding4(registry, "OK", "every directory under the declared roots is named by it") : finding4(
|
|
1146
1436
|
registry,
|
|
1147
1437
|
"FAIL",
|
|
1148
1438
|
`${named2.length} directory(ies) it never names: ${named2.join(", ")}`
|
|
@@ -1153,9 +1443,9 @@ var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
|
1153
1443
|
var HOIST_KEY = "publicHoistPattern";
|
|
1154
1444
|
var HOIST_REPAIR = "rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install";
|
|
1155
1445
|
var hoistPatterns = (root) => {
|
|
1156
|
-
const path =
|
|
1157
|
-
if (!
|
|
1158
|
-
const lines =
|
|
1446
|
+
const path = join7(root, WORKSPACE_YAML);
|
|
1447
|
+
if (!existsSync6(path)) return void 0;
|
|
1448
|
+
const lines = readFileSync7(path, "utf8").split("\n");
|
|
1159
1449
|
const at = lines.findIndex((line) => new RegExp(`^${HOIST_KEY}\\s*:`).test(line));
|
|
1160
1450
|
if (at < 0) return void 0;
|
|
1161
1451
|
const patterns = [];
|
|
@@ -1172,7 +1462,7 @@ var publicHoists = (root, workspaces) => {
|
|
|
1172
1462
|
const patterns = hoistPatterns(root);
|
|
1173
1463
|
if (patterns === void 0) {
|
|
1174
1464
|
return [
|
|
1175
|
-
|
|
1465
|
+
finding4(
|
|
1176
1466
|
WORKSPACE_YAML,
|
|
1177
1467
|
"SKIP",
|
|
1178
1468
|
`no ${HOIST_KEY} is declared here, so nothing claims a package should be linked at the root`
|
|
@@ -1186,21 +1476,21 @@ var publicHoists = (root, workspaces) => {
|
|
|
1186
1476
|
const shape = matching(pattern);
|
|
1187
1477
|
const hoisted = named2.filter((name) => shape.test(name));
|
|
1188
1478
|
if (hoisted.length === 0) {
|
|
1189
|
-
return
|
|
1479
|
+
return finding4(
|
|
1190
1480
|
pattern,
|
|
1191
1481
|
"SKIP",
|
|
1192
1482
|
`this ${HOIST_KEY} matches no workspace package here, so what it should have linked at the root is a question this cannot answer`
|
|
1193
1483
|
);
|
|
1194
1484
|
}
|
|
1195
|
-
const pruned = hoisted.filter((name) => !
|
|
1485
|
+
const pruned = hoisted.filter((name) => !existsSync6(join7(root, "node_modules", name)));
|
|
1196
1486
|
if (pruned.length === 0) {
|
|
1197
|
-
return
|
|
1487
|
+
return finding4(
|
|
1198
1488
|
pattern,
|
|
1199
1489
|
"OK",
|
|
1200
1490
|
`every workspace package this ${HOIST_KEY} matches is linked at the root`
|
|
1201
1491
|
);
|
|
1202
1492
|
}
|
|
1203
|
-
return
|
|
1493
|
+
return finding4(
|
|
1204
1494
|
pattern,
|
|
1205
1495
|
"FAIL",
|
|
1206
1496
|
`the ${HOIST_KEY} \`${pattern}\` has no root link for ${pruned.map((name) => `node_modules/${name}`).join(", ")} \u2014 pnpm prunes one during an unrelated add and then answers "Already up to date" over a clean git status forever. Repair: ${HOIST_REPAIR}`
|
|
@@ -1210,7 +1500,7 @@ var publicHoists = (root, workspaces) => {
|
|
|
1210
1500
|
var checkDrift = ({
|
|
1211
1501
|
readers = READERS,
|
|
1212
1502
|
root,
|
|
1213
|
-
userSettings =
|
|
1503
|
+
userSettings = join7(homedir(), SETTINGS),
|
|
1214
1504
|
workspaces
|
|
1215
1505
|
}) => {
|
|
1216
1506
|
const config = readGeonosis(root);
|
|
@@ -1236,21 +1526,21 @@ var checkDrift = ({
|
|
|
1236
1526
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1237
1527
|
import {
|
|
1238
1528
|
cpSync,
|
|
1239
|
-
existsSync as
|
|
1529
|
+
existsSync as existsSync7,
|
|
1240
1530
|
mkdirSync,
|
|
1241
1531
|
mkdtempSync,
|
|
1242
|
-
readFileSync as
|
|
1532
|
+
readFileSync as readFileSync8,
|
|
1243
1533
|
rmSync,
|
|
1244
1534
|
writeFileSync
|
|
1245
1535
|
} from "fs";
|
|
1246
1536
|
import { tmpdir } from "os";
|
|
1247
|
-
import { dirname as dirname2, join as
|
|
1537
|
+
import { dirname as dirname2, join as join8, resolve } from "path";
|
|
1248
1538
|
import { corpusOf, readManifest } from "@geonosis/lint-parity";
|
|
1249
1539
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
1250
1540
|
var severityOf = (level) => Array.isArray(level) ? level[0] : level;
|
|
1251
1541
|
var enabledRulesOf = (rules, plugin) => Object.keys(rules).filter((id) => id.startsWith(`${plugin}/`) && !OFF.has(severityOf(rules[id]))).toSorted();
|
|
1252
1542
|
var passes = (findings) => !findings.some((one) => one.verdict === "FAIL" || one.verdict === "UNJUDGED");
|
|
1253
|
-
var
|
|
1543
|
+
var finding5 = (subject, verdict, message) => ({
|
|
1254
1544
|
check: "exercised",
|
|
1255
1545
|
message,
|
|
1256
1546
|
subject,
|
|
@@ -1262,9 +1552,9 @@ var refusal = (error) => {
|
|
|
1262
1552
|
return said2.length > LIMIT ? `${said2.slice(0, LIMIT)}\u2026` : said2;
|
|
1263
1553
|
};
|
|
1264
1554
|
var reasonFrom = (config, oxlint) => {
|
|
1265
|
-
const dir = mkdtempSync(
|
|
1555
|
+
const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-why-"));
|
|
1266
1556
|
try {
|
|
1267
|
-
const probe =
|
|
1557
|
+
const probe = join8(dir, "probe.tsx");
|
|
1268
1558
|
writeFileSync(probe, "export const probe = 1\n");
|
|
1269
1559
|
const run = spawnSync2(
|
|
1270
1560
|
oxlint,
|
|
@@ -1354,8 +1644,8 @@ var throughProbes = ({
|
|
|
1354
1644
|
}) => {
|
|
1355
1645
|
const refused = /* @__PURE__ */ new Map();
|
|
1356
1646
|
const placed = /* @__PURE__ */ new Set();
|
|
1357
|
-
const dir = mkdtempSync(
|
|
1358
|
-
const here =
|
|
1647
|
+
const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-probe-"));
|
|
1648
|
+
const here = join8(dir, "corpus");
|
|
1359
1649
|
try {
|
|
1360
1650
|
cpSync(corpus, here, { recursive: true });
|
|
1361
1651
|
for (const rule of silent) {
|
|
@@ -1392,7 +1682,7 @@ var throughProbes = ({
|
|
|
1392
1682
|
mounted = rehomed;
|
|
1393
1683
|
}
|
|
1394
1684
|
for (const file of mounted) {
|
|
1395
|
-
const at2 =
|
|
1685
|
+
const at2 = join8(here, file.path);
|
|
1396
1686
|
if (placed.has(at2)) continue;
|
|
1397
1687
|
placed.add(at2);
|
|
1398
1688
|
mkdirSync(dirname2(at2), { recursive: true });
|
|
@@ -1405,12 +1695,12 @@ var throughProbes = ({
|
|
|
1405
1695
|
refused.set(rule, String(error.message));
|
|
1406
1696
|
}
|
|
1407
1697
|
}
|
|
1408
|
-
const probeConfig = JSON.parse(
|
|
1698
|
+
const probeConfig = JSON.parse(readFileSync8(config.path, "utf8"));
|
|
1409
1699
|
const configDir = dirname2(config.path);
|
|
1410
1700
|
probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
|
|
1411
1701
|
(spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec), spec)
|
|
1412
1702
|
);
|
|
1413
|
-
const at =
|
|
1703
|
+
const at = join8(here, ".oxlintrc-geonosis-probe.json");
|
|
1414
1704
|
writeFileSync(at, JSON.stringify(probeConfig, null, 2));
|
|
1415
1705
|
const run = spawnSync2(
|
|
1416
1706
|
oxlint,
|
|
@@ -1440,14 +1730,14 @@ var checkExercised = async ({
|
|
|
1440
1730
|
repoCorpus,
|
|
1441
1731
|
root
|
|
1442
1732
|
}) => {
|
|
1443
|
-
const said2 = (verdict, message) =>
|
|
1444
|
-
if (repoCorpus !== void 0 && !
|
|
1733
|
+
const said2 = (verdict, message) => finding5(config.relative, verdict, message);
|
|
1734
|
+
if (repoCorpus !== void 0 && !existsSync7(repoCorpus)) {
|
|
1445
1735
|
return said2(
|
|
1446
1736
|
"FAIL",
|
|
1447
1737
|
`geonosis.json declares a reach corpus at ${relativeToRoot(root, repoCorpus)} and there is nothing there \u2014 a corpus that cannot be read is a claim, not evidence`
|
|
1448
1738
|
);
|
|
1449
1739
|
}
|
|
1450
|
-
if (!
|
|
1740
|
+
if (!existsSync7(corpus)) {
|
|
1451
1741
|
return said2(
|
|
1452
1742
|
"SKIP",
|
|
1453
1743
|
`the plugin loaded from here ships no corpus at ${relativeToRoot(root, corpus)} \u2014 nothing declares which rules it can be evidence about`
|
|
@@ -1520,341 +1810,154 @@ var checkExercised = async ({
|
|
|
1520
1810
|
);
|
|
1521
1811
|
}
|
|
1522
1812
|
const unplaceable = inert.map((rule) => `${rule}: ${probed.refused.get(rule) ?? ""}`);
|
|
1523
|
-
const kitNote = unprobed.length === 0 || loadedProbes === null ? "" : `the loaded plugin ships no probe for ${unprobed.length === 1 ? "this enabled rule" : `${unprobed.length} enabled rules`} \u2014 the kit's omission, not this repo's (geonosis #131): ${unprobed.join(", ")}`;
|
|
1524
|
-
const unknowable = loadedProbes === null ? unprobed.map(
|
|
1525
|
-
(rule) => `${rule} declares no probe, so ${optionSetsOf(config, rule).every((options) => options.length === 0) ? "its scope" : under(config, rule)} does not reach the corpus`
|
|
1526
|
-
) : [];
|
|
1527
|
-
if (unplaceable.length > 0 || unknowable.length > 0) {
|
|
1528
|
-
return said2(
|
|
1529
|
-
"UNJUDGED",
|
|
1530
|
-
named([...unknowable, ...unplaceable]) + (kitNote === "" ? "" : ` \u2014 and ${kitNote}`)
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
if (kitNote !== "") return said2("WARN", kitNote);
|
|
1534
|
-
const through = exercised.length === 1 ? "1 through its declared probe under this repo\u2019s options" : `${exercised.length} through their declared probes under this repo\u2019s options`;
|
|
1535
|
-
return said2(
|
|
1536
|
-
"OK",
|
|
1537
|
-
`${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${through}. ${BASE}`
|
|
1538
|
-
);
|
|
1539
|
-
};
|
|
1540
|
-
|
|
1541
|
-
// src/envelope.ts
|
|
1542
|
-
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
1543
|
-
import { join as join7 } from "path";
|
|
1544
|
-
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1545
|
-
var NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one";
|
|
1546
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1547
|
-
var finding5 = (verdict, subject, message) => ({
|
|
1548
|
-
check: "envelope",
|
|
1549
|
-
message,
|
|
1550
|
-
subject,
|
|
1551
|
-
verdict
|
|
1552
|
-
});
|
|
1553
|
-
var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
|
|
1554
|
-
var NEXT = "Next: re-run the tool that wrote it and read the numbers it prints \u2014 a run that has lost count of its own inputs is a bug in that tool, not in this tree";
|
|
1555
|
-
var judge = (subject, name, parsed) => {
|
|
1556
|
-
if (!isRecord2(parsed)) return finding5("FAIL", subject, `is not an object. ${NEXT}`);
|
|
1557
|
-
const tool = parsed["tool"];
|
|
1558
|
-
if (typeof tool !== "string" || tool.trim() === "") {
|
|
1559
|
-
return finding5(
|
|
1560
|
-
"FAIL",
|
|
1561
|
-
subject,
|
|
1562
|
-
`names no tool, so nothing here says which run it is about. ${NEXT}`
|
|
1563
|
-
);
|
|
1564
|
-
}
|
|
1565
|
-
if (tool !== name) {
|
|
1566
|
-
return finding5(
|
|
1567
|
-
"FAIL",
|
|
1568
|
-
subject,
|
|
1569
|
-
`is written by "${tool}", not "${name}" \u2014 a tool writing another tool's envelope leaves both of their numbers unattributable. ${NEXT}`
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
|
-
const version = parsed["version"];
|
|
1573
|
-
if (typeof version !== "string" || version.trim() === "") {
|
|
1574
|
-
return finding5(
|
|
1575
|
-
"FAIL",
|
|
1576
|
-
subject,
|
|
1577
|
-
`${tool}: names no version, so nothing dates this run and a stale envelope reads exactly like a fresh one. ${NEXT}`
|
|
1578
|
-
);
|
|
1579
|
-
}
|
|
1580
|
-
const considered = parsed["considered"];
|
|
1581
|
-
const read = parsed["read"];
|
|
1582
|
-
const refused = lengthOf(parsed["refused"]);
|
|
1583
|
-
const excused = lengthOf(parsed["excused"]);
|
|
1584
|
-
if (typeof considered !== "number" || typeof read !== "number" || refused === void 0 || excused === void 0) {
|
|
1585
|
-
return finding5(
|
|
1586
|
-
"FAIL",
|
|
1587
|
-
subject,
|
|
1588
|
-
`${tool}: has no considered/read/refused/excused to check \u2014 the four numbers ARE the envelope, and a file without them measures nothing. ${NEXT}`
|
|
1589
|
-
);
|
|
1590
|
-
}
|
|
1591
|
-
const accounted = read + refused + excused;
|
|
1592
|
-
return considered === accounted ? finding5(
|
|
1593
|
-
"OK",
|
|
1594
|
-
subject,
|
|
1595
|
-
`${tool} considered ${considered} and accounts for all of them \u2014 ${read} read + ${refused} refused + ${excused} excused`
|
|
1596
|
-
) : finding5(
|
|
1597
|
-
"FAIL",
|
|
1598
|
-
subject,
|
|
1599
|
-
`${tool}: considered ${considered} but accounts for ${accounted} \u2014 ${read} read + ${refused} refused + ${excused} excused. It reported on fewer things than it was handed, and every verdict it printed is over the smaller number. ${NEXT}`
|
|
1600
|
-
);
|
|
1601
|
-
};
|
|
1602
|
-
var checkEnvelopes = ({ root }) => {
|
|
1603
|
-
const dir = join7(root, ENVELOPES_DIR);
|
|
1604
|
-
const files = existsSync6(dir) ? readdirSync4(dir).filter((name) => name.endsWith(".json")).toSorted() : [];
|
|
1605
|
-
if (files.length === 0) return [finding5("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1606
|
-
return files.map((name) => {
|
|
1607
|
-
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1608
|
-
let parsed;
|
|
1609
|
-
try {
|
|
1610
|
-
parsed = JSON.parse(readFileSync7(join7(dir, name), "utf8"));
|
|
1611
|
-
} catch (error) {
|
|
1612
|
-
return finding5("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1613
|
-
}
|
|
1614
|
-
return judge(subject, name.replace(/\.json$/, ""), parsed);
|
|
1615
|
-
});
|
|
1616
|
-
};
|
|
1617
|
-
|
|
1618
|
-
// src/repo-corpus.ts
|
|
1619
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
1620
|
-
import { join as join8 } from "path";
|
|
1621
|
-
var GEONOSIS_FILE = "geonosis.json";
|
|
1622
|
-
var repoCorpusOf = (root) => {
|
|
1623
|
-
const path = join8(root, GEONOSIS_FILE);
|
|
1624
|
-
if (!existsSync7(path)) return void 0;
|
|
1625
|
-
try {
|
|
1626
|
-
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
1627
|
-
const declared = parsed.doctor?.corpus;
|
|
1628
|
-
return typeof declared === "string" && declared !== "" ? join8(root, declared) : void 0;
|
|
1629
|
-
} catch {
|
|
1630
|
-
return void 0;
|
|
1631
|
-
}
|
|
1632
|
-
};
|
|
1633
|
-
|
|
1634
|
-
// src/group.ts
|
|
1635
|
-
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
1636
|
-
import { join as join9 } from "path";
|
|
1637
|
-
var FIXED_GROUP = [
|
|
1638
|
-
"@geonosis/cli",
|
|
1639
|
-
"@geonosis/doctor",
|
|
1640
|
-
"@geonosis/evals",
|
|
1641
|
-
"@geonosis/integrations",
|
|
1642
|
-
"@geonosis/ledger",
|
|
1643
|
-
"@geonosis/lint-parity",
|
|
1644
|
-
"@geonosis/mcp",
|
|
1645
|
-
"@geonosis/observability",
|
|
1646
|
-
"@geonosis/oxlint-plugin-biological-architecture",
|
|
1647
|
-
"@geonosis/policy",
|
|
1648
|
-
"@geonosis/rails",
|
|
1649
|
-
"@geonosis/ratchet",
|
|
1650
|
-
"@geonosis/release",
|
|
1651
|
-
"@geonosis/review",
|
|
1652
|
-
"@geonosis/testbed",
|
|
1653
|
-
"@geonosis/themekit",
|
|
1654
|
-
"@geonosis/verify",
|
|
1655
|
-
"@geonosis/verify-arch",
|
|
1656
|
-
"@geonosis/visual-diff",
|
|
1657
|
-
"@geonosis/walk",
|
|
1658
|
-
"create-geonosis",
|
|
1659
|
-
"geonosis"
|
|
1660
|
-
];
|
|
1661
|
-
var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
|
|
1662
|
-
var declaredGroupsOf = (root) => {
|
|
1663
|
-
const path = join9(root, GEONOSIS_FILE);
|
|
1664
|
-
if (!existsSync8(path)) return void 0;
|
|
1665
|
-
let parsed;
|
|
1666
|
-
try {
|
|
1667
|
-
parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
1668
|
-
} catch {
|
|
1669
|
-
return void 0;
|
|
1670
|
-
}
|
|
1671
|
-
const declared = parsed.doctor?.groups;
|
|
1672
|
-
if (declared === void 0) return void 0;
|
|
1673
|
-
if (!Array.isArray(declared) || declared.length === 0) {
|
|
1674
|
-
throw new DoctorError(
|
|
1675
|
-
`${GEONOSIS_FILE} declares doctor.groups and names no group in it \u2014 say which packages must move together, or remove the key and get the kit's own group`
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
return declared.map((one, at) => {
|
|
1679
|
-
const group = one;
|
|
1680
|
-
if (typeof group.name !== "string" || !Array.isArray(group.packages) || group.packages.length < 2) {
|
|
1681
|
-
throw new DoctorError(
|
|
1682
|
-
`${GEONOSIS_FILE} doctor.groups[${at}] needs a name and at least two packages \u2014 one package cannot disagree with itself`
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
|
-
return { name: group.name, packages: group.packages };
|
|
1686
|
-
});
|
|
1687
|
-
};
|
|
1688
|
-
var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
1689
|
-
var versionOf = (dir, name) => {
|
|
1690
|
-
let entry;
|
|
1691
|
-
try {
|
|
1692
|
-
entry = resolveFrom(dir, name);
|
|
1693
|
-
} catch {
|
|
1694
|
-
return { kind: "absent" };
|
|
1695
|
-
}
|
|
1696
|
-
try {
|
|
1697
|
-
const manifest = JSON.parse(
|
|
1698
|
-
readFileSync9(join9(packageDirOf(entry, name), "package.json"), "utf8")
|
|
1813
|
+
const kitNote = unprobed.length === 0 || loadedProbes === null ? "" : `the loaded plugin ships no probe for ${unprobed.length === 1 ? "this enabled rule" : `${unprobed.length} enabled rules`}, so the question cannot be asked here at all \u2014 the kit's omission, not this repo's, and nothing in this tree is the fix (geonosis #131): ${unprobed.join(", ")}`;
|
|
1814
|
+
const unknowable = loadedProbes === null ? unprobed.map(
|
|
1815
|
+
(rule) => `${rule} declares no probe, so ${optionSetsOf(config, rule).every((options) => options.length === 0) ? "its scope" : under(config, rule)} does not reach the corpus`
|
|
1816
|
+
) : [];
|
|
1817
|
+
if (unplaceable.length > 0 || unknowable.length > 0) {
|
|
1818
|
+
return said2(
|
|
1819
|
+
"UNJUDGED",
|
|
1820
|
+
named([...unknowable, ...unplaceable]) + (kitNote === "" ? "" : ` \u2014 and ${kitNote}`)
|
|
1699
1821
|
);
|
|
1700
|
-
return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
|
|
1701
|
-
} catch (error) {
|
|
1702
|
-
return { kind: "unreadable", why: String(error.message).split("\n")[0] ?? "" };
|
|
1703
1822
|
}
|
|
1823
|
+
if (kitNote !== "") return said2("SKIP", kitNote);
|
|
1824
|
+
const through = exercised.length === 1 ? "1 through its declared probe under this repo\u2019s options" : `${exercised.length} through their declared probes under this repo\u2019s options`;
|
|
1825
|
+
return said2(
|
|
1826
|
+
"OK",
|
|
1827
|
+
`${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${through}. ${BASE}`
|
|
1828
|
+
);
|
|
1704
1829
|
};
|
|
1705
|
-
|
|
1706
|
-
|
|
1830
|
+
|
|
1831
|
+
// src/envelope.ts
|
|
1832
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
1833
|
+
import { join as join9 } from "path";
|
|
1834
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1835
|
+
var THIS_TOOL = "doctor";
|
|
1836
|
+
var OWN = `${THIS_TOOL}.json`;
|
|
1837
|
+
var NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one (this run\u2019s own doctor.json is not one of them: it is written after these checks, and balanced at write time)";
|
|
1838
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1839
|
+
var finding6 = (verdict, subject, message) => ({
|
|
1840
|
+
check: "envelope",
|
|
1707
1841
|
message,
|
|
1708
1842
|
subject,
|
|
1709
1843
|
verdict
|
|
1710
1844
|
});
|
|
1711
|
-
var
|
|
1712
|
-
var
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1845
|
+
var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
|
|
1846
|
+
var NEXT = "Next: re-run the tool that wrote it and read the numbers it prints \u2014 a run that has lost count of its own inputs is a bug in that tool, not in this tree";
|
|
1847
|
+
var judge = (subject, name, parsed) => {
|
|
1848
|
+
if (!isRecord2(parsed)) return finding6("FAIL", subject, `is not an object. ${NEXT}`);
|
|
1849
|
+
const tool = parsed["tool"];
|
|
1850
|
+
if (typeof tool !== "string" || tool.trim() === "") {
|
|
1851
|
+
return finding6(
|
|
1852
|
+
"FAIL",
|
|
1853
|
+
subject,
|
|
1854
|
+
`names no tool, so nothing here says which run it is about. ${NEXT}`
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (tool !== name) {
|
|
1858
|
+
return finding6(
|
|
1859
|
+
"FAIL",
|
|
1860
|
+
subject,
|
|
1861
|
+
`is written by "${tool}", not "${name}" \u2014 a tool writing another tool's envelope leaves both of their numbers unattributable. ${NEXT}`
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
const version = parsed["version"];
|
|
1865
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
1866
|
+
return finding6(
|
|
1867
|
+
"FAIL",
|
|
1868
|
+
subject,
|
|
1869
|
+
`${tool}: names no version, so nothing dates this run and a stale envelope reads exactly like a fresh one. ${NEXT}`
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
const considered = parsed["considered"];
|
|
1873
|
+
const read = parsed["read"];
|
|
1874
|
+
const refused = lengthOf(parsed["refused"]);
|
|
1875
|
+
const excused = lengthOf(parsed["excused"]);
|
|
1876
|
+
if (typeof considered !== "number" || typeof read !== "number" || refused === void 0 || excused === void 0) {
|
|
1877
|
+
return finding6(
|
|
1878
|
+
"FAIL",
|
|
1879
|
+
subject,
|
|
1880
|
+
`${tool}: has no considered/read/refused/excused to check \u2014 the four numbers ARE the envelope, and a file without them measures nothing. ${NEXT}`
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
const accounted = read + refused + excused;
|
|
1884
|
+
return considered === accounted ? finding6(
|
|
1885
|
+
"OK",
|
|
1886
|
+
subject,
|
|
1887
|
+
`${tool} considered ${considered} and accounts for all of them \u2014 ${read} read + ${refused} refused + ${excused} excused`
|
|
1888
|
+
) : finding6(
|
|
1718
1889
|
"FAIL",
|
|
1719
|
-
|
|
1890
|
+
subject,
|
|
1891
|
+
`${tool}: considered ${considered} but accounts for ${accounted} \u2014 ${read} read + ${refused} refused + ${excused} excused. It reported on fewer things than it was handed, and every verdict it printed is over the smaller number. ${NEXT}`
|
|
1720
1892
|
);
|
|
1721
1893
|
};
|
|
1722
|
-
var
|
|
1723
|
-
|
|
1724
|
-
const
|
|
1725
|
-
return finding6(
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1894
|
+
var checkEnvelopes = ({ root }) => {
|
|
1895
|
+
const dir = join9(root, ENVELOPES_DIR);
|
|
1896
|
+
const files = existsSync8(dir) ? readdirSync4(dir).filter((name) => name.endsWith(".json") && name !== OWN).toSorted() : [];
|
|
1897
|
+
if (files.length === 0) return [finding6("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1898
|
+
return files.map((name) => {
|
|
1899
|
+
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1900
|
+
let parsed;
|
|
1901
|
+
try {
|
|
1902
|
+
parsed = JSON.parse(readFileSync9(join9(dir, name), "utf8"));
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
return finding6("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1905
|
+
}
|
|
1906
|
+
return judge(subject, name.replace(/\.json$/, ""), parsed);
|
|
1907
|
+
});
|
|
1730
1908
|
};
|
|
1731
|
-
|
|
1732
|
-
|
|
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;
|
|
1733
1920
|
try {
|
|
1734
|
-
|
|
1735
|
-
readFileSync9(join9(packageDirOf(resolveFrom(dir, name), name), "package.json"), "utf8")
|
|
1736
|
-
);
|
|
1921
|
+
record = JSON.parse(readFileSync10(at, "utf8"));
|
|
1737
1922
|
} catch {
|
|
1738
|
-
return
|
|
1923
|
+
return [];
|
|
1739
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] : []);
|
|
1740
1926
|
};
|
|
1741
|
-
var
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
manifest.devDependencies,
|
|
1750
|
-
manifest.optionalDependencies,
|
|
1751
|
-
manifest.peerDependencies
|
|
1752
|
-
].flatMap((block) => Object.keys(block ?? {}))
|
|
1753
|
-
)
|
|
1754
|
-
];
|
|
1755
|
-
var rootDeclarations = (root, workspaces) => {
|
|
1756
|
-
const here = workspaces.find((one) => one.relative === "");
|
|
1757
|
-
if (here === void 0) return [];
|
|
1758
|
-
const declared = declaredIn2(here.manifest);
|
|
1759
|
-
const door = DOORS.find((name) => declared.includes(name));
|
|
1760
|
-
if (door === void 0) return [];
|
|
1761
|
-
const owned = new Set(
|
|
1762
|
-
[door, ...DOORS].flatMap((name) => {
|
|
1763
|
-
const manifest = manifestOf(root, name);
|
|
1764
|
-
return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
|
|
1765
|
-
})
|
|
1766
|
-
);
|
|
1767
|
-
const callers = [
|
|
1768
|
-
...Object.values(here.manifest.scripts ?? {}),
|
|
1769
|
-
...hookLines(root).map((one) => one.line)
|
|
1770
|
-
].join("\n");
|
|
1771
|
-
const spare = declared.filter(
|
|
1772
|
-
(name) => !DOORS.includes(name) && owned.has(name) && !binNamesOf(manifestOf(root, name), name).some(
|
|
1773
|
-
(bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(callers)
|
|
1774
|
-
)
|
|
1775
|
-
);
|
|
1776
|
-
if (spare.length === 0) {
|
|
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) {
|
|
1777
1935
|
return [
|
|
1778
|
-
|
|
1779
|
-
"
|
|
1780
|
-
"
|
|
1781
|
-
|
|
1782
|
-
|
|
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
|
+
}
|
|
1783
1942
|
];
|
|
1784
1943
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
var checkGroup = ({
|
|
1794
|
-
groups,
|
|
1795
|
-
root,
|
|
1796
|
-
workspaces
|
|
1797
|
-
}) => {
|
|
1798
|
-
const findings = [...rootDeclarations(root, workspaces)];
|
|
1799
|
-
for (const group of groups ?? [KIT_GROUP]) {
|
|
1800
|
-
const found = [];
|
|
1801
|
-
for (const workspace of workspaces) {
|
|
1802
|
-
const at = labelOf(workspace);
|
|
1803
|
-
const here = [];
|
|
1804
|
-
for (const name of group.packages) {
|
|
1805
|
-
const reading = versionOf(workspace.dir, name);
|
|
1806
|
-
if (reading.kind === "read") here.push({ at, name, version: reading.version });
|
|
1807
|
-
if (reading.kind === "unreadable") {
|
|
1808
|
-
findings.push(
|
|
1809
|
-
finding6(
|
|
1810
|
-
`${at} \u2192 ${name}`,
|
|
1811
|
-
"UNJUDGED",
|
|
1812
|
-
`it resolves from here and ${reading.why} \u2014 this copy could be any version, so the group cannot be said to hold or not to hold`
|
|
1813
|
-
)
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
|
|
1818
|
-
if (split2 !== void 0) findings.push(split2);
|
|
1819
|
-
found.push(...here);
|
|
1820
|
-
}
|
|
1821
|
-
if (found.length === 0) {
|
|
1822
|
-
findings.push(
|
|
1823
|
-
finding6(
|
|
1824
|
-
group.name,
|
|
1825
|
-
"SKIP",
|
|
1826
|
-
`no workspace under ${root} resolves any of ${group.packages.join(", ")}, so nothing here versions together`
|
|
1827
|
-
)
|
|
1828
|
-
);
|
|
1829
|
-
continue;
|
|
1830
|
-
}
|
|
1831
|
-
const names = [...new Set(found.map((one) => one.name))].toSorted();
|
|
1832
|
-
const split = names.flatMap((name) => {
|
|
1833
|
-
const line = repoWide(
|
|
1834
|
-
name,
|
|
1835
|
-
found.filter((one) => one.name === name)
|
|
1836
|
-
);
|
|
1837
|
-
return line === void 0 ? [] : [line];
|
|
1838
|
-
});
|
|
1839
|
-
findings.push(...split);
|
|
1840
|
-
if (split.length === 0) {
|
|
1841
|
-
const versions = [...new Set(found.map((one) => one.version))];
|
|
1842
|
-
findings.push(
|
|
1843
|
-
finding6(
|
|
1844
|
-
group.name,
|
|
1845
|
-
"OK",
|
|
1846
|
-
`${names.length} of ${group.packages.length} installed, at ${versions.join(", ")}, one copy each across ${new Set(found.map((one) => one.at)).size} workspaces`
|
|
1847
|
-
)
|
|
1848
|
-
);
|
|
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"
|
|
1849
1952
|
}
|
|
1850
|
-
|
|
1851
|
-
return findings;
|
|
1953
|
+
];
|
|
1852
1954
|
};
|
|
1853
1955
|
|
|
1854
1956
|
// src/loaded.ts
|
|
1855
|
-
import { readFileSync as
|
|
1856
|
-
import { join as
|
|
1957
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
1958
|
+
import { join as join11, sep as sep3 } from "path";
|
|
1857
1959
|
var SCOPE = "@geonosis/";
|
|
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.";
|
|
1858
1961
|
var BLOCKS = [
|
|
1859
1962
|
"dependencies",
|
|
1860
1963
|
"devDependencies",
|
|
@@ -1966,7 +2069,7 @@ var oneConfig = async ({
|
|
|
1966
2069
|
}
|
|
1967
2070
|
return held ? said2("OK", `loaded ${loaded.version} = declared ${declared.spec} (${declared.at})`) : said2(
|
|
1968
2071
|
"FAIL",
|
|
1969
|
-
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}.
|
|
2072
|
+
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Run \`geonosis update --to ${declared.spec}\`: it removes the WHOLE node_modules of every non-root workspace carrying one, never the root's, and reinstalls. ${NOT_THE_SCOPE_DIRECTORY} The linter is not running what the tree declares until then`
|
|
1970
2073
|
);
|
|
1971
2074
|
};
|
|
1972
2075
|
var labelOf2 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
@@ -2005,7 +2108,7 @@ var shipperOf = (root, bin) => {
|
|
|
2005
2108
|
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2006
2109
|
try {
|
|
2007
2110
|
const manifest = JSON.parse(
|
|
2008
|
-
|
|
2111
|
+
readFileSync11(join11(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
2009
2112
|
);
|
|
2010
2113
|
const declared = manifest.bin;
|
|
2011
2114
|
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
@@ -2053,6 +2156,7 @@ var gitHooks = (root) => {
|
|
|
2053
2156
|
};
|
|
2054
2157
|
var checkLoaded = async ({
|
|
2055
2158
|
configs,
|
|
2159
|
+
home,
|
|
2056
2160
|
root,
|
|
2057
2161
|
workspaces
|
|
2058
2162
|
}) => {
|
|
@@ -2071,12 +2175,23 @@ var checkLoaded = async ({
|
|
|
2071
2175
|
for (const specifier of [...specifiers].toSorted()) {
|
|
2072
2176
|
findings.push(await copiesOf({ root, specifier, workspaces }));
|
|
2073
2177
|
}
|
|
2178
|
+
findings.push(...checkClaudePlugin({ home, train: await trainVersion(root, [...specifiers]) }));
|
|
2074
2179
|
return findings;
|
|
2075
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
|
+
};
|
|
2076
2191
|
|
|
2077
2192
|
// src/observability.ts
|
|
2078
|
-
import { readFileSync as
|
|
2079
|
-
import { join as
|
|
2193
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2194
|
+
import { join as join12 } from "path";
|
|
2080
2195
|
var GEONOSIS_FILE2 = "geonosis.json";
|
|
2081
2196
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
2082
2197
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
@@ -2090,7 +2205,7 @@ var finding8 = (verdict, subject, message) => ({
|
|
|
2090
2205
|
var readGeonosis2 = (root) => {
|
|
2091
2206
|
let text;
|
|
2092
2207
|
try {
|
|
2093
|
-
text =
|
|
2208
|
+
text = readFileSync12(join12(root, GEONOSIS_FILE2), "utf8");
|
|
2094
2209
|
} catch {
|
|
2095
2210
|
return { present: false };
|
|
2096
2211
|
}
|
|
@@ -2163,7 +2278,7 @@ var ageFinding = (config, root, now) => {
|
|
|
2163
2278
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
2164
2279
|
let record;
|
|
2165
2280
|
try {
|
|
2166
|
-
record = JSON.parse(
|
|
2281
|
+
record = JSON.parse(readFileSync12(join12(root, file), "utf8"));
|
|
2167
2282
|
} catch (error) {
|
|
2168
2283
|
return finding8(
|
|
2169
2284
|
"FAIL",
|
|
@@ -2240,8 +2355,8 @@ var checkObservability = async ({
|
|
|
2240
2355
|
};
|
|
2241
2356
|
|
|
2242
2357
|
// src/runner.ts
|
|
2243
|
-
import { existsSync as
|
|
2244
|
-
import { join as
|
|
2358
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
2359
|
+
import { join as join13 } from "path";
|
|
2245
2360
|
var TEST_FAILURES = "testFailures";
|
|
2246
2361
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
2247
2362
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
@@ -2267,17 +2382,17 @@ var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(ent
|
|
|
2267
2382
|
var PROVES = /--prove\b/;
|
|
2268
2383
|
var everythingRun2 = (root, workspaces) => {
|
|
2269
2384
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2270
|
-
const dir =
|
|
2271
|
-
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) => {
|
|
2272
2387
|
try {
|
|
2273
|
-
return
|
|
2388
|
+
return readFileSync13(join13(dir, name), "utf8");
|
|
2274
2389
|
} catch {
|
|
2275
2390
|
return "";
|
|
2276
2391
|
}
|
|
2277
2392
|
});
|
|
2278
2393
|
let tiers = {};
|
|
2279
2394
|
try {
|
|
2280
|
-
const config = JSON.parse(
|
|
2395
|
+
const config = JSON.parse(readFileSync13(join13(root, "geonosis.json"), "utf8"));
|
|
2281
2396
|
tiers = config.verify ?? {};
|
|
2282
2397
|
} catch {
|
|
2283
2398
|
}
|
|
@@ -2358,8 +2473,8 @@ var checkRunner = ({
|
|
|
2358
2473
|
};
|
|
2359
2474
|
|
|
2360
2475
|
// src/doctor.ts
|
|
2361
|
-
import { homedir as
|
|
2362
|
-
import { join as
|
|
2476
|
+
import { homedir as homedir3 } from "os";
|
|
2477
|
+
import { join as join15 } from "path";
|
|
2363
2478
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2364
2479
|
|
|
2365
2480
|
// src/engine.ts
|
|
@@ -2373,9 +2488,8 @@ var declaredIn3 = (manifest) => new Set(
|
|
|
2373
2488
|
].flatMap((block) => Object.keys(block ?? {}))
|
|
2374
2489
|
);
|
|
2375
2490
|
var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
|
|
2376
|
-
var
|
|
2377
|
-
|
|
2378
|
-
);
|
|
2491
|
+
var labelOf3 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
2492
|
+
var governedBy = (dir, workspaces) => workspaces.filter((one) => holds3(one.dir, dir) || holds3(dir, one.dir));
|
|
2379
2493
|
var enabledHere2 = (config, plugin) => [
|
|
2380
2494
|
.../* @__PURE__ */ new Set([
|
|
2381
2495
|
...enabledRulesOf(config.rules, plugin),
|
|
@@ -2387,18 +2501,36 @@ var checkEngines = async ({
|
|
|
2387
2501
|
entry,
|
|
2388
2502
|
workspaces
|
|
2389
2503
|
}) => {
|
|
2390
|
-
const
|
|
2391
|
-
if (
|
|
2504
|
+
const plugin = await presumptionsOf(entry).catch(() => void 0);
|
|
2505
|
+
if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0 && Object.keys(plugin.floors).length === 0) {
|
|
2392
2506
|
return [];
|
|
2393
2507
|
}
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
+
};
|
|
2520
|
+
const governed = governedBy(config.dir, workspaces);
|
|
2521
|
+
const declared = new Set(governed.flatMap((one) => [...declaredIn3(one.manifest)]));
|
|
2522
|
+
const asked = governed.map(labelOf3).join(", ");
|
|
2523
|
+
return enabledHere2(config, plugin.namespace).flatMap((rule) => {
|
|
2524
|
+
const floorless = pending(rule);
|
|
2525
|
+
const presumes = plugin.presumed[rule.slice(plugin.namespace.length + 1)];
|
|
2526
|
+
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) {
|
|
2527
|
+
return floorless;
|
|
2528
|
+
}
|
|
2398
2529
|
return [
|
|
2530
|
+
...floorless,
|
|
2399
2531
|
{
|
|
2400
2532
|
check: "exercised",
|
|
2401
|
-
message: `it presumes ${presumes.engine}, and
|
|
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`,
|
|
2402
2534
|
subject: `${config.relative} \u2192 ${rule}`,
|
|
2403
2535
|
verdict: "WARN"
|
|
2404
2536
|
}
|
|
@@ -2407,8 +2539,8 @@ var checkEngines = async ({
|
|
|
2407
2539
|
};
|
|
2408
2540
|
|
|
2409
2541
|
// src/rails.ts
|
|
2410
|
-
import { existsSync as
|
|
2411
|
-
import { join as
|
|
2542
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
2543
|
+
import { join as join14 } from "path";
|
|
2412
2544
|
var GEONOSIS2 = "geonosis.json";
|
|
2413
2545
|
var PROJECT = ".claude/settings.json";
|
|
2414
2546
|
var LOCAL = ".claude/settings.local.json";
|
|
@@ -2434,10 +2566,10 @@ var networkIn = (parsed) => {
|
|
|
2434
2566
|
};
|
|
2435
2567
|
};
|
|
2436
2568
|
var sourceAt = (path, label) => {
|
|
2437
|
-
if (!
|
|
2569
|
+
if (!existsSync11(path)) return { network: void 0, path: label, unreadable: false };
|
|
2438
2570
|
try {
|
|
2439
2571
|
return {
|
|
2440
|
-
network: networkIn(JSON.parse(
|
|
2572
|
+
network: networkIn(JSON.parse(readFileSync14(path, "utf8"))),
|
|
2441
2573
|
path: label,
|
|
2442
2574
|
unreadable: false
|
|
2443
2575
|
};
|
|
@@ -2446,11 +2578,11 @@ var sourceAt = (path, label) => {
|
|
|
2446
2578
|
}
|
|
2447
2579
|
};
|
|
2448
2580
|
var declaredEgress = (root) => {
|
|
2449
|
-
const at =
|
|
2450
|
-
if (!
|
|
2581
|
+
const at = join14(root, GEONOSIS2);
|
|
2582
|
+
if (!existsSync11(at)) return void 0;
|
|
2451
2583
|
let parsed;
|
|
2452
2584
|
try {
|
|
2453
|
-
parsed = JSON.parse(
|
|
2585
|
+
parsed = JSON.parse(readFileSync14(at, "utf8"));
|
|
2454
2586
|
} catch {
|
|
2455
2587
|
return void 0;
|
|
2456
2588
|
}
|
|
@@ -2462,8 +2594,8 @@ var declaredEgress = (root) => {
|
|
|
2462
2594
|
};
|
|
2463
2595
|
};
|
|
2464
2596
|
var deniedEgress = (root) => {
|
|
2465
|
-
const at =
|
|
2466
|
-
if (!
|
|
2597
|
+
const at = join14(root, RUN_RECORD);
|
|
2598
|
+
if (!existsSync11(at)) {
|
|
2467
2599
|
return [
|
|
2468
2600
|
finding10(
|
|
2469
2601
|
"deniedEgress",
|
|
@@ -2474,7 +2606,7 @@ var deniedEgress = (root) => {
|
|
|
2474
2606
|
}
|
|
2475
2607
|
let parsed;
|
|
2476
2608
|
try {
|
|
2477
|
-
parsed = JSON.parse(
|
|
2609
|
+
parsed = JSON.parse(readFileSync14(at, "utf8"));
|
|
2478
2610
|
} catch (error) {
|
|
2479
2611
|
return [
|
|
2480
2612
|
finding10(
|
|
@@ -2515,8 +2647,8 @@ var checkRails = ({
|
|
|
2515
2647
|
const sources = [
|
|
2516
2648
|
sourceAt(managedSettings, managedSettings),
|
|
2517
2649
|
sourceAt(userSettings, userSettings),
|
|
2518
|
-
sourceAt(
|
|
2519
|
-
sourceAt(
|
|
2650
|
+
sourceAt(join14(root, PROJECT), PROJECT),
|
|
2651
|
+
sourceAt(join14(root, LOCAL), LOCAL)
|
|
2520
2652
|
];
|
|
2521
2653
|
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2522
2654
|
(one) => finding10(
|
|
@@ -2552,6 +2684,15 @@ var missingOption = (error) => {
|
|
|
2552
2684
|
const named2 = error.option;
|
|
2553
2685
|
return typeof named2 === "string" ? named2 : HEADER.exec(String(error.message))?.[1];
|
|
2554
2686
|
};
|
|
2687
|
+
var refusesBare = (rule) => {
|
|
2688
|
+
try {
|
|
2689
|
+
rule.create({ options: [], report: () => {
|
|
2690
|
+
} });
|
|
2691
|
+
return false;
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
return missingOption(error) !== void 0;
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2555
2696
|
var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
|
|
2556
2697
|
(rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
|
|
2557
2698
|
);
|
|
@@ -2563,26 +2704,37 @@ var checkRequiredOptions = async ({
|
|
|
2563
2704
|
const namespace = loaded?.default?.meta?.name;
|
|
2564
2705
|
const rules = loaded?.default?.rules;
|
|
2565
2706
|
if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
|
|
2566
|
-
|
|
2707
|
+
const enabled = configured(config, namespace);
|
|
2708
|
+
if (enabled.length === 0) return [];
|
|
2709
|
+
const missing = [];
|
|
2710
|
+
let gated = 0;
|
|
2711
|
+
for (const [id, options] of enabled) {
|
|
2567
2712
|
const rule = rules[id.slice(namespace.length + 1)];
|
|
2568
|
-
if (rule === void 0)
|
|
2713
|
+
if (rule === void 0) continue;
|
|
2714
|
+
if (refusesBare(rule)) gated += 1;
|
|
2569
2715
|
try {
|
|
2570
2716
|
rule.create({ options, report: () => {
|
|
2571
2717
|
} });
|
|
2572
|
-
return [];
|
|
2573
2718
|
} catch (error) {
|
|
2574
2719
|
const option = missingOption(error);
|
|
2575
|
-
if (option === void 0)
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
}
|
|
2583
|
-
];
|
|
2720
|
+
if (option === void 0) continue;
|
|
2721
|
+
missing.push({
|
|
2722
|
+
check: "loaded",
|
|
2723
|
+
message: `it is enabled here with no \`${option}\`, and the rule refuses to be constructed without one \u2014 oxlint stops the whole run at load, linting nothing. Configure \`${option}\`, or turn the rule off`,
|
|
2724
|
+
subject: `${config.relative} \u2192 ${id}`,
|
|
2725
|
+
verdict: "FAIL"
|
|
2726
|
+
});
|
|
2584
2727
|
}
|
|
2585
|
-
}
|
|
2728
|
+
}
|
|
2729
|
+
if (missing.length > 0) return missing;
|
|
2730
|
+
return [
|
|
2731
|
+
{
|
|
2732
|
+
check: "loaded",
|
|
2733
|
+
message: `${gated} rules require options, ${gated} carry them`,
|
|
2734
|
+
subject: config.relative,
|
|
2735
|
+
verdict: "OK"
|
|
2736
|
+
}
|
|
2737
|
+
];
|
|
2586
2738
|
};
|
|
2587
2739
|
|
|
2588
2740
|
// src/doctor.ts
|
|
@@ -2611,7 +2763,7 @@ var exercisedOf = ({
|
|
|
2611
2763
|
];
|
|
2612
2764
|
}
|
|
2613
2765
|
const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
|
|
2614
|
-
const [reach, engines
|
|
2766
|
+
const [reach, engines] = await Promise.all([
|
|
2615
2767
|
checkExercised({
|
|
2616
2768
|
config,
|
|
2617
2769
|
corpus,
|
|
@@ -2622,15 +2774,28 @@ var exercisedOf = ({
|
|
|
2622
2774
|
}),
|
|
2623
2775
|
// #159: the rules whose engine this tree has not got. Beside the reach line rather than
|
|
2624
2776
|
// inside it — one is about the corpus, the other about this repo's own manifests.
|
|
2625
|
-
checkEngines({ config, entry, workspaces })
|
|
2626
|
-
// #202: and the rules this config enables without an option they require, which the
|
|
2627
|
-
// lint can only report as a run it could not make.
|
|
2628
|
-
checkRequiredOptions({ config, entry })
|
|
2777
|
+
checkEngines({ config, entry, workspaces })
|
|
2629
2778
|
]);
|
|
2630
|
-
return [reach, ...engines
|
|
2779
|
+
return [reach, ...engines];
|
|
2631
2780
|
})
|
|
2632
2781
|
)
|
|
2633
2782
|
);
|
|
2783
|
+
var requiredOptionsOf = async (configs) => {
|
|
2784
|
+
const found = await Promise.all(
|
|
2785
|
+
configs.flatMap(
|
|
2786
|
+
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map(async (specifier) => {
|
|
2787
|
+
let entry;
|
|
2788
|
+
try {
|
|
2789
|
+
entry = resolveFrom(config.dir, specifier);
|
|
2790
|
+
} catch {
|
|
2791
|
+
return [];
|
|
2792
|
+
}
|
|
2793
|
+
return checkRequiredOptions({ config, entry });
|
|
2794
|
+
})
|
|
2795
|
+
)
|
|
2796
|
+
);
|
|
2797
|
+
return found.flat();
|
|
2798
|
+
};
|
|
2634
2799
|
var skip = (message) => [
|
|
2635
2800
|
{ check: "baseline", message, subject: RATCHET_FILE, verdict: "SKIP" }
|
|
2636
2801
|
];
|
|
@@ -2654,6 +2819,7 @@ var countsOf = (findings) => findings.reduce((counts, one) => ({ ...counts, [one
|
|
|
2654
2819
|
});
|
|
2655
2820
|
var runDoctor = async ({
|
|
2656
2821
|
baseline,
|
|
2822
|
+
home = homedir3(),
|
|
2657
2823
|
only,
|
|
2658
2824
|
oxlint,
|
|
2659
2825
|
root,
|
|
@@ -2668,7 +2834,13 @@ var runDoctor = async ({
|
|
|
2668
2834
|
}
|
|
2669
2835
|
const asked = (check) => only === void 0 || only.includes(check);
|
|
2670
2836
|
const runs = [
|
|
2671
|
-
[
|
|
2837
|
+
[
|
|
2838
|
+
"loaded",
|
|
2839
|
+
async () => [
|
|
2840
|
+
...await checkLoaded({ configs, home, root, workspaces }),
|
|
2841
|
+
...await requiredOptionsOf(configs)
|
|
2842
|
+
]
|
|
2843
|
+
],
|
|
2672
2844
|
[
|
|
2673
2845
|
"group",
|
|
2674
2846
|
() => {
|
|
@@ -2699,16 +2871,22 @@ var runDoctor = async ({
|
|
|
2699
2871
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
2700
2872
|
["drift", () => checkDrift({ root, workspaces })],
|
|
2701
2873
|
["deployed", () => checkDeployed({ root })],
|
|
2702
|
-
["rails", () => checkRails({ root, userSettings:
|
|
2874
|
+
["rails", () => checkRails({ root, userSettings: join15(home, USER_SETTINGS) })]
|
|
2703
2875
|
];
|
|
2704
2876
|
const collected = [];
|
|
2705
|
-
|
|
2877
|
+
const ran = [];
|
|
2878
|
+
for (const [check, run] of runs) {
|
|
2879
|
+
if (!asked(check)) continue;
|
|
2880
|
+
ran.push(check);
|
|
2881
|
+
collected.push(...await run());
|
|
2882
|
+
}
|
|
2706
2883
|
const found = ordered(collected);
|
|
2707
2884
|
const findings = strict ? found.map((one) => one.verdict === "WARN" ? { ...one, verdict: "FAIL" } : one) : found;
|
|
2708
2885
|
return {
|
|
2709
2886
|
counts: countsOf(findings),
|
|
2710
2887
|
findings,
|
|
2711
2888
|
ok: passes(findings),
|
|
2889
|
+
ran,
|
|
2712
2890
|
root
|
|
2713
2891
|
};
|
|
2714
2892
|
};
|
|
@@ -2765,6 +2943,8 @@ export {
|
|
|
2765
2943
|
DEPLOYED_FILE,
|
|
2766
2944
|
NOT_WRITTEN,
|
|
2767
2945
|
checkDeployed,
|
|
2946
|
+
GEONOSIS_FILE,
|
|
2947
|
+
repoCorpusOf,
|
|
2768
2948
|
CHECKS,
|
|
2769
2949
|
DoctorError,
|
|
2770
2950
|
resolveFrom,
|
|
@@ -2772,6 +2952,10 @@ export {
|
|
|
2772
2952
|
pluginVersionOf,
|
|
2773
2953
|
corpusOfPlugin,
|
|
2774
2954
|
relativeToRoot,
|
|
2955
|
+
FIXED_GROUP,
|
|
2956
|
+
KIT_GROUP,
|
|
2957
|
+
declaredGroupsOf,
|
|
2958
|
+
checkGroup,
|
|
2775
2959
|
COMPOSITION_ROOT,
|
|
2776
2960
|
FLOOR_PACKAGES,
|
|
2777
2961
|
READERS,
|
|
@@ -2781,12 +2965,10 @@ export {
|
|
|
2781
2965
|
ENVELOPES_DIR,
|
|
2782
2966
|
NO_ENVELOPES,
|
|
2783
2967
|
checkEnvelopes,
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
declaredGroupsOf,
|
|
2789
|
-
checkGroup,
|
|
2968
|
+
INSTALLED_PLUGINS,
|
|
2969
|
+
PLUGIN_NAME,
|
|
2970
|
+
installedPluginVersions,
|
|
2971
|
+
checkClaudePlugin,
|
|
2790
2972
|
SCOPE,
|
|
2791
2973
|
satisfies,
|
|
2792
2974
|
declaredFor,
|