claudeup 4.37.0 → 4.38.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 (33) hide show
  1. package/package.json +4 -4
  2. package/scripts/verify-community-registry.ts +272 -0
  3. package/src/__tests__/community-fetch.test.ts +545 -0
  4. package/src/__tests__/community-registry.test.ts +269 -0
  5. package/src/__tests__/community-staleness.test.ts +722 -0
  6. package/src/__tests__/open-file.test.ts +59 -0
  7. package/src/__tests__/style-wrap.test.ts +220 -0
  8. package/src/__tests__/styles-manager.test.ts +1124 -0
  9. package/src/__tests__/styles-origins.test.ts +416 -0
  10. package/src/__tests__/styles-screen-state.test.ts +460 -0
  11. package/src/__tests__/styles-status-line.test.ts +72 -0
  12. package/src/__tests__/styles-sync.test.ts +452 -0
  13. package/src/__tests__/tabbar-layout.test.ts +62 -0
  14. package/src/__tests__/terminology-filler.test.ts +214 -0
  15. package/src/data/community-styles.ts +521 -0
  16. package/src/main.tsx +15 -0
  17. package/src/services/catalog-cache-store.ts +101 -7
  18. package/src/services/community-fetcher.ts +90 -0
  19. package/src/services/community-styles.ts +1194 -0
  20. package/src/services/styles-manager.ts +1400 -0
  21. package/src/services/terminology-filler.ts +266 -0
  22. package/src/ui/App.tsx +15 -3
  23. package/src/ui/adapters/stylesAdapter.ts +403 -0
  24. package/src/ui/components/TabBar.tsx +43 -9
  25. package/src/ui/components/primitives/ActionHints.tsx +4 -1
  26. package/src/ui/components/primitives/ListCategoryRow.tsx +10 -1
  27. package/src/ui/registry.ts +6 -0
  28. package/src/ui/renderers/styleRenderers.tsx +809 -0
  29. package/src/ui/screens/StylesScreen.tsx +1089 -0
  30. package/src/ui/screens/index.ts +1 -0
  31. package/src/ui/state/reducer.ts +113 -1
  32. package/src/ui/state/types.ts +60 -2
  33. package/src/utils/open-file.ts +84 -0
@@ -0,0 +1,214 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import fs from "fs-extra";
6
+ import { splitFrontmatter } from "../services/styles-manager.js";
7
+ import {
8
+ buildPrompt,
9
+ fillTemplate,
10
+ fillTerminology,
11
+ parseTerminologyRows,
12
+ terminologyStyleFile,
13
+ } from "../services/terminology-filler.js";
14
+
15
+ /** The shape the shipped template actually has. */
16
+ const TEMPLATE = [
17
+ "### Terminology",
18
+ "",
19
+ "One concept, one name, everywhere.",
20
+ "",
21
+ "| Use | Not | Because |",
22
+ "|---|---|---|",
23
+ "| <!-- filled during /style:apply --> | | |",
24
+ "",
25
+ "Rules that hold regardless of the table above:",
26
+ "",
27
+ "- Use the domain's word, not the implementation's.",
28
+ ].join("\n");
29
+
30
+ describe("parseTerminologyRows", () => {
31
+ test("reads well-formed rows", () => {
32
+ expect(
33
+ parseTerminologyRows(
34
+ "| booking | reservation | the domain says booking\n",
35
+ ),
36
+ ).toEqual([
37
+ {
38
+ use: "booking",
39
+ not: "reservation",
40
+ because: "the domain says booking",
41
+ },
42
+ ]);
43
+ });
44
+
45
+ test("ignores commentary around the table", () => {
46
+ // `claude -p` often frames its answer, however firmly it is told not to.
47
+ const out = [
48
+ "Here are the rows I found:",
49
+ "",
50
+ "| booking | reservation | the domain says booking |",
51
+ "| tenant | account | billing is per tenant |",
52
+ "",
53
+ "Let me know if you want more.",
54
+ ].join("\n");
55
+ expect(parseTerminologyRows(out)).toHaveLength(2);
56
+ });
57
+
58
+ test("drops a header row and separator the model added anyway", () => {
59
+ const out = [
60
+ "| Use | Not | Because |",
61
+ "|---|---|---|",
62
+ "| booking | reservation | the domain says booking |",
63
+ ].join("\n");
64
+ expect(parseTerminologyRows(out)).toEqual([
65
+ {
66
+ use: "booking",
67
+ not: "reservation",
68
+ because: "the domain says booking",
69
+ },
70
+ ]);
71
+ });
72
+
73
+ test("drops an aligned separator too", () => {
74
+ expect(parseTerminologyRows("|:---|:---:|---:|\n| a | b | c |")).toEqual([
75
+ { use: "a", not: "b", because: "c" },
76
+ ]);
77
+ });
78
+
79
+ test("skips rows with the wrong number of cells", () => {
80
+ expect(parseTerminologyRows("| only | two |")).toEqual([]);
81
+ expect(parseTerminologyRows("| a | b | c | d |")).toEqual([]);
82
+ });
83
+
84
+ test("skips rows missing the two cells that carry meaning", () => {
85
+ expect(parseTerminologyRows("| | | because |")).toEqual([]);
86
+ expect(parseTerminologyRows("| use | | because |")).toEqual([]);
87
+ });
88
+
89
+ test("returns nothing for output with no table at all", () => {
90
+ expect(parseTerminologyRows("I could not find any terminology.")).toEqual(
91
+ [],
92
+ );
93
+ });
94
+ });
95
+
96
+ describe("fillTemplate", () => {
97
+ test("replaces the placeholder row", () => {
98
+ const filled = fillTemplate(TEMPLATE, [
99
+ { use: "booking", not: "reservation", because: "the domain says so" },
100
+ ]);
101
+ expect(filled).not.toContain("filled during");
102
+ expect(filled).toContain("| booking | reservation | the domain says so |");
103
+ });
104
+
105
+ test("keeps the rules below the table verbatim", () => {
106
+ // These are substantive guidance. Losing them would leave a style that
107
+ // looks filled in and says almost nothing.
108
+ const filled = fillTemplate(TEMPLATE, [
109
+ { use: "a", not: "b", because: "c" },
110
+ ]);
111
+ expect(filled).toContain("Rules that hold regardless of the table above:");
112
+ expect(filled).toContain(
113
+ "- Use the domain's word, not the implementation's.",
114
+ );
115
+ expect(filled).toContain("### Terminology");
116
+ });
117
+
118
+ test("still places rows if the placeholder is gone", () => {
119
+ // Guards against the upstream template changing shape and this silently
120
+ // producing an empty table.
121
+ const noPlaceholder = TEMPLATE.replace(
122
+ "| <!-- filled during /style:apply --> | | |\n",
123
+ "",
124
+ );
125
+ const filled = fillTemplate(noPlaceholder, [
126
+ { use: "a", not: "b", because: "c" },
127
+ ]);
128
+ expect(filled).toContain("| a | b | c |");
129
+ });
130
+
131
+ test("refuses to produce a table with no rows", () => {
132
+ expect(() => fillTemplate(TEMPLATE, [])).toThrow(/No usable table rows/);
133
+ });
134
+ });
135
+
136
+ describe("terminologyStyleFile", () => {
137
+ test("keeps coding instructions on", () => {
138
+ const { frontmatter } = splitFrontmatter(terminologyStyleFile("### x"));
139
+ expect(frontmatter["keep-coding-instructions"]).toBe("true");
140
+ expect(frontmatter.name).toBe("terminology");
141
+ });
142
+ });
143
+
144
+ describe("buildPrompt", () => {
145
+ test("asks for rows only and includes the template for context", () => {
146
+ const prompt = buildPrompt(TEMPLATE);
147
+ expect(prompt).toContain("ONLY markdown table rows");
148
+ expect(prompt).toContain("No header row");
149
+ expect(prompt).toContain("### Terminology");
150
+ });
151
+ });
152
+
153
+ describe("fillTerminology", () => {
154
+ let dir: string;
155
+
156
+ beforeEach(async () => {
157
+ dir = await mkdtemp(join(tmpdir(), "claudeup-terminology-"));
158
+ });
159
+ afterEach(async () => {
160
+ await rm(dir, { recursive: true, force: true });
161
+ });
162
+
163
+ test("writes the filled style into the project so it commits with the repo", async () => {
164
+ const result = await fillTerminology({
165
+ projectPath: dir,
166
+ templateBody: TEMPLATE,
167
+ run: async () =>
168
+ "| booking | reservation | the domain says booking |\n| tenant | account | billing is per tenant |",
169
+ });
170
+
171
+ expect(result.rows).toHaveLength(2);
172
+ expect(result.path).toBe(
173
+ join(dir, ".claude", "output-styles", "terminology.md"),
174
+ );
175
+
176
+ const written = await fs.readFile(result.path, "utf8");
177
+ const { frontmatter, body } = splitFrontmatter(written);
178
+ expect(frontmatter.name).toBe("terminology");
179
+ expect(frontmatter["keep-coding-instructions"]).toBe("true");
180
+ expect(body).toContain("| booking | reservation |");
181
+ expect(body).toContain("Rules that hold regardless of the table above:");
182
+ });
183
+
184
+ test("writes nothing when the model returns no usable rows", async () => {
185
+ await expect(
186
+ fillTerminology({
187
+ projectPath: dir,
188
+ templateBody: TEMPLATE,
189
+ run: async () => "I could not determine any terminology.",
190
+ }),
191
+ ).rejects.toThrow(/no usable table rows/i);
192
+
193
+ // A half-written or empty style file is worse than none: it would show up
194
+ // under Team looking finished.
195
+ expect(await fs.pathExists(join(dir, ".claude", "output-styles"))).toBe(
196
+ false,
197
+ );
198
+ });
199
+
200
+ test("propagates a failure from the subprocess without writing", async () => {
201
+ await expect(
202
+ fillTerminology({
203
+ projectPath: dir,
204
+ templateBody: TEMPLATE,
205
+ run: async () => {
206
+ throw new Error("claude exited 1");
207
+ },
208
+ }),
209
+ ).rejects.toThrow(/claude exited 1/);
210
+ expect(await fs.pathExists(join(dir, ".claude", "output-styles"))).toBe(
211
+ false,
212
+ );
213
+ });
214
+ });