@cavuno/board 1.44.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CONTRIBUTING.md +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +12 -7
  5. package/SECURITY.md +28 -0
  6. package/TRADEMARKS.md +16 -0
  7. package/dist/_spec-sW85fuvT.d.mts +2731 -0
  8. package/dist/_spec-sW85fuvT.d.ts +2731 -0
  9. package/dist/bin.mjs +38 -349
  10. package/dist/{board-DZCw7A6P.d.ts → board-DKXyxwNN.d.ts} +2 -2
  11. package/dist/{board-BwFW1aU2.d.mts → board-QFeHT4UN.d.mts} +2 -2
  12. package/dist/doctor.d.mts +6 -19
  13. package/dist/doctor.d.ts +6 -19
  14. package/dist/doctor.js +38 -345
  15. package/dist/doctor.mjs +37 -344
  16. package/dist/filters.d.mts +5 -5
  17. package/dist/filters.d.ts +5 -5
  18. package/dist/format.d.mts +17 -19
  19. package/dist/format.d.ts +17 -19
  20. package/dist/go.d.mts +2 -2
  21. package/dist/go.d.ts +2 -2
  22. package/dist/go.js +1 -1
  23. package/dist/go.mjs +1 -1
  24. package/dist/index.d.mts +16 -18
  25. package/dist/index.d.ts +16 -18
  26. package/dist/index.js +39 -28
  27. package/dist/index.mjs +39 -28
  28. package/dist/{jobs-BVazo4mA.d.ts → jobs-CuRzTDb1.d.mts} +15 -13
  29. package/dist/{jobs-CSMshXGV.d.mts → jobs-DTnCZGtb.d.ts} +15 -13
  30. package/dist/paths.d.mts +6 -6
  31. package/dist/paths.d.ts +6 -6
  32. package/dist/paths.js +1 -1
  33. package/dist/paths.mjs +1 -1
  34. package/dist/roles-vY0AXhoc.d.mts +3 -3
  35. package/dist/roles-vY0AXhoc.d.ts +3 -3
  36. package/dist/route-contract.d.mts +16 -16
  37. package/dist/route-contract.d.ts +16 -16
  38. package/dist/route-contract.js +1 -1
  39. package/dist/route-contract.mjs +1 -1
  40. package/dist/{salaries-DMQsjLrV.d.ts → salaries-BOpR51Ny.d.mts} +3 -3
  41. package/dist/{salaries-IJlchnWk.d.mts → salaries-QoNpzRCT.d.ts} +3 -3
  42. package/dist/{search-B7gr_wOd.d.ts → search-B5ROY6PK.d.ts} +2 -2
  43. package/dist/{search-B6FJifRE.d.mts → search-f93nrSg3.d.mts} +2 -2
  44. package/dist/seo.d.mts +17 -18
  45. package/dist/seo.d.ts +17 -18
  46. package/dist/server.d.mts +14 -32
  47. package/dist/server.d.ts +14 -32
  48. package/dist/sitemap.d.mts +7 -7
  49. package/dist/sitemap.d.ts +7 -7
  50. package/dist/sitemap.js +4 -2
  51. package/dist/sitemap.mjs +4 -2
  52. package/dist/skills.d.mts +1 -1
  53. package/dist/skills.d.ts +1 -1
  54. package/dist/skills.js +1 -1
  55. package/dist/suggest.d.mts +2 -2
  56. package/dist/suggest.d.ts +2 -2
  57. package/dist/theme.d.mts +10 -13
  58. package/dist/theme.d.ts +10 -13
  59. package/dist/theme.js +1 -1
  60. package/dist/theme.mjs +1 -1
  61. package/dist/{ui-copy-CKfFTtLk.d.mts → ui-copy-CL-VN1xb.d.mts} +2 -3
  62. package/dist/{ui-copy-CKfFTtLk.d.ts → ui-copy-CL-VN1xb.d.ts} +2 -3
  63. package/dist/well-known.d.mts +3 -3
  64. package/dist/well-known.d.ts +3 -3
  65. package/dist/well-known.js +1 -1
  66. package/dist/well-known.mjs +1 -1
  67. package/package.json +46 -38
  68. package/skills/cavuno-board-client/SKILL.md +1 -2
  69. package/skills/cavuno-board-filters/SKILL.md +2 -2
  70. package/skills/cavuno-board-format/SKILL.md +1 -1
  71. package/skills/cavuno-board-i18n/SKILL.md +3 -3
  72. package/skills/cavuno-board-job-posting/SKILL.md +1 -1
  73. package/skills/cavuno-board-messaging/SKILL.md +2 -3
  74. package/skills/cavuno-board-paywall/SKILL.md +1 -1
  75. package/skills/cavuno-board-salaries/SKILL.md +1 -1
  76. package/skills/cavuno-board-seo/SKILL.md +4 -6
  77. package/skills/cavuno-board-server/SKILL.md +1 -1
  78. package/skills/cavuno-board-setup/SKILL.md +2 -1
  79. package/skills/cavuno-board-sitemap/SKILL.md +1 -1
  80. package/skills/cavuno-board-smoke-test/SKILL.md +4 -13
  81. package/skills/flavors/tanstack-start/SKILL.md +1 -2
  82. package/skills/manifest.json +1 -1
  83. package/dist/_spec-CoL3oYpG.d.mts +0 -5296
  84. package/dist/_spec-CoL3oYpG.d.ts +0 -5296
package/dist/bin.mjs CHANGED
@@ -189,6 +189,25 @@ function isOffendingSink(lines, index) {
189
189
  if (!hasCookieWriteSink(stripped)) return false;
190
190
  return DOMAIN_SCOPE.test(statementWindow(lines, index));
191
191
  }
192
+ var GENERATED_DIRS = /* @__PURE__ */ new Set([
193
+ "paraglide",
194
+ "generated",
195
+ "_generated",
196
+ "__generated__"
197
+ ]);
198
+ var GENERATED_FILE = /\.gen\.(?:ts|tsx|js|jsx|mjs|cjs)$/;
199
+ var GENERATED_BANNER = /@generated|generated by|generated from|auto-?generated|do not edit|don't edit/i;
200
+ var BANNER_SCAN_LINES = 5;
201
+ function hasGeneratedBanner(text) {
202
+ const lines = text.split(/\r?\n/);
203
+ const limit = Math.min(lines.length, BANNER_SCAN_LINES);
204
+ for (let i = 0; i < limit; i += 1) {
205
+ const line = lines[i];
206
+ if (!isCommentLine(line)) return false;
207
+ if (GENERATED_BANNER.test(line)) return true;
208
+ }
209
+ return false;
210
+ }
192
211
  function walkSourceFiles(dir, out) {
193
212
  let entries;
194
213
  try {
@@ -198,6 +217,7 @@ function walkSourceFiles(dir, out) {
198
217
  }
199
218
  for (const name of entries) {
200
219
  if (name === "node_modules" || name.startsWith(".")) continue;
220
+ if (GENERATED_DIRS.has(name) || GENERATED_FILE.test(name)) continue;
201
221
  const full = join(dir, name);
202
222
  let st;
203
223
  try {
@@ -237,6 +257,7 @@ function checkCookieCodecConformance(projectRoot) {
237
257
  } catch {
238
258
  continue;
239
259
  }
260
+ if (hasGeneratedBanner(text)) continue;
240
261
  const lines = text.split(/\r?\n/);
241
262
  for (let i = 0; i < lines.length; i += 1) {
242
263
  if (!isOffendingSink(lines, i)) continue;
@@ -247,7 +268,7 @@ function checkCookieCodecConformance(projectRoot) {
247
268
  return [
248
269
  COOKIE(
249
270
  "pass",
250
- "no domain-scoped Set-Cookie or document.cookie writes in src/"
271
+ "no domain-scoped Set-Cookie or document.cookie writes in app-authored src/ (generated output skipped)"
251
272
  )
252
273
  ];
253
274
  }
@@ -373,86 +394,18 @@ async function runReadProbes(fetchImpl, frontendUrl) {
373
394
  ];
374
395
  }
375
396
 
376
- // src/doctor/sandbox.ts
377
- var SANDBOX_GATE = record("sandbox.persona-gate", 1);
378
- var EXPECTED_PERSONA_COUNT = 8;
379
- function skipSandboxPersonaGate(reason) {
380
- return SANDBOX_GATE("skip", reason);
381
- }
382
- async function checkSandboxPersonaGate(fetchImpl, env, boardContext) {
383
- if (boardContext === null) {
384
- return SANDBOX_GATE(
385
- "skip",
386
- "board did not resolve \u2014 fix static.board first"
387
- );
388
- }
389
- const url = `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}/sandbox/personas`;
390
- const result = await probe(fetchImpl, url);
391
- if (boardContext.sandbox !== true) {
392
- return result.status === 404 ? SANDBOX_GATE(
393
- "pass",
394
- "sandbox persona endpoint refuses (404) on a non-sandbox board"
395
- ) : SANDBOX_GATE(
396
- "fail",
397
- `sandbox persona endpoint is reachable on a NON-sandbox board (HTTP ${result.status}) \u2014 the capability gate is not holding`
398
- );
399
- }
400
- if (!result.ok) {
401
- return SANDBOX_GATE(
402
- "fail",
403
- `sandbox persona roster did not resolve (HTTP ${result.status}) \u2014 the persona seed may have failed`
404
- );
405
- }
406
- let personas;
407
- let password;
408
- try {
409
- const parsed = JSON.parse(result.body);
410
- personas = parsed.personas;
411
- password = parsed.password;
412
- } catch {
413
- return SANDBOX_GATE(
414
- "fail",
415
- "sandbox persona endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
416
- );
417
- }
418
- if (!Array.isArray(personas) || personas.length !== EXPECTED_PERSONA_COUNT) {
419
- return SANDBOX_GATE(
420
- "fail",
421
- `expected ${EXPECTED_PERSONA_COUNT} personas, got ${Array.isArray(personas) ? personas.length : "a non-array"} \u2014 the persona seed is incomplete`
422
- );
423
- }
424
- const unseeded = personas.filter((persona) => persona.seeded !== true);
425
- if (unseeded.length > 0) {
426
- const ids = unseeded.map((persona) => typeof persona.id === "string" ? persona.id : "?").join(", ");
427
- return SANDBOX_GATE(
428
- "fail",
429
- `${unseeded.length} of ${EXPECTED_PERSONA_COUNT} personas are not seeded on the board (${ids}) \u2014 the persona seed failed or was purged; run POST /v1/boards/{key}/sandbox/reseed to restore the roster`
430
- );
431
- }
432
- if (typeof password !== "string" || password.length === 0) {
433
- return SANDBOX_GATE(
434
- "fail",
435
- "roster present but the published password is missing"
436
- );
437
- }
438
- return SANDBOX_GATE(
439
- "pass",
440
- `sandbox roster complete and seeded (${EXPECTED_PERSONA_COUNT} personas)`
441
- );
442
- }
443
-
444
397
  // src/doctor/theme.ts
445
398
  import { createHash } from "crypto";
446
399
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
447
400
  import { join as join2 } from "path";
448
401
  var THEME = record("static.theme", 1);
449
- function checkThemeFreshness(projectRoot, context) {
402
+ function checkThemeFreshness(projectRoot) {
450
403
  const tokensPath = join2(projectRoot, "src/tokens.css");
451
404
  if (!existsSync2(tokensPath)) {
452
405
  return [
453
406
  THEME(
454
407
  "skip",
455
- "board is not tokens-migrated (ADR-0065) \u2014 no src/tokens.css"
408
+ "no src/tokens.css \u2014 local theme freshness check not applicable"
456
409
  )
457
410
  ];
458
411
  }
@@ -469,232 +422,9 @@ function checkThemeFreshness(projectRoot, context) {
469
422
  )
470
423
  ];
471
424
  }
472
- if (!context) {
473
- return [
474
- THEME(
475
- "skip",
476
- "local derivations fresh; platform snapshot unverified \u2014 board context unavailable"
477
- )
478
- ];
479
- }
480
- if (context.themeSnapshotHash !== tokensHash) {
481
- return [
482
- THEME(
483
- "fail",
484
- `platform theme snapshot is ${context.themeSnapshotHash ? "stale" : "missing"} \u2014 emails render ${context.themeSnapshotHash ? "an old" : "the legacy"} theme; sync it: \`npm run gen:theme -- --payload | npx convex run boards/themeSnapshot:sync\``
485
- )
486
- ];
487
- }
488
425
  return [
489
- THEME(
490
- "pass",
491
- `tokens.css \u21C4 resolved module \u21C4 platform snapshot (${tokensHash.slice(0, 12)}\u2026)`
492
- )
493
- ];
494
- }
495
-
496
- // src/doctor/writes.ts
497
- var WRITE = {
498
- board: record("write.board", 3),
499
- register: record("write.register", 3),
500
- login: record("write.login", 3),
501
- email: record("write.email", 3),
502
- apply: record("write.apply", 3),
503
- alert: record("write.alert", 3)
504
- };
505
- var DEPENDENT_CHECKS = [
506
- WRITE.register,
507
- WRITE.login,
508
- WRITE.email,
509
- WRITE.apply,
510
- WRITE.alert
511
- ];
512
- function skipWriteProbes(reason) {
513
- return Object.values(WRITE).map((make) => make("skip", reason));
514
- }
515
- var FETCH_TIMEOUT_MS2 = 1e4;
516
- var RESEND_API_BASE = "https://api.resend.com";
517
- async function request(fetchImpl, url, init) {
518
- try {
519
- const response = await fetchImpl(url, {
520
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2),
521
- ...init
522
- });
523
- let json = null;
524
- try {
525
- json = await response.json();
526
- } catch {
527
- json = null;
528
- }
529
- return { ok: response.ok, status: response.status, json };
530
- } catch (error) {
531
- return {
532
- ok: false,
533
- status: 0,
534
- json: { error: error instanceof Error ? error.message : String(error) }
535
- };
536
- }
537
- }
538
- function post(fetchImpl, url, body, bearer) {
539
- return request(fetchImpl, url, {
540
- method: "POST",
541
- headers: {
542
- "content-type": "application/json",
543
- accept: "application/json",
544
- ...bearer ? { authorization: `Bearer ${bearer}` } : {}
545
- },
546
- body: JSON.stringify(body)
547
- });
548
- }
549
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
550
- async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, timeoutMs) {
551
- const deadline = Date.now() + timeoutMs;
552
- const subjectMarker = `[To: ${recipient}]`;
553
- do {
554
- const list = await request(
555
- fetchImpl,
556
- `${RESEND_API_BASE}/emails?limit=20`,
557
- {
558
- headers: { authorization: `Bearer ${apiKey}` }
559
- }
560
- );
561
- if (!list.ok) {
562
- return WRITE.email(
563
- "fail",
564
- `Resend list returned HTTP ${list.status} \u2014 wrong RESEND_API_KEY?`
565
- );
566
- }
567
- const items = list.json?.data ?? [];
568
- const match = items.find(
569
- (item) => item.subject?.includes(subjectMarker) || item.to?.includes(recipient)
570
- );
571
- if (match) {
572
- return WRITE.email(
573
- "pass",
574
- `verification send observed in Resend for ${recipient}`
575
- );
576
- }
577
- await sleep(pollMs);
578
- } while (Date.now() < deadline);
579
- return WRITE.email(
580
- "fail",
581
- `no verification send for ${recipient} appeared in Resend within ${timeoutMs}ms`
582
- );
583
- }
584
- async function runWriteProbes(options) {
585
- const { fetchImpl } = options;
586
- const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
587
- const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
588
- const email = `doctor+${nonce}@cavuno.com`;
589
- const password = `doctor-probe-${nonce}-A1!`;
590
- let context = options.boardContext ?? null;
591
- if (context === null) {
592
- const r = await request(fetchImpl, base);
593
- if (!r.ok) {
594
- return [
595
- WRITE.board("fail", `board context did not resolve (HTTP ${r.status})`),
596
- ...DEPENDENT_CHECKS.map(
597
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
598
- )
599
- ];
600
- }
601
- context = r.json ?? {};
602
- }
603
- if (context.sandbox !== true) {
604
- return [
605
- WRITE.board(
606
- "fail",
607
- "refusing write probes: this board is not the platform sandbox (`sandbox` is not true) \u2014 point PUBLIC_CAVUNO_BOARD at the sandbox pk_"
608
- ),
609
- ...DEPENDENT_CHECKS.map(
610
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
611
- )
612
- ];
613
- }
614
- const results = [
615
- WRITE.board("pass", "board is the platform sandbox")
426
+ THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
616
427
  ];
617
- const registered = await post(fetchImpl, `${base}/auth/register`, {
618
- role: "candidate",
619
- method: "emailpass",
620
- email,
621
- password,
622
- displayName: "Doctor Probe"
623
- });
624
- const sessionOk = (r) => r.ok && typeof r.json?.accessToken === "string";
625
- results.push(
626
- sessionOk(registered) ? WRITE.register("pass", `candidate registered (${email})`) : WRITE.register(
627
- "fail",
628
- `register returned HTTP ${registered.status}` + (registered.status === 429 ? " \u2014 rate-limited: register allows 5 signups / 15 min per IP; wait before rerunning" : "")
629
- )
630
- );
631
- let accessToken = null;
632
- if (sessionOk(registered)) {
633
- const login = await post(fetchImpl, `${base}/auth/login`, {
634
- email,
635
- password
636
- });
637
- if (sessionOk(login)) {
638
- accessToken = login.json.accessToken;
639
- results.push(WRITE.login("pass", "emailpass login returns a session"));
640
- } else {
641
- results.push(WRITE.login("fail", `login returned HTTP ${login.status}`));
642
- }
643
- } else {
644
- results.push(WRITE.login("skip", "not run \u2014 register failed"));
645
- }
646
- if (!sessionOk(registered)) {
647
- results.push(WRITE.email("skip", "not run \u2014 register failed"));
648
- } else if (!options.resendApiKey) {
649
- results.push(
650
- WRITE.email(
651
- "skip",
652
- "RESEND_API_KEY not set \u2014 the verification send was NOT verified (platform-operator check)"
653
- )
654
- );
655
- } else {
656
- results.push(
657
- await observeVerificationEmail(
658
- fetchImpl,
659
- options.resendApiKey,
660
- email,
661
- options.emailPollMs ?? 2e3,
662
- options.emailTimeoutMs ?? 3e4
663
- )
664
- );
665
- }
666
- if (!accessToken) {
667
- results.push(WRITE.apply("skip", "not run \u2014 no session (see above)"));
668
- } else {
669
- const listing = await request(fetchImpl, `${base}/jobs?limit=1`);
670
- const card = listing.json?.data?.[0];
671
- if (!listing.ok || !card?.slug) {
672
- results.push(
673
- WRITE.apply(
674
- "fail",
675
- listing.ok ? "the board has no jobs to apply to \u2014 is the sandbox seeded?" : `jobs listing returned HTTP ${listing.status}`
676
- )
677
- );
678
- } else {
679
- const applied = await post(
680
- fetchImpl,
681
- `${base}/jobs/${encodeURIComponent(card.slug)}/apply`,
682
- {},
683
- accessToken
684
- );
685
- results.push(
686
- applied.ok && typeof applied.json?.id === "string" ? WRITE.apply("pass", `applied to ${card.slug}`) : WRITE.apply("fail", `apply returned HTTP ${applied.status}`)
687
- );
688
- }
689
- }
690
- const alert = await post(fetchImpl, `${base}/job-alerts`, {
691
- email,
692
- consent: true
693
- });
694
- results.push(
695
- alert.ok ? WRITE.alert("pass", "job-alert subscription accepted") : WRITE.alert("fail", `job-alerts returned HTTP ${alert.status}`)
696
- );
697
- return results;
698
428
  }
699
429
 
700
430
  // src/doctor/run.ts
@@ -733,31 +463,21 @@ async function checkBoardResolves(fetchImpl, env) {
733
463
  `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
734
464
  );
735
465
  if (!result.ok) {
736
- return {
737
- result: STATIC_BOARD(
738
- "fail",
739
- `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
740
- ),
741
- context: null
742
- };
466
+ return STATIC_BOARD(
467
+ "fail",
468
+ `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
469
+ );
743
470
  }
744
- let parsed = null;
745
471
  try {
746
- parsed = JSON.parse(result.body);
472
+ const parsed = JSON.parse(result.body);
747
473
  if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
748
474
  } catch {
749
- return {
750
- result: STATIC_BOARD(
751
- "fail",
752
- "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
753
- ),
754
- context: null
755
- };
475
+ return STATIC_BOARD(
476
+ "fail",
477
+ "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
478
+ );
756
479
  }
757
- return {
758
- result: STATIC_BOARD("pass", "publishable key resolves the board"),
759
- context: parsed
760
- };
480
+ return STATIC_BOARD("pass", "publishable key resolves the board");
761
481
  }
762
482
  var SKILL_ROOTS = [
763
483
  ".claude/skills",
@@ -820,46 +540,19 @@ async function runDoctor(options) {
820
540
  } else {
821
541
  results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
822
542
  }
823
- let boardContext = null;
824
543
  if (envOk) {
825
- const resolved = await checkBoardResolves(fetchImpl, env);
826
- boardContext = resolved.context;
827
- results.push(resolved.result);
544
+ results.push(await checkBoardResolves(fetchImpl, env));
828
545
  } else {
829
546
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
830
547
  }
831
548
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
832
- results.push(
833
- ...checkThemeFreshness(options.projectRoot ?? process.cwd(), boardContext)
834
- );
549
+ results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
835
550
  results.push(
836
551
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
837
552
  );
838
- results.push(
839
- envOk ? await checkSandboxPersonaGate(fetchImpl, env, boardContext) : skipSandboxPersonaGate("env checks failed \u2014 fix them first")
840
- );
841
553
  results.push(
842
554
  ...options.frontendUrl ? await runReadProbes(fetchImpl, options.frontendUrl) : skipReadProbes("no --frontend <url> given")
843
555
  );
844
- if (!options.sandbox) {
845
- results.push(
846
- ...skipWriteProbes(
847
- "write probes not enabled \u2014 run with --sandbox against the platform sandbox"
848
- )
849
- );
850
- } else if (!envOk) {
851
- results.push(...skipWriteProbes("env checks failed \u2014 fix them first"));
852
- } else {
853
- results.push(
854
- ...await runWriteProbes({
855
- env,
856
- fetchImpl,
857
- resendApiKey: options.resendApiKey,
858
- nonce: options.writeProbeNonce,
859
- boardContext
860
- })
861
- );
862
- }
863
556
  return { results, summary: summarize(results) };
864
557
  }
865
558
 
@@ -911,11 +604,7 @@ async function doctor(argv) {
911
604
  apiUrl: process.env.PUBLIC_CAVUNO_API_URL,
912
605
  boardKey: process.env.PUBLIC_CAVUNO_BOARD
913
606
  },
914
- frontendUrl,
915
- // Tier-3 write probes: opt-in via --sandbox, and refused server-side
916
- // unless the resolved board is platform-marked test (writes.ts gate).
917
- sandbox: argv.includes("--sandbox"),
918
- resendApiKey: process.env.RESEND_API_KEY
607
+ frontendUrl
919
608
  });
920
609
  console.log("\n@cavuno/board doctor");
921
610
  for (const result of results) {
@@ -1,11 +1,11 @@
1
- import { S as Schemas } from './_spec-CoL3oYpG.js';
1
+ import { S as Schemas } from './_spec-sW85fuvT.js';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
6
  type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
7
  /**
8
- * An operator-defined custom job-field definition (CAV-294). Board-wide;
8
+ * An operator-defined custom job-field definition. Board-wide;
9
9
  * use it to render and localize a job's opaque `customFieldValues` (resolve
10
10
  * option `key`s → labels, honour field `type` and display order). Shared with
11
11
  * the Operator API's custom-field surface (one canonical schema).
@@ -1,11 +1,11 @@
1
- import { S as Schemas } from './_spec-CoL3oYpG.mjs';
1
+ import { S as Schemas } from './_spec-sW85fuvT.mjs';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
6
  type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
7
  /**
8
- * An operator-defined custom job-field definition (CAV-294). Board-wide;
8
+ * An operator-defined custom job-field definition. Board-wide;
9
9
  * use it to render and localize a job's opaque `customFieldValues` (resolve
10
10
  * option `key`s → labels, honour field `type` and display order). Shared with
11
11
  * the Operator API's custom-field surface (one canonical schema).
package/dist/doctor.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Doctor check primitives (ADR-0058 Wave B): the pure logic behind
2
+ * Doctor check primitives: the pure logic behind
3
3
  * `npx @cavuno/board doctor` — env validation, JSON-LD extraction,
4
4
  * sitemap parsing, and the pass/fail/skip summary. Everything here is
5
5
  * synchronous and I/O-free so it unit-tests without a network; the
@@ -8,8 +8,8 @@
8
8
  type CheckStatus = 'pass' | 'fail' | 'skip';
9
9
  interface CheckResult {
10
10
  id: string;
11
- /** 1 static · 2 read probes · 3 write probes (sandbox-only). */
12
- tier: 1 | 2 | 3;
11
+ /** 1 static · 2 read probes. */
12
+ tier: 1 | 2;
13
13
  status: CheckStatus;
14
14
  detail: string;
15
15
  }
@@ -25,13 +25,9 @@ interface DoctorSummary {
25
25
  }
26
26
 
27
27
  /**
28
- * Doctor orchestrator (ADR-0058 Wave B): tier-1 static checks (this
29
- * file), tier-2 read probes (read.ts), and opt-in tier-3 write probes
30
- * (writes.ts — sandbox-only, gated on the board context's `sandbox`
31
- * flag). Each tier is a run/skip module pair dispatched the same way, so
32
- * every check always appears in the results — unrun checks SKIP loudly,
33
- * nothing silently vanishes. Fetch is injected for tests; the CLI passes
34
- * global fetch.
28
+ * Public project diagnostics: static configuration checks and optional read
29
+ * probes against the consumer's frontend. The command is intentionally
30
+ * non-destructive and needs no Cavuno operator credentials.
35
31
  */
36
32
  interface RunDoctorOptions {
37
33
  env: DoctorEnv;
@@ -40,15 +36,6 @@ interface RunDoctorOptions {
40
36
  /** Consumer project root for the skills-freshness check. Default: cwd. */
41
37
  projectRoot?: string;
42
38
  fetchImpl?: typeof fetch;
43
- /**
44
- * Enable tier-3 write probes (`--sandbox`). The probes themselves gate
45
- * on the resolved board's `sandbox` flag; this option only opts in.
46
- */
47
- sandbox?: boolean;
48
- /** Platform-operator Resend credential for the email-boundary check. */
49
- resendApiKey?: string;
50
- /** Test override for the per-run probe identity. */
51
- writeProbeNonce?: string;
52
39
  }
53
40
  interface DoctorRun {
54
41
  results: CheckResult[];
package/dist/doctor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Doctor check primitives (ADR-0058 Wave B): the pure logic behind
2
+ * Doctor check primitives: the pure logic behind
3
3
  * `npx @cavuno/board doctor` — env validation, JSON-LD extraction,
4
4
  * sitemap parsing, and the pass/fail/skip summary. Everything here is
5
5
  * synchronous and I/O-free so it unit-tests without a network; the
@@ -8,8 +8,8 @@
8
8
  type CheckStatus = 'pass' | 'fail' | 'skip';
9
9
  interface CheckResult {
10
10
  id: string;
11
- /** 1 static · 2 read probes · 3 write probes (sandbox-only). */
12
- tier: 1 | 2 | 3;
11
+ /** 1 static · 2 read probes. */
12
+ tier: 1 | 2;
13
13
  status: CheckStatus;
14
14
  detail: string;
15
15
  }
@@ -25,13 +25,9 @@ interface DoctorSummary {
25
25
  }
26
26
 
27
27
  /**
28
- * Doctor orchestrator (ADR-0058 Wave B): tier-1 static checks (this
29
- * file), tier-2 read probes (read.ts), and opt-in tier-3 write probes
30
- * (writes.ts — sandbox-only, gated on the board context's `sandbox`
31
- * flag). Each tier is a run/skip module pair dispatched the same way, so
32
- * every check always appears in the results — unrun checks SKIP loudly,
33
- * nothing silently vanishes. Fetch is injected for tests; the CLI passes
34
- * global fetch.
28
+ * Public project diagnostics: static configuration checks and optional read
29
+ * probes against the consumer's frontend. The command is intentionally
30
+ * non-destructive and needs no Cavuno operator credentials.
35
31
  */
36
32
  interface RunDoctorOptions {
37
33
  env: DoctorEnv;
@@ -40,15 +36,6 @@ interface RunDoctorOptions {
40
36
  /** Consumer project root for the skills-freshness check. Default: cwd. */
41
37
  projectRoot?: string;
42
38
  fetchImpl?: typeof fetch;
43
- /**
44
- * Enable tier-3 write probes (`--sandbox`). The probes themselves gate
45
- * on the resolved board's `sandbox` flag; this option only opts in.
46
- */
47
- sandbox?: boolean;
48
- /** Platform-operator Resend credential for the email-boundary check. */
49
- resendApiKey?: string;
50
- /** Test override for the per-run probe identity. */
51
- writeProbeNonce?: string;
52
39
  }
53
40
  interface DoctorRun {
54
41
  results: CheckResult[];