magus-cli 7.7.0 → 7.9.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 (140) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/src/__tests__/blackbox-parity.test.ts +45 -26
  4. package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
  5. package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
  6. package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
  7. package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
  8. package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
  9. package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
  10. package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
  11. package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
  12. package/src/__tests__/blind-780-deprecated.test.ts +631 -0
  13. package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
  14. package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
  15. package/src/__tests__/blind-780-review2.test.ts +1178 -0
  16. package/src/__tests__/cli-apply-seams.test.ts +2 -67
  17. package/src/__tests__/cli-tool-commands.test.ts +131 -6
  18. package/src/__tests__/cli-tools-adapter.test.ts +52 -2
  19. package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
  20. package/src/__tests__/cli-update-view.test.ts +3 -3
  21. package/src/__tests__/deprecated-remove.test.ts +827 -0
  22. package/src/__tests__/deps-adapter.test.ts +191 -0
  23. package/src/__tests__/deps-check-hook.test.ts +220 -0
  24. package/src/__tests__/deps-plan.test.ts +896 -0
  25. package/src/__tests__/deps-report.test.ts +197 -0
  26. package/src/__tests__/doctor-bins.test.ts +134 -36
  27. package/src/__tests__/doctor-json.test.ts +261 -0
  28. package/src/__tests__/doctor.test.ts +159 -1
  29. package/src/__tests__/failure-popup-review2.test.tsx +896 -0
  30. package/src/__tests__/failure-popup.test.tsx +419 -0
  31. package/src/__tests__/final-fixes-780.test.tsx +740 -0
  32. package/src/__tests__/fixtures/bins.ts +55 -0
  33. package/src/__tests__/format-update-outcome.test.ts +6 -32
  34. package/src/__tests__/gitignore-profile.test.ts +1 -9
  35. package/src/__tests__/install-plan.test.ts +15 -1
  36. package/src/__tests__/marketplace-refresh.test.ts +9 -3
  37. package/src/__tests__/models-screen-state.test.ts +18 -40
  38. package/src/__tests__/no-shell-spawn.test.ts +77 -0
  39. package/src/__tests__/npm-package-name.test.ts +6 -9
  40. package/src/__tests__/plugin-requires.test.ts +138 -153
  41. package/src/__tests__/plugin-resolution.test.ts +52 -13
  42. package/src/__tests__/plugin-row-text.test.ts +30 -37
  43. package/src/__tests__/profile-dependencies.test.ts +7 -0
  44. package/src/__tests__/profile-startup-old-format.test.ts +0 -3
  45. package/src/__tests__/profile-startup.test.ts +40 -12
  46. package/src/__tests__/requires-probe.test.ts +858 -0
  47. package/src/__tests__/resolver.test.ts +129 -52
  48. package/src/__tests__/run.test.ts +108 -0
  49. package/src/__tests__/stale-load-repair.test.ts +31 -23
  50. package/src/__tests__/tool-probe.test.ts +1 -1
  51. package/src/__tests__/toolchain.test.ts +123 -52
  52. package/src/__tests__/update-apply.test.ts +13 -29
  53. package/src/__tests__/update-plan.test.ts +24 -18
  54. package/src/__tests__/useStaleLoadModal.test.ts +1 -57
  55. package/src/__tests__/version-unknown-state.test.ts +15 -17
  56. package/src/cli/ansi.ts +2 -7
  57. package/src/cli/deps-view.ts +213 -0
  58. package/src/cli/doctor.ts +320 -54
  59. package/src/cli/install.ts +55 -71
  60. package/src/cli/profile.ts +20 -2
  61. package/src/cli/router.ts +6 -2
  62. package/src/cli/update-view.ts +58 -15
  63. package/src/cli/update.ts +55 -12
  64. package/src/data/cli-tools.ts +24 -9
  65. package/src/services/active-closure.ts +3 -3
  66. package/src/services/catalog-cache-store.ts +8 -6
  67. package/src/services/claude-cli.ts +106 -19
  68. package/src/services/claude-settings.ts +88 -384
  69. package/src/services/cli-tool-commands.ts +104 -3
  70. package/src/services/deps-plan.ts +962 -0
  71. package/src/services/deps-report.ts +265 -0
  72. package/src/services/doctor-bins.ts +115 -43
  73. package/src/services/install-plan.ts +11 -1
  74. package/src/services/local-marketplace.ts +12 -206
  75. package/src/services/marketplace-refresh.ts +4 -4
  76. package/src/services/plugin-env-needs.ts +69 -0
  77. package/src/services/plugin-manager.ts +35 -37
  78. package/src/services/plugin-requires.ts +93 -162
  79. package/src/services/plugin-resolution.ts +34 -42
  80. package/src/services/profile-activate.ts +1 -2
  81. package/src/services/profile-startup.ts +18 -43
  82. package/src/services/profile-state.ts +72 -2
  83. package/src/services/resolver.ts +263 -59
  84. package/src/services/tool-probe.ts +18 -25
  85. package/src/services/toolchain.ts +117 -111
  86. package/src/services/update-engine.ts +345 -45
  87. package/src/services/update-plan.ts +296 -36
  88. package/src/shared/plugin/hooks/bun-missing.sh +39 -0
  89. package/src/shared/plugin/hooks/deps-check.ts +224 -0
  90. package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
  91. package/src/types/index.ts +60 -16
  92. package/src/ui/App.tsx +81 -171
  93. package/src/ui/adapters/cliToolsAdapter.ts +252 -12
  94. package/src/ui/adapters/depsAdapter.ts +129 -0
  95. package/src/ui/adapters/failureReport.ts +137 -0
  96. package/src/ui/adapters/pluginsAdapter.ts +297 -45
  97. package/src/ui/adapters/startupReports.ts +260 -0
  98. package/src/ui/components/modals/FailureListModal.tsx +231 -0
  99. package/src/ui/components/modals/MessageModal.tsx +7 -12
  100. package/src/ui/components/modals/ModalContainer.tsx +10 -5
  101. package/src/ui/components/modals/index.ts +1 -0
  102. package/src/ui/hooks/startupQueue.ts +56 -0
  103. package/src/ui/hooks/useGitignoreModal.ts +30 -52
  104. package/src/ui/hooks/useProfileStartupModal.ts +119 -142
  105. package/src/ui/hooks/useStaleLoadModal.ts +35 -65
  106. package/src/ui/keymap.ts +14 -1
  107. package/src/ui/renderers/cliToolRenderers.tsx +135 -2
  108. package/src/ui/renderers/mcpRenderers.tsx +110 -3
  109. package/src/ui/renderers/pluginRenderers.tsx +77 -6
  110. package/src/ui/renderers/pluginRowText.ts +25 -25
  111. package/src/ui/renderers/pluginStatusText.ts +18 -1
  112. package/src/ui/screens/AliasScreen.tsx +38 -45
  113. package/src/ui/screens/CliToolsScreen.tsx +199 -98
  114. package/src/ui/screens/EnvVarsScreen.tsx +17 -5
  115. package/src/ui/screens/GitignoreScreen.tsx +48 -67
  116. package/src/ui/screens/McpRegistryScreen.tsx +67 -38
  117. package/src/ui/screens/McpScreen.tsx +46 -19
  118. package/src/ui/screens/ModelsScreen.tsx +46 -86
  119. package/src/ui/screens/PluginsScreen.tsx +315 -259
  120. package/src/ui/screens/ProfilesScreen.tsx +79 -40
  121. package/src/ui/screens/SkillsScreen.tsx +83 -80
  122. package/src/ui/screens/StylesScreen.tsx +211 -275
  123. package/src/ui/state/AppContext.tsx +66 -44
  124. package/src/ui/state/DimensionsContext.tsx +3 -22
  125. package/src/ui/state/reducer.ts +12 -100
  126. package/src/ui/state/types.ts +48 -69
  127. package/src/utils/command-utils.ts +4 -5
  128. package/src/utils/run.ts +123 -52
  129. package/src/utils/string-utils.ts +9 -0
  130. package/src/utils/user-path.ts +60 -0
  131. package/src/__tests__/go-module.test.ts +0 -43
  132. package/src/__tests__/notices-rules.test.ts +0 -476
  133. package/src/__tests__/notices.test.tsx +0 -604
  134. package/src/__tests__/plugin-setup.test.ts +0 -1016
  135. package/src/__tests__/shell-script-callers.test.ts +0 -92
  136. package/src/__tests__/styles-status-line.test.ts +0 -72
  137. package/src/__tests__/version-snapshot.test.ts +0 -212
  138. package/src/services/plugin-setup.ts +0 -965
  139. package/src/services/version-snapshot.ts +0 -189
  140. package/src/utils/go-module.ts +0 -36
@@ -15,7 +15,13 @@ import { existsSync } from "node:fs";
15
15
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
16
16
  import { tmpdir } from "node:os";
17
17
  import { join } from "node:path";
18
- import { doctorExitCode } from "../cli/doctor.js";
18
+ import {
19
+ doctorExitCode,
20
+ enabledHere,
21
+ findPluginLoadProblems,
22
+ isClaimedHere,
23
+ pluginLoadLines,
24
+ } from "../cli/doctor.js";
19
25
  import { checkProject, fixDoctorIssues } from "../services/doctor.js";
20
26
  import type { DoctorResult } from "../services/doctor.js";
21
27
 
@@ -492,3 +498,155 @@ describe("doctorExitCode", () => {
492
498
  expect(doctorExitCode({ fixable: 2, unfixable: 1, fix: true })).toBe(1);
493
499
  });
494
500
  });
501
+
502
+ /**
503
+ * M7: a plugin recorded as installed here that Claude Code loads NO version of
504
+ * in this folder. It was skipped by the stale-load comparison and then counted
505
+ * among the plugins that "load at their installed version".
506
+ */
507
+ describe("doctor plugin loads: a plugin that loads nothing", () => {
508
+ const HERE = "/nonexistent/magus-doctor-m7/project";
509
+ const row = (projectPath: string, version: string) => ({
510
+ scope: "project" as const,
511
+ projectPath,
512
+ version,
513
+ installPath: `/nonexistent/cache/${version}`,
514
+ installedAt: "2026-01-01T00:00:00.000Z",
515
+ lastUpdated: "2026-01-01T00:00:00.000Z",
516
+ });
517
+
518
+ it("is its own finding, with magus update as the fix, and never counted as healthy", () => {
519
+ const findings = findPluginLoadProblems(
520
+ { "dev@magus": "8.0.0", "kanban@magus": "1.6.2" },
521
+ {
522
+ "dev@magus": [row(HERE, "8.0.0")],
523
+ // Its only row belongs to another folder, so nothing loads here —
524
+ // and this folder turns it on, so it IS claimed here.
525
+ "kanban@magus": [row("/nonexistent/magus-doctor-m7/other", "1.6.2")],
526
+ },
527
+ HERE,
528
+ { "kanban@magus": true },
529
+ );
530
+ expect(findings).toEqual({ stale: [], loadsNothing: ["kanban@magus"] });
531
+ const lines = pluginLoadLines(findings, 2);
532
+ expect(lines).toContain(
533
+ " ✗ kanban@magus is installed, but Claude Code loads no version of it in this folder",
534
+ );
535
+ expect(lines).toContain(" Run `magus update` to fix it.");
536
+ expect(lines.some((l) => l.includes("All 2 installed plugins load"))).toBe(
537
+ false,
538
+ );
539
+ });
540
+
541
+ it("NEGATIVE CONTROL: when every plugin loads its version, all are healthy", () => {
542
+ const findings = findPluginLoadProblems(
543
+ { "dev@magus": "8.0.0" },
544
+ { "dev@magus": [row(HERE, "8.0.0")] },
545
+ HERE,
546
+ );
547
+ expect(findings).toEqual({ stale: [], loadsNothing: [] });
548
+ expect(pluginLoadLines(findings, 1)).toContain(
549
+ " ✓ All 1 installed plugins load at their installed version.",
550
+ );
551
+ });
552
+ });
553
+
554
+ /**
555
+ * M1: "claimed here" means claimed by something that APPLIES to this folder —
556
+ * a scope that turns it on here, or a registry row the loader would consider
557
+ * here. A row for an unrelated repository is neither, so a stale
558
+ * `installedPluginVersions` entry plus such a row is not "loads nothing".
559
+ *
560
+ * Real (temp) repositories, because relevance is decided by repository root and
561
+ * a path in no repository would make every foreign row irrelevant trivially.
562
+ */
563
+ describe("doctor plugin loads: claimed here, not anywhere", () => {
564
+ let base: string;
565
+ let here: string;
566
+ let sibling: string;
567
+ let unrelated: string;
568
+ const row = (
569
+ projectPath: string | undefined,
570
+ version: string,
571
+ scope: "project" | "local" | "user" = "project",
572
+ ) => ({
573
+ scope,
574
+ ...(projectPath ? { projectPath } : {}),
575
+ version,
576
+ installPath: `/nonexistent/cache/${version || "none"}`,
577
+ installedAt: "2026-01-01T00:00:00.000Z",
578
+ lastUpdated: "2026-01-01T00:00:00.000Z",
579
+ });
580
+
581
+ beforeEach(async () => {
582
+ base = await mkdtemp(join(tmpdir(), "magus-doctor-m1-"));
583
+ here = join(base, "repo-a");
584
+ sibling = join(here, "packages", "other");
585
+ unrelated = join(base, "repo-b");
586
+ await mkdir(join(here, ".git"), { recursive: true });
587
+ await mkdir(sibling, { recursive: true });
588
+ await mkdir(join(unrelated, ".git"), { recursive: true });
589
+ });
590
+
591
+ afterEach(async () => {
592
+ await rm(base, { recursive: true, force: true });
593
+ });
594
+
595
+ it("rows only for an unrelated repo plus a stale installedPluginVersions claim is NOT a finding", () => {
596
+ const findings = findPluginLoadProblems(
597
+ { "kanban@magus": "1.6.2" },
598
+ { "kanban@magus": [row(unrelated, "1.6.2"), row(unrelated, "1.5.0")] },
599
+ here,
600
+ {},
601
+ );
602
+ expect(findings).toEqual({ stale: [], loadsNothing: [] });
603
+ expect(
604
+ isClaimedHere("kanban@magus", [row(unrelated, "1.6.2")], here, {}),
605
+ ).toBe(false);
606
+ });
607
+
608
+ it("CONTROL: the same rows with the plugin turned on here IS a finding", () => {
609
+ const findings = findPluginLoadProblems(
610
+ { "kanban@magus": "1.6.2" },
611
+ { "kanban@magus": [row(unrelated, "1.6.2")] },
612
+ here,
613
+ { "kanban@magus": true },
614
+ );
615
+ expect(findings.loadsNothing).toEqual(["kanban@magus"]);
616
+ });
617
+
618
+ it("CONTROL: a relevant row that resolves no version IS a finding", () => {
619
+ // Same repository, other folder — the loader considers it here.
620
+ expect(
621
+ findPluginLoadProblems(
622
+ { "kanban@magus": "1.6.2" },
623
+ { "kanban@magus": [row(sibling, "")] },
624
+ here,
625
+ {},
626
+ ).loadsNothing,
627
+ ).toEqual(["kanban@magus"]);
628
+ // A machine-wide row applies to every folder.
629
+ expect(
630
+ findPluginLoadProblems(
631
+ { "kanban@magus": "1.6.2" },
632
+ { "kanban@magus": [row(undefined, "", "user")] },
633
+ here,
634
+ {},
635
+ ).loadsNothing,
636
+ ).toEqual(["kanban@magus"]);
637
+ });
638
+
639
+ it("enabledHere applies user, then project, then local — a later scope wins", () => {
640
+ const enabled = enabledHere(
641
+ { enabledPlugins: { "a@magus": true, "b@magus": true } },
642
+ { enabledPlugins: { "b@magus": false, "c@magus": true } },
643
+ { enabledPlugins: { "c@magus": false } },
644
+ );
645
+ expect(enabled).toEqual({
646
+ "a@magus": true,
647
+ "b@magus": false,
648
+ "c@magus": false,
649
+ });
650
+ expect(enabledHere({}, {}, {})).toEqual({});
651
+ });
652
+ });