@brainervirus/workit-core 1.0.4 → 1.0.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/core/doctor.ts +125 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "description": "Workit shared core — task, policy, evidence, review, decision, worker, and writer state for agentic coding workflows",
6
6
  "keywords": [
@@ -27,6 +27,7 @@ import { getDiagnosticLogger, isConfigObject } from "./config";
27
27
  import { packageRoot } from "./package-root";
28
28
  import {
29
29
  CURSOR_RUNTIME_PACKAGE,
30
+ OPENCODE_NPM_PIN,
30
31
  cursorHookDrift,
31
32
  cursorHooksEntry,
32
33
  cursorMcpServerEntry,
@@ -111,6 +112,8 @@ export type DoctorOptions = {
111
112
  dev?: string;
112
113
  cwd?: string;
113
114
  opencodeConfig?: string;
115
+ /** OpenCode npm `@latest` package cache root (test seam). */
116
+ opencodePackageCacheDir?: string;
114
117
  cursorSettings?: string;
115
118
  cursorMcp?: string;
116
119
  cursorPluginDir?: string;
@@ -129,6 +132,7 @@ type Resolved = {
129
132
  cwd: string;
130
133
  dev: string | null;
131
134
  opencodeConfig: string;
135
+ opencodePackageCacheDir: string;
132
136
  cursorSettings: string;
133
137
  cursorMcp: string;
134
138
  cursorPluginDir: string;
@@ -179,6 +183,9 @@ const resolve = (options: DoctorOptions): Resolved => {
179
183
  dev,
180
184
  opencodeConfig:
181
185
  options.opencodeConfig ?? path.join(home, ".config", "opencode", "opencode.json"),
186
+ opencodePackageCacheDir:
187
+ options.opencodePackageCacheDir ??
188
+ path.join(home, ".cache", "opencode", "packages", "@brainervirus", "workit-opencode@latest"),
182
189
  cursorSettings: options.cursorSettings ?? path.join(home, ".cursor", "settings.json"),
183
190
  cursorMcp: options.cursorMcp ?? path.join(home, ".cursor", "mcp.json"),
184
191
  cursorPluginDir:
@@ -771,6 +778,13 @@ const checkStalePin = (res: Resolved): DoctorCheck => {
771
778
  // `@latest` installs skip the probe entirely: the selector resolves fresh
772
779
  // at launch, so the installed package.json version is metadata, not a
773
780
  // freshness signal.
781
+ // OpenCode npm pins (`@brainervirus/workit-opencode` / `@…@latest`) are different:
782
+ // OpenCode freezes the first resolved version under
783
+ // `~/.cache/opencode/packages/@brainervirus/workit-opencode@latest` and does not
784
+ // re-resolve on later launches. Doctor compares that cache to the published
785
+ // package (same CA-04 fail-open seam) and tells the user to delete the cache
786
+ // directory so OpenCode can resolve `@latest` again. Exact `@version` pins and
787
+ // `file://` checkout pins skip the comparison.
774
788
  // Test seams (no spawns on the canonical path): WORKIT_DOCTOR_STALE_REGISTRY_VERSION
775
789
  // short-circuits the probe with the resolved latest version; the optional
776
790
  // WORKIT_DOCTOR_STALE_REGISTRY_CMD replaces the `npm view` binary (a
@@ -786,7 +800,15 @@ const pluginMcpSelectors = (res: Resolved): string[] | null => {
786
800
  return args.map(String).filter((a) => a.startsWith("--package="));
787
801
  };
788
802
 
789
- const registryLatestVersion = (res: Resolved): string | null => {
803
+ const isFloatingOpenCodeNpmPin = (entry: string): boolean => {
804
+ const s = entry.trim();
805
+ return s === OPENCODE_NPM_PIN || s === `${OPENCODE_NPM_PIN}@latest`;
806
+ };
807
+
808
+ const registryLatestVersion = (
809
+ res: Resolved,
810
+ pkgName: string = CURSOR_RUNTIME_PACKAGE,
811
+ ): string | null => {
790
812
  const seam = res.env.WORKIT_DOCTOR_STALE_REGISTRY_VERSION;
791
813
  if (typeof seam === "string" && seam) {
792
814
  return /^\d+(?:\.\d+){1,2}$/.test(seam) ? seam : null;
@@ -795,7 +817,7 @@ const registryLatestVersion = (res: Resolved): string | null => {
795
817
  res.env.WORKIT_DOCTOR_STALE_REGISTRY_CMD ?? (commandOnPath("npm", res.env) ? "npm" : null);
796
818
  if (!cmd) return null;
797
819
  try {
798
- const r = spawnSync(cmd, ["view", CURSOR_RUNTIME_PACKAGE, "version"], {
820
+ const r = spawnSync(cmd, ["view", pkgName, "version"], {
799
821
  encoding: "utf8",
800
822
  timeout: 20_000,
801
823
  env: res.env,
@@ -812,12 +834,96 @@ const installedPluginVersion = (res: Resolved): string | null => {
812
834
  return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
813
835
  };
814
836
 
837
+ const installedOpenCodeCacheVersion = (res: Resolved): string | null => {
838
+ const pkg = readJson(
839
+ path.join(
840
+ res.opencodePackageCacheDir,
841
+ "node_modules",
842
+ "@brainervirus",
843
+ "workit-opencode",
844
+ "package.json",
845
+ ),
846
+ );
847
+ return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
848
+ };
849
+
850
+ /** OpenCode freezes the first `@latest` resolve in its package cache — detect lag. */
851
+ const checkOpenCodePackageCache = (res: Resolved): DoctorCheck & { registryProbed?: boolean } => {
852
+ if (!existsSync(res.opencodeConfig)) {
853
+ return {
854
+ id: "stale_install",
855
+ status: "pass",
856
+ detail: "no opencode config — package cache not inspected",
857
+ };
858
+ }
859
+ const entries = pluginEntries(readJson(res.opencodeConfig));
860
+ if (entries.length === 0) {
861
+ return {
862
+ id: "stale_install",
863
+ status: "pass",
864
+ detail: "no workit opencode pin — package cache not inspected",
865
+ };
866
+ }
867
+ const pin = entries[0]!;
868
+ if (pin.startsWith("file:") || pin.startsWith("git+file:")) {
869
+ return {
870
+ id: "stale_install",
871
+ status: "pass",
872
+ detail: "opencode checkout pin — package cache not used for npm resolution",
873
+ };
874
+ }
875
+ if (!isFloatingOpenCodeNpmPin(pin)) {
876
+ return {
877
+ id: "stale_install",
878
+ status: "pass",
879
+ detail: `opencode pin ${pin} is an exact version — not compared to registry latest`,
880
+ };
881
+ }
882
+ const installed = installedOpenCodeCacheVersion(res);
883
+ if (installed === null) {
884
+ return {
885
+ id: "stale_install",
886
+ status: "pass",
887
+ detail: "opencode @latest package cache empty — OpenCode will resolve on next launch",
888
+ };
889
+ }
890
+ const expected = registryLatestVersion(res, OPENCODE_NPM_PIN);
891
+ if (expected === null) {
892
+ return {
893
+ id: "registry_unreachable",
894
+ status: "warn",
895
+ registryProbed: true,
896
+ detail:
897
+ "registry_unreachable: cannot compare OpenCode @latest cache against published workit-opencode",
898
+ fix: `Retry when npm is reachable, or remove ${res.opencodePackageCacheDir} and restart OpenCode to re-resolve`,
899
+ };
900
+ }
901
+ if (!semverAtLeast(installed, expected)) {
902
+ return {
903
+ id: "stale_install",
904
+ status: "fail",
905
+ registryProbed: true,
906
+ detail: `stale_install: OpenCode @latest cache has workit-opencode ${installed} behind published ${expected}`,
907
+ fix: `Remove ${res.opencodePackageCacheDir} and restart OpenCode — it re-resolves @latest on the next launch`,
908
+ };
909
+ }
910
+ return {
911
+ id: "stale_install",
912
+ status: "pass",
913
+ registryProbed: true,
914
+ detail: `OpenCode @latest cache workit-opencode ${installed} matches published ${expected}`,
915
+ };
916
+ };
917
+
815
918
  const checkStaleInstall = (res: Resolved): DoctorCheck & { registryProbed?: boolean } => {
919
+ if (res.host === "opencode") {
920
+ return checkOpenCodePackageCache(res);
921
+ }
816
922
  if (res.host !== "cursor" && res.host !== "cli") {
817
923
  return {
818
924
  id: "stale_install",
819
925
  status: "pass",
820
- detail: "cursor plugin not inspected on the opencode host",
926
+ detail: "cursor plugin not inspected on this host",
821
927
  };
822
928
  }
823
929
  // Symlink installs into pnpm dlx / _npx caches break when the cache is cleared.
@@ -930,21 +1036,31 @@ const checkStaleInstall = (res: Resolved): DoctorCheck & { registryProbed?: bool
930
1036
  fix: "Re-run install-cursor-plugin.sh — it refreshes the plugin directory with the current build",
931
1037
  };
932
1038
  }
933
- return {
934
- id: "stale_install",
935
- status: "pass",
1039
+ const cursorOk = {
1040
+ id: "stale_install" as const,
1041
+ status: "pass" as const,
936
1042
  registryProbed: true,
937
1043
  detail: `installed local-dist workit-cursor ${installed} matches the published runtime ${expected}`,
938
1044
  };
1045
+ if (res.host === "cli") {
1046
+ const oc = checkOpenCodePackageCache(res);
1047
+ if (oc.status !== "pass") return oc;
1048
+ }
1049
+ return cursorOk;
939
1050
  }
940
- return {
941
- id: "stale_install",
942
- status: "pass",
1051
+ const cursorOk = {
1052
+ id: "stale_install" as const,
1053
+ status: "pass" as const,
943
1054
  detail:
944
1055
  installed === null
945
1056
  ? "installed workit-cursor selectors are canonical"
946
1057
  : `installed workit-cursor ${installed} is metadata on the canonical @latest install (fresh at launch)`,
947
1058
  };
1059
+ if (res.host === "cli") {
1060
+ const oc = checkOpenCodePackageCache(res);
1061
+ if (oc.status !== "pass") return oc;
1062
+ }
1063
+ return cursorOk;
948
1064
  };
949
1065
 
950
1066
  const checkDuplicateRegistration = (res: Resolved): DoctorCheck => {