@ccoalm/ccl-skills 0.15.0 → 0.15.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 (32) hide show
  1. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +80 -4
  2. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +16 -4
  3. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +13 -2
  4. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +53 -0
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +7 -1
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +65 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +4 -4
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +8 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +14 -14
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +1 -1
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +40 -83
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +2 -2
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +44 -37
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +1 -1
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +8 -5
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +3 -3
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +13 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +31 -7
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +74 -3
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +103 -21
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +74 -1
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py +428 -0
  24. package/dist/assets/release.json +45 -30
  25. package/dist/claude-adapter.js +14 -7
  26. package/dist/codex-host.d.ts +1 -3
  27. package/dist/codex-host.js +6 -9
  28. package/dist/host-probe.d.ts +11 -0
  29. package/dist/host-probe.js +27 -0
  30. package/dist/opencode-adapter.js +24 -19
  31. package/dist/unified.js +11 -9
  32. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "npmPackage": "@ccoalm/ccl-skills",
4
- "version": "0.15.0",
5
- "sourceCommit": "7c3a2fbb2253f218e26acdb729edf16481982b26",
4
+ "version": "0.15.1",
5
+ "sourceCommit": "32570b61c4ffb37f6e931ba7f2d16b6617136507",
6
6
  "sourceState": "clean",
7
7
  "files": [
8
8
  {
@@ -162,12 +162,12 @@
162
162
  },
163
163
  {
164
164
  "path": "marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts",
165
- "sha256": "6480da2f8a293209cf4e2f8c290bf7b7b6e007a1ff4fd3c272d3408239b56ca9",
165
+ "sha256": "4cc8c560cbbcfe9c7723c9986ea93645ca1926503a0ea80ae93af80f0eabd10e",
166
166
  "mode": 420
167
167
  },
168
168
  {
169
169
  "path": "marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md",
170
- "sha256": "449d9bee6e89b66c7247bf27c417967fa0a7d8b56e51098a49c57e5a4ad67c3a",
170
+ "sha256": "323c5055bf608ac70fffac47d8581a3e31c3f21489fd325983f022511e711a55",
171
171
  "mode": 420
172
172
  },
173
173
  {
@@ -197,7 +197,7 @@
197
197
  },
198
198
  {
199
199
  "path": "marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh",
200
- "sha256": "cb033d816d33ea43a71b1a5a7e2a056cf95f31f6dabff9e6bece27a1d94e15ee",
200
+ "sha256": "76efb7c5d98b8944aaee11c537aabc82259faddfa6b60bb2bfabdbf7d0602ce2",
201
201
  "mode": 493
202
202
  },
203
203
  {
@@ -207,7 +207,7 @@
207
207
  },
208
208
  {
209
209
  "path": "marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh",
210
- "sha256": "d8d9600bd37608c13bf8ef91e07302f30a0435ccfd634c5365361f18b74030cc",
210
+ "sha256": "df5d23aef020066b427d0424c200eb64863427ea03a90de84ba5c3fbe76d450d",
211
211
  "mode": 493
212
212
  },
213
213
  {
@@ -372,7 +372,7 @@
372
372
  },
373
373
  {
374
374
  "path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py",
375
- "sha256": "56e1eaf01a4af639260cfb7193dc6c14eef9bd883751e4831f7e4925db11af81",
375
+ "sha256": "31841b7feda1fc2ddf36654cfdc94df0bbee84d590b23cde867aae821e2b1218",
376
376
  "mode": 493
377
377
  },
378
378
  {
@@ -477,7 +477,7 @@
477
477
  },
478
478
  {
479
479
  "path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh",
480
- "sha256": "51ea90cd42ce9661f84c1a0f5daee4f2a17072b5f0cdfa5d3c0e27a0ec2af235",
480
+ "sha256": "55366dc96415e175c1b9a23f6fa6e118679bb656ed96c818f4a534ca14826ef0",
481
481
  "mode": 493
482
482
  },
483
483
  {
@@ -1087,7 +1087,7 @@
1087
1087
  },
1088
1088
  {
1089
1089
  "path": "marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md",
1090
- "sha256": "e1363c5ca688cb596e1a96127dd14730cd679cd6fb7344c33b6acfac6f0258dc",
1090
+ "sha256": "8124c37f30783970ce681ba1aa05cfe507f0054640ed68b19f89da32a35105ce",
1091
1091
  "mode": 420
1092
1092
  },
1093
1093
  {
@@ -1132,7 +1132,7 @@
1132
1132
  },
1133
1133
  {
1134
1134
  "path": "marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md",
1135
- "sha256": "6cec53c15ec23b7bd5ef060fb5442667924593fdcb72e6b4f2866eef3343dfd0",
1135
+ "sha256": "3da20332d76fba3b982f5b920794074f7599826f063352fe5c394aaf5e66ad58",
1136
1136
  "mode": 420
1137
1137
  },
1138
1138
  {
@@ -1217,7 +1217,7 @@
1217
1217
  },
1218
1218
  {
1219
1219
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md",
1220
- "sha256": "a08191d88586922182e83183282f9ef6f94c46d2097de05179f689dd6e2761ed",
1220
+ "sha256": "9f929f6b9bdc03c88f415cf99169d1b7bd508a49a0a80d9f2da2ffc4e079f4a7",
1221
1221
  "mode": 420
1222
1222
  },
1223
1223
  {
@@ -1227,7 +1227,7 @@
1227
1227
  },
1228
1228
  {
1229
1229
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md",
1230
- "sha256": "f936a8f95c1fad672fcdaadb723af2ea316fbd0dc0270c9f77b846e7d8704bdb",
1230
+ "sha256": "40c7c658a86e08afd47aa7e6cecdf71e2af08ef6c7b5768c5571e17f78b55b32",
1231
1231
  "mode": 420
1232
1232
  },
1233
1233
  {
@@ -1237,7 +1237,7 @@
1237
1237
  },
1238
1238
  {
1239
1239
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md",
1240
- "sha256": "4eaa070fb7669b5be1e33f267558f49f09d9102d07906efb579570adbf9a2d4e",
1240
+ "sha256": "41b7e324969a3d21065fa9139ecabfda86439a5b02eeb18bac4300c1de0e6767",
1241
1241
  "mode": 420
1242
1242
  },
1243
1243
  {
@@ -1252,7 +1252,7 @@
1252
1252
  },
1253
1253
  {
1254
1254
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md",
1255
- "sha256": "4158a44920b029352eff86ca1eb555218073b69b0cc58ea39286745fee312d08",
1255
+ "sha256": "936383132340fec08539ded8cce57a9d3027af3b90eab6673af562b5c358a412",
1256
1256
  "mode": 420
1257
1257
  },
1258
1258
  {
@@ -1272,12 +1272,12 @@
1272
1272
  },
1273
1273
  {
1274
1274
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md",
1275
- "sha256": "6adefb920ae5b0b2496ef5feed9b1b5c44c944111da0bd282d0afecbd30de599",
1275
+ "sha256": "e0f46825eeb423505e44d2deb7d3c4abecd859bc7b61c304085b02dc2a798533",
1276
1276
  "mode": 420
1277
1277
  },
1278
1278
  {
1279
1279
  "path": "marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md",
1280
- "sha256": "17c0927bfaaf723480a73f962d9c5675fe41710e487ae8c8a62eaf6b2617c3fa",
1280
+ "sha256": "558b1d6864527d981d5811fb709acb08405218aced5a1515ae9394ab2fb1a81a",
1281
1281
  "mode": 420
1282
1282
  },
1283
1283
  {
@@ -1332,7 +1332,7 @@
1332
1332
  },
1333
1333
  {
1334
1334
  "path": "marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md",
1335
- "sha256": "17b5f7eee4049894cb855de31878abfda1196a20278a0ccd5c03c87ecf4d720c",
1335
+ "sha256": "3cf969368a7c0343c74ea891fbf78dde8f63295e73c7121a700757df9d0ab1e5",
1336
1336
  "mode": 420
1337
1337
  },
1338
1338
  {
@@ -1977,7 +1977,7 @@
1977
1977
  },
1978
1978
  {
1979
1979
  "path": "marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md",
1980
- "sha256": "45c277944ab2129ee6ff50cd135b527eee8f48514ba1957fd7bc4d929ca5e8f8",
1980
+ "sha256": "31f46a35743b8da351f5a517a3d15a0cbe92ca4f0b778d21aec6d0b5574a9546",
1981
1981
  "mode": 420
1982
1982
  },
1983
1983
  {
@@ -2042,7 +2042,7 @@
2042
2042
  },
2043
2043
  {
2044
2044
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md",
2045
- "sha256": "105606307b875fab01dacf8e7492966de6148fe45ae8336b2fbc95cce03c336b",
2045
+ "sha256": "98c8ef6d9250f96feb9e996e108ca473475c9b690c7a0efc57ed5a30e2665d2c",
2046
2046
  "mode": 420
2047
2047
  },
2048
2048
  {
@@ -2127,7 +2127,7 @@
2127
2127
  },
2128
2128
  {
2129
2129
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md",
2130
- "sha256": "244615335846d51d47798dc428c543ff7ae03b09026deda649f8a2ba07ca9f2d",
2130
+ "sha256": "006325a862d0bac253bfdd61c940c29fa20b1ddcf1ddebae4726a7a866a45f5a",
2131
2131
  "mode": 420
2132
2132
  },
2133
2133
  {
@@ -2207,7 +2207,7 @@
2207
2207
  },
2208
2208
  {
2209
2209
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb",
2210
- "sha256": "14c3c7c1437c6d70c5ab48ac8b1312fab3a421de31b110da84fa0528762b924c",
2210
+ "sha256": "70d0b824c79f701606f752e815fbe7b643324b6f56d601e4e55b3ceae578ad62",
2211
2211
  "mode": 493
2212
2212
  },
2213
2213
  {
@@ -2247,7 +2247,7 @@
2247
2247
  },
2248
2248
  {
2249
2249
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb",
2250
- "sha256": "b24246416f7b26bf7d1e262c388dc4a752135193a8ac2ca06a7e1e6fb1caca85",
2250
+ "sha256": "c2a4816b7c7043a7868c0ced2b7a4046b5aba088dda622ffec49360306f59ce1",
2251
2251
  "mode": 493
2252
2252
  },
2253
2253
  {
@@ -2277,7 +2277,7 @@
2277
2277
  },
2278
2278
  {
2279
2279
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py",
2280
- "sha256": "c6e82dbbf5b9ea8ac05e38cba1f9404cde4c6f9609bfef8fd645ea3c61d1647e",
2280
+ "sha256": "d65c8a76ae6d4ceacbe16885abc3e0c51d6fd152d491437db7e1e8fc9988ad08",
2281
2281
  "mode": 493
2282
2282
  },
2283
2283
  {
@@ -2307,7 +2307,7 @@
2307
2307
  },
2308
2308
  {
2309
2309
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh",
2310
- "sha256": "8c7ddf91ab9987bd8aa34eb204e420a9e930270bcf877fb743cc1f1a0f3a89e4",
2310
+ "sha256": "dbc090be57b6f14795e6b7dcd79abe83bb1efa3d2f090040c4b7225b4e10d2fa",
2311
2311
  "mode": 493
2312
2312
  },
2313
2313
  {
@@ -2405,6 +2405,11 @@
2405
2405
  "sha256": "8ddd704eac6f4ffdc5d5b6566f9a2b3d5e940b944af448586dfec0e920a54ee5",
2406
2406
  "mode": 493
2407
2407
  },
2408
+ {
2409
+ "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py",
2410
+ "sha256": "b40fb184ba6e98d767f3d9c8e9c7d20a834bba79e644b1626c6071aa777d3f60",
2411
+ "mode": 420
2412
+ },
2408
2413
  {
2409
2414
  "path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh",
2410
2415
  "sha256": "b3284aef648eb0b6bde2cd6084b4ac9b7d060e3fb244dace59fe491bab0e05e2",
@@ -3264,7 +3269,7 @@
3264
3269
  },
3265
3270
  {
3266
3271
  "path": "claude-adapter.js",
3267
- "sha256": "661a9486abcf1bf44ecb34ef77c66b8bf1ab5d5fbd006587888d092c4348119c",
3272
+ "sha256": "75386ab4c98d7e4056fa22683168da1867536bdc2de4d02c8b6fbef68e908713",
3268
3273
  "mode": 420
3269
3274
  },
3270
3275
  {
@@ -3289,12 +3294,12 @@
3289
3294
  },
3290
3295
  {
3291
3296
  "path": "codex-host.d.ts",
3292
- "sha256": "4a45f6fc30732210a684cde96b37dc96242a56b565723f8267edbdfd5d44011c",
3297
+ "sha256": "1f1987b0820ff80c915e2a66f5324fa3790465d23b9d1fca55be178ed1c0dbdd",
3293
3298
  "mode": 420
3294
3299
  },
3295
3300
  {
3296
3301
  "path": "codex-host.js",
3297
- "sha256": "53d2b44b4dacab1170402a70ed857fb46645dd91b6b8a08b0454f43bb033b120",
3302
+ "sha256": "09649bcb081b97b947a1b83441d0ac8fd2868461e5f295f241d731932d3ba04a",
3298
3303
  "mode": 420
3299
3304
  },
3300
3305
  {
@@ -3307,6 +3312,16 @@
3307
3312
  "sha256": "4e2cf0e4742d91d8f9b2024d7df3fc93690fa3a71ab35a4762bed59705e87044",
3308
3313
  "mode": 420
3309
3314
  },
3315
+ {
3316
+ "path": "host-probe.d.ts",
3317
+ "sha256": "49974a5794f49a27b1bbacdb476f4d6049aad756e17a0f9856172053ed746173",
3318
+ "mode": 420
3319
+ },
3320
+ {
3321
+ "path": "host-probe.js",
3322
+ "sha256": "db73d028fa5fc9f70887c09539bcce10ff21b0cef76101445c8820028153e2d5",
3323
+ "mode": 420
3324
+ },
3310
3325
  {
3311
3326
  "path": "index.d.ts",
3312
3327
  "sha256": "37bf7ef65f13c512112942fa78257e84d42535993669cd97fbea2374dd20e174",
@@ -3334,7 +3349,7 @@
3334
3349
  },
3335
3350
  {
3336
3351
  "path": "opencode-adapter.js",
3337
- "sha256": "51f09cfe6138cfe3db8587a644de528e51177af273d374db303cd1675c947c28",
3352
+ "sha256": "637d3df5182ad6a51ed310736be65471fd0b432c078483af163d340ac04d6460",
3338
3353
  "mode": 420
3339
3354
  },
3340
3355
  {
@@ -3374,7 +3389,7 @@
3374
3389
  },
3375
3390
  {
3376
3391
  "path": "unified.js",
3377
- "sha256": "2aeabe2364755dc91d7058bf3e3b8e2bb4086a0392da6b8f87a3892879334915",
3392
+ "sha256": "e5f2870f5ba9689b3cc903c9403b8ade85b7df5364e2037fe7a3eed04aeedc4a",
3378
3393
  "mode": 420
3379
3394
  },
3380
3395
  {
@@ -3408,5 +3423,5 @@
3408
3423
  "mode": 420
3409
3424
  }
3410
3425
  ],
3411
- "snapshotHash": "6e6c0f7d8b6ed499d7cf1a4701958fbd38d70219ddb72f1d985b5ea8605853cc"
3426
+ "snapshotHash": "8fd7e270f237790aff425a4e1ece90ddd91919b4a94191b3bfb17e2e75780e04"
3412
3427
  }
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import { atomicJson, canonicalAlias, copyRegularTree, verifyTree } from "./fs-safe.js";
6
6
  import { manifestFor, readManifest, readRelease } from "./manifest.js";
7
7
  import { compare } from "./version.js";
8
+ import { probeHostVersion } from "./host-probe.js";
8
9
  const MARKET = "ccl-skills-npm", REF = `ccl-skills@${MARKET}`;
9
10
  function paths(context) {
10
11
  const rawHome = context.home ?? (context.env ? context.env.HOME : process.env.HOME);
@@ -46,9 +47,9 @@ function command(args, context, allowFailure = false) {
46
47
  return result;
47
48
  }
48
49
  function state(context) {
49
- const version = command(["--version"], context, true);
50
- if (version.status !== 0)
51
- return { host: false, marketplace: null, plugin: false, legacy: false };
50
+ const version = probeHostVersion("claude", context.env || process.env);
51
+ if (!version.ok)
52
+ return { host: false, hostFailure: { code: 4, status: `host-${version.kind}`, message: version.message } };
52
53
  const marketsRaw = command(["plugin", "marketplace", "list", "--json"], context), pluginsRaw = command(["plugin", "list", "--json"], context);
53
54
  let markets, plugins;
54
55
  try {
@@ -100,6 +101,8 @@ function prepare(context, old) {
100
101
  }
101
102
  function removeRegistration(context) {
102
103
  const current = state(context);
104
+ if (!current.host)
105
+ throw new Error(current.hostFailure.message);
103
106
  if (current.plugin)
104
107
  command(["plugin", "uninstall", REF, "--scope", "user"], context);
105
108
  if (current.marketplace)
@@ -109,6 +112,8 @@ function installRegistration(marketplace, context) {
109
112
  command(["plugin", "marketplace", "add", marketplace, "--scope", "user"], context);
110
113
  command(["plugin", "install", REF, "--scope", "user"], context);
111
114
  const current = state(context);
115
+ if (!current.host)
116
+ throw new Error(current.hostFailure.message);
112
117
  if (!current.plugin || !samePath(current.marketplace || "", marketplace))
113
118
  throw new Error("Claude did not expose the exact npm marketplace candidate");
114
119
  }
@@ -117,7 +122,7 @@ function doctor(context) {
117
122
  assertExclusiveRoot(p.claudeHome, p.root);
118
123
  const current = state(context);
119
124
  if (!current.host)
120
- return { code: 4, status: "host-missing", message: "Claude CLI is not installed" };
125
+ return current.hostFailure;
121
126
  if (current.legacy)
122
127
  return { code: 3, status: "double-install", message: "Git marketplace and npm marketplace are both present; keep exactly one" };
123
128
  const manifest = readManifest(p.manifest);
@@ -138,7 +143,7 @@ function installOrUpdate(commandName, options, context) {
138
143
  assertExclusiveRoot(p.claudeHome, p.root, true);
139
144
  const current = state(context);
140
145
  if (!current.host)
141
- return { code: 4, status: "host-missing", message: "Claude CLI is not installed" };
146
+ return current.hostFailure;
142
147
  if (current.legacy)
143
148
  return { code: 3, status: "double-install", message: "Remove the Git marketplace install or choose it instead of npm" };
144
149
  const old = readManifest(p.manifest), release = readRelease(p.release);
@@ -199,18 +204,20 @@ function uninstall(options, context) {
199
204
  const p = paths(context);
200
205
  assertExclusiveRoot(p.claudeHome, p.root);
201
206
  const manifest = readManifest(p.manifest), current = state(context);
207
+ if (!current.host)
208
+ return { ...current.hostFailure, message: `${current.hostFailure.message}; refusing to drop ownership evidence while registration cannot be verified` };
202
209
  if (!manifest && !current.plugin && !current.marketplace)
203
210
  return { code: 0, status: "absent", message: "Claude npm install is already absent" };
204
211
  if (!manifest)
205
212
  return { code: 3, status: "unowned-registration", message: "refusing to remove an unowned Claude registration" };
206
- if (!current.host)
207
- return { code: 4, status: "host-missing", message: "Claude CLI is not installed; refusing to drop ownership evidence while registration cannot be verified" };
208
213
  if (!options.yes)
209
214
  return { code: 0, status: "dry-run", message: "Claude uninstall preview", plan: ["remove npm plugin", "remove npm marketplace", "delete exclusive owned snapshots"] };
210
215
  if (context.isInterrupted?.())
211
216
  return { code: 130, status: "interrupted", message: "Claude uninstall interrupted before registration mutation", interrupted: true };
212
217
  removeRegistration(context);
213
218
  const after = state(context);
219
+ if (!after.host)
220
+ return { code: 5, status: "partial", message: `${after.hostFailure.message}; uninstall state is unverified, retaining ownership evidence` };
214
221
  if (after.plugin || after.marketplace)
215
222
  return { code: 5, status: "partial", message: "Claude registration remains after uninstall" };
216
223
  try {
@@ -11,12 +11,10 @@ export declare function checkHost(codexHome?: string): {
11
11
  ok: false;
12
12
  kind: string;
13
13
  message: string;
14
- version?: undefined;
14
+ output?: undefined;
15
15
  } | {
16
16
  ok: true;
17
17
  version: string;
18
- kind?: undefined;
19
- message?: undefined;
20
18
  };
21
19
  export declare function parsePluginList(text: string, marketplaceSource: string | null): {
22
20
  ok: boolean;
@@ -4,6 +4,7 @@ import { isAbsolute, join, resolve } from "node:path";
4
4
  import { LEGACY_REF, MARKET, REF } from "./paths.js";
5
5
  import { canonicalAlias } from "./fs-safe.js";
6
6
  import { compare, parseVersion } from "./version.js";
7
+ import { probeHostVersion } from "./host-probe.js";
7
8
  function call(args, codexHome) {
8
9
  const env = {
9
10
  ...process.env,
@@ -16,14 +17,10 @@ function call(args, codexHome) {
16
17
  });
17
18
  }
18
19
  export function checkHost(codexHome) {
19
- const p = call(["--version"], codexHome);
20
- if (p.error || p.status !== 0)
21
- return {
22
- ok: false,
23
- kind: "missing",
24
- message: "Codex CLI is missing",
25
- };
26
- const version = parseVersion(`${p.stdout}\n${p.stderr}`);
20
+ const p = probeHostVersion("codex", { ...process.env, ...(codexHome ? { CODEX_HOME: codexHome } : {}) });
21
+ if (!p.ok)
22
+ return p;
23
+ const version = parseVersion(p.output);
27
24
  if (!version || compare(version, "0.133.0") < 0)
28
25
  return {
29
26
  ok: false,
@@ -37,7 +34,7 @@ export function parsePluginList(text, marketplaceSource) {
37
34
  if (lines.length === 0 ||
38
35
  (lines.length === 1 && lines[0] === "No marketplace plugins found."))
39
36
  return { ok: true, plugin: false, legacy: false };
40
- const header = /^PLUGIN\s+STATUS\s+VERSION\s+PATH\s*$/;
37
+ const header = /^PLUGIN\s+STATUS\s+VERSION\s+(?:PATH|SOURCE)\s*$/;
41
38
  const provenance = /^Marketplace `([^`]+)`$/;
42
39
  const sensitiveRefs = new Set();
43
40
  let plugin = false;
@@ -0,0 +1,11 @@
1
+ export declare function probeHostVersion(command: string, env?: NodeJS.ProcessEnv): {
2
+ ok: false;
3
+ kind: string;
4
+ message: string;
5
+ output?: undefined;
6
+ } | {
7
+ ok: true;
8
+ output: string;
9
+ kind?: undefined;
10
+ message?: undefined;
11
+ };
@@ -0,0 +1,27 @@
1
+ import { spawnSync } from "node:child_process";
2
+ // Version discovery must not hold up other hosts or wait for interactive input.
3
+ const HOST_PROBE_TIMEOUT_MS = 10000;
4
+ export function probeHostVersion(command, env = process.env) {
5
+ const result = spawnSync(command, ["--version"], {
6
+ encoding: "utf8",
7
+ env,
8
+ stdio: ["ignore", "pipe", "pipe"],
9
+ timeout: HOST_PROBE_TIMEOUT_MS,
10
+ // spawnSync still waits if a child catches SIGTERM and does not exit.
11
+ killSignal: "SIGKILL",
12
+ });
13
+ if (result.error || result.status !== 0) {
14
+ const errorCode = result.error?.code;
15
+ const kind = errorCode === "ETIMEDOUT" ? "timeout" : errorCode === "ENOENT" ? "missing" : "probe-failed";
16
+ return {
17
+ ok: false,
18
+ kind,
19
+ message: kind === "timeout"
20
+ ? `${command} --version timed out after ${HOST_PROBE_TIMEOUT_MS}ms`
21
+ : kind === "missing"
22
+ ? `${command} CLI is unavailable (ENOENT)`
23
+ : `${command} --version failed (${errorCode || (result.signal ? `signal ${result.signal}` : result.error ? "spawn error" : `exit ${result.status}`)})`,
24
+ };
25
+ }
26
+ return { ok: true, output: `${result.stdout}\n${result.stderr}` };
27
+ }
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { atomicJson, sha256 } from "./fs-safe.js";
7
7
  import { readRelease } from "./manifest.js";
8
8
  import { compare } from "./version.js";
9
+ import { probeHostVersion } from "./host-probe.js";
9
10
  const PACKAGE = "@ccoalm/ccl-skills";
10
11
  function paths(context) {
11
12
  const rawHome = context.home ?? (context.env ? context.env.HOME : process.env.HOME);
@@ -21,7 +22,7 @@ function assertExclusiveRoot(base, root, createBase = false) {
21
22
  throw new Error("managed root is not canonical");
22
23
  if (createBase)
23
24
  mkdirSync(base, { recursive: true });
24
- for (const component of [base, root]) {
25
+ for (const component of [base, root, join(root, "snapshots")]) {
25
26
  if (!existsSync(component))
26
27
  continue;
27
28
  const stat = lstatSync(component);
@@ -124,10 +125,6 @@ function readManifest(path, root) {
124
125
  }).sort((a, b) => a.destination.localeCompare(b.destination));
125
126
  return { schema: 1, npmPackage: PACKAGE, installedAt: raw.installedAt, version: raw.version, sourceCommit: raw.sourceCommit, sourceKind: raw.sourceKind, snapshot, snapshotHash: raw.snapshotHash, entries };
126
127
  }
127
- function hostAvailable(context) {
128
- const result = spawnSync("opencode", ["--version"], { encoding: "utf8", env: context.env || process.env });
129
- return !result.error && result.status === 0;
130
- }
131
128
  function sharedPath(base, rel) {
132
129
  const path = resolve(base, safeRel(rel, "OpenCode destination"));
133
130
  if (!path.startsWith(`${resolve(base)}${sep}`))
@@ -190,17 +187,19 @@ function actualFiles(directory) {
190
187
  function preflight(base, next, old) {
191
188
  assertSafeSharedParents(base, next);
192
189
  const previous = new Map((old?.entries || []).map((entry) => [entry.destination, entry]));
190
+ const drift = [];
193
191
  if (old) {
192
+ assertSafeSharedParents(base, old.entries);
194
193
  for (const entry of old.entries) {
195
194
  const path = sharedPath(base, entry.destination);
196
195
  if (!existsSync(path) || (regular(path).mode & 0o777) !== entry.mode || sha256(path) !== entry.sha256)
197
- throw new Error(`owned OpenCode file drifted: ${entry.destination}`);
196
+ drift.push(entry.destination);
198
197
  }
199
198
  const skillNames = new Set(old.entries.filter((entry) => entry.destination.startsWith("skills/")).map((entry) => entry.destination.split("/")[1]));
200
199
  for (const skill of skillNames) {
201
200
  const expected = old.entries.filter((entry) => entry.destination.startsWith(`skills/${skill}/`)).map((entry) => entry.destination.slice(`skills/${skill}/`.length)).sort();
202
201
  if (JSON.stringify(actualFiles(join(base, "skills", skill))) !== JSON.stringify(expected))
203
- throw new Error(`owned OpenCode skill directory drifted: ${skill}`);
202
+ drift.push(`skills/${skill}`);
204
203
  }
205
204
  }
206
205
  const nextSkills = new Set(next.filter((entry) => entry.destination.startsWith("skills/")).map((entry) => entry.destination.split("/")[1]));
@@ -211,6 +210,7 @@ function preflight(base, next, old) {
211
210
  for (const entry of next)
212
211
  if (!previous.has(entry.destination) && existsSync(sharedPath(base, entry.destination)))
213
212
  throw new Error(`OpenCode path collision: ${entry.destination}`);
213
+ return drift;
214
214
  }
215
215
  function prepareSnapshot(p, candidate) {
216
216
  const snapshotRel = join("snapshots", candidate.snapshotHash, "source"), snapshot = join(p.root, snapshotRel);
@@ -305,8 +305,9 @@ function writeShared(p, snapshot, entries, old, isInterrupted, copyShared = (fro
305
305
  function doctor(context) {
306
306
  const p = paths(context);
307
307
  assertExclusiveRoot(p.base, p.root);
308
- if (!hostAvailable(context))
309
- return { code: 4, status: "host-missing", message: "OpenCode CLI is not installed" };
308
+ const host = probeHostVersion("opencode", context.env || process.env);
309
+ if (!host.ok)
310
+ return { code: 4, status: `host-${host.kind}`, message: host.message };
310
311
  const legacy = existsSync(join(p.base, "ccl-skills/install-manifest.json")), manifest = readManifest(p.manifest, p.root);
311
312
  if (legacy && manifest)
312
313
  return { code: 3, status: "double-install", message: "legacy checkout and npm OpenCode installs are both present; keep exactly one" };
@@ -315,20 +316,22 @@ function doctor(context) {
315
316
  return { code: 3, status: legacy ? "legacy-install" : "unowned-registration", message: "OpenCode ccl-skills files exist without an npm ownership receipt" };
316
317
  return { code: 3, status: "absent", message: "ccl-skills is not installed for OpenCode" };
317
318
  }
318
- const drift = [];
319
- for (const entry of manifest.entries) {
320
- const path = sharedPath(p.base, entry.destination);
321
- if (!existsSync(path) || sha256(path) !== entry.sha256)
322
- drift.push(entry.destination);
319
+ let drift = [], reason;
320
+ try {
321
+ drift = preflight(p.base, manifest.entries, manifest);
322
+ }
323
+ catch (error) {
324
+ reason = String(error);
323
325
  }
324
- if (drift.length)
325
- return { code: 3, status: "shared-drift", message: "OpenCode shared files changed after npm install; they will never be auto-deleted", details: { drift } };
326
+ if (drift.length || reason)
327
+ return { code: 3, status: "shared-drift", message: "OpenCode shared files changed after npm install; they will never be auto-deleted", details: { drift, ...(reason ? { reason } : {}) } };
326
328
  return { code: 0, status: "healthy", message: `OpenCode uses @ccoalm/ccl-skills ${manifest.version}` };
327
329
  }
328
330
  function installOrUpdate(commandName, options, context) {
329
331
  const p = paths(context);
330
- if (!hostAvailable(context))
331
- return { code: 4, status: "host-missing", message: "OpenCode CLI is not installed" };
332
+ const host = probeHostVersion("opencode", context.env || process.env);
333
+ if (!host.ok)
334
+ return { code: 4, status: `host-${host.kind}`, message: host.message };
332
335
  let candidate;
333
336
  try {
334
337
  candidate = source(context);
@@ -350,7 +353,9 @@ function installOrUpdate(commandName, options, context) {
350
353
  return { code: 3, status: "downgrade-refused", message: "newer OpenCode assets are installed" };
351
354
  }
352
355
  try {
353
- preflight(p.base, candidate.entries, old);
356
+ const drift = preflight(p.base, candidate.entries, old);
357
+ if (drift.length)
358
+ throw new Error(`owned OpenCode paths drifted: ${drift.join(", ")}`);
354
359
  }
355
360
  catch (error) {
356
361
  return { code: 3, status: "collision", message: String(error) };
package/dist/unified.js CHANGED
@@ -1,14 +1,9 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { probeHostVersion } from "./host-probe.js";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { runClaude } from "./claude-adapter.js";
5
5
  import { run as runCodex } from "./operations.js";
6
6
  import { runOpenCode } from "./opencode-adapter.js";
7
- function available(host) {
8
- const command = host === "opencode" ? "opencode" : host;
9
- const result = spawnSync(command, ["--version"], { encoding: "utf8" });
10
- return !result.error && result.status === 0;
11
- }
12
7
  function owned(host) {
13
8
  const home = process.env.HOME;
14
9
  if (!home)
@@ -57,8 +52,15 @@ export function runUnified(command, options = {}, codexContext = {}) {
57
52
  if (options.host)
58
53
  return dispatch(options.host, command, options, codexContext);
59
54
  const includeOwned = command === "doctor" || command === "uninstall";
60
- const hosts = ["claude", "codex", "opencode"].filter((host) => command === "update" ? available(host) && owned(host) : available(host) || (includeOwned && owned(host)));
55
+ const candidates = ["claude", "codex", "opencode"].filter((host) => command !== "update" || owned(host));
56
+ const probes = candidates.map((host) => ({ host, probe: probeHostVersion(host) }));
57
+ const hosts = probes.filter(({ host, probe }) => probe.ok || probe.kind !== "missing" || (includeOwned && owned(host))).map(({ host }) => host);
61
58
  if (!hosts.length)
62
- return { code: 4, status: "host-missing", message: "none of Claude Code, Codex, or OpenCode is installed" };
63
- return runHostSequence(hosts, (host) => dispatch(host, command, { ...options, host }, codexContext));
59
+ return { code: 4, status: "host-missing", message: "no Claude Code, Codex, or OpenCode CLI is available" };
60
+ return runHostSequence(hosts, (host) => {
61
+ const probe = probes.find((entry) => entry.host === host).probe;
62
+ if (!probe.ok && probe.kind !== "missing")
63
+ return { code: 4, status: `host-${probe.kind}`, message: probe.message };
64
+ return dispatch(host, command, { ...options, host }, codexContext);
65
+ });
64
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccoalm/ccl-skills",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Reusable workflows that help coding agents plan, build, test, review, and release software — for Claude Code, Codex, and OpenCode",
5
5
  "keywords": ["skills", "agent-skills", "claude", "claude-code", "codex", "opencode", "agent", "ai", "ai-agents", "cli", "anthropic", "developer-tools"],
6
6
  "type": "module",