owlrunkit 0.20.0 → 0.22.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +102 -10
  3. package/docs/architecture/OWLCODA_RUNKIT_0201_RELEASE_TRUTH_AND_PROFILE_NORMALIZATION_PATCH_DECISION_20260813.md +95 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_021_TEAM_DELIVERY_PHASE2_PRODUCT_DECISION_20260813.md +249 -0
  5. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +117 -12
  6. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  7. package/package.json +1 -1
  8. package/packages/attest/README.md +7 -1
  9. package/packages/attest/consumer-surface-v3.json +275 -0
  10. package/packages/attest/distribution-manifest-v1.json +24 -0
  11. package/packages/attest/schemas/canonical-candidate-identity-v1.schema.json +112 -0
  12. package/packages/attest/schemas/foreign-workspace-filesystem-identity-v1.schema.json +32 -0
  13. package/packages/attest/schemas/quick-verification-receipt-v4.schema.json +226 -0
  14. package/packages/attest/src/candidate-identity.mjs +454 -0
  15. package/packages/attest/src/index.mjs +11 -0
  16. package/packages/attest/src/quick-receipt-contract.mjs +126 -5
  17. package/packages/attest/src/quick.mjs +53 -8
  18. package/{registry-release-policy-v1.json → registry-release-policy-v2.json} +8 -9
  19. package/sbom.spdx.json +4 -4
  20. package/scripts/runkit-contract/core-contract.mjs +19 -1
  21. package/scripts/runkit-contract/fleet-skill-plan.mjs +223 -0
  22. package/scripts/runkit-contract/formal-workflow.mjs +3 -1
  23. package/scripts/runkit-contract/hook-recovery.mjs +496 -0
  24. package/scripts/runkit-contract/inspect-presentation.mjs +187 -0
  25. package/scripts/runkit-contract/install-codex-skill.mjs +1 -0
  26. package/scripts/runkit-contract/onboarding-bootstrap.mjs +15 -0
  27. package/scripts/runkit-contract/onboarding-doctor.mjs +378 -9
  28. package/scripts/runkit-contract/profile-onboarding.mjs +6 -0
  29. package/scripts/runkit-contract/project-cli-resolver.mjs +17 -1
  30. package/scripts/runkit-contract/quick-metrics.mjs +9 -5
  31. package/scripts/runkit-contract/quick-verify.mjs +108 -24
  32. package/scripts/runkit-contract/registry-adoption-gate.mjs +12 -3
  33. package/scripts/runkit-contract/registry-adoption.mjs +10 -5
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +332 -30
  36. package/scripts/runkit-contract/schemas/delivery-lifecycle-v1.schema.json +91 -0
  37. package/scripts/runkit-contract/schemas/external-gate-v1.schema.json +68 -0
  38. package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +140 -0
  39. package/scripts/runkit-contract/schemas/hook-recovery-v1.schema.json +592 -0
  40. package/scripts/runkit-contract/schemas/inspect-compact-v1.schema.json +93 -0
  41. package/scripts/runkit-contract/schemas/inspect-compact-v2.schema.json +122 -0
  42. package/scripts/runkit-contract/schemas/team-delivery-status-v2.schema.json +219 -0
  43. package/scripts/runkit-contract/schemas/team-delivery-status-v3.schema.json +113 -0
  44. package/scripts/runkit-contract/schemas/team-project-event-v6.schema.json +82 -0
  45. package/scripts/runkit-contract/schemas/team-project-event-v7.schema.json +80 -0
  46. package/scripts/runkit-contract/schemas/team-project-status-v4.schema.json +208 -0
  47. package/scripts/runkit-contract/schemas/team-recommendation-v2.schema.json +120 -0
  48. package/scripts/runkit-contract/sync-release-identity.mjs +87 -0
  49. package/scripts/runkit-contract/team-delivery.mjs +203 -3
  50. package/scripts/runkit-contract/team-project.mjs +1032 -11
@@ -46,6 +46,16 @@ const TRANSACTION_PATHS = Object.freeze([
46
46
  "adoption",
47
47
  ]);
48
48
 
49
+ function shellQuote(value) {
50
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`;
51
+ }
52
+
53
+ function requestedSuccessorSpec(value) {
54
+ return /^owlrunkit@[0-9]+\.[0-9]+\.[0-9]+$/u.test(value ?? "")
55
+ ? value
56
+ : null;
57
+ }
58
+
49
59
  function pathEntryExists(filePath) {
50
60
  try {
51
61
  lstatSync(filePath);
@@ -301,6 +311,7 @@ export async function planOnboardingBootstrapV1({
301
311
  } = {}) {
302
312
  const root = resolveBoundedWorkspaceRoot(workspaceRoot);
303
313
  const issueCodes = [];
314
+ const requestedSuccessor = requestedSuccessorSpec(exactSpec);
304
315
  if (exactSpec !== EXACT_SPEC) issueCodes.push("bootstrap_exact_spec_required");
305
316
  const local = readLocalInstallBinding({ workspaceRoot: root });
306
317
  if (local.status !== "bound") {
@@ -385,6 +396,8 @@ export async function planOnboardingBootstrapV1({
385
396
  issueCodes: [...new Set(issueCodes)].sort(),
386
397
  nextCommand: issueCodes.length === 0
387
398
  ? `npx --no-install owlrunkit bootstrap --workspace ${JSON.stringify(root)} --exact ${EXACT_SPEC} --apply`
399
+ : issueCodes.includes("bootstrap_exact_spec_required") && requestedSuccessor !== null
400
+ ? `cd ${shellQuote(root)} && npm install --save-exact ${requestedSuccessor} && npx --no-install owlrunkit bootstrap --workspace ${shellQuote(root)} --exact ${requestedSuccessor} --dry-run`
388
401
  : "repair bootstrap preflight issues and rerun --dry-run",
389
402
  authorizationGranted: false,
390
403
  };
@@ -573,6 +586,7 @@ export async function applyOnboardingBootstrapV1({
573
586
  "doctor",
574
587
  () => runDoctor({
575
588
  workspaceRoot: root,
589
+ discoverSharedSkill: true,
576
590
  registryUrl: OFFICIAL_NPM_REGISTRY,
577
591
  registryClient,
578
592
  }),
@@ -677,6 +691,7 @@ export function formatOnboardingBootstrapHumanV1(result) {
677
691
  const issues = result.issueCodes?.join(", ") || "unknown issue";
678
692
  return [
679
693
  `Bootstrap blocked: ${issues}`,
694
+ ...(result.nextCommand ? [`Repair: ${result.nextCommand}`] : []),
680
695
  `Rollback: ${result.rollbackOutcome ?? "not_required"}.`,
681
696
  ...(result.receiptPath ? [`Receipt: ${result.receiptPath}`] : []),
682
697
  "No Git, release, deployment, production, or business authority was granted.",
@@ -1,12 +1,15 @@
1
1
  import {
2
2
  closeSync,
3
+ existsSync,
3
4
  fstatSync,
4
5
  lstatSync,
5
6
  openSync,
6
7
  readSync,
7
8
  realpathSync,
8
9
  } from "node:fs";
10
+ import { homedir } from "node:os";
9
11
  import path from "node:path";
12
+ import { userInfo } from "node:os";
10
13
 
11
14
  import {
12
15
  CORE_VERSION,
@@ -20,12 +23,19 @@ import {
20
23
  } from "./registry-adoption-gate.mjs";
21
24
  import { resolveProjectCli } from "./project-cli-resolver.mjs";
22
25
  import { inspectProjectControlState } from "./project-control-state.mjs";
26
+ import {
27
+ inspectFleetForSkillChange,
28
+ inspectInstalledSkillCompatibility,
29
+ } from "./install-codex-skill.mjs";
23
30
 
24
31
  const PACKAGE_NAME = "owlrunkit";
25
32
  const EXPECTED_VERSION = CORE_VERSION;
26
33
  const MAX_JSON_BYTES = 1_048_576;
27
34
  const MAX_JSON_DEPTH = 64;
28
35
  const MAX_JSON_NODES = 50_000;
36
+ const PACKAGE_SKILL_RELATIVE = path.join(
37
+ "integrations", "codex", "skills", "owlcoda-runkit",
38
+ );
29
39
 
30
40
  class StrictJsonParser {
31
41
  constructor(text, { maxDepth = MAX_JSON_DEPTH, maxNodes = MAX_JSON_NODES } = {}) {
@@ -286,6 +296,81 @@ export function readWorkspaceJsonBounded(root, relativePath, options) {
286
296
  return text === null ? null : parseJsonStrict(text, options);
287
297
  }
288
298
 
299
+ function parsedSkillRoot(requestedRoot) {
300
+ if (typeof requestedRoot !== "string" || requestedRoot.length === 0) return null;
301
+ try {
302
+ const root = realpathSync(requestedRoot);
303
+ const skillText = readWorkspaceFileBounded(root, "SKILL.md");
304
+ const config = readWorkspaceJsonBounded(
305
+ root,
306
+ path.join("assets", "templates", "config.json"),
307
+ );
308
+ const version = skillText.match(/standalone `owlrunkit@(\d+\.\d+\.\d+)`/u)?.[1] ?? null;
309
+ const configCoreVersion = config?.core?.coreVersion ?? null;
310
+ return {
311
+ root,
312
+ skillPath: path.join(root, "SKILL.md"),
313
+ version,
314
+ configCoreVersion,
315
+ status: version !== null && configCoreVersion === version ? "readable" : "invalid",
316
+ };
317
+ } catch {
318
+ return {
319
+ root: path.resolve(requestedRoot),
320
+ skillPath: path.resolve(requestedRoot, "SKILL.md"),
321
+ version: null,
322
+ configCoreVersion: null,
323
+ status: "invalid",
324
+ };
325
+ }
326
+ }
327
+
328
+ function discoveredSharedSkillRoot() {
329
+ const requested = process.env.OWLCODA_RUNKIT_PROCESS_SKILL_ROOT;
330
+ if (typeof requested === "string" && requested.length > 0) return requested;
331
+ const candidates = [
332
+ path.join(homedir(), ".agents", "skills", "owlcoda-runkit"),
333
+ path.join(homedir(), ".codex", "skills", "owlcoda-runkit"),
334
+ ];
335
+ return candidates.find(candidate => existsSync(candidate)) ?? null;
336
+ }
337
+
338
+ export function inspectDoctorSkillStateV1({ packageRoot, sharedSkillRoot } = {}) {
339
+ const packageSkill = typeof packageRoot === "string"
340
+ ? parsedSkillRoot(path.join(packageRoot, PACKAGE_SKILL_RELATIVE))
341
+ : null;
342
+ const selectedSharedRoot = sharedSkillRoot ?? discoveredSharedSkillRoot();
343
+ const sharedSkill = selectedSharedRoot === null ? null : parsedSkillRoot(selectedSharedRoot);
344
+ const warningCodes = [];
345
+ if (sharedSkill === null) warningCodes.push("shared_skill_not_found");
346
+ else if (sharedSkill.status !== "readable") warningCodes.push("shared_skill_invalid");
347
+ else if (packageSkill?.status === "readable" && sharedSkill.version !== packageSkill.version) {
348
+ warningCodes.push("shared_skill_version_drift");
349
+ }
350
+ const packageIssueCodes = [];
351
+ if (packageSkill?.status !== "readable") warningCodes.push("package_skill_unavailable");
352
+ const skillDrift = warningCodes.includes("shared_skill_version_drift");
353
+ return {
354
+ schemaVersion: "OwlCodaRunKitDoctorSkillStateV1",
355
+ packageVersion: packageSkill?.version ?? null,
356
+ configCoreVersion: packageSkill?.configCoreVersion ?? null,
357
+ packageSkillVersion: packageSkill?.version ?? null,
358
+ packageSkillPath: packageSkill?.skillPath ?? null,
359
+ processSkillVersion: sharedSkill?.version ?? null,
360
+ processSkillPath: sharedSkill?.skillPath ?? null,
361
+ loadedSkillVersion: "unknown",
362
+ skillDrift,
363
+ blocking: false,
364
+ issueCodes: packageIssueCodes,
365
+ warningCodes,
366
+ recommendedAction: skillDrift
367
+ ? `Start a new Agent session and load the package-bound Skill at ${packageSkill.skillPath}.`
368
+ : warningCodes.length > 0
369
+ ? `Use the package-bound Skill at ${packageSkill?.skillPath ?? "the installed package"}.`
370
+ : "none",
371
+ };
372
+ }
373
+
289
374
  async function readResponseJsonBounded(response) {
290
375
  if (!response.body) throw new Error("registry response body is missing");
291
376
  const reader = response.body.getReader();
@@ -478,6 +563,75 @@ function ignoreFileCoversRuntime(root, ignoreFile) {
478
563
  return ignored;
479
564
  }
480
565
 
566
+ const ESLINT_FLAT_CONFIG_FILES = Object.freeze([
567
+ "eslint.config.js",
568
+ "eslint.config.mjs",
569
+ "eslint.config.cjs",
570
+ "eslint.config.ts",
571
+ "eslint.config.mts",
572
+ "eslint.config.cts",
573
+ ]);
574
+
575
+ function eslintIgnoreListCoversRuntime(listText) {
576
+ let ignored = false;
577
+ for (const match of listText.matchAll(/(["'])([^"']*)\1/gu)) {
578
+ const negated = match[2].startsWith("!");
579
+ const pattern = (negated ? match[2].slice(1) : match[2])
580
+ .replace(/^\.\//u, "")
581
+ .replace(/^\*\*\//u, "");
582
+ if (new Set([
583
+ ".owlcoda/",
584
+ ".owlcoda/**",
585
+ ".owlcoda/runkit/",
586
+ ".owlcoda/runkit/**",
587
+ ]).has(pattern)) {
588
+ ignored = !negated;
589
+ } else if (negated && pattern.startsWith(".owlcoda/runkit/")) {
590
+ ignored = false;
591
+ }
592
+ }
593
+ return ignored;
594
+ }
595
+
596
+ function eslintFlatConfigCoversRuntime(text) {
597
+ const importsGlobalHelper = /(?:import\s*\{[^}]*\bglobalIgnores\b[^}]*\}\s*from|(?:const|let|var)\s*\{[^}]*\bglobalIgnores\b[^}]*\}\s*=\s*require\s*\()\s*["'](?:eslint\/config|@eslint\/config-helpers)["']/su.test(text);
598
+ if (importsGlobalHelper) {
599
+ for (const match of text.matchAll(/\bglobalIgnores\s*\(\s*\[([^\]]*)\]/gsu)) {
600
+ if (eslintIgnoreListCoversRuntime(match[1])) return true;
601
+ }
602
+ }
603
+ for (const match of text.matchAll(/\{([^{}]*)\}/gsu)) {
604
+ const body = match[1];
605
+ const withoutOptionalName = body.replace(
606
+ /(?:^|,)\s*name\s*:\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')\s*(?=,|$)/su,
607
+ "",
608
+ );
609
+ const ignores = /^\s*,?\s*ignores\s*:\s*\[([^\]]*)\]\s*,?\s*$/su.exec(
610
+ withoutOptionalName,
611
+ );
612
+ if (ignores === null) continue;
613
+ if (eslintIgnoreListCoversRuntime(ignores[1])) return true;
614
+ }
615
+ return false;
616
+ }
617
+
618
+ function eslintFlatConfig(root) {
619
+ for (const relative of ESLINT_FLAT_CONFIG_FILES) {
620
+ if (!existsSync(path.join(root, relative))) continue;
621
+ let text;
622
+ try {
623
+ text = readWorkspaceFileBounded(root, relative);
624
+ } catch {
625
+ return { relative, coversRuntime: false };
626
+ }
627
+ return {
628
+ relative,
629
+ coversRuntime: eslintFlatConfigCoversRuntime(text),
630
+ };
631
+ }
632
+ return null;
633
+ }
634
+
481
635
  const BROAD_COLLECTION_TOOLS = Object.freeze([
482
636
  {
483
637
  tool: "biome",
@@ -544,14 +698,18 @@ function runtimeIsolationCheck(root) {
544
698
  if (typeof value !== "string" || !hasBroadDotTarget(value)) continue;
545
699
  for (const descriptor of BROAD_COLLECTION_TOOLS) {
546
700
  if (!descriptor.command.test(value)) continue;
701
+ const flatConfig = descriptor.tool === "eslint" ? eslintFlatConfig(root) : null;
702
+ if (flatConfig?.coversRuntime) continue;
547
703
  if (
704
+ flatConfig === null
705
+ &&
548
706
  descriptor.ignoreFileSupported
549
707
  && ignoreFileCoversRuntime(root, descriptor.ignoreFile)
550
708
  ) {
551
709
  continue;
552
710
  }
553
711
  affectedScripts.push({
554
- ignoreFile: descriptor.ignoreFile,
712
+ ignoreFile: flatConfig?.relative ?? descriptor.ignoreFile,
555
713
  script,
556
714
  tool: descriptor.tool,
557
715
  });
@@ -568,6 +726,21 @@ export function inspectRuntimeIsolationV1({ workspaceRoot } = {}) {
568
726
  return runtimeIsolationCheck(resolveBoundedWorkspaceRoot(workspaceRoot));
569
727
  }
570
728
 
729
+ export function inspectLocalMaintenanceV1({
730
+ workspaceRoot,
731
+ packageRoot,
732
+ sharedSkillRoot,
733
+ } = {}) {
734
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
735
+ return {
736
+ schemaVersion: "OwlCodaRunKitLocalMaintenanceV1",
737
+ profileLaunchers: readProfileLauncherReadiness({ workspaceRoot: root }),
738
+ runtimeIsolation: runtimeIsolationCheck(root),
739
+ skill: inspectDoctorSkillStateV1({ packageRoot, sharedSkillRoot }),
740
+ authorizationGranted: false,
741
+ };
742
+ }
743
+
571
744
  function configCheck(root) {
572
745
  try {
573
746
  const config = readWorkspaceJsonBounded(
@@ -577,7 +750,13 @@ function configCheck(root) {
577
750
  );
578
751
  if (config === null) return { status: "missing" };
579
752
  const gate = validateProjectConfigV2(config);
580
- return gate.valid ? { status: "ok" } : { status: "invalid", issues: gate.issues };
753
+ return gate.valid
754
+ ? {
755
+ status: "ok",
756
+ coreVersion: config.core.coreVersion,
757
+ coreManifestSha256: config.core.coreManifestSha256,
758
+ }
759
+ : { status: "invalid", issues: gate.issues };
581
760
  } catch {
582
761
  return { status: "invalid" };
583
762
  }
@@ -689,11 +868,17 @@ export function readAdoptionReadiness({ workspaceRoot } = {}) {
689
868
  return adoptionCheck(root);
690
869
  }
691
870
 
692
- export async function runDoctor({
871
+ async function buildDoctorReport({
693
872
  workspaceRoot,
873
+ sharedSkillRoot,
874
+ discoverSharedSkill = false,
694
875
  registryUrl,
695
876
  timeoutMs = 1_500,
696
877
  registryClient,
878
+ includeSharedSkill = false,
879
+ skillRoot = path.join(userInfo().homedir, ".agents", "skills", "owlcoda-runkit"),
880
+ skillInspector = inspectInstalledSkillCompatibility,
881
+ skillFleetInspector = inspectFleetForSkillChange,
697
882
  } = {}) {
698
883
  const root = resolveBoundedWorkspaceRoot(workspaceRoot);
699
884
  const core = currentCoreIdentity();
@@ -715,6 +900,76 @@ export async function runDoctor({
715
900
  const profiles = await doctorProfilesCheck(root);
716
901
  const adoption = adoptionCheck(root, installed);
717
902
  const runtimeIsolation = runtimeIsolationCheck(root);
903
+ const skill = includeSharedSkill && (sharedSkillRoot !== undefined || discoverSharedSkill)
904
+ ? {
905
+ ...inspectDoctorSkillStateV1({
906
+ packageRoot: installed.status === "bound" ? installed.packageRoot : undefined,
907
+ sharedSkillRoot,
908
+ }),
909
+ packageVersion: installed.status === "bound" ? installed.version : null,
910
+ configCoreVersion: config.status === "ok" ? config.coreVersion : null,
911
+ }
912
+ : includeSharedSkill
913
+ ? {
914
+ schemaVersion: "OwlCodaRunKitDoctorSkillStateV1",
915
+ packageVersion: installed.status === "bound" ? installed.version : null,
916
+ configCoreVersion: core.coreVersion,
917
+ packageSkillVersion: null,
918
+ packageSkillPath: null,
919
+ processSkillVersion: null,
920
+ processSkillPath: null,
921
+ loadedSkillVersion: "unknown",
922
+ skillDrift: false,
923
+ blocking: false,
924
+ issueCodes: [],
925
+ warningCodes: [],
926
+ recommendedAction: "none",
927
+ }
928
+ : null;
929
+ let sharedSkill = null;
930
+ if (includeSharedSkill) {
931
+ let inspectedSkill;
932
+ try {
933
+ inspectedSkill = await skillInspector({ targetRoot: skillRoot });
934
+ } catch (error) {
935
+ inspectedSkill = {
936
+ status: "invalid",
937
+ installedSkillVersion: null,
938
+ issues: [error instanceof Error ? error.message : String(error)],
939
+ };
940
+ }
941
+ let activationBlockedReason = new Set(["unmanaged", "drifted"]).has(
942
+ inspectedSkill.status,
943
+ )
944
+ ? `shared_skill_integrity_invalid:${inspectedSkill.status}`
945
+ : null;
946
+ if (inspectedSkill.status !== "valid" && activationBlockedReason === null) {
947
+ try {
948
+ await skillFleetInspector();
949
+ } catch (error) {
950
+ activationBlockedReason = error instanceof Error ? error.message : String(error);
951
+ }
952
+ }
953
+ const sharedSkillDrift = inspectedSkill.status === "version_mismatch";
954
+ sharedSkill = {
955
+ status: inspectedSkill.status === "valid"
956
+ ? "valid"
957
+ : sharedSkillDrift
958
+ ? "expected_nonblocking_drift"
959
+ : "unavailable_nonblocking",
960
+ blocking: false,
961
+ projectCliVersion: installed.version ?? null,
962
+ projectCoreVersion: core.coreVersion,
963
+ sharedSkillVersion: inspectedSkill.installedSkillVersion ?? null,
964
+ issueCodes: [...new Set(inspectedSkill.issues ?? [])].sort(),
965
+ activationBlockedReason,
966
+ recommendedAction: inspectedSkill.status === "valid"
967
+ ? null
968
+ : activationBlockedReason?.startsWith("skill_update_blocked_active_execution:")
969
+ ? "wait_for_active_execution_or_lease_then_refresh_shared_skill"
970
+ : "run_fleet_safe_shared_skill_refresh",
971
+ };
972
+ }
718
973
  const checks = {
719
974
  node: {
720
975
  status: nodeOk ? "ok" : "unsupported",
@@ -744,6 +999,8 @@ export async function runDoctor({
744
999
  detected: readProjectScripts(root),
745
1000
  },
746
1001
  runtimeIsolation,
1002
+ ...(skill === null ? {} : { skill }),
1003
+ ...(sharedSkill === null ? {} : { sharedSkill }),
747
1004
  lifecycle: {
748
1005
  status: lifecycle.status,
749
1006
  activeRunIds: lifecycle.activeRunIds,
@@ -762,6 +1019,7 @@ export async function runDoctor({
762
1019
  if (installed.status !== "bound") {
763
1020
  issueCodes.push(...(installed.issueCodes ?? ["registry_install_binding_mismatch"]));
764
1021
  }
1022
+ issueCodes.push(...(skill?.issueCodes ?? []));
765
1023
  if (
766
1024
  registry.status === "registry_verified"
767
1025
  && (
@@ -779,11 +1037,14 @@ export async function runDoctor({
779
1037
  ...runtimeIsolation.affectedScripts
780
1038
  .map(({ script, tool }) => `runkit_runtime_collection_risk:${tool}:${script}`),
781
1039
  ...(profileLauncherWarningCount > 0 ? ["profile_launcher_warnings"] : []),
1040
+ ...(skill?.warningCodes ?? []),
782
1041
  ].sort();
783
1042
  const lifecycleNextAction = lifecycle.recovery.nextAllowedAction;
784
- const maintenanceNextAction = profileLauncherWarningCount > 0
785
- ? "review_profile_launchers"
786
- : null;
1043
+ const maintenanceNextAction = skill?.skillDrift
1044
+ ? "reload_package_bound_skill"
1045
+ : profileLauncherWarningCount > 0
1046
+ ? "review_profile_launchers"
1047
+ : null;
787
1048
  const nextAllowedAction = uniqueIssues.length === 0
788
1049
  ? lifecycleNextAction !== "plan_new_execution"
789
1050
  ? lifecycleNextAction
@@ -802,7 +1063,9 @@ export async function runDoctor({
802
1063
  ? "repair_execution_artifacts"
803
1064
  : "repair_registry_binding";
804
1065
  return {
805
- schemaVersion: "OwlCodaRunKitDoctorReportV1",
1066
+ schemaVersion: includeSharedSkill
1067
+ ? "OwlCodaRunKitDoctorReportV2"
1068
+ : "OwlCodaRunKitDoctorReportV1",
806
1069
  status: uniqueIssues.length === 0 ? "ready" : "issues_found",
807
1070
  checks,
808
1071
  issueCodes: uniqueIssues,
@@ -810,7 +1073,9 @@ export async function runDoctor({
810
1073
  profileLauncherWarnings: {
811
1074
  count: profileLauncherWarningCount,
812
1075
  blocking: false,
813
- recommendedAction: maintenanceNextAction,
1076
+ recommendedAction: profileLauncherWarningCount > 0
1077
+ ? "review_profile_launchers"
1078
+ : null,
814
1079
  },
815
1080
  lifecycleNextAction,
816
1081
  maintenanceNextAction,
@@ -819,6 +1084,85 @@ export async function runDoctor({
819
1084
  };
820
1085
  }
821
1086
 
1087
+ export async function runDoctor(options = {}) {
1088
+ return buildDoctorReport({ ...options, includeSharedSkill: false });
1089
+ }
1090
+
1091
+ export async function runDoctorV2(options = {}) {
1092
+ return buildDoctorReport({ ...options, includeSharedSkill: true });
1093
+ }
1094
+
1095
+ function shellQuote(value) {
1096
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`;
1097
+ }
1098
+
1099
+ function doctorRuntimeIsolationGuidance(runtimeIsolation) {
1100
+ return runtimeIsolation.affectedScripts.map((affected) => ({
1101
+ configurationFile: affected.ignoreFile,
1102
+ configurationStyle: affected.tool === "eslint"
1103
+ && affected.ignoreFile.startsWith("eslint.config.")
1104
+ ? "flat_config_global_ignores"
1105
+ : "ignore_file",
1106
+ recommendedPattern: affected.tool === "eslint"
1107
+ && affected.ignoreFile.startsWith("eslint.config.")
1108
+ ? ".owlcoda/runkit/**"
1109
+ : ".owlcoda/runkit/",
1110
+ script: affected.script,
1111
+ tool: affected.tool,
1112
+ }));
1113
+ }
1114
+
1115
+ export async function runDoctorV3(options = {}) {
1116
+ const report = await buildDoctorReport({ ...options, includeSharedSkill: true });
1117
+ const skillRoot = options.skillRoot
1118
+ ?? path.join(userInfo().homedir, ".agents", "skills", "owlcoda-runkit");
1119
+ const archiveRoot = path.join(
1120
+ userInfo().homedir,
1121
+ ".agents",
1122
+ "skill-archives",
1123
+ "owlcoda-runkit",
1124
+ );
1125
+ const sharedSkill = report.checks.sharedSkill;
1126
+ const activation = sharedSkill.status === "valid"
1127
+ ? {
1128
+ status: "not_evaluated",
1129
+ reason: "shared_skill_already_current",
1130
+ }
1131
+ : sharedSkill.activationBlockedReason
1132
+ ? {
1133
+ status: "blocked",
1134
+ reason: sharedSkill.activationBlockedReason,
1135
+ }
1136
+ : {
1137
+ status: "eligible",
1138
+ reason: "fleet_check_passed",
1139
+ };
1140
+ return {
1141
+ ...report,
1142
+ schemaVersion: "OwlCodaRunKitDoctorReportV3",
1143
+ checks: {
1144
+ ...report.checks,
1145
+ runtimeIsolation: {
1146
+ ...report.checks.runtimeIsolation,
1147
+ guidance: doctorRuntimeIsolationGuidance(report.checks.runtimeIsolation),
1148
+ },
1149
+ sharedSkill: {
1150
+ ...sharedSkill,
1151
+ activation: {
1152
+ ...activation,
1153
+ checkCommand: `npx --no-install owlrunkit fleet skill-plan --skill-root ${shellQuote(skillRoot)} --archive-root ${shellQuote(archiveRoot)}`,
1154
+ },
1155
+ },
1156
+ },
1157
+ profileLauncherWarnings: {
1158
+ ...report.profileLauncherWarnings,
1159
+ diagnosticCommand: report.profileLauncherWarnings.count > 0
1160
+ ? "npx --no-install owlrunkit profiles reconcile --workspace . --dry-run"
1161
+ : null,
1162
+ },
1163
+ };
1164
+ }
1165
+
822
1166
  export function formatDoctorHuman(report) {
823
1167
  const otherWarnings = report.warningCodes.filter(
824
1168
  (code) => code !== "profile_launcher_warnings",
@@ -828,13 +1172,38 @@ export function formatDoctorHuman(report) {
828
1172
  `Node: ${report.checks.node.status} (${report.checks.node.version})`,
829
1173
  `Core: ${report.checks.core.status} (${report.checks.core.version})`,
830
1174
  `Installed package: ${report.checks.installedPackage.status}`,
1175
+ ...(report.checks.skill
1176
+ ? [
1177
+ `Skill: package ${report.checks.skill.packageSkillVersion ?? "unknown"}; process ${report.checks.skill.processSkillVersion ?? "unknown"}${report.checks.skill.skillDrift ? " (drift, non-blocking)" : ""}; loaded ${report.checks.skill.loadedSkillVersion}.`,
1178
+ `Package-bound Skill: ${report.checks.skill.packageSkillPath ?? "unavailable"}`,
1179
+ `Process-wide Skill: ${report.checks.skill.processSkillPath ?? "unavailable"}`,
1180
+ ]
1181
+ : []),
831
1182
  `Registry: ${report.checks.registry.status}`,
832
1183
  `Profiles: ${report.checks.profiles.status}`,
833
1184
  `Adoption: ${report.checks.adoption.status}`,
1185
+ ...(report.checks.sharedSkill
1186
+ ? [`Shared Skill: ${report.checks.sharedSkill.status} (${report.checks.sharedSkill.sharedSkillVersion ?? "missing"}; project Core ${report.checks.sharedSkill.projectCoreVersion})`]
1187
+ : []),
1188
+ ...(report.checks.sharedSkill?.activationBlockedReason
1189
+ && !report.checks.sharedSkill?.activation
1190
+ ? [`Shared Skill activation: ${report.checks.sharedSkill.activationBlockedReason}`]
1191
+ : []),
1192
+ ...(report.checks.sharedSkill?.activation
1193
+ ? [
1194
+ `Shared Skill activation: ${report.checks.sharedSkill.activation.status} (${report.checks.sharedSkill.activation.reason})`,
1195
+ `Fleet-safe check: ${report.checks.sharedSkill.activation.checkCommand}`,
1196
+ ]
1197
+ : []),
834
1198
  `Lifecycle: ${report.checks.lifecycle.status}`,
835
1199
  ...(report.issueCodes.length > 0 ? [`Issues: ${report.issueCodes.join(", ")}`] : []),
836
1200
  ...(report.profileLauncherWarnings.count > 0
837
- ? [`Profile launcher warnings: ${report.profileLauncherWarnings.count} (non-blocking)`]
1201
+ ? [
1202
+ `Profile launcher warnings: ${report.profileLauncherWarnings.count} (non-blocking)`,
1203
+ ...(report.profileLauncherWarnings.diagnosticCommand
1204
+ ? [`Profile launcher diagnostic: ${report.profileLauncherWarnings.diagnosticCommand}`]
1205
+ : []),
1206
+ ]
838
1207
  : []),
839
1208
  ...(otherWarnings.length > 0 ? [`Warnings: ${otherWarnings.join(", ")}`] : []),
840
1209
  `Next: ${report.nextAllowedAction === "adopt_current_release"
@@ -1826,6 +1826,12 @@ export function reconcileProfilesV2({
1826
1826
  authorizationGranted: false,
1827
1827
  };
1828
1828
  if (!apply || proposedProfilesSha256 === currentProfilesSha256) return plan;
1829
+ if (normalizationOnly) {
1830
+ return {
1831
+ ...plan,
1832
+ status: "profiles_already_current",
1833
+ };
1834
+ }
1829
1835
 
1830
1836
  let runtimeRoot;
1831
1837
  let receiptRoot;
@@ -15,10 +15,26 @@ const MAX_CORE_TOTAL_BYTES = 67_108_864;
15
15
  const PACKAGE_NAME = "owlrunkit";
16
16
  const OFFICIAL_CLI_ENTRYPOINT =
17
17
  "scripts/runkit-contract/runkit-bootstrap.mjs";
18
- const SELF_NORMALIZED_QUICK_CORE_VERSIONS = new Set(["0.19.2", "0.20.0"]);
18
+ const SELF_NORMALIZED_QUICK_CORE_VERSIONS = new Set([
19
+ "0.19.2",
20
+ "0.20.0",
21
+ "0.20.1",
22
+ "0.21.0",
23
+ "0.21.1",
24
+ "0.22.0",
25
+ ]);
19
26
  const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
20
27
  const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
21
28
  const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
29
+ ["0.21.0", new Set([
30
+ "sha256:f85b9bf8c32705d5f5dfebdb653e83ac8d1ac0468214c17f230cbd2de04246fd",
31
+ ])],
32
+ ["0.20.1", new Set([
33
+ "sha256:8af2b9792731efa37f596255a7677dcec6e2fd3cc079dfdb0e6ff29e8017e784",
34
+ ])],
35
+ ["0.20.0", new Set([
36
+ "sha256:30098aa9344c17c1ed0b5ed45ef1c590b3214b824402f584029c1ecb38ca7a70",
37
+ ])],
22
38
  ["0.19.2", new Set([
23
39
  "sha256:c46ae9d8ed0572e5636ab5725f922f8db3ee5bf0f966b200bce7b2287c9267c8",
24
40
  ])],
@@ -22,12 +22,13 @@ const INVALID_ATTESTATION_ISSUES = new Set([
22
22
  "attestation_material_missing",
23
23
  "receipt_material_hash_mismatch",
24
24
  ]);
25
- const V3_MUTATION_ISSUES = new Set([
25
+ const SUCCESSOR_MUTATION_ISSUES = new Set([
26
26
  "source_mutated_during_verification",
27
27
  "bound_ignored_path_mutated_during_verification",
28
28
  "dependency_environment_mutated_during_verification",
29
29
  "consumer_source_mutated_during_verification",
30
30
  "quick_isolation_cleanup_failed",
31
+ "foreign_workspace_mutated_during_verification",
31
32
  ]);
32
33
 
33
34
  function readHistoricalReceipt(receiptPath) {
@@ -38,11 +39,14 @@ function readHistoricalReceipt(receiptPath) {
38
39
  && quickSnapshotFingerprintValid(receipt.workspaceAfter);
39
40
  const sourceChanged = receipt.workspaceBefore.sourceFingerprint
40
41
  !== receipt.workspaceAfter.sourceFingerprint;
41
- const receiptV3 = receipt.schemaVersion === "OwlCodaQuickVerificationReceiptV3";
42
+ const successorReceipt = [
43
+ "OwlCodaQuickVerificationReceiptV3",
44
+ "OwlCodaQuickVerificationReceiptV4",
45
+ ].includes(receipt.schemaVersion);
42
46
  const mutationBindingValid = receipt.mutationDecision === "source_unchanged"
43
- ? !sourceChanged && !receipt.issueCodes.some(issue => V3_MUTATION_ISSUES.has(issue))
44
- : receiptV3
45
- ? receipt.issueCodes.some(issue => V3_MUTATION_ISSUES.has(issue))
47
+ ? !sourceChanged && !receipt.issueCodes.some(issue => SUCCESSOR_MUTATION_ISSUES.has(issue))
48
+ : successorReceipt
49
+ ? receipt.issueCodes.some(issue => SUCCESSOR_MUTATION_ISSUES.has(issue))
46
50
  : sourceChanged && receipt.issueCodes.includes("source_mutated_during_verification");
47
51
  if (!snapshotsValid || !mutationBindingValid) {
48
52
  throw new Error("quick_receipt_source_binding_invalid");