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.
- package/README.md +1 -1
- package/package.json +4 -4
- package/src/__tests__/blackbox-parity.test.ts +45 -26
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
- package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
- package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
- package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
- package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
- package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
- package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1178 -0
- package/src/__tests__/cli-apply-seams.test.ts +2 -67
- package/src/__tests__/cli-tool-commands.test.ts +131 -6
- package/src/__tests__/cli-tools-adapter.test.ts +52 -2
- package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/deps-adapter.test.ts +191 -0
- package/src/__tests__/deps-check-hook.test.ts +220 -0
- package/src/__tests__/deps-plan.test.ts +896 -0
- package/src/__tests__/deps-report.test.ts +197 -0
- package/src/__tests__/doctor-bins.test.ts +134 -36
- package/src/__tests__/doctor-json.test.ts +261 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +896 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +740 -0
- package/src/__tests__/fixtures/bins.ts +55 -0
- package/src/__tests__/format-update-outcome.test.ts +6 -32
- package/src/__tests__/gitignore-profile.test.ts +1 -9
- package/src/__tests__/install-plan.test.ts +15 -1
- package/src/__tests__/marketplace-refresh.test.ts +9 -3
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/no-shell-spawn.test.ts +77 -0
- package/src/__tests__/npm-package-name.test.ts +6 -9
- package/src/__tests__/plugin-requires.test.ts +138 -153
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/requires-probe.test.ts +858 -0
- package/src/__tests__/resolver.test.ts +129 -52
- package/src/__tests__/run.test.ts +108 -0
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/tool-probe.test.ts +1 -1
- package/src/__tests__/toolchain.test.ts +123 -52
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +24 -18
- package/src/__tests__/useStaleLoadModal.test.ts +1 -57
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/ansi.ts +2 -7
- package/src/cli/deps-view.ts +213 -0
- package/src/cli/doctor.ts +320 -54
- package/src/cli/install.ts +55 -71
- package/src/cli/profile.ts +20 -2
- package/src/cli/router.ts +6 -2
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +55 -12
- package/src/data/cli-tools.ts +24 -9
- package/src/services/active-closure.ts +3 -3
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +88 -384
- package/src/services/cli-tool-commands.ts +104 -3
- package/src/services/deps-plan.ts +962 -0
- package/src/services/deps-report.ts +265 -0
- package/src/services/doctor-bins.ts +115 -43
- package/src/services/install-plan.ts +11 -1
- package/src/services/local-marketplace.ts +12 -206
- package/src/services/marketplace-refresh.ts +4 -4
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +35 -37
- package/src/services/plugin-requires.ts +93 -162
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-activate.ts +1 -2
- package/src/services/profile-startup.ts +18 -43
- package/src/services/profile-state.ts +72 -2
- package/src/services/resolver.ts +263 -59
- package/src/services/tool-probe.ts +18 -25
- package/src/services/toolchain.ts +117 -111
- package/src/services/update-engine.ts +345 -45
- package/src/services/update-plan.ts +296 -36
- package/src/shared/plugin/hooks/bun-missing.sh +39 -0
- package/src/shared/plugin/hooks/deps-check.ts +224 -0
- package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
- package/src/types/index.ts +60 -16
- package/src/ui/App.tsx +81 -171
- package/src/ui/adapters/cliToolsAdapter.ts +252 -12
- package/src/ui/adapters/depsAdapter.ts +129 -0
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +297 -45
- package/src/ui/adapters/startupReports.ts +260 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -12
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +30 -52
- package/src/ui/hooks/useProfileStartupModal.ts +119 -142
- package/src/ui/hooks/useStaleLoadModal.ts +35 -65
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/cliToolRenderers.tsx +135 -2
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -45
- package/src/ui/screens/CliToolsScreen.tsx +199 -98
- package/src/ui/screens/EnvVarsScreen.tsx +17 -5
- package/src/ui/screens/GitignoreScreen.tsx +48 -67
- package/src/ui/screens/McpRegistryScreen.tsx +67 -38
- package/src/ui/screens/McpScreen.tsx +46 -19
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +315 -259
- package/src/ui/screens/ProfilesScreen.tsx +79 -40
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +66 -44
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +12 -100
- package/src/ui/state/types.ts +48 -69
- package/src/utils/command-utils.ts +4 -5
- package/src/utils/run.ts +123 -52
- package/src/utils/string-utils.ts +9 -0
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/go-module.test.ts +0 -43
- package/src/__tests__/notices-rules.test.ts +0 -476
- package/src/__tests__/notices.test.tsx +0 -604
- package/src/__tests__/plugin-setup.test.ts +0 -1016
- package/src/__tests__/shell-script-callers.test.ts +0 -92
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/plugin-setup.ts +0 -965
- package/src/services/version-snapshot.ts +0 -189
- 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 {
|
|
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
|
+
});
|