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.
- package/package.json +4 -4
- package/src/__tests__/blind/plugin-deps/doctor.test.ts +1 -0
- package/src/__tests__/blind/review-followups/bun-install-path.test.ts +57 -0
- package/src/__tests__/blind/review-followups/d1-empty-lock.test.ts +94 -0
- package/src/__tests__/blind/review-followups/d2-disappearing-lock.test.ts +90 -0
- package/src/__tests__/blind/review-followups/d3-release-temp-cleanup.test.ts +83 -0
- package/src/__tests__/blind/review-followups/d4-tool-stdout.test.ts +152 -0
- package/src/__tests__/blind/review-followups/installer-redirect.test.ts +74 -0
- package/src/__tests__/blind/review-followups/ledger-permissions.test.ts +78 -0
- package/src/__tests__/blind/review-followups/ledger-pruning.test.ts +79 -0
- package/src/__tests__/blind/review-followups/path-advice.test.ts +46 -0
- package/src/__tests__/blind/review-followups/stats-requires.test.ts +16 -0
- package/src/__tests__/deps-plan.test.ts +3 -2
- package/src/__tests__/plugin-mcp-source-shapes.test.ts +250 -0
- package/src/__tests__/worktree-profiles.test.ts +583 -0
- package/src/cli/doctor.ts +24 -2
- package/src/cli/update-view.ts +73 -0
- package/src/cli/update.ts +21 -0
- package/src/cli/worktrees.ts +32 -0
- package/src/services/deps-plan.ts +273 -81
- package/src/services/git-worktree.ts +41 -0
- package/src/services/plugin-mcp-config.ts +3 -1
- package/src/services/worktree-profiles.ts +181 -0
- package/src/shared/plugin/hooks/bun-missing.sh +35 -10
- package/src/shared/plugin/hooks/deps-check.ts +25 -9
- package/src/shared/plugin/hooks/lib/requires-probe.ts +101 -5
- package/src/ui/App.tsx +32 -1
- package/src/ui/adapters/startupReports.ts +22 -2
- package/src/utils/run.ts +21 -1
package/src/cli/update-view.ts
CHANGED
|
@@ -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
|
+
}
|