@geonosis/doctor 1.0.0 → 1.1.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.
@@ -144,6 +144,164 @@ var checkBaseline = ({ ref, root }) => {
144
144
  ];
145
145
  };
146
146
 
147
+ // src/deployed.ts
148
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
149
+ import { join as join2 } from "path";
150
+ var DEPLOYED_FILE = ".geonosis/deployed.json";
151
+ var NOT_WRITTEN = "no .geonosis/deployed.json \u2014 it is written by the pipeline after promote, and its absence is not a pass";
152
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
153
+ var listOf = (found) => Array.isArray(found) ? found.filter((one) => typeof one === "string") : [];
154
+ var finding2 = (verdict, subject, message) => ({
155
+ check: "deployed",
156
+ message,
157
+ subject,
158
+ verdict
159
+ });
160
+ var parseJsonc = (source) => {
161
+ let out = "";
162
+ for (let index = 0; index < source.length; index += 1) {
163
+ const char = source[index] ?? "";
164
+ if (char === '"') {
165
+ const start = index;
166
+ index += 1;
167
+ for (; index < source.length; index += 1) {
168
+ if (source[index] === "\\") {
169
+ index += 1;
170
+ continue;
171
+ }
172
+ if (source[index] === '"') break;
173
+ }
174
+ out += source.slice(start, index + 1);
175
+ continue;
176
+ }
177
+ if (char === "/" && source[index + 1] === "/") {
178
+ const end = source.indexOf("\n", index);
179
+ index = end === -1 ? source.length : end - 1;
180
+ continue;
181
+ }
182
+ if (char === "/" && source[index + 1] === "*") {
183
+ const end = source.indexOf("*/", index + 2);
184
+ index = end === -1 ? source.length : end + 1;
185
+ continue;
186
+ }
187
+ out += char;
188
+ }
189
+ return JSON.parse(out.replaceAll(/,(\s*[\]}])/g, "$1"));
190
+ };
191
+ var BINDING_LISTS = [
192
+ "ai",
193
+ "analytics_engine_datasets",
194
+ "browser",
195
+ "d1_databases",
196
+ "dispatch_namespaces",
197
+ "durable_objects",
198
+ "hyperdrive",
199
+ "kv_namespaces",
200
+ "mtls_certificates",
201
+ "queues",
202
+ "r2_buckets",
203
+ "send_email",
204
+ "services",
205
+ "vectorize",
206
+ "version_metadata",
207
+ "workflows"
208
+ ];
209
+ var bindingsOf = (found) => {
210
+ if (Array.isArray(found)) return found.flatMap(bindingsOf);
211
+ if (!isRecord(found)) return [];
212
+ const named2 = found["binding"] ?? found["name"];
213
+ return [
214
+ ...typeof named2 === "string" ? [named2] : [],
215
+ ...Object.entries(found).filter(([key]) => key === "bindings" || key === "producers" || key === "consumers").flatMap(([, one]) => bindingsOf(one))
216
+ ];
217
+ };
218
+ var declaredIn = (config) => {
219
+ const triggers = config["triggers"];
220
+ const one = config["route"];
221
+ const listed = [
222
+ ...Array.isArray(config["routes"]) ? config["routes"] : [],
223
+ ...typeof one === "string" ? [one] : []
224
+ ];
225
+ return {
226
+ bindings: BINDING_LISTS.flatMap((key) => bindingsOf(config[key])),
227
+ crons: isRecord(triggers) ? listOf(triggers["crons"]) : [],
228
+ routes: listed.map((route) => isRecord(route) ? route["pattern"] : route).filter((pattern) => typeof pattern === "string")
229
+ };
230
+ };
231
+ var differences = (kind, declared, deployed) => {
232
+ const missing = declared.filter((name) => !deployed.includes(name));
233
+ const extra = deployed.filter((name) => !declared.includes(name));
234
+ return [
235
+ ...missing.length === 0 ? [] : [`declared but not deployed ${kind}: ${missing.join(", ")}`],
236
+ ...extra.length === 0 ? [] : [`deployed but not declared ${kind}: ${extra.join(", ")}`]
237
+ ];
238
+ };
239
+ var releaseOf = (root) => {
240
+ const path = join2(root, "geonosis.json");
241
+ if (!existsSync(path)) return void 0;
242
+ try {
243
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
244
+ return isRecord(parsed) && isRecord(parsed["release"]) ? parsed["release"] : {};
245
+ } catch {
246
+ return {};
247
+ }
248
+ };
249
+ var checkDeployed = ({ root }) => {
250
+ const release = releaseOf(root);
251
+ if (release === void 0) return [];
252
+ const configs = listOf(release["wrangler"]);
253
+ const secrets = listOf(release["secrets"]);
254
+ if (configs.length === 0 && secrets.length === 0) {
255
+ return [
256
+ finding2(
257
+ "SKIP",
258
+ "geonosis.json",
259
+ "release.wrangler and release.secrets are both empty \u2014 this repo has declared nothing a deployment is supposed to carry"
260
+ )
261
+ ];
262
+ }
263
+ const env = typeof release["wranglerEnv"] === "string" ? release["wranglerEnv"] : void 0;
264
+ const declared = { bindings: [], crons: [], routes: [] };
265
+ for (const relative of configs) {
266
+ let parsed;
267
+ try {
268
+ parsed = parseJsonc(readFileSync2(join2(root, relative), "utf8"));
269
+ } catch (error) {
270
+ return [finding2("FAIL", relative, `could not be read: ${error.message}`)];
271
+ }
272
+ if (!isRecord(parsed)) return [finding2("FAIL", relative, "is not a wrangler configuration")];
273
+ const environments = parsed["env"];
274
+ const block = env !== void 0 && isRecord(environments) && isRecord(environments[env]) ? environments[env] : parsed;
275
+ const one = declaredIn(block);
276
+ declared.bindings.push(...one.bindings);
277
+ declared.crons.push(...one.crons);
278
+ declared.routes.push(...one.routes);
279
+ }
280
+ const path = join2(root, DEPLOYED_FILE);
281
+ if (!existsSync(path)) return [finding2("SKIP", DEPLOYED_FILE, NOT_WRITTEN)];
282
+ let reported;
283
+ try {
284
+ reported = JSON.parse(readFileSync2(path, "utf8"));
285
+ } catch (error) {
286
+ return [finding2("FAIL", DEPLOYED_FILE, `is not readable JSON: ${error.message}`)];
287
+ }
288
+ if (!isRecord(reported)) return [finding2("FAIL", DEPLOYED_FILE, "is not an object")];
289
+ const triggers = isRecord(reported["triggers"]) ? reported["triggers"] : {};
290
+ const drift = [
291
+ ...differences("crons", declared.crons, listOf(triggers["crons"])),
292
+ ...differences("routes", declared.routes, listOf(triggers["routes"])),
293
+ ...differences("bindings", declared.bindings, listOf(reported["bindings"])),
294
+ ...differences("secrets", secrets, listOf(reported["secrets"]))
295
+ ];
296
+ return [
297
+ drift.length === 0 ? finding2(
298
+ "OK",
299
+ DEPLOYED_FILE,
300
+ `what the tree declares is what the pipeline reported${typeof reported["at"] === "string" ? ` at ${reported["at"]}` : ""}`
301
+ ) : finding2("FAIL", DEPLOYED_FILE, drift.join("; "))
302
+ ];
303
+ };
304
+
147
305
  // src/types.ts
148
306
  var CHECKS = [
149
307
  "loaded",
@@ -151,7 +309,8 @@ var CHECKS = [
151
309
  "baseline",
152
310
  "runner",
153
311
  "drift",
154
- "observability"
312
+ "observability",
313
+ "deployed"
155
314
  ];
156
315
  var DoctorError = class extends Error {
157
316
  constructor(message) {
@@ -161,18 +320,18 @@ var DoctorError = class extends Error {
161
320
  };
162
321
 
163
322
  // src/resolve.ts
164
- import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
323
+ import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync } from "fs";
165
324
  import { createRequire } from "module";
166
- import { dirname, join as join2 } from "path";
325
+ import { dirname, join as join3 } from "path";
167
326
  import { pathToFileURL } from "url";
168
- var resolveFrom = (dir, specifier) => createRequire(join2(dir, "noop.js")).resolve(specifier);
327
+ var resolveFrom = (dir, specifier) => createRequire(join3(dir, "noop.js")).resolve(specifier);
169
328
  var packageDirOf = (entry, name) => {
170
329
  let dir = dirname(entry);
171
330
  for (; ; ) {
172
- const manifest = join2(dir, "package.json");
173
- if (existsSync(manifest)) {
331
+ const manifest = join3(dir, "package.json");
332
+ if (existsSync2(manifest)) {
174
333
  try {
175
- const parsed = JSON.parse(readFileSync2(manifest, "utf8"));
334
+ const parsed = JSON.parse(readFileSync3(manifest, "utf8"));
176
335
  if (parsed.name === name) return dir;
177
336
  } catch {
178
337
  }
@@ -194,7 +353,16 @@ var pluginVersionOf = async (entry) => {
194
353
  }
195
354
  return version;
196
355
  };
197
- var corpusOfPlugin = (from, specifier) => join2(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
356
+ var probesOf = async (entry, plugin) => {
357
+ const loaded = await import(pathToFileURL(entry).href);
358
+ return Object.fromEntries(
359
+ Object.entries(loaded.default?.rules ?? {}).filter(([, rule]) => typeof rule?.probe === "function").map(([name, rule]) => [
360
+ `${plugin}/${name}`,
361
+ rule.probe
362
+ ])
363
+ );
364
+ };
365
+ var corpusOfPlugin = (from, specifier) => join3(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
198
366
  var real = (path) => {
199
367
  try {
200
368
  return realpathSync(path);
@@ -205,8 +373,8 @@ var real = (path) => {
205
373
  var relativeToRoot = (root, path) => relativePath(real(root), real(path));
206
374
 
207
375
  // src/drift.ts
208
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
209
- import { join as join3, sep as sep2 } from "path";
376
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
377
+ import { join as join4, sep as sep2 } from "path";
210
378
  var WORKFLOWS = ".github/workflows";
211
379
  var SETTINGS = ".claude/settings.json";
212
380
  var GEONOSIS = "geonosis.json";
@@ -214,7 +382,7 @@ var LAW = "CLAUDE.md";
214
382
  var CEILING = 200;
215
383
  var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
216
384
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
217
- var finding2 = (subject, verdict, message) => ({
385
+ var finding3 = (subject, verdict, message) => ({
218
386
  check: "drift",
219
387
  message,
220
388
  subject,
@@ -232,27 +400,27 @@ var filesUnder = (dir, match) => {
232
400
  }
233
401
  for (const entry of entries) {
234
402
  if (entry.isDirectory()) {
235
- if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join3(at, entry.name));
403
+ if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join4(at, entry.name));
236
404
  continue;
237
405
  }
238
- if (entry.isFile() && match(entry.name)) found.push(join3(at, entry.name));
406
+ if (entry.isFile() && match(entry.name)) found.push(join4(at, entry.name));
239
407
  }
240
408
  };
241
409
  walk2(dir);
242
410
  return found;
243
411
  };
244
412
  var ci = (root) => {
245
- const dir = join3(root, WORKFLOWS);
246
- if (!existsSync2(dir)) {
247
- return [finding2(WORKFLOWS, "SKIP", "there are no workflows here to read")];
413
+ const dir = join4(root, WORKFLOWS);
414
+ if (!existsSync3(dir)) {
415
+ return [finding3(WORKFLOWS, "SKIP", "there are no workflows here to read")];
248
416
  }
249
417
  return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
250
418
  const at = relativePath(root, path);
251
- return SWITCHED_OFF.test(readFileSync3(path, "utf8")) ? finding2(
419
+ return SWITCHED_OFF.test(readFileSync4(path, "utf8")) ? finding3(
252
420
  at,
253
421
  "FAIL",
254
422
  "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"
255
- ) : finding2(at, "OK", "nothing in it is switched off");
423
+ ) : finding3(at, "OK", "nothing in it is switched off");
256
424
  });
257
425
  };
258
426
  var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep2}`);
@@ -268,10 +436,10 @@ var orphanTests = (root, workspaces) => {
268
436
  orphaned.set(at, [...orphaned.get(at) ?? [], relativePath(root, path)]);
269
437
  }
270
438
  if (orphaned.size === 0) {
271
- return [finding2("test files", "OK", "every test file sits under a workspace that runs tests")];
439
+ return [finding3("test files", "OK", "every test file sits under a workspace that runs tests")];
272
440
  }
273
441
  return [...orphaned.entries()].map(
274
- ([at, files]) => finding2(
442
+ ([at, files]) => finding3(
275
443
  at,
276
444
  "FAIL",
277
445
  `${files.length} test file(s) here and no test script to run them \u2014 ${files.slice(0, 3).join(", ")}`
@@ -279,10 +447,10 @@ var orphanTests = (root, workspaces) => {
279
447
  );
280
448
  };
281
449
  var readGeonosis = (root) => {
282
- const path = join3(root, GEONOSIS);
283
- if (!existsSync2(path)) return void 0;
450
+ const path = join4(root, GEONOSIS);
451
+ if (!existsSync3(path)) return void 0;
284
452
  try {
285
- return JSON.parse(readFileSync3(path, "utf8"));
453
+ return JSON.parse(readFileSync4(path, "utf8"));
286
454
  } catch {
287
455
  return void 0;
288
456
  }
@@ -291,34 +459,34 @@ var law = (root, config) => {
291
459
  const declared = config?.law ?? {};
292
460
  const file = typeof declared.file === "string" ? declared.file : LAW;
293
461
  const ceiling = typeof declared.maxLines === "number" ? declared.maxLines : CEILING;
294
- const path = join3(root, file);
295
- if (!existsSync2(path)) {
296
- return [finding2(file, "SKIP", "there is no law file here to measure")];
462
+ const path = join4(root, file);
463
+ if (!existsSync3(path)) {
464
+ return [finding3(file, "SKIP", "there is no law file here to measure")];
297
465
  }
298
- const source = readFileSync3(path, "utf8");
466
+ const source = readFileSync4(path, "utf8");
299
467
  const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
300
468
  return [
301
- lines > ceiling ? finding2(
469
+ lines > ceiling ? finding3(
302
470
  file,
303
471
  "WARN",
304
472
  `${lines} lines against a ceiling of ${ceiling} \u2014 depth belongs in the skills and in the rules, where it is read`
305
- ) : finding2(file, "OK", `${lines} lines, under the ceiling of ${ceiling}`)
473
+ ) : finding3(file, "OK", `${lines} lines, under the ceiling of ${ceiling}`)
306
474
  ];
307
475
  };
308
476
  var hooks = (root) => {
309
- const path = join3(root, SETTINGS);
310
- if (!existsSync2(path)) {
477
+ const path = join4(root, SETTINGS);
478
+ if (!existsSync3(path)) {
311
479
  return [
312
- finding2(
480
+ finding3(
313
481
  SETTINGS,
314
482
  "WARN",
315
483
  "nothing here installs the kit\u2019s plugin, so none of the hooks, agents or skills reach this repo \u2014 the gates run, the method does not"
316
484
  )
317
485
  ];
318
486
  }
319
- const source = readFileSync3(path, "utf8");
487
+ const source = readFileSync4(path, "utf8");
320
488
  return [
321
- source.includes("geonosis") ? finding2(SETTINGS, "OK", "it installs the kit\u2019s plugin") : finding2(SETTINGS, "WARN", "it names no geonosis plugin or marketplace")
489
+ source.includes("geonosis") ? finding3(SETTINGS, "OK", "it installs the kit\u2019s plugin") : finding3(SETTINGS, "WARN", "it names no geonosis plugin or marketplace")
322
490
  ];
323
491
  };
324
492
  var READERS = {
@@ -339,7 +507,7 @@ var resolves = (root, name) => {
339
507
  var blocks = (root, config, readers) => {
340
508
  if (config === void 0) {
341
509
  return [
342
- finding2(
510
+ finding3(
343
511
  GEONOSIS,
344
512
  "SKIP",
345
513
  "there is no geonosis.json here, so no block says what this repo asks the kit to do"
@@ -351,7 +519,7 @@ var blocks = (root, config, readers) => {
351
519
  const installed = resolves(root, name);
352
520
  if (declared && !installed) {
353
521
  return [
354
- finding2(
522
+ finding3(
355
523
  name,
356
524
  "WARN",
357
525
  `geonosis.json has a "${block}" block and ${name} is not installed here \u2014 nothing reads it`
@@ -360,21 +528,21 @@ var blocks = (root, config, readers) => {
360
528
  }
361
529
  if (!declared && installed) {
362
530
  return [
363
- finding2(
531
+ finding3(
364
532
  name,
365
533
  "WARN",
366
534
  `${name} is installed and geonosis.json has no "${block}" block \u2014 it runs on its defaults, whatever they are`
367
535
  )
368
536
  ];
369
537
  }
370
- return declared ? [finding2(name, "OK", `a "${block}" block, and ${name} to read it`)] : [];
538
+ return declared ? [finding3(name, "OK", `a "${block}" block, and ${name} to read it`)] : [];
371
539
  });
372
540
  };
373
541
  var pluginDirsConfigured = (root) => {
374
- const path = join3(root, ".oxlintrc.json");
375
- if (!existsSync2(path)) return void 0;
542
+ const path = join4(root, ".oxlintrc.json");
543
+ if (!existsSync3(path)) return void 0;
376
544
  try {
377
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
545
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
378
546
  const rule = parsed.rules?.["biological-architecture/no-unregistered-plugin-dir"];
379
547
  const options = Array.isArray(rule) ? rule[1] : void 0;
380
548
  if (options?.roots === void 0 || typeof options.registry !== "string") return void 0;
@@ -391,26 +559,26 @@ var pluginDirs = (root) => {
391
559
  const configured = pluginDirsConfigured(root);
392
560
  if (configured === void 0) {
393
561
  return [
394
- finding2(
562
+ finding3(
395
563
  "plugin directories",
396
564
  "SKIP",
397
565
  "no-unregistered-plugin-dir is not configured here, so this repo has not said where its integrations live"
398
566
  )
399
567
  ];
400
568
  }
401
- const registryPath = join3(root, configured.registry);
402
- if (!existsSync2(registryPath)) {
403
- return [finding2(configured.registry, "FAIL", "the registry the rule names is not there")];
569
+ const registryPath = join4(root, configured.registry);
570
+ if (!existsSync3(registryPath)) {
571
+ return [finding3(configured.registry, "FAIL", "the registry the rule names is not there")];
404
572
  }
405
- const registry = readFileSync3(registryPath, "utf8");
573
+ const registry = readFileSync4(registryPath, "utf8");
406
574
  const unreachable = [];
407
575
  for (const rootDir of configured.roots) {
408
- const at = join3(root, rootDir);
409
- if (!existsSync2(at)) continue;
576
+ const at = join4(root, rootDir);
577
+ if (!existsSync3(at)) continue;
410
578
  for (const entry of readdirSync2(at, { withFileTypes: true })) {
411
579
  if (!entry.isDirectory()) continue;
412
580
  const hasManifest = configured.manifests.some(
413
- (name) => existsSync2(join3(at, entry.name, name))
581
+ (name) => existsSync3(join4(at, entry.name, name))
414
582
  );
415
583
  if (hasManifest && !registry.includes(entry.name)) {
416
584
  unreachable.push(`${rootDir}/${entry.name}`);
@@ -418,11 +586,11 @@ var pluginDirs = (root) => {
418
586
  }
419
587
  }
420
588
  return [
421
- unreachable.length === 0 ? finding2(
589
+ unreachable.length === 0 ? finding3(
422
590
  configured.registry,
423
591
  "OK",
424
592
  "every directory under the declared roots is named by it"
425
- ) : finding2(
593
+ ) : finding3(
426
594
  configured.registry,
427
595
  "FAIL",
428
596
  `${unreachable.length} directory(ies) it never names: ${unreachable.join(", ")}`
@@ -447,14 +615,15 @@ var checkDrift = ({
447
615
 
448
616
  // src/exercised.ts
449
617
  import { spawnSync as spawnSync2 } from "child_process";
450
- import { existsSync as existsSync3, mkdtempSync, rmSync, writeFileSync } from "fs";
618
+ import { cpSync, existsSync as existsSync4, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
451
619
  import { tmpdir } from "os";
452
- import { join as join4 } from "path";
620
+ import { dirname as dirname2, join as join5 } from "path";
453
621
  import { corpusOf, readManifest } from "@geonosis/lint-parity";
454
622
  var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
455
623
  var severityOf = (level) => Array.isArray(level) ? level[0] : level;
456
624
  var enabledRulesOf = (rules, plugin) => Object.keys(rules).filter((id) => id.startsWith(`${plugin}/`) && !OFF.has(severityOf(rules[id]))).toSorted();
457
- var finding3 = (subject, verdict, message) => ({
625
+ var passes = (findings) => !findings.some((one) => one.verdict === "FAIL" || one.verdict === "UNJUDGED");
626
+ var finding4 = (subject, verdict, message) => ({
458
627
  check: "exercised",
459
628
  message,
460
629
  subject,
@@ -466,9 +635,9 @@ var refusal = (error) => {
466
635
  return said.length > LIMIT ? `${said.slice(0, LIMIT)}\u2026` : said;
467
636
  };
468
637
  var reasonFrom = (config, oxlint) => {
469
- const dir = mkdtempSync(join4(tmpdir(), "geonosis-doctor-why-"));
638
+ const dir = mkdtempSync(join5(tmpdir(), "geonosis-doctor-why-"));
470
639
  try {
471
- const probe = join4(dir, "probe.tsx");
640
+ const probe = join5(dir, "probe.tsx");
472
641
  writeFileSync(probe, "export const probe = 1\n");
473
642
  const run = spawnSync2(
474
643
  oxlint,
@@ -502,21 +671,59 @@ var ownReach = ({
502
671
  fired: new Set(reach.filter((one) => one.firedInA).map((one) => one.rule))
503
672
  };
504
673
  };
505
- var checkExercised = ({
674
+ var optionsOf = (level) => Array.isArray(level) ? level.slice(1) : [];
675
+ var under = (config, rule) => `${rule} under ${JSON.stringify(optionsOf(config.rules[rule]))}`;
676
+ var throughProbes = ({
506
677
  config,
507
678
  corpus,
508
679
  oxlint,
680
+ probes,
681
+ silent
682
+ }) => {
683
+ const refused = /* @__PURE__ */ new Map();
684
+ const dir = mkdtempSync(join5(tmpdir(), "geonosis-doctor-probe-"));
685
+ const here = join5(dir, "corpus");
686
+ try {
687
+ cpSync(corpus, here, { recursive: true });
688
+ for (const rule of silent) {
689
+ const write = probes[rule];
690
+ if (write === void 0) continue;
691
+ try {
692
+ const probe = write(optionsOf(config.rules[rule]));
693
+ const at = join5(here, probe.path);
694
+ mkdirSync(dirname2(at), { recursive: true });
695
+ writeFileSync(at, probe.source);
696
+ } catch (error) {
697
+ refused.set(rule, String(error.message));
698
+ }
699
+ }
700
+ const reach = corpusOf({
701
+ configA: config.path,
702
+ configB: config.path,
703
+ corpus: here,
704
+ oxlint
705
+ }).reach;
706
+ return { fired: new Set(reach.filter((one) => one.firedInA).map((one) => one.rule)), refused };
707
+ } finally {
708
+ rmSync(dir, { force: true, recursive: true });
709
+ }
710
+ };
711
+ var checkExercised = async ({
712
+ config,
713
+ corpus,
714
+ entry,
715
+ oxlint,
509
716
  repoCorpus,
510
717
  root
511
718
  }) => {
512
- const said = (verdict, message) => finding3(config.relative, verdict, message);
513
- if (repoCorpus !== void 0 && !existsSync3(repoCorpus)) {
719
+ const said = (verdict, message) => finding4(config.relative, verdict, message);
720
+ if (repoCorpus !== void 0 && !existsSync4(repoCorpus)) {
514
721
  return said(
515
722
  "FAIL",
516
723
  `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`
517
724
  );
518
725
  }
519
- if (!existsSync3(corpus)) {
726
+ if (!existsSync4(corpus)) {
520
727
  return said(
521
728
  "SKIP",
522
729
  `the plugin loaded from here ships no corpus at ${relativeToRoot(root, corpus)} \u2014 nothing declares which rules it can be evidence about`
@@ -564,7 +771,38 @@ var checkExercised = ({
564
771
  }
565
772
  const silent = enabled.filter((rule) => !fired.has(rule) && !own.fired.has(rule));
566
773
  const where = own.claimed.length === 0 ? "" : ` (${own.fired.size} by this repo's own corpus)`;
567
- return silent.length === 0 ? said("OK", `${enabled.length} enabled, ${enabled.length} exercised${where}`) : said("FAIL", `${countOf(silent, "fire")} nowhere in the corpus: ${named(silent)}`);
774
+ if (silent.length === 0) {
775
+ return said("OK", `${enabled.length} enabled, ${enabled.length} exercised${where}`);
776
+ }
777
+ const probes = entry === void 0 ? {} : await probesOf(entry, manifest.plugin).catch(() => ({}));
778
+ const declared = silent.filter((rule) => probes[rule] !== void 0);
779
+ const unprobed = silent.filter((rule) => probes[rule] === void 0);
780
+ let probed = { fired: /* @__PURE__ */ new Set(), refused: /* @__PURE__ */ new Map() };
781
+ if (declared.length > 0) {
782
+ try {
783
+ probed = throughProbes({ config, corpus, oxlint, probes, silent: declared });
784
+ } catch (error) {
785
+ return said("FAIL", `the declared probes could not be run \u2014 ${refusal(error)}`);
786
+ }
787
+ }
788
+ const exercised = declared.filter((rule) => probed.fired.has(rule));
789
+ const inert = declared.filter((rule) => !probed.fired.has(rule));
790
+ const placed = inert.filter((rule) => !probed.refused.has(rule));
791
+ if (placed.length > 0) {
792
+ return said(
793
+ "FAIL",
794
+ `${countOf(placed, "fire")} nowhere in the corpus and nothing through the probe each declares either: ${named(placed.map((rule) => under(config, rule)))}`
795
+ );
796
+ }
797
+ const unplaceable = [
798
+ ...unprobed.map(
799
+ (rule) => `${rule} declares no probe, so ${optionsOf(config.rules[rule]).length === 0 ? "its scope" : JSON.stringify(optionsOf(config.rules[rule]))} does not reach the corpus`
800
+ ),
801
+ ...inert.map((rule) => `${rule}: ${probed.refused.get(rule) ?? ""}`)
802
+ ];
803
+ if (unplaceable.length > 0) return said("UNJUDGED", named(unplaceable));
804
+ 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`;
805
+ return said("OK", `${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${through}`);
568
806
  };
569
807
 
570
808
  // src/loaded.ts
@@ -638,7 +876,7 @@ var declaredFor = ({
638
876
  }
639
877
  return void 0;
640
878
  };
641
- var finding4 = (subject, verdict, message) => ({
879
+ var finding5 = (subject, verdict, message) => ({
642
880
  check: "loaded",
643
881
  message,
644
882
  subject,
@@ -655,7 +893,7 @@ var oneConfig = async ({
655
893
  specifier,
656
894
  workspaces
657
895
  }) => {
658
- const said = (verdict, message) => finding4(config.relative, verdict, `${specifier}: ${message}`);
896
+ const said = (verdict, message) => finding5(config.relative, verdict, `${specifier}: ${message}`);
659
897
  let loaded;
660
898
  try {
661
899
  loaded = await versionAt(resolveFrom(config.dir, specifier), specifier, root);
@@ -707,10 +945,10 @@ var copiesOf = async ({
707
945
  found.set(at, { from: [labelOf(workspace)], version: await pluginVersionOf(entry) });
708
946
  }
709
947
  if (found.size === 0) {
710
- return finding4(specifier, "FAIL", "no workspace in this tree can resolve it at all");
948
+ return finding5(specifier, "FAIL", "no workspace in this tree can resolve it at all");
711
949
  }
712
950
  const listed = [...found.entries()].map(([at, one]) => `${at} ${one.version} (${one.from.join(", ")})`).join("; ");
713
- return found.size === 1 ? finding4(specifier, "OK", `1 copy \u2014 ${listed}`) : finding4(
951
+ return found.size === 1 ? finding5(specifier, "OK", `1 copy \u2014 ${listed}`) : finding5(
714
952
  specifier,
715
953
  "WARN",
716
954
  `${found.size} copies \u2014 ${listed}. Which one oxlint runs depends on which directory its config sits in.`
@@ -725,7 +963,7 @@ var checkLoaded = async ({
725
963
  const specifiers = /* @__PURE__ */ new Set();
726
964
  for (const config of configs) {
727
965
  if (config.error !== void 0) {
728
- findings.push(finding4(config.relative, "FAIL", config.error));
966
+ findings.push(finding5(config.relative, "FAIL", config.error));
729
967
  continue;
730
968
  }
731
969
  for (const specifier of config.jsPlugins.filter((name) => name.startsWith(SCOPE))) {
@@ -740,13 +978,13 @@ var checkLoaded = async ({
740
978
  };
741
979
 
742
980
  // src/observability.ts
743
- import { readFileSync as readFileSync4 } from "fs";
744
- import { join as join5 } from "path";
981
+ import { readFileSync as readFileSync5 } from "fs";
982
+ import { join as join6 } from "path";
745
983
  var GEONOSIS_FILE = "geonosis.json";
746
984
  var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
747
985
  var DEFAULT_MAX_AGE_SECONDS = 3600;
748
986
  var HEAD_TIMEOUT_MS = 3e3;
749
- var finding5 = (verdict, subject, message) => ({
987
+ var finding6 = (verdict, subject, message) => ({
750
988
  check: "observability",
751
989
  message,
752
990
  subject,
@@ -755,7 +993,7 @@ var finding5 = (verdict, subject, message) => ({
755
993
  var readGeonosis2 = (root) => {
756
994
  let text;
757
995
  try {
758
- text = readFileSync4(join5(root, GEONOSIS_FILE), "utf8");
996
+ text = readFileSync5(join6(root, GEONOSIS_FILE), "utf8");
759
997
  } catch {
760
998
  return { present: false };
761
999
  }
@@ -773,25 +1011,25 @@ var readGeonosis2 = (root) => {
773
1011
  var exporterFinding = (config) => {
774
1012
  const sink = config.sink;
775
1013
  if (typeof sink !== "string" || sink.trim() === "") {
776
- return finding5(
1014
+ return finding6(
777
1015
  "FAIL",
778
1016
  GEONOSIS_FILE,
779
1017
  "observability.sink is not set, so nothing here says where errors are supposed to go \u2014 and a repo that cannot name its exporter has not got one"
780
1018
  );
781
1019
  }
782
1020
  if (REACHES_NOTHING.has(sink.toLowerCase())) {
783
- return finding5(
1021
+ return finding6(
784
1022
  "WARN",
785
1023
  GEONOSIS_FILE,
786
1024
  `the configured sink is "${sink}", which answers ok and reaches nothing. Correct in a dev tree; in a deployed one it is the instrument that cannot fail.`
787
1025
  );
788
1026
  }
789
- return finding5("OK", GEONOSIS_FILE, `the configured sink is "${sink}"`);
1027
+ return finding6("OK", GEONOSIS_FILE, `the configured sink is "${sink}"`);
790
1028
  };
791
1029
  var reachableFinding = async (config) => {
792
1030
  const endpoint = config.endpoint;
793
1031
  if (typeof endpoint !== "string" || endpoint.trim() === "") {
794
- return finding5(
1032
+ return finding6(
795
1033
  "SKIP",
796
1034
  GEONOSIS_FILE,
797
1035
  "no observability.endpoint was named, so whether the exporter is reachable was not asked"
@@ -801,13 +1039,13 @@ var reachableFinding = async (config) => {
801
1039
  const timer = setTimeout(() => controller.abort(), HEAD_TIMEOUT_MS);
802
1040
  try {
803
1041
  const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
804
- return finding5(
1042
+ return finding6(
805
1043
  "OK",
806
1044
  GEONOSIS_FILE,
807
1045
  `${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
808
1046
  );
809
1047
  } catch (error) {
810
- return finding5(
1048
+ return finding6(
811
1049
  "FAIL",
812
1050
  GEONOSIS_FILE,
813
1051
  `${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
@@ -819,7 +1057,7 @@ var reachableFinding = async (config) => {
819
1057
  var ageFinding = (config, root, now) => {
820
1058
  const file = config.lastEventFile;
821
1059
  if (typeof file !== "string" || file.trim() === "") {
822
- return finding5(
1060
+ return finding6(
823
1061
  "SKIP",
824
1062
  GEONOSIS_FILE,
825
1063
  "no observability.lastEventFile was configured, so when the last event arrived is not a question anything here can answer. Have the sink write { at, id, sink } on every capture and name the file."
@@ -828,27 +1066,27 @@ var ageFinding = (config, root, now) => {
828
1066
  const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
829
1067
  let record;
830
1068
  try {
831
- record = JSON.parse(readFileSync4(join5(root, file), "utf8"));
1069
+ record = JSON.parse(readFileSync5(join6(root, file), "utf8"));
832
1070
  } catch (error) {
833
- return finding5(
1071
+ return finding6(
834
1072
  "FAIL",
835
1073
  file,
836
1074
  `the last event file could not be read: ${error.message}. A sink that has never written one has never captured anything.`
837
1075
  );
838
1076
  }
839
1077
  if (typeof record.at !== "number" || !Number.isFinite(record.at)) {
840
- return finding5(
1078
+ return finding6(
841
1079
  "FAIL",
842
1080
  file,
843
1081
  'the last event record has no numeric "at", so its age cannot be read \u2014 and an age nobody can read is not an age inside the window'
844
1082
  );
845
1083
  }
846
1084
  const ageSeconds = Math.round((now - record.at) / 1e3);
847
- return ageSeconds > maxAgeSeconds ? finding5(
1085
+ return ageSeconds > maxAgeSeconds ? finding6(
848
1086
  "FAIL",
849
1087
  file,
850
1088
  `the last event arrived ${ageSeconds}s ago, past the ${maxAgeSeconds}s window. An exporter that stopped, a key that was rotated and a sink that has been dropping since Tuesday all look exactly like this, and all of them leave a green build.`
851
- ) : finding5(
1089
+ ) : finding6(
852
1090
  "OK",
853
1091
  file,
854
1092
  `the last event arrived ${ageSeconds}s ago, inside the ${maxAgeSeconds}s window`
@@ -857,16 +1095,16 @@ var ageFinding = (config, root, now) => {
857
1095
  var probeFinding = (config) => {
858
1096
  const probe = config.probe;
859
1097
  if (typeof probe === "string" && probe.trim() !== "") {
860
- return finding5("OK", GEONOSIS_FILE, `the probe that proves this exporter is "${probe}"`);
1098
+ return finding6("OK", GEONOSIS_FILE, `the probe that proves this exporter is "${probe}"`);
861
1099
  }
862
1100
  if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
863
- return finding5(
1101
+ return finding6(
864
1102
  "OK",
865
1103
  GEONOSIS_FILE,
866
1104
  "no probe command, but a last event file is read above, so something does look at this exporter"
867
1105
  );
868
1106
  }
869
- return finding5(
1107
+ return finding6(
870
1108
  "WARN",
871
1109
  GEONOSIS_FILE,
872
1110
  "neither observability.probe nor observability.lastEventFile is configured, so nothing in this repo has ever established that a report reaches the sink. Name a probe command \u2014 the doctor reports it, your gate runs it."
@@ -879,7 +1117,7 @@ var checkObservability = async ({
879
1117
  const read = readGeonosis2(root);
880
1118
  if (read.error !== void 0) {
881
1119
  return [
882
- finding5(
1120
+ finding6(
883
1121
  "FAIL",
884
1122
  GEONOSIS_FILE,
885
1123
  `${GEONOSIS_FILE} could not be parsed: ${read.error}. A config nobody can read has not been read, and every question below would have been answered from a default nobody chose.`
@@ -888,7 +1126,7 @@ var checkObservability = async ({
888
1126
  }
889
1127
  if (!read.present || read.config === void 0) {
890
1128
  return [
891
- finding5(
1129
+ finding6(
892
1130
  "SKIP",
893
1131
  GEONOSIS_FILE,
894
1132
  `no observability block in ${GEONOSIS_FILE}, so nothing here knows where this repo sends its errors. Add { sink, endpoint, lastEventFile | probe, maxAgeSeconds } to have this asked.`
@@ -905,16 +1143,16 @@ var checkObservability = async ({
905
1143
  };
906
1144
 
907
1145
  // src/repo-corpus.ts
908
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
909
- import { join as join6 } from "path";
1146
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
1147
+ import { join as join7 } from "path";
910
1148
  var GEONOSIS_FILE2 = "geonosis.json";
911
1149
  var repoCorpusOf = (root) => {
912
- const path = join6(root, GEONOSIS_FILE2);
913
- if (!existsSync4(path)) return void 0;
1150
+ const path = join7(root, GEONOSIS_FILE2);
1151
+ if (!existsSync5(path)) return void 0;
914
1152
  try {
915
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
1153
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
916
1154
  const declared = parsed.doctor?.corpus;
917
- return typeof declared === "string" && declared !== "" ? join6(root, declared) : void 0;
1155
+ return typeof declared === "string" && declared !== "" ? join7(root, declared) : void 0;
918
1156
  } catch {
919
1157
  return void 0;
920
1158
  }
@@ -923,8 +1161,9 @@ var repoCorpusOf = (root) => {
923
1161
  // src/runner.ts
924
1162
  var TEST_FAILURES = "testFailures";
925
1163
  var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
1164
+ var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
926
1165
  var WRITES_A_REPORT = /--reporter[= ]\S*json|--outputFile/i;
927
- var finding6 = (subject, verdict, message) => ({
1166
+ var finding7 = (subject, verdict, message) => ({
928
1167
  check: "runner",
929
1168
  message,
930
1169
  subject,
@@ -952,7 +1191,7 @@ var checkRunner = ({
952
1191
  if (script === "") return [];
953
1192
  const subject = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
954
1193
  const said = (verdict, message) => [
955
- finding6(subject, verdict, message)
1194
+ finding7(subject, verdict, message)
956
1195
  ];
957
1196
  if (!RUNS_A_RUNNER.test(script)) {
958
1197
  return said(
@@ -967,9 +1206,15 @@ var checkRunner = ({
967
1206
  if (counter !== void 0) {
968
1207
  return said("OK", `read by the ratchet's "${keyOf(counter)}" counter in report mode`);
969
1208
  }
1209
+ if (RUNS_BUN_TEST.test(script)) {
1210
+ return said(
1211
+ "OK",
1212
+ `"${script}" is judged by its exit code, and bun test's exit code is a verdict: a planted failure exits 1, measured on bun 1.4.0 (2026-08-30). Nothing further is required here.`
1213
+ );
1214
+ }
970
1215
  return said(
971
1216
  "WARN",
972
- `"${script}" \u2014 the test runner's exit code is the only verdict here; vitest-pool-workers exited 0 on failing tests for weeks in a consumer. Give the ratchet a ${TEST_FAILURES} counter in report mode covering this workspace, or have the script write a JSON report.`
1217
+ `"${script}" runs vitest, whose exit code is the only verdict here; @cloudflare/vitest-pool-workers exited 0 on failing tests for weeks in a consumer. Give the ratchet a ${TEST_FAILURES} counter in report mode covering this workspace, or have the script write a JSON report.`
973
1218
  );
974
1219
  });
975
1220
  };
@@ -981,28 +1226,33 @@ var exercisedOf = ({
981
1226
  oxlint,
982
1227
  repoCorpus,
983
1228
  root
984
- }) => configs.flatMap(
985
- (config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map((specifier) => {
986
- let corpus;
987
- try {
988
- corpus = corpusOfPlugin(config.dir, specifier);
989
- } catch (error) {
990
- return {
991
- check: "exercised",
992
- message: `${specifier}: ${String(error.message)}`,
993
- subject: config.relative,
994
- verdict: "SKIP"
995
- };
996
- }
997
- const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
998
- return checkExercised({
999
- config,
1000
- corpus,
1001
- oxlint,
1002
- ...own === void 0 ? {} : { repoCorpus: own },
1003
- root
1004
- });
1005
- })
1229
+ }) => Promise.all(
1230
+ configs.flatMap(
1231
+ (config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map(async (specifier) => {
1232
+ let corpus;
1233
+ let entry;
1234
+ try {
1235
+ corpus = corpusOfPlugin(config.dir, specifier);
1236
+ entry = resolveFrom(config.dir, specifier);
1237
+ } catch (error) {
1238
+ return {
1239
+ check: "exercised",
1240
+ message: `${specifier}: ${String(error.message)}`,
1241
+ subject: config.relative,
1242
+ verdict: "SKIP"
1243
+ };
1244
+ }
1245
+ const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
1246
+ return checkExercised({
1247
+ config,
1248
+ corpus,
1249
+ entry,
1250
+ oxlint,
1251
+ ...own === void 0 ? {} : { repoCorpus: own },
1252
+ root
1253
+ });
1254
+ })
1255
+ )
1006
1256
  );
1007
1257
  var skip = (message) => [
1008
1258
  { check: "baseline", message, subject: RATCHET_FILE, verdict: "SKIP" }
@@ -1020,7 +1270,7 @@ var baselineOf = ({
1020
1270
  return ref === void 0 ? skip("no ref was named and this repo has no origin/main to fall back on") : checkBaseline({ ref, root });
1021
1271
  };
1022
1272
  var ordered = (findings) => CHECKS.flatMap((check) => findings.filter((one) => one.check === check));
1023
- var EMPTY = { FAIL: 0, OK: 0, SKIP: 0, WARN: 0 };
1273
+ var EMPTY = { FAIL: 0, OK: 0, SKIP: 0, UNJUDGED: 0, WARN: 0 };
1024
1274
  var countsOf = (findings) => findings.reduce((counts, one) => ({ ...counts, [one.verdict]: counts[one.verdict] + 1 }), {
1025
1275
  ...EMPTY
1026
1276
  });
@@ -1036,7 +1286,7 @@ var runDoctor = async ({
1036
1286
  const repoCorpus = repoCorpusOf(root);
1037
1287
  const found = ordered([
1038
1288
  ...await checkLoaded({ configs, root, workspaces }),
1039
- ...exercisedOf({
1289
+ ...await exercisedOf({
1040
1290
  configs,
1041
1291
  oxlint: binary,
1042
1292
  ...repoCorpus === void 0 ? {} : { repoCorpus },
@@ -1045,13 +1295,14 @@ var runDoctor = async ({
1045
1295
  ...baselineOf({ baseline, root }),
1046
1296
  ...checkRunner({ ratchet: readRatchet(root), workspaces }),
1047
1297
  ...await checkObservability({ now: Date.now(), root }),
1048
- ...checkDrift({ root, workspaces })
1298
+ ...checkDrift({ root, workspaces }),
1299
+ ...checkDeployed({ root })
1049
1300
  ]);
1050
1301
  const findings = strict ? found.map((one) => one.verdict === "WARN" ? { ...one, verdict: "FAIL" } : one) : found;
1051
1302
  return {
1052
1303
  counts: countsOf(findings),
1053
1304
  findings,
1054
- ok: !findings.some((one) => one.verdict === "FAIL"),
1305
+ ok: passes(findings),
1055
1306
  root
1056
1307
  };
1057
1308
  };
@@ -1059,13 +1310,14 @@ var runDoctor = async ({
1059
1310
  // src/report.ts
1060
1311
  var ABOUT = {
1061
1312
  baseline: "a number that may only shrink, against another ref",
1313
+ deployed: "what the pipeline reported deploying is what the tree declares",
1062
1314
  drift: "the gates that were set up and are no longer running",
1063
1315
  exercised: "every enabled rule fires on at least one corpus file",
1064
1316
  loaded: "the plugin oxlint would load is the one the manifest pins",
1065
1317
  observability: "an exporter is configured, reachable, and something arrived through it lately",
1066
1318
  runner: "something reads the test runner\u2019s own report, not its exit code"
1067
1319
  };
1068
- var WIDTH = 4;
1320
+ var WIDTH = 8;
1069
1321
  var lineOf = (one) => ` ${one.verdict.padEnd(WIDTH)} ${one.subject}: ${one.message}`;
1070
1322
  var sectionOf = (check, findings) => {
1071
1323
  const mine = findings.filter((one) => one.check === check);
@@ -1079,8 +1331,8 @@ var formatDoctor = ({ counts, findings, ok, root }) => [
1079
1331
  `geonosis-doctor \u2014 ${root}`,
1080
1332
  "",
1081
1333
  ...CHECKS.flatMap((check) => sectionOf(check, findings)),
1082
- `${CHECKS.length} checks, ${findings.length} lines: ${counts.OK} ok, ${counts.WARN} warned, ${counts.SKIP} skipped, ${counts.FAIL} failed`,
1083
- ok ? "doctor PASS \u2014 nothing here says the gates are measuring something other than what they claim." : "doctor FAIL \u2014 a line above is a gate reporting on something other than what it names.",
1334
+ `${CHECKS.length} checks, ${findings.length} lines: ${counts.OK} ok, ${counts.WARN} warned, ${counts.SKIP} skipped, ${counts.UNJUDGED} unjudged, ${counts.FAIL} failed`,
1335
+ ok ? "doctor PASS \u2014 nothing here says the gates are measuring something other than what they claim." : "doctor FAIL \u2014 a line above is a gate reporting on something other than what it names, or a question this could not ask at all.",
1084
1336
  ""
1085
1337
  ].join("\n");
1086
1338
  var formatJson = (report) => `${JSON.stringify(report, null, 2)}
@@ -1097,6 +1349,9 @@ export {
1097
1349
  readRatchet,
1098
1350
  defaultRef,
1099
1351
  checkBaseline,
1352
+ DEPLOYED_FILE,
1353
+ NOT_WRITTEN,
1354
+ checkDeployed,
1100
1355
  CHECKS,
1101
1356
  DoctorError,
1102
1357
  resolveFrom,