magus-cli 7.9.0 → 7.10.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 (29) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/blind/plugin-deps/doctor.test.ts +1 -0
  3. package/src/__tests__/blind/review-followups/bun-install-path.test.ts +57 -0
  4. package/src/__tests__/blind/review-followups/d1-empty-lock.test.ts +94 -0
  5. package/src/__tests__/blind/review-followups/d2-disappearing-lock.test.ts +90 -0
  6. package/src/__tests__/blind/review-followups/d3-release-temp-cleanup.test.ts +83 -0
  7. package/src/__tests__/blind/review-followups/d4-tool-stdout.test.ts +152 -0
  8. package/src/__tests__/blind/review-followups/installer-redirect.test.ts +74 -0
  9. package/src/__tests__/blind/review-followups/ledger-permissions.test.ts +78 -0
  10. package/src/__tests__/blind/review-followups/ledger-pruning.test.ts +79 -0
  11. package/src/__tests__/blind/review-followups/path-advice.test.ts +46 -0
  12. package/src/__tests__/blind/review-followups/stats-requires.test.ts +16 -0
  13. package/src/__tests__/deps-plan.test.ts +3 -2
  14. package/src/__tests__/plugin-mcp-source-shapes.test.ts +250 -0
  15. package/src/__tests__/worktree-profiles.test.ts +583 -0
  16. package/src/cli/doctor.ts +24 -2
  17. package/src/cli/update-view.ts +73 -0
  18. package/src/cli/update.ts +21 -0
  19. package/src/cli/worktrees.ts +32 -0
  20. package/src/services/deps-plan.ts +273 -81
  21. package/src/services/git-worktree.ts +41 -0
  22. package/src/services/plugin-mcp-config.ts +3 -1
  23. package/src/services/worktree-profiles.ts +181 -0
  24. package/src/shared/plugin/hooks/bun-missing.sh +35 -10
  25. package/src/shared/plugin/hooks/deps-check.ts +25 -9
  26. package/src/shared/plugin/hooks/lib/requires-probe.ts +101 -5
  27. package/src/ui/App.tsx +32 -1
  28. package/src/ui/adapters/startupReports.ts +22 -2
  29. package/src/utils/run.ts +21 -1
@@ -34,6 +34,10 @@ import {
34
34
  type UpdatePlan,
35
35
  isKeptDeprecated,
36
36
  } from "../services/update-plan.js";
37
+ import type {
38
+ UnsetWorktree,
39
+ WorktreeSetupResult,
40
+ } from "../services/worktree-profiles.js";
37
41
  import { type ReportBadgeKind, reportBadge } from "../ui/theme-mode.js";
38
42
  import { brand } from "../ui/theme.js";
39
43
  import { formatCommand } from "../utils/command-utils.js";
@@ -903,3 +907,72 @@ export function pendingHeadline(plan: UpdatePlan): string {
903
907
  !plan.skills.some((s) => s.action === "install");
904
908
  return onlyRemovals ? "Changes pending" : "Updates available";
905
909
  }
910
+
911
+ // -- other worktrees -----------------------------------------------------------
912
+ //
913
+ // The other working trees of this repository that have no profile set up
914
+ // (`findUnsetWorktrees`). `magus update` sets up the ones the project already
915
+ // answered for, exactly as it does its own folder, and says so in one line.
916
+ // No section and no question: there is nothing to decide. Read-only runs
917
+ // (`magus doctor`, `--check`, `--dry-run`) get the same one line as a warning.
918
+
919
+ /** `Worktree a` or `Worktrees a, b, c + 2 more`, so the row stays one line. */
920
+ function worktreeLabel(names: readonly string[]): string {
921
+ const shown = names.slice(0, 3).join(", ");
922
+ const list = names.length > 3 ? `${shown} + ${names.length - 3} more` : shown;
923
+ return `${names.length === 1 ? "Worktree" : "Worktrees"} ${bold(list)}`;
924
+ }
925
+
926
+ /**
927
+ * The worktree rows: after a setup (`results`), one `✓` line for every
928
+ * worktree set up and a `✗` line for each failure; on a read-only run
929
+ * (`results` null), one `⚠` line for the ones `magus update` would set up.
930
+ * A worktree that needs a choice gets its own `⚠` line either way. Empty for
931
+ * an empty list: a repository with nothing to say prints nothing.
932
+ */
933
+ export function worktreeLines(
934
+ worktrees: readonly UnsetWorktree[],
935
+ results: readonly WorktreeSetupResult[] | null,
936
+ ): string[] {
937
+ if (worktrees.length === 0) return [];
938
+ const lines = [""];
939
+ if (results === null) {
940
+ const auto = worktrees.filter((w) => w.autoActivate).map((w) => w.name);
941
+ if (auto.length > 0) {
942
+ const one = auto.length === 1;
943
+ lines.push(
944
+ warn(
945
+ `${worktreeLabel(auto)} ${one ? "is" : "are"} missing this project's plugins. magus update sets ${one ? "it" : "them"} up.`,
946
+ ),
947
+ );
948
+ }
949
+ } else {
950
+ const done = results.filter((r) => r.ok).map((r) => r.name);
951
+ if (done.length > 0) {
952
+ lines.push(
953
+ ok(
954
+ `${worktreeLabel(done)} now ${done.length === 1 ? "has" : "have"} this project's plugins.`,
955
+ ),
956
+ );
957
+ }
958
+ for (const r of results) {
959
+ if (!r.ok) {
960
+ lines.push(
961
+ fail(
962
+ `Worktree ${bold(r.name)} was not set up: ${r.reason.replace(/\.+$/, "")}.`,
963
+ ),
964
+ );
965
+ }
966
+ }
967
+ }
968
+ for (const w of worktrees) {
969
+ if (!w.autoActivate) {
970
+ lines.push(
971
+ warn(
972
+ `Worktree ${bold(w.name)} is missing this project's plugins. Run magus update there to choose a profile.`,
973
+ ),
974
+ );
975
+ }
976
+ }
977
+ return lines;
978
+ }
package/src/cli/update.ts CHANGED
@@ -84,6 +84,7 @@ import {
84
84
  planSkillUpdates,
85
85
  summarizePlan,
86
86
  } from "../services/update-plan.js";
87
+ import type { ProfileManifest } from "../types/index.js";
87
88
  import { brand } from "../ui/theme.js";
88
89
  import { bold, dim, fg, width } from "./ansi.js";
89
90
  import { ensureManifest, requireActiveProfile } from "./bootstrap.js";
@@ -108,6 +109,7 @@ import {
108
109
  unknownToolsNotice,
109
110
  warn as warnLine,
110
111
  } from "./update-view.js";
112
+ import { setUpOtherWorktrees } from "./worktrees.js";
111
113
 
112
114
  interface UpdateFlags {
113
115
  /** Report drift and exit 1 if anything is behind. The CI gate. */
@@ -250,6 +252,25 @@ export async function runUpdateCommand(
250
252
  // what it promised. A refused `--check` exits 1, because that is a gate.
251
253
  if (!current) return flags.dryRun ? 0 : 1;
252
254
 
255
+ const code = await updateProfile(flags, projectPath, current, startedAt);
256
+
257
+ // This project is on a profile; now the other worktrees of the repository,
258
+ // set up the same way without asking. After the update, so their line is
259
+ // not lost above the plan. Read-only under `--check` and `--dry-run`, and
260
+ // never a change to the exit code: those are other projects.
261
+ await setUpOtherWorktrees(projectPath, {
262
+ mayWrite: !flags.check && !flags.dryRun,
263
+ });
264
+ return code;
265
+ }
266
+
267
+ /** The update itself, once the project's own profile gate has passed. */
268
+ async function updateProfile(
269
+ flags: UpdateFlags,
270
+ projectPath: string,
271
+ current: ProfileManifest,
272
+ startedAt: number,
273
+ ): Promise<number> {
253
274
  const profileIds = Object.keys(current.profiles);
254
275
  if (flags.profile && !current.profiles[flags.profile]) {
255
276
  console.error(
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The other worktrees of this repository at the end of `magus update`: the ones
3
+ * with no profile set up. Each one whose project already declares a profile,
4
+ * with nothing of the user's to replace, is set up without asking, exactly as
5
+ * `magus update` sets up its own folder. Then one line says so.
6
+ *
7
+ * Detection and apply are `services/worktree-profiles.ts`, shared with
8
+ * `magus doctor` and the TUI; the words are `update-view.ts`.
9
+ */
10
+
11
+ import {
12
+ findUnsetWorktrees,
13
+ setUpWorktrees,
14
+ } from "../services/worktree-profiles.js";
15
+ import { worktreeLines } from "./update-view.js";
16
+
17
+ /**
18
+ * Set up what can be set up when `mayWrite`, and print the rows. Under
19
+ * `--check` and `--dry-run` it only reports. Never changes the command's exit
20
+ * code: these are other projects, and a failure in one is reported on its row.
21
+ */
22
+ export async function setUpOtherWorktrees(
23
+ projectPath: string,
24
+ opts: { mayWrite: boolean },
25
+ ): Promise<void> {
26
+ const worktrees = await findUnsetWorktrees(projectPath);
27
+ if (worktrees.length === 0) return;
28
+ const results = opts.mayWrite
29
+ ? await setUpWorktrees(worktrees.filter((w) => w.autoActivate))
30
+ : null;
31
+ for (const line of worktreeLines(worktrees, results)) console.log(line);
32
+ }