claudeup 6.6.0 → 6.7.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "6.6.0",
3
+ "version": "6.7.1",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -64,8 +64,8 @@
64
64
  "typescript": "^5.6.3"
65
65
  },
66
66
  "optionalDependencies": {
67
- "claudeup-darwin-arm64": "6.6.0",
68
- "claudeup-darwin-x64": "6.6.0",
69
- "claudeup-linux-x64": "6.6.0"
67
+ "claudeup-darwin-arm64": "6.7.1",
68
+ "claudeup-darwin-x64": "6.7.1",
69
+ "claudeup-linux-x64": "6.7.1"
70
70
  }
71
71
  }
@@ -240,11 +240,11 @@ describe("conventions-manager", () => {
240
240
  const content = [
241
241
  "# Project CLAUDE.md",
242
242
  "",
243
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
243
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
244
244
  "## Code Analysis Conventions",
245
245
  "",
246
246
  "> Managed by plugin",
247
- "<!-- END magus:code-analysis -->",
247
+ "<!-- END magus:code-search -->",
248
248
  "",
249
249
  "Other content",
250
250
  ].join("\n");
@@ -253,7 +253,7 @@ describe("conventions-manager", () => {
253
253
 
254
254
  expect(result.errors).toEqual([]);
255
255
  expect(result.sections).toHaveLength(1);
256
- expect(result.sections[0].section).toBe("code-analysis");
256
+ expect(result.sections[0].section).toBe("code-search");
257
257
  expect(result.sections[0].version).toBe("1.0.0");
258
258
  expect(result.sections[0].content).toContain("Code Analysis Conventions");
259
259
  expect(result.sections[0].startLine).toBe(3);
@@ -262,9 +262,9 @@ describe("conventions-manager", () => {
262
262
 
263
263
  it("should parse multiple sections", () => {
264
264
  const content = [
265
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
265
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
266
266
  "Section 1 content",
267
- "<!-- END magus:code-analysis -->",
267
+ "<!-- END magus:code-search -->",
268
268
  "",
269
269
  "<!-- BEGIN magus:terminal v2.0.0 -->",
270
270
  "Section 2 content",
@@ -275,13 +275,13 @@ describe("conventions-manager", () => {
275
275
 
276
276
  expect(result.errors).toEqual([]);
277
277
  expect(result.sections).toHaveLength(2);
278
- expect(result.sections[0].section).toBe("code-analysis");
278
+ expect(result.sections[0].section).toBe("code-search");
279
279
  expect(result.sections[1].section).toBe("terminal");
280
280
  });
281
281
 
282
282
  it("should detect missing END marker", () => {
283
283
  const content = [
284
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
284
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
285
285
  "Some content without an end marker",
286
286
  ].join("\n");
287
287
 
@@ -289,18 +289,18 @@ describe("conventions-manager", () => {
289
289
 
290
290
  expect(result.errors).toHaveLength(1);
291
291
  expect(result.errors[0].type).toBe("missing_end");
292
- expect(result.errors[0]).toHaveProperty("section", "code-analysis");
292
+ expect(result.errors[0]).toHaveProperty("section", "code-search");
293
293
  });
294
294
 
295
295
  it("should detect duplicate sections", () => {
296
296
  const content = [
297
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
297
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
298
298
  "First",
299
- "<!-- END magus:code-analysis -->",
299
+ "<!-- END magus:code-search -->",
300
300
  "",
301
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
301
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
302
302
  "Second",
303
- "<!-- END magus:code-analysis -->",
303
+ "<!-- END magus:code-search -->",
304
304
  ].join("\n");
305
305
 
306
306
  const result = parseClaudeMdContent(content);
@@ -309,7 +309,7 @@ describe("conventions-manager", () => {
309
309
  (e) => e.type === "duplicate_section",
310
310
  );
311
311
  expect(dupError).toBeDefined();
312
- expect(dupError).toHaveProperty("section", "code-analysis");
312
+ expect(dupError).toHaveProperty("section", "code-search");
313
313
  });
314
314
 
315
315
  it("should ignore markers inside code blocks", () => {
@@ -473,9 +473,9 @@ describe("conventions-manager", () => {
473
473
  const content = [
474
474
  "# Project",
475
475
  "",
476
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
476
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
477
477
  "Conventions here",
478
- "<!-- END magus:code-analysis -->",
478
+ "<!-- END magus:code-search -->",
479
479
  ].join("\n");
480
480
  await writeFile(claudeMdPath, content);
481
481
 
@@ -505,7 +505,7 @@ describe("conventions-manager", () => {
505
505
 
506
506
  const result = await injectClaudeMdSection(
507
507
  claudeMdPath,
508
- "code-analysis",
508
+ "code-search",
509
509
  "1.0.0",
510
510
  "## Code Analysis\n\nPrivate paths here",
511
511
  );
@@ -513,9 +513,9 @@ describe("conventions-manager", () => {
513
513
  expect(result.action).toBe("inserted");
514
514
 
515
515
  const content = await readFile(claudeMdPath, "utf-8");
516
- expect(content).toContain("<!-- BEGIN magus:code-analysis v1.0.0 -->");
516
+ expect(content).toContain("<!-- BEGIN magus:code-search v1.0.0 -->");
517
517
  expect(content).toContain("## Code Analysis");
518
- expect(content).toContain("<!-- END magus:code-analysis -->");
518
+ expect(content).toContain("<!-- END magus:code-search -->");
519
519
  });
520
520
 
521
521
  it("should insert new section into existing file", async () => {
@@ -524,7 +524,7 @@ describe("conventions-manager", () => {
524
524
 
525
525
  const result = await injectClaudeMdSection(
526
526
  claudeMdPath,
527
- "code-analysis",
527
+ "code-search",
528
528
  "1.0.0",
529
529
  "New content",
530
530
  );
@@ -534,7 +534,7 @@ describe("conventions-manager", () => {
534
534
  const content = await readFile(claudeMdPath, "utf-8");
535
535
  expect(content).toContain("# Project");
536
536
  expect(content).toContain("Existing content");
537
- expect(content).toContain("<!-- BEGIN magus:code-analysis v1.0.0 -->");
537
+ expect(content).toContain("<!-- BEGIN magus:code-search v1.0.0 -->");
538
538
  });
539
539
 
540
540
  it("should update section when version changes", async () => {
@@ -542,15 +542,15 @@ describe("conventions-manager", () => {
542
542
  const initial = [
543
543
  "# Project",
544
544
  "",
545
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
545
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
546
546
  "Old content",
547
- "<!-- END magus:code-analysis -->",
547
+ "<!-- END magus:code-search -->",
548
548
  ].join("\n");
549
549
  await writeFile(claudeMdPath, initial);
550
550
 
551
551
  const result = await injectClaudeMdSection(
552
552
  claudeMdPath,
553
- "code-analysis",
553
+ "code-search",
554
554
  "2.0.0",
555
555
  "New content",
556
556
  );
@@ -568,15 +568,15 @@ describe("conventions-manager", () => {
568
568
  it("should skip when version is unchanged", async () => {
569
569
  const claudeMdPath = join(testDir, "CLAUDE.md");
570
570
  const initial = [
571
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
571
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
572
572
  "Current content",
573
- "<!-- END magus:code-analysis -->",
573
+ "<!-- END magus:code-search -->",
574
574
  ].join("\n");
575
575
  await writeFile(claudeMdPath, initial);
576
576
 
577
577
  const result = await injectClaudeMdSection(
578
578
  claudeMdPath,
579
- "code-analysis",
579
+ "code-search",
580
580
  "1.0.0",
581
581
  "Different content but same version",
582
582
  );
@@ -598,7 +598,7 @@ describe("conventions-manager", () => {
598
598
 
599
599
  const result = await injectClaudeMdSection(
600
600
  claudeMdPath,
601
- "code-analysis",
601
+ "code-search",
602
602
  "1.0.0",
603
603
  "New content",
604
604
  );
@@ -610,13 +610,13 @@ describe("conventions-manager", () => {
610
610
  it("should refuse when file has duplicate sections", async () => {
611
611
  const claudeMdPath = join(testDir, "CLAUDE.md");
612
612
  const duplicate = [
613
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
613
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
614
614
  "First",
615
- "<!-- END magus:code-analysis -->",
615
+ "<!-- END magus:code-search -->",
616
616
  "",
617
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
617
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
618
618
  "Second",
619
- "<!-- END magus:code-analysis -->",
619
+ "<!-- END magus:code-search -->",
620
620
  ].join("\n");
621
621
  await writeFile(claudeMdPath, duplicate);
622
622
 
@@ -642,21 +642,21 @@ describe("conventions-manager", () => {
642
642
  const content = [
643
643
  "# Project",
644
644
  "",
645
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
645
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
646
646
  "Conventions here",
647
- "<!-- END magus:code-analysis -->",
647
+ "<!-- END magus:code-search -->",
648
648
  "",
649
649
  "Other content",
650
650
  ].join("\n");
651
651
  await writeFile(claudeMdPath, content);
652
652
 
653
- const result = await removeClaudeMdSection(claudeMdPath, "code-analysis");
653
+ const result = await removeClaudeMdSection(claudeMdPath, "code-search");
654
654
 
655
655
  expect(result.removed).toBe(true);
656
656
 
657
657
  const newContent = await readFile(claudeMdPath, "utf-8");
658
- expect(newContent).not.toContain("BEGIN magus:code-analysis");
659
- expect(newContent).not.toContain("END magus:code-analysis");
658
+ expect(newContent).not.toContain("BEGIN magus:code-search");
659
+ expect(newContent).not.toContain("END magus:code-search");
660
660
  expect(newContent).toContain("# Project");
661
661
  expect(newContent).toContain("Other content");
662
662
  });
@@ -665,7 +665,7 @@ describe("conventions-manager", () => {
665
665
  const claudeMdPath = join(testDir, "CLAUDE.md");
666
666
  await writeFile(claudeMdPath, "# Project\n\nSome content\n");
667
667
 
668
- const result = await removeClaudeMdSection(claudeMdPath, "code-analysis");
668
+ const result = await removeClaudeMdSection(claudeMdPath, "code-search");
669
669
 
670
670
  expect(result.removed).toBe(false);
671
671
  });
@@ -673,7 +673,7 @@ describe("conventions-manager", () => {
673
673
  it("should no-op for non-existent file", async () => {
674
674
  const claudeMdPath = join(testDir, "CLAUDE.md");
675
675
 
676
- const result = await removeClaudeMdSection(claudeMdPath, "code-analysis");
676
+ const result = await removeClaudeMdSection(claudeMdPath, "code-search");
677
677
 
678
678
  expect(result.removed).toBe(false);
679
679
  });
@@ -724,9 +724,9 @@ describe("conventions-manager", () => {
724
724
  it("should preserve other sections when removing one", async () => {
725
725
  const claudeMdPath = join(testDir, "CLAUDE.md");
726
726
  const content = [
727
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
727
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
728
728
  "First section",
729
- "<!-- END magus:code-analysis -->",
729
+ "<!-- END magus:code-search -->",
730
730
  "",
731
731
  "<!-- BEGIN magus:terminal v2.0.0 -->",
732
732
  "Second section",
@@ -734,10 +734,10 @@ describe("conventions-manager", () => {
734
734
  ].join("\n");
735
735
  await writeFile(claudeMdPath, content);
736
736
 
737
- await removeClaudeMdSection(claudeMdPath, "code-analysis");
737
+ await removeClaudeMdSection(claudeMdPath, "code-search");
738
738
 
739
739
  const newContent = await readFile(claudeMdPath, "utf-8");
740
- expect(newContent).not.toContain("code-analysis");
740
+ expect(newContent).not.toContain("code-search");
741
741
  expect(newContent).toContain("<!-- BEGIN magus:terminal v2.0.0 -->");
742
742
  expect(newContent).toContain("Second section");
743
743
  });
@@ -751,9 +751,9 @@ describe("conventions-manager", () => {
751
751
  it("should list all managed sections", async () => {
752
752
  const claudeMdPath = join(testDir, "CLAUDE.md");
753
753
  const content = [
754
- "<!-- BEGIN magus:code-analysis v1.0.0 -->",
754
+ "<!-- BEGIN magus:code-search v1.0.0 -->",
755
755
  "First",
756
- "<!-- END magus:code-analysis -->",
756
+ "<!-- END magus:code-search -->",
757
757
  "",
758
758
  "<!-- BEGIN magus:terminal v2.1.0 -->",
759
759
  "Second",
@@ -764,7 +764,7 @@ describe("conventions-manager", () => {
764
764
  const sections = await listClaudeMdSections(claudeMdPath);
765
765
 
766
766
  expect(sections).toHaveLength(2);
767
- expect(sections[0].section).toBe("code-analysis");
767
+ expect(sections[0].section).toBe("code-search");
768
768
  expect(sections[0].version).toBe("1.0.0");
769
769
  expect(sections[1].section).toBe("terminal");
770
770
  expect(sections[1].version).toBe("2.1.0");
@@ -25,7 +25,7 @@ const plugin = (over: Partial<PluginInfo>): PluginInfo =>
25
25
 
26
26
  describe("isEnabledButNotInstalled", () => {
27
27
  test("enabled in project scope with no installed version is flagged", () => {
28
- // The real case: terminal/code-analysis are in this project's enabledPlugins
28
+ // The real case: terminal/code-search are in this project's enabledPlugins
29
29
  // but have no registry entry for it and none at user scope.
30
30
  expect(
31
31
  isEnabledButNotInstalled(
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The agent tier table is keyed by agent id, and nothing else checks those ids.
3
+ *
4
+ * `DEFAULT_AGENT_GRADES` in `data/models-presets.ts` names
5
+ * agents as `plugin:name`. A rename in the marketplace (`code-search:analyze` →
6
+ * something else, `dev:synthesizer` → `dev:aggregator`) breaks the key with no signal: the
7
+ * renamed agent silently falls to the preset's fallback, and the stale key routes nothing.
8
+ * This test reads the marketplace source tree beside this repo and fails on either drift.
9
+ *
10
+ * Two directions:
11
+ * (a) every preset key names an agent that exists (or is a Claude Code built-in);
12
+ * (b) every marketplace agent is classified — in the tier table, in the frontmatter map,
13
+ * a vision agent, or explicitly left to the fallback in FALLBACK_BY_DESIGN below.
14
+ * Absence is a legitimate routing choice (fallback = `normal`), but it must be a
15
+ * choice: a new agent fails here until someone decides.
16
+ */
17
+ import { describe, expect, test } from "bun:test";
18
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
19
+ import { join, resolve } from "node:path";
20
+ import { DEFAULT_AGENT_GRADES, VISION_AGENTS } from "../data/models-presets.js";
21
+ import { PREDEFINED_PROFILES } from "../data/predefined-profiles.js";
22
+
23
+ const REPO = resolve(import.meta.dir, "..", "..", "..", "..");
24
+ const MARKETPLACE = join(REPO, ".claude-plugin", "marketplace.json");
25
+ const inMagusSrc = existsSync(MARKETPLACE) && existsSync(join(REPO, "plugins"));
26
+
27
+ /** Claude Code's own agents: not in any marketplace, addressed by bare name. */
28
+ const BUILT_IN = new Set([
29
+ "Explore",
30
+ "Plan",
31
+ "general-purpose",
32
+ "statusline-setup",
33
+ "claude-code-guide",
34
+ ]);
35
+
36
+ /**
37
+ * Marketplace agents deliberately left to the preset's `fallback` grade. Each entry is a
38
+ * decision, not an omission: remove the line when the agent gets a tier. A NEW agent is not
39
+ * on this list, so adding one to the marketplace fails (b) until it is placed somewhere.
40
+ */
41
+ const FALLBACK_BY_DESIGN = new Set([
42
+ "dev:developer",
43
+ "dev:devops",
44
+ "dev:frontend-developer",
45
+ "dev:researcher",
46
+ "dev:qa-engineer",
47
+ "madbench:bench",
48
+ "video-editing:timeline-builder",
49
+ "video-editing:transcriber",
50
+ "video-editing:video-processor",
51
+ ]);
52
+
53
+ interface MarketplaceAgent {
54
+ id: string;
55
+ file: string;
56
+ frontmatterModel: string | undefined;
57
+ }
58
+
59
+ /** Every `plugins/<p>/agents/*.md` of every plugin the marketplace lists, by frontmatter name. */
60
+ function marketplaceAgents(): MarketplaceAgent[] {
61
+ const manifest = JSON.parse(readFileSync(MARKETPLACE, "utf8")) as {
62
+ plugins: { name: string; source: string }[];
63
+ };
64
+ const out: MarketplaceAgent[] = [];
65
+ for (const plugin of manifest.plugins) {
66
+ const agentsDir = join(REPO, plugin.source, "agents");
67
+ if (!existsSync(agentsDir)) continue;
68
+ for (const entry of readdirSync(agentsDir)) {
69
+ if (!entry.endsWith(".md")) continue;
70
+ const file = join(agentsDir, entry);
71
+ const text = readFileSync(file, "utf8");
72
+ const fm = text.match(/^---\n([\s\S]*?)\n---/);
73
+ if (!fm) continue;
74
+ const name = fm[1].match(/^name:\s*(.+?)\s*$/m)?.[1];
75
+ if (!name) continue;
76
+ const model = fm[1].match(/^model:\s*(.+?)\s*$/m)?.[1];
77
+ out.push({
78
+ id: `${plugin.name}:${name}`,
79
+ file: join(plugin.source, "agents", entry),
80
+ frontmatterModel: model,
81
+ });
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ describe.skipIf(!inMagusSrc)(
88
+ "models-presets agent ids track the marketplace",
89
+ () => {
90
+ const agents = marketplaceAgents();
91
+ const ids = new Set(agents.map((a) => a.id));
92
+ const presetKeys = [...Object.keys(DEFAULT_AGENT_GRADES)];
93
+
94
+ test("reads a non-trivial marketplace", () => {
95
+ expect(agents.length).toBeGreaterThan(10);
96
+ });
97
+
98
+ test("(a) every preset key names an existing agent or a built-in", () => {
99
+ const dead = presetKeys.filter((k) => !BUILT_IN.has(k) && !ids.has(k));
100
+ expect(dead).toEqual([]);
101
+ });
102
+
103
+ test("(b) every marketplace agent is classified somewhere", () => {
104
+ const classified = new Set<string>([
105
+ ...presetKeys,
106
+ ...VISION_AGENTS,
107
+ ...FALLBACK_BY_DESIGN,
108
+ ]);
109
+ const unplaced = agents
110
+ .filter((a) => !classified.has(a.id))
111
+ .map((a) => a.id);
112
+ expect(unplaced).toEqual([]);
113
+ });
114
+
115
+ test("FALLBACK_BY_DESIGN and VISION_AGENTS name agents that still exist", () => {
116
+ const stale = [...FALLBACK_BY_DESIGN, ...VISION_AGENTS].filter(
117
+ (k) => !ids.has(k),
118
+ );
119
+ expect(stale).toEqual([]);
120
+ });
121
+
122
+ // A profile's magusPlugins is an install list, not documentation: resolver.ts expands
123
+ // each entry to `<name>@<marketplace>: latest`. A plugin rename moves under it silently,
124
+ // which is how 13.0.0's code-analysis -> code-search rename left six profiles installing
125
+ // a plugin the marketplace no longer lists. The agent checks above never read this array.
126
+ test("(d) every predefined profile names plugins the marketplace still lists", () => {
127
+ const marketplace = JSON.parse(readFileSync(MARKETPLACE, "utf8")) as {
128
+ plugins: { name: string }[];
129
+ };
130
+ const known = new Set(marketplace.plugins.map((p) => p.name));
131
+ const dead: string[] = [];
132
+ for (const profile of PREDEFINED_PROFILES) {
133
+ for (const entry of profile.magusPlugins ?? []) {
134
+ const name = entry.split("@")[0];
135
+ if (!known.has(name)) dead.push(`${profile.id}: ${entry}`);
136
+ }
137
+ }
138
+ expect(dead).toEqual([]);
139
+ });
140
+ },
141
+ );
@@ -83,7 +83,7 @@ describe("resolveMarketplacePlugins — offline fallback", () => {
83
83
  "magus",
84
84
  buildLocalMarketplace("magus", [
85
85
  {
86
- name: "code-analysis",
86
+ name: "code-search",
87
87
  version: "5.3.0",
88
88
  description: "Codebase investigation tools",
89
89
  category: "development",
@@ -108,12 +108,10 @@ describe("resolveMarketplacePlugins — offline fallback", () => {
108
108
 
109
109
  expect(resolved.plugins).toHaveLength(2);
110
110
  expect(resolved.plugins.map((p) => p.name).sort()).toEqual([
111
- "code-analysis",
111
+ "code-search",
112
112
  "dev",
113
113
  ]);
114
- const codeAnalysis = resolved.plugins.find(
115
- (p) => p.name === "code-analysis",
116
- );
114
+ const codeAnalysis = resolved.plugins.find((p) => p.name === "code-search");
117
115
  expect(codeAnalysis?.version).toBe("5.3.0");
118
116
  expect(codeAnalysis?.description).toBe("Codebase investigation tools");
119
117
  expect(codeAnalysis?.category).toBe("development");
@@ -91,9 +91,9 @@ describe("resolveExtends", () => {
91
91
  extends: "growth-marketer",
92
92
  };
93
93
  const merged = resolveExtends(entry);
94
- // image-generate/video-editing ship on the magus-marketing channel and
94
+ // image/video-editing ship on the magus-marketing channel and
95
95
  // pin it explicitly — they must NOT be rewritten to @magus.
96
- expect(merged.plugins!["image-generate@magus-marketing"]).toBe("latest");
96
+ expect(merged.plugins!["image@magus-marketing"]).toBe("latest");
97
97
  expect(merged.plugins!["video-editing@magus-marketing"]).toBe("latest");
98
98
  expect(merged.plugins!["image-generate@magus"]).toBeUndefined();
99
99
  // Bare names still default to @magus.
@@ -302,7 +302,7 @@ describe("marketplaces derived from plugin ids", () => {
302
302
  name: "Mixed",
303
303
  plugins: {
304
304
  "dev@magus": "latest",
305
- "image-generate@magus-marketing": "latest",
305
+ "image@magus-marketing": "latest",
306
306
  "feature-dev@claude-plugins-official": "latest",
307
307
  },
308
308
  },
@@ -40,19 +40,18 @@ export const DEFAULT_AGENT_GRADES: Record<string, Grade> = {
40
40
  // what is true; both were on `smart` because "writes an important document" got confused
41
41
  // with "makes an expensive judgement".
42
42
  "dev:spec-writer": "normal",
43
- "dev:synthesizer": "normal",
43
+ "dev:aggregator": "normal",
44
44
 
45
45
  // Reading a lot is not analysing.
46
46
  //
47
- // `detective` is read-only: it locates implementations, traces a path and reports
47
+ // `analyze` is read-only: it locates implementations, traces a path and reports
48
48
  // file:line. That is high-volume retrieval with light inference — the same shape as
49
49
  // `Explore`, with more structure in the output. It sat on `smart` because "investigates"
50
50
  // sounds like thinking; what it actually does is look.
51
- "code-analysis:detective": "cheap",
51
+ "code-search:analyze": "cheap",
52
52
 
53
- // Search, transcription and bookkeeping: bounded, mechanical, high-volume.
53
+ // Search and bookkeeping: bounded, mechanical, high-volume.
54
54
  Explore: "cheap",
55
- "dev:scribe": "cheap",
56
55
  "dev:stack-detector": "cheap",
57
56
  "dev:docs": "cheap",
58
57
  "terminal:tui-navigator": "cheap",
@@ -69,7 +68,7 @@ export const DEFAULT_AGENT_GRADES: Record<string, Grade> = {
69
68
  * answers to "what will this preset cost me", and the lump sum shows neither.
70
69
  *
71
70
  * Read out of `plugins/dev/commands/{dev,debug,investigate}.md` — agents only. Those files
72
- * also name skills (`dev:context-detection`, `code-analysis:investigate`) which are not
71
+ * also name skills (`dev:context-detection`, `code-search:investigate`) which are not
73
72
  * dispatched through the Agent tool and so are not routed at all.
74
73
  */
75
74
  export const WORKFLOWS: { name: string; agents: string[] }[] = [
@@ -79,7 +78,7 @@ export const WORKFLOWS: { name: string; agents: string[] }[] = [
79
78
  "dev:stack-detector",
80
79
  "dev:architect",
81
80
  "dev:developer",
82
- "dev:test-architect",
81
+ "dev:qa-engineer",
83
82
  "dev:reviewer",
84
83
  ],
85
84
  },
@@ -87,14 +86,14 @@ export const WORKFLOWS: { name: string; agents: string[] }[] = [
87
86
  name: "debug",
88
87
  agents: [
89
88
  "dev:stack-detector",
90
- "code-analysis:detective",
89
+ "code-search:analyze",
91
90
  "dev:debugger",
92
91
  "dev:developer",
93
92
  ],
94
93
  },
95
94
  {
96
95
  name: "investigate",
97
- agents: ["code-analysis:detective", "dev:researcher"],
96
+ agents: ["code-search:analyze", "dev:researcher"],
98
97
  },
99
98
  ];
100
99
 
@@ -102,7 +101,7 @@ export const WORKFLOWS: { name: string; agents: string[] }[] = [
102
101
  * Agents whose model is a CAPABILITY choice, not a spend choice.
103
102
  *
104
103
  * The three tiers answer "how much model", and that is the wrong question for an agent that
105
- * reads images. `designer:design-review` compares a rendered screen against a reference and
104
+ * reads images. `designer:review` compares a rendered screen against a reference and
106
105
  * `designer:ui` audits a screenshot for usability: what they need is the model that SEES
107
106
  * best, which is not a point on a cheap-to-smart line and does not move when the preset
108
107
  * changes.
@@ -112,7 +111,7 @@ export const WORKFLOWS: { name: string; agents: string[] }[] = [
112
111
  * preset's `fallback`, which is a defensible default and an honest one; pinning them to a
113
112
  * model id guessed here is neither.
114
113
  */
115
- export const VISION_AGENTS = ["designer:design-review", "designer:ui"] as const;
114
+ export const VISION_AGENTS = ["designer:review", "designer:ui"] as const;
116
115
 
117
116
  function preset(
118
117
  name: string,
@@ -52,7 +52,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
52
52
  magusPlugins: [
53
53
  "setup",
54
54
  "multimodel",
55
- "code-analysis",
55
+ "code-search",
56
56
  "dev",
57
57
  "browser-use",
58
58
  "designer",
@@ -90,7 +90,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
90
90
  icon: "★",
91
91
  magusPlugins: [
92
92
  "dev",
93
- "code-analysis",
93
+ "code-search",
94
94
  "terminal",
95
95
  "setup",
96
96
  "designer",
@@ -129,7 +129,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
129
129
  name: "Backend Forge",
130
130
  description: "API development, debugging, code quality, data workflows",
131
131
  icon: "★",
132
- magusPlugins: ["dev", "code-analysis", "terminal", "setup", "multimodel"],
132
+ magusPlugins: ["dev", "code-search", "terminal", "setup", "multimodel"],
133
133
  anthropicPlugins: [
134
134
  "feature-dev",
135
135
  "code-review",
@@ -159,7 +159,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
159
159
  icon: "★",
160
160
  magusPlugins: [
161
161
  "dev",
162
- "code-analysis",
162
+ "code-search",
163
163
  "terminal",
164
164
  "setup",
165
165
  "multimodel",
@@ -194,11 +194,11 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
194
194
  icon: "★",
195
195
  magusPlugins: [
196
196
  "dev",
197
- "code-analysis",
197
+ "code-search",
198
198
  "terminal",
199
199
  "setup",
200
200
  "browser-use",
201
- "image-generate@magus-marketing",
201
+ "image@magus-marketing",
202
202
  "video-editing@magus-marketing",
203
203
  ],
204
204
  anthropicPlugins: [
@@ -226,7 +226,7 @@ export const PREDEFINED_PROFILES: PredefinedProfile[] = [
226
226
  description:
227
227
  "Planning, code review, coordination, and broad repo visibility",
228
228
  icon: "★",
229
- magusPlugins: ["dev", "code-analysis", "terminal", "setup", "multimodel"],
229
+ magusPlugins: ["dev", "code-search", "terminal", "setup", "multimodel"],
230
230
  anthropicPlugins: [
231
231
  "code-review",
232
232
  "pr-review-toolkit",
@@ -347,7 +347,7 @@ async function overlayRegistryVersions(
347
347
  // binary, and `claude plugin install` writes it at no scope — so only
348
348
  // claudeup's own past writes can be in there, and they go stale silently.
349
349
  // Measured: user settings claimed browser-use 1.1.2 / terminal 4.0.2 /
350
- // code-analysis 5.1.0 while the registry said 1.4.0 / 4.1.4 / 5.3.1, and
350
+ // code-search 5.1.0 while the registry said 1.4.0 / 4.1.4 / 5.3.1, and
351
351
  // none of the claimed versions had a cache directory. Keeping the stale
352
352
  // value made every one of them read as a permanent pending update.
353
353
  if (entries && entries.length > 0) delete merged[pluginId];
@@ -727,7 +727,7 @@ export function describeCommunityFailure(
727
727
  // ─── Staleness: advisory, two-phase, never automatic ─────────────────────────
728
728
 
729
729
  /**
730
- * 24 hours, matching image-generate's staleness precedent in the root CLAUDE.md.
730
+ * 24 hours, matching the image plugin's staleness precedent in the root CLAUDE.md.
731
731
  *
732
732
  * The TTL is not only a "when to re-check" rule — it is also a "how long may we
733
733
  * keep SAYING up-to-date" rule. A check from three days ago is evidence about