magus-cli 7.5.3 → 7.6.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 (68) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/blackbox-convergence.test.ts +1 -2
  3. package/src/__tests__/blackbox-parity.test.ts +16 -14
  4. package/src/__tests__/blackbox-plugin-state.test.ts +13 -15
  5. package/src/__tests__/cli-apply-seams.test.ts +3 -3
  6. package/src/__tests__/cli-profile-prompts.test.ts +1165 -0
  7. package/src/__tests__/content-drift.test.ts +0 -1
  8. package/src/__tests__/files-missing-scope.test.ts +235 -0
  9. package/src/__tests__/{enabled-not-installed.test.ts → files-missing.test.ts} +32 -26
  10. package/src/__tests__/format-update-outcome.test.ts +367 -0
  11. package/src/__tests__/installed-version-scope.test.tsx +425 -0
  12. package/src/__tests__/key-focus-guard.test.tsx +260 -0
  13. package/src/__tests__/mate-availability.test.ts +11 -13
  14. package/src/__tests__/plugin-contents.test.ts +0 -1
  15. package/src/__tests__/plugin-detail-scopes.test.tsx +270 -0
  16. package/src/__tests__/plugin-row-text.test.ts +18 -21
  17. package/src/__tests__/profile-command.test.ts +2 -2
  18. package/src/__tests__/profile-migration-offer.test.ts +5 -4
  19. package/src/__tests__/profile-name-on-save.test.ts +443 -0
  20. package/src/__tests__/profile-skills.test.ts +3 -3
  21. package/src/__tests__/profile-startup-nested.test.ts +329 -0
  22. package/src/__tests__/profile-startup-old-format.test.ts +390 -0
  23. package/src/__tests__/profile-startup.test.ts +1 -1
  24. package/src/__tests__/profile-state.test.ts +27 -22
  25. package/src/__tests__/prompt-eof.test.ts +245 -0
  26. package/src/__tests__/scope-action.test.ts +35 -38
  27. package/src/__tests__/scope-row-lookup.test.ts +126 -0
  28. package/src/__tests__/select-restore.test.ts +209 -0
  29. package/src/__tests__/stale-load-repair.test.ts +1 -2
  30. package/src/__tests__/update-apply.test.ts +18 -2
  31. package/src/__tests__/update-null-readback.test.ts +300 -0
  32. package/src/__tests__/update-plan-parity.test.ts +249 -0
  33. package/src/__tests__/update-plan.test.ts +66 -67
  34. package/src/__tests__/version-unknown-state.test.ts +38 -39
  35. package/src/cli/bootstrap.ts +271 -66
  36. package/src/cli/doctor.ts +4 -3
  37. package/src/cli/install.ts +83 -27
  38. package/src/cli/prompt.ts +19 -5
  39. package/src/cli/router.ts +1 -0
  40. package/src/cli/select.ts +168 -0
  41. package/src/cli/update-view.ts +120 -6
  42. package/src/cli/update.ts +6 -4
  43. package/src/services/claude-settings.ts +28 -0
  44. package/src/services/manifest.ts +31 -0
  45. package/src/services/mate-availability.ts +23 -29
  46. package/src/services/plugin-manager.ts +247 -105
  47. package/src/services/profile-absorb.ts +10 -7
  48. package/src/services/profile-activate.ts +41 -19
  49. package/src/services/profile-adopt.ts +9 -2
  50. package/src/services/profile-startup.ts +206 -5
  51. package/src/services/profile-state.ts +8 -7
  52. package/src/services/tracked-live-files.ts +24 -3
  53. package/src/services/update-engine.ts +28 -10
  54. package/src/services/update-plan.ts +78 -94
  55. package/src/ui/App.tsx +96 -56
  56. package/src/ui/adapters/pluginsAdapter.ts +1 -1
  57. package/src/ui/components/modals/ModalContainer.tsx +3 -0
  58. package/src/ui/components/primitives/ScopeSquares.tsx +10 -5
  59. package/src/ui/hooks/useGitignoreModal.ts +21 -3
  60. package/src/ui/hooks/useProfileStartupModal.ts +214 -15
  61. package/src/ui/hooks/useStaleLoadModal.ts +5 -1
  62. package/src/ui/input/KeyRouter.tsx +43 -2
  63. package/src/ui/keymap.ts +1 -1
  64. package/src/ui/renderers/pluginRenderers.tsx +39 -45
  65. package/src/ui/renderers/pluginRowText.ts +6 -2
  66. package/src/ui/renderers/pluginStatusText.ts +4 -3
  67. package/src/ui/screens/PluginsScreen.tsx +28 -41
  68. package/src/cli/tui-prompt.tsx +0 -143
@@ -36,7 +36,6 @@ function info(overrides: Partial<PluginInfo> & { id: string }): PluginInfo {
36
36
  description: "",
37
37
  marketplace: overrides.id.split("@")[1] ?? "magus",
38
38
  marketplaceDisplay: "",
39
- enabled: true,
40
39
  ...overrides,
41
40
  } as PluginInfo;
42
41
  }
@@ -111,7 +110,7 @@ describe("planPluginUpdates — advancing a 'latest' pin", () => {
111
110
  info({
112
111
  id: "dev@magus",
113
112
  version: "4.7.0",
114
- projectScope: { enabled: true, version: "4.6.1" },
113
+ projectScope: { installed: true, version: "4.6.1" },
115
114
  }),
116
115
  ),
117
116
  );
@@ -131,7 +130,7 @@ describe("planPluginUpdates — advancing a 'latest' pin", () => {
131
130
  info({
132
131
  id: "dev@magus",
133
132
  version: "4.7.0",
134
- projectScope: { enabled: true, version: "4.7.0" },
133
+ projectScope: { installed: true, version: "4.7.0" },
135
134
  }),
136
135
  ),
137
136
  );
@@ -146,7 +145,7 @@ describe("planPluginUpdates — advancing a 'latest' pin", () => {
146
145
  info({
147
146
  id: "dev@magus",
148
147
  version: "4.5.0",
149
- projectScope: { enabled: true, version: "4.7.0" },
148
+ projectScope: { installed: true, version: "4.7.0" },
150
149
  }),
151
150
  ),
152
151
  );
@@ -165,7 +164,7 @@ describe("planPluginUpdates — a failed check is never 'up to date'", () => {
165
164
  // The stale local clone reports the same version we have installed:
166
165
  // without the flag this is indistinguishable from a clean check.
167
166
  version: "4.6.1",
168
- projectScope: { enabled: true, version: "4.6.1" },
167
+ projectScope: { installed: true, version: "4.6.1" },
169
168
  updateCheckFailed: true,
170
169
  updateCheckFailure: {
171
170
  marketplace: "magus",
@@ -192,7 +191,7 @@ describe("planPluginUpdates — a failed check is never 'up to date'", () => {
192
191
  info({
193
192
  id: "dev@magus",
194
193
  version: "4.6.1",
195
- projectScope: { enabled: true, version: "4.6.1" },
194
+ projectScope: { installed: true, version: "4.6.1" },
196
195
  updateCheckFailed: true,
197
196
  }),
198
197
  ),
@@ -212,8 +211,8 @@ describe("planPluginUpdates — content drift", () => {
212
211
  id: "dev@magus",
213
212
  version: "4.6.1",
214
213
  contentStale: true,
215
- userScope: { enabled: true, version: "4.6.1" },
216
- projectScope: { enabled: true, version: "4.6.1" },
214
+ userScope: { installed: true, version: "4.6.1" },
215
+ projectScope: { installed: true, version: "4.6.1" },
217
216
  }),
218
217
  ),
219
218
  );
@@ -236,7 +235,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
236
235
  info({
237
236
  id: "dev@magus",
238
237
  version: "9.9.9",
239
- projectScope: { enabled: true, version: "4.0.0" },
238
+ projectScope: { installed: true, version: "4.0.0" },
240
239
  }),
241
240
  ),
242
241
  );
@@ -251,7 +250,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
251
250
  info({
252
251
  id: "dev@magus",
253
252
  version: "9.9.9",
254
- projectScope: { enabled: true, version: "9.9.9" },
253
+ projectScope: { installed: true, version: "9.9.9" },
255
254
  }),
256
255
  ),
257
256
  );
@@ -271,7 +270,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
271
270
  info({
272
271
  id: "dev@magus",
273
272
  version: "9.9.9",
274
- projectScope: { enabled: true, version: "9.9.9" },
273
+ projectScope: { installed: true, version: "9.9.9" },
275
274
  }),
276
275
  ),
277
276
  );
@@ -288,7 +287,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
288
287
  info({
289
288
  id: "dev@magus",
290
289
  version: "9.9.9",
291
- projectScope: { enabled: true, version: "9.9.9" },
290
+ projectScope: { installed: true, version: "9.9.9" },
292
291
  }),
293
292
  ),
294
293
  ),
@@ -303,7 +302,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
303
302
  info({
304
303
  id: "dev@magus",
305
304
  version: "4.6.1",
306
- projectScope: { enabled: true, version: "4.0.0" },
305
+ projectScope: { installed: true, version: "4.0.0" },
307
306
  }),
308
307
  ),
309
308
  );
@@ -319,7 +318,7 @@ describe("planPluginUpdates — an exact pin plans against what is deliverable",
319
318
  info({
320
319
  id: "dev@magus",
321
320
  version: null,
322
- projectScope: { enabled: true, version: "4.0.0" },
321
+ projectScope: { installed: true, version: "4.0.0" },
323
322
  }),
324
323
  ),
325
324
  );
@@ -339,8 +338,8 @@ describe("per-scope decisions — a stale scope is never masked", () => {
339
338
  info({
340
339
  id: "dev@magus",
341
340
  version: "4.7.0",
342
- userScope: { enabled: true, version: "4.7.0" },
343
- projectScope: { enabled: true, version: "4.6.1" },
341
+ userScope: { installed: true, version: "4.7.0" },
342
+ projectScope: { installed: true, version: "4.6.1" },
344
343
  }),
345
344
  ),
346
345
  );
@@ -360,8 +359,8 @@ describe("per-scope decisions — a stale scope is never masked", () => {
360
359
  info({
361
360
  id: "dev@magus",
362
361
  version: "4.6.1",
363
- userScope: { enabled: true, version: "4.6.1" },
364
- projectScope: { enabled: true, version: "4.0.0" },
362
+ userScope: { installed: true, version: "4.6.1" },
363
+ projectScope: { installed: true, version: "4.0.0" },
365
364
  }),
366
365
  ),
367
366
  );
@@ -377,8 +376,8 @@ describe("per-scope decisions — a stale scope is never masked", () => {
377
376
  info({
378
377
  id: "dev@magus",
379
378
  version: "9.9.9",
380
- userScope: { enabled: true, version: "4.6.1" },
381
- projectScope: { enabled: true, version: "4.0.0" },
379
+ userScope: { installed: true, version: "4.6.1" },
380
+ projectScope: { installed: true, version: "4.0.0" },
382
381
  }),
383
382
  ),
384
383
  );
@@ -394,8 +393,8 @@ describe("per-scope decisions — a stale scope is never masked", () => {
394
393
  info({
395
394
  id: "dev@magus",
396
395
  version: "4.7.0",
397
- userScope: { enabled: true, version: "4.7.0" },
398
- projectScope: { enabled: true, version: "4.7.0" },
396
+ userScope: { installed: true, version: "4.7.0" },
397
+ projectScope: { installed: true, version: "4.7.0" },
399
398
  }),
400
399
  ),
401
400
  );
@@ -422,7 +421,7 @@ describe("per-scope decisions — a stale scope is never masked", () => {
422
421
  info({
423
422
  id: "dev@magus",
424
423
  version: null,
425
- projectScope: { enabled: true, version: "4.6.1" },
424
+ projectScope: { installed: true, version: "4.6.1" },
426
425
  }),
427
426
  ),
428
427
  );
@@ -529,7 +528,7 @@ describe("planIsBehind — the --check gate", () => {
529
528
  info({
530
529
  id: "dev@magus",
531
530
  version: "4.6.1",
532
- projectScope: { enabled: true, version: "4.6.1" },
531
+ projectScope: { installed: true, version: "4.6.1" },
533
532
  updateCheckFailed: true,
534
533
  }),
535
534
  ),
@@ -549,7 +548,7 @@ describe("planIsBehind — the --check gate", () => {
549
548
  info({
550
549
  id: "code-review@claude-plugins-official",
551
550
  version: null,
552
- projectScope: { enabled: true, version: "3ea32df27be7" },
551
+ projectScope: { installed: true, version: "3ea32df27be7" },
553
552
  }),
554
553
  ),
555
554
  ),
@@ -566,7 +565,7 @@ describe("planIsBehind — the --check gate", () => {
566
565
  info({
567
566
  id: "dev@magus",
568
567
  version: "4.7.0",
569
- projectScope: { enabled: true, version: "4.6.1" },
568
+ projectScope: { installed: true, version: "4.6.1" },
570
569
  }),
571
570
  ),
572
571
  ),
@@ -590,7 +589,7 @@ describe("planIsBehind — the --check gate", () => {
590
589
  info({
591
590
  id: "dev@magus",
592
591
  version: "4.6.1",
593
- projectScope: { enabled: true, version: "4.6.1" },
592
+ projectScope: { installed: true, version: "4.6.1" },
594
593
  contentStale: true,
595
594
  }),
596
595
  ),
@@ -608,7 +607,7 @@ describe("planIsBehind — the --check gate", () => {
608
607
  info({
609
608
  id: "dev@magus",
610
609
  version: "4.7.0",
611
- projectScope: { enabled: true, version: "4.7.0" },
610
+ projectScope: { installed: true, version: "4.7.0" },
612
611
  }),
613
612
  ),
614
613
  ),
@@ -635,7 +634,7 @@ describe("nothing ever plans a downgrade", () => {
635
634
  info({
636
635
  id: "dev@magus",
637
636
  version: "9.9.9",
638
- projectScope: { enabled: true, version: "10.0.0" },
637
+ projectScope: { installed: true, version: "10.0.0" },
639
638
  }),
640
639
  ),
641
640
  );
@@ -653,7 +652,7 @@ describe("nothing ever plans a downgrade", () => {
653
652
  info({
654
653
  id: "dev@magus",
655
654
  version: null,
656
- projectScope: { enabled: true, version: "5.0.0" },
655
+ projectScope: { installed: true, version: "5.0.0" },
657
656
  }),
658
657
  ),
659
658
  );
@@ -669,8 +668,8 @@ describe("nothing ever plans a downgrade", () => {
669
668
  info({
670
669
  id: "dev@magus",
671
670
  version: "9.9.9",
672
- userScope: { enabled: true, version: "10.0.0" },
673
- projectScope: { enabled: true, version: "4.0.0" },
671
+ userScope: { installed: true, version: "10.0.0" },
672
+ projectScope: { installed: true, version: "4.0.0" },
674
673
  }),
675
674
  ),
676
675
  );
@@ -681,9 +680,9 @@ describe("nothing ever plans a downgrade", () => {
681
680
  test("scopesNeeding never returns a scope ahead of the target", () => {
682
681
  const i = info({
683
682
  id: "dev@magus",
684
- userScope: { enabled: true, version: "10.0.0" },
685
- projectScope: { enabled: true, version: "9.9.9" },
686
- localScope: { enabled: true, version: "1.0.0" },
683
+ userScope: { installed: true, version: "10.0.0" },
684
+ projectScope: { installed: true, version: "9.9.9" },
685
+ localScope: { installed: true, version: "1.0.0" },
687
686
  });
688
687
  expect(scopesNeeding(i, "9.9.9")).toEqual(["local"]);
689
688
  });
@@ -695,9 +694,9 @@ describe("scopesNeeding", () => {
695
694
  // reappears on every run.
696
695
  const i = info({
697
696
  id: "dev@magus",
698
- userScope: { enabled: true, version: "4.6.1" },
699
- projectScope: { enabled: true, version: "4.6.1" },
700
- localScope: { enabled: true, version: "4.7.0" },
697
+ userScope: { installed: true, version: "4.6.1" },
698
+ projectScope: { installed: true, version: "4.6.1" },
699
+ localScope: { installed: true, version: "4.7.0" },
701
700
  });
702
701
  expect(scopesNeeding(i, "4.7.0")).toEqual(["user", "project"]);
703
702
  });
@@ -705,7 +704,7 @@ describe("scopesNeeding", () => {
705
704
  test("a scope already on the target is left alone", () => {
706
705
  const i = info({
707
706
  id: "dev@magus",
708
- projectScope: { enabled: true, version: "4.7.0" },
707
+ projectScope: { installed: true, version: "4.7.0" },
709
708
  });
710
709
  expect(scopesNeeding(i, "4.7.0")).toEqual([]);
711
710
  });
@@ -897,7 +896,7 @@ describe("planHasWork / summarizePlan", () => {
897
896
  info({
898
897
  id: "dev@magus",
899
898
  version: "4.7.0",
900
- projectScope: { enabled: true, version: "4.7.0" },
899
+ projectScope: { installed: true, version: "4.7.0" },
901
900
  }),
902
901
  ),
903
902
  ),
@@ -915,7 +914,7 @@ describe("planHasWork / summarizePlan", () => {
915
914
  info({
916
915
  id: "b@magus",
917
916
  version: "2.0.0",
918
- projectScope: { enabled: true, version: "1.0.0" },
917
+ projectScope: { installed: true, version: "1.0.0" },
919
918
  }),
920
919
  ),
921
920
  ),
@@ -977,7 +976,7 @@ function orphan(
977
976
  version: installedVersion,
978
977
  installedVersion,
979
978
  isOrphaned: true,
980
- projectScope: { enabled: true, version: installedVersion },
979
+ projectScope: { installed: true, version: installedVersion },
981
980
  ...over,
982
981
  });
983
982
  }
@@ -1092,7 +1091,7 @@ describe("classifyPlugin — an unorderable version is assumed current", () => {
1092
1091
  info({
1093
1092
  id: "playground@official",
1094
1093
  version: "7.3.0",
1095
- projectScope: { enabled: true, version: "unknown" },
1094
+ projectScope: { installed: true, version: "unknown" },
1096
1095
  }),
1097
1096
  ),
1098
1097
  );
@@ -1109,7 +1108,7 @@ describe("classifyPlugin — an unorderable version is assumed current", () => {
1109
1108
  info({
1110
1109
  id: "playground@official",
1111
1110
  version: null,
1112
- projectScope: { enabled: true, version: "3ea32df27be7" },
1111
+ projectScope: { installed: true, version: "3ea32df27be7" },
1113
1112
  }),
1114
1113
  ),
1115
1114
  );
@@ -1130,8 +1129,8 @@ describe("classifyPlugin — an unorderable version is assumed current", () => {
1130
1129
  info({
1131
1130
  id: "dev@magus",
1132
1131
  version: "4.7.0",
1133
- userScope: { enabled: true, version: "unknown" },
1134
- projectScope: { enabled: true, version: "4.6.1" },
1132
+ userScope: { installed: true, version: "unknown" },
1133
+ projectScope: { installed: true, version: "4.6.1" },
1135
1134
  }),
1136
1135
  ),
1137
1136
  );
@@ -1140,21 +1139,21 @@ describe("classifyPlugin — an unorderable version is assumed current", () => {
1140
1139
  });
1141
1140
 
1142
1141
  describe("classifyPlugin — install reasons", () => {
1143
- test("enabled in settings with no files anywhere says so", () => {
1142
+ test("installed with no files anywhere says files missing", () => {
1144
1143
  const [item] = planPluginUpdates(
1145
1144
  { "dev@magus": "latest" },
1146
1145
  catalog(
1147
1146
  info({
1148
1147
  id: "dev@magus",
1149
1148
  version: "4.7.0",
1150
- projectScope: { enabled: true },
1149
+ projectScope: { installed: true },
1151
1150
  }),
1152
1151
  ),
1153
1152
  );
1154
1153
  expect(item).toMatchObject({
1155
1154
  action: "install",
1156
- reason: "enabled-not-installed",
1157
- note: "enabled in settings, but no files on disk",
1155
+ reason: "files-missing",
1156
+ note: "files missing",
1158
1157
  });
1159
1158
  });
1160
1159
 
@@ -1176,9 +1175,9 @@ describe("classifyPlugin — beforeVersions records every installed scope", () =
1176
1175
  info({
1177
1176
  id: "dev@magus",
1178
1177
  version: "4.7.0",
1179
- userScope: { enabled: true, version: "4.6.0" },
1180
- projectScope: { enabled: true, version: "4.6.1" },
1181
- localScope: { enabled: true },
1178
+ userScope: { installed: true, version: "4.6.0" },
1179
+ projectScope: { installed: true, version: "4.6.1" },
1180
+ localScope: { installed: true },
1182
1181
  }),
1183
1182
  ),
1184
1183
  );
@@ -1242,7 +1241,7 @@ describe("classifyPlugin and planPluginUpdates are the same function", () => {
1242
1241
  info({
1243
1242
  id: "b@magus",
1244
1243
  version: "2.0.0",
1245
- projectScope: { enabled: true, version: "1.0.0" },
1244
+ projectScope: { installed: true, version: "1.0.0" },
1246
1245
  }),
1247
1246
  info({ id: "a@magus", version: "1.0.0" }),
1248
1247
  );
@@ -1259,7 +1258,7 @@ describe("pinForRow — which pin a TUI row is judged against", () => {
1259
1258
  test("the profile's pin wins, exact or latest", () => {
1260
1259
  const i = info({
1261
1260
  id: "dev@magus",
1262
- projectScope: { enabled: true, version: "1.0.0" },
1261
+ projectScope: { installed: true, version: "1.0.0" },
1263
1262
  });
1264
1263
  expect(pinForRow(i, { "dev@magus": "4.6.1" })).toBe("4.6.1");
1265
1264
  expect(pinForRow(i, { "dev@magus": "latest" })).toBe("latest");
@@ -1275,19 +1274,19 @@ describe("pinForRow — which pin a TUI row is judged against", () => {
1275
1274
  for (const scope of ["userScope", "projectScope", "localScope"] as const) {
1276
1275
  const i = info({
1277
1276
  id: "dev@magus",
1278
- [scope]: { enabled: false, version: "1.0.0" },
1277
+ [scope]: { installed: true, version: "1.0.0" },
1279
1278
  });
1280
1279
  expect(pinForRow(i, {})).toBe("latest");
1281
1280
  }
1282
1281
  });
1283
1282
 
1284
- test("undeclared, enabled but not installed is judged against latest", () => {
1283
+ test("undeclared, files missing is judged against latest", () => {
1285
1284
  expect(
1286
- pinForRow(info({ id: "dev@magus", localScope: { enabled: true } }), {}),
1285
+ pinForRow(info({ id: "dev@magus", localScope: { installed: true } }), {}),
1287
1286
  ).toBe("latest");
1288
1287
  });
1289
1288
 
1290
- test("undeclared and neither installed nor enabled gets NO status", () => {
1289
+ test("undeclared and not installed gets NO status", () => {
1291
1290
  // A catalog entry the user is only browsing. `install` would claim the
1292
1291
  // profile wants it, which nothing says.
1293
1292
  expect(
@@ -1309,7 +1308,7 @@ describe("classifyPluginRows", () => {
1309
1308
  info({
1310
1309
  id: "installed@magus",
1311
1310
  version: "2.0.0",
1312
- projectScope: { enabled: true, version: "1.0.0" },
1311
+ projectScope: { installed: true, version: "1.0.0" },
1313
1312
  }),
1314
1313
  info({ id: "browse@magus", version: "3.0.0" }),
1315
1314
  ];
@@ -1334,7 +1333,7 @@ describe("classifyPluginRows", () => {
1334
1333
  info({
1335
1334
  id: "dev@magus",
1336
1335
  version: "4.7.0",
1337
- projectScope: { enabled: true, version: "4.6.1" },
1336
+ projectScope: { installed: true, version: "4.6.1" },
1338
1337
  }),
1339
1338
  info({ id: "terminal@magus", version: "5.0.1" }),
1340
1339
  ];
@@ -1413,7 +1412,7 @@ describe("classifyPlugin — an older version LOADS than the one installed", ()
1413
1412
  version: "0.5.0",
1414
1413
  installedVersion: "0.5.0",
1415
1414
  loadedVersion: "0.3.0",
1416
- projectScope: { enabled: true, version: "0.5.0" },
1415
+ projectScope: { installed: true, version: "0.5.0" },
1417
1416
  ...over,
1418
1417
  });
1419
1418
 
@@ -1463,7 +1462,7 @@ describe("classifyPlugin — an older version LOADS than the one installed", ()
1463
1462
  catalog(
1464
1463
  madbench({
1465
1464
  installedVersion: "0.4.0",
1466
- projectScope: { enabled: true, version: "0.4.0" },
1465
+ projectScope: { installed: true, version: "0.4.0" },
1467
1466
  }),
1468
1467
  ),
1469
1468
  );
@@ -1502,7 +1501,7 @@ describe("classifyPlugin — an older version LOADS than the one installed", ()
1502
1501
  catalog(
1503
1502
  madbench({
1504
1503
  installedVersion: "0.4.0",
1505
- projectScope: { enabled: true, version: "0.4.0" },
1504
+ projectScope: { installed: true, version: "0.4.0" },
1506
1505
  }),
1507
1506
  ),
1508
1507
  );
@@ -1631,9 +1630,9 @@ describe("classifyPlugin — an older version LOADS than the one installed", ()
1631
1630
  version: "0.3.0",
1632
1631
  installedVersion: "0.3.0",
1633
1632
  loadedVersion: "0.3.0",
1634
- projectScope: { enabled: true, version: "0.3.0" },
1633
+ projectScope: { installed: true, version: "0.3.0" },
1635
1634
  // Higher, in a scope this project did not ask for and cannot downgrade.
1636
- userScope: { enabled: true, version: "0.5.0" },
1635
+ userScope: { installed: true, version: "0.5.0" },
1637
1636
  ...over,
1638
1637
  });
1639
1638
 
@@ -1721,7 +1720,7 @@ describe("classifyPlugin — an older version LOADS than the one installed", ()
1721
1720
  version: "7.4.0",
1722
1721
  installedVersion: "7.4.0",
1723
1722
  loadedVersion: "7.4.0",
1724
- projectScope: { enabled: true, version: "7.4.0" },
1723
+ projectScope: { installed: true, version: "7.4.0" },
1725
1724
  }),
1726
1725
  ),
1727
1726
  ),
@@ -106,45 +106,46 @@ describe("isBehind — the question callers actually ask", () => {
106
106
  });
107
107
  });
108
108
 
109
- describe("buildScopeStatus — installation is a registry fact, not a settings key", () => {
110
- test("an installed but NOT enabled plugin still reports its scope", () => {
111
- // The bug: scope was `enabledPlugins[id] !== undefined ? {...} : undefined`,
112
- // so a plugin with a real registry row but no settings key rendered
113
- // identically to one that was never installed.
109
+ describe("buildScopeStatus — installed is the settings flag", () => {
110
+ test("a registry row with no settings key reports its scope, not installed", () => {
111
+ // A registry row alone never makes Claude Code load a plugin — it can be
112
+ // inherited from another checkout of the same repository.
114
113
  const scope = buildScopeStatus({}, { "dev@magus": "7.2.0" }, "dev@magus");
115
114
  expect(scope).toBeDefined();
116
115
  expect(scope?.version).toBe("7.2.0");
117
- expect(scope?.enabled).toBe(false);
118
- expect(isInstalledInScope(scope)).toBe(true);
116
+ expect(scope?.installed).toBe(false);
117
+ expect(isInstalledInScope(scope)).toBe(false);
119
118
  });
120
119
 
121
- test("enabled with no install is still reported, as a broken state", () => {
120
+ test("installed with no version is still reported — files missing", () => {
122
121
  const scope = buildScopeStatus({ "dev@magus": true }, {}, "dev@magus");
123
122
  expect(scope).toBeDefined();
124
- expect(scope?.enabled).toBe(true);
123
+ expect(scope?.installed).toBe(true);
125
124
  expect(scope?.version).toBeUndefined();
126
- expect(isInstalledInScope(scope)).toBe(false);
125
+ expect(isInstalledInScope(scope)).toBe(true);
127
126
  });
128
127
 
129
- test("neither enabled nor installed has no scope at all", () => {
128
+ test("neither a settings key nor a registry row has no scope at all", () => {
130
129
  expect(buildScopeStatus({}, {}, "dev@magus")).toBeUndefined();
131
130
  });
132
131
  });
133
132
 
134
- describe("isInstalledInScope — presence of files, not permission to load", () => {
135
- test("installed and disabled is still installed", () => {
136
- // The old rule was `!!scope?.enabled && !!scope.version`, which made
137
- // disabling a plugin look like uninstalling it.
138
- expect(isInstalledInScope({ enabled: false, version: "7.2.0" })).toBe(true);
133
+ describe("isInstalledInScope — the settings flag, not a registry row", () => {
134
+ test("a registry version with the flag off is not installed", () => {
135
+ expect(isInstalledInScope({ installed: false, version: "7.2.0" })).toBe(
136
+ false,
137
+ );
139
138
  });
140
139
 
141
140
  test("the 0.0.0 sentinel counts as installed", () => {
142
- expect(isInstalledInScope({ enabled: true, version: "0.0.0" })).toBe(true);
141
+ expect(isInstalledInScope({ installed: true, version: "0.0.0" })).toBe(
142
+ true,
143
+ );
143
144
  });
144
145
 
145
- test("enabled with no version is not installed", () => {
146
- expect(isInstalledInScope({ enabled: true, version: undefined })).toBe(
147
- false,
146
+ test("installed with no version is installed — files missing", () => {
147
+ expect(isInstalledInScope({ installed: true, version: undefined })).toBe(
148
+ true,
148
149
  );
149
150
  });
150
151
  });
@@ -157,7 +158,6 @@ const info = (over: Partial<PluginInfo>): PluginInfo =>
157
158
  description: "",
158
159
  marketplace: "magus",
159
160
  marketplaceDisplay: "Magus",
160
- enabled: true,
161
161
  ...over,
162
162
  }) as PluginInfo;
163
163
 
@@ -265,7 +265,7 @@ describe("REVIEW-B regressions — found by the patch-quality panel", () => {
265
265
  info({
266
266
  id: "playground@official",
267
267
  version: "7.3.0",
268
- projectScope: { enabled: true, version: "unknown" },
268
+ projectScope: { installed: true, version: "unknown" },
269
269
  }),
270
270
  ],
271
271
  ]),
@@ -286,7 +286,7 @@ describe("REVIEW-B regressions — found by the patch-quality panel", () => {
286
286
  info({
287
287
  id: "dev@magus",
288
288
  version: "7.3.0",
289
- projectScope: { enabled: true, version: "7.3.0" },
289
+ projectScope: { installed: true, version: "7.3.0" },
290
290
  }),
291
291
  ],
292
292
  ]),
@@ -297,26 +297,25 @@ describe("REVIEW-B regressions — found by the patch-quality panel", () => {
297
297
  });
298
298
 
299
299
  describe("resolveScopeAction — the scope key must never silently uninstall", () => {
300
- test("installed but DISABLED resolves to install, not uninstall", () => {
300
+ test("a registry row that is not installed resolves to install, not uninstall", () => {
301
301
  // The hazard: the scope key runs cliUninstallPlugin with no confirmation.
302
- // Relaxing isInstalledInScope made this combination fall through to
303
- // "uninstall", so pressing the key on a plugin the user had merely
304
- // switched off would delete it.
302
+ // A registry row with the settings flag off is not installed here, so the
303
+ // key must add it, never delete it.
305
304
  expect(
306
- resolveScopeAction({ enabled: false, version: "3.0.2" }, false),
305
+ resolveScopeAction({ installed: false, version: "3.0.2" }, false),
307
306
  ).toBe("install");
308
307
  });
309
308
 
310
- test("installed AND enabled at the latest version still offers uninstall", () => {
311
- expect(resolveScopeAction({ enabled: true, version: "3.0.2" }, false)).toBe(
312
- "uninstall",
313
- );
309
+ test("installed at the latest version still offers uninstall", () => {
310
+ expect(
311
+ resolveScopeAction({ installed: true, version: "3.0.2" }, false),
312
+ ).toBe("uninstall");
314
313
  });
315
314
 
316
- test("installed, enabled, and behind still offers update", () => {
317
- expect(resolveScopeAction({ enabled: true, version: "3.0.1" }, true)).toBe(
318
- "update",
319
- );
315
+ test("installed and behind still offers update", () => {
316
+ expect(
317
+ resolveScopeAction({ installed: true, version: "3.0.1" }, true),
318
+ ).toBe("update");
320
319
  });
321
320
 
322
321
  test("not installed at all resolves to install", () => {
@@ -338,7 +337,7 @@ describe("installed with no comparable catalog version — two different causes"
338
337
  info({
339
338
  id: "feature-dev@official",
340
339
  version: null,
341
- projectScope: { enabled: true, version: "3ea32df27be7" },
340
+ projectScope: { installed: true, version: "3ea32df27be7" },
342
341
  }),
343
342
  ],
344
343
  ]),
@@ -362,7 +361,7 @@ describe("installed with no comparable catalog version — two different causes"
362
361
  info({
363
362
  id: "other@magus",
364
363
  version: "1.0.0",
365
- projectScope: { enabled: true, version: "1.0.0" },
364
+ projectScope: { installed: true, version: "1.0.0" },
366
365
  }),
367
366
  ],
368
367
  ]),
@@ -392,7 +391,7 @@ describe('the "0.0.0" registry value keeps its reason', () => {
392
391
  info({
393
392
  id: "explanatory-output-style@official",
394
393
  version: null,
395
- projectScope: { enabled: true, version: "0.0.0" },
394
+ projectScope: { installed: true, version: "0.0.0" },
396
395
  }),
397
396
  ],
398
397
  ]),