@uluops/setup 0.2.0 → 0.6.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 (253) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -89
  3. package/assets/auto-tracker-save.mjs +142 -0
  4. package/assets/claude-code/agents/anxiety-reader-agent.md +464 -0
  5. package/assets/{agents → claude-code/agents}/api-contract-validator-agent.md +9 -228
  6. package/assets/{agents → claude-code/agents}/aristotle-analyst-agent.md +51 -4
  7. package/assets/{agents → claude-code/agents}/aristotle-explorer-agent.md +6 -2
  8. package/assets/{agents → claude-code/agents}/aristotle-forecaster-agent.md +15 -230
  9. package/assets/{agents → claude-code/agents}/aristotle-validator-agent.md +12 -252
  10. package/assets/{agents → claude-code/agents}/assumption-excavator-agent.md +21 -247
  11. package/assets/{agents → claude-code/agents}/code-auditor-agent.md +12 -255
  12. package/assets/{agents → claude-code/agents}/code-optimizer-agent.md +15 -236
  13. package/assets/{agents → claude-code/agents}/code-validator-agent.md +31 -300
  14. package/assets/claude-code/agents/docs-validator-agent.md +472 -0
  15. package/assets/{agents → claude-code/agents}/frontend-validator-agent.md +15 -258
  16. package/assets/{agents → claude-code/agents}/mcp-validator-agent.md +8 -252
  17. package/assets/{agents → claude-code/agents}/pre-implementation-architect-agent.md +8 -224
  18. package/assets/{agents → claude-code/agents}/prompt-engineer-agent.md +57 -290
  19. package/assets/{agents → claude-code/agents}/prompt-pattern-analyzer-agent.md +10 -225
  20. package/assets/{agents → claude-code/agents}/prompt-quality-validator-agent.md +11 -249
  21. package/assets/{agents → claude-code/agents}/public-interface-validator-agent.md +15 -268
  22. package/assets/claude-code/agents/release-readiness-agent.md +495 -0
  23. package/assets/{agents → claude-code/agents}/security-analyst-agent.md +236 -480
  24. package/assets/{agents → claude-code/agents}/test-architect-agent.md +16 -259
  25. package/assets/{agents → claude-code/agents}/type-safety-validator-agent.md +23 -266
  26. package/assets/{agents → claude-code/agents}/workflow-synthesis-agent.md +23 -226
  27. package/assets/claude-code/commands/agents/anxiety-reader.md +157 -0
  28. package/assets/{commands → claude-code/commands}/agents/api-contract.md +156 -135
  29. package/assets/{commands → claude-code/commands}/agents/architect.md +156 -135
  30. package/assets/claude-code/commands/agents/aristotle-analyst.md +157 -0
  31. package/assets/claude-code/commands/agents/aristotle-explorer.md +157 -0
  32. package/assets/claude-code/commands/agents/aristotle-forecaster.md +157 -0
  33. package/assets/claude-code/commands/agents/aristotle-validator.md +157 -0
  34. package/assets/{commands → claude-code/commands}/agents/assumption-excavator.md +49 -6
  35. package/assets/{commands → claude-code/commands}/agents/audit.md +156 -136
  36. package/assets/{commands → claude-code/commands}/agents/docs-validate.md +156 -133
  37. package/assets/{commands → claude-code/commands}/agents/frontend.md +156 -135
  38. package/assets/{commands → claude-code/commands}/agents/mcp-validate.md +156 -136
  39. package/assets/{commands → claude-code/commands}/agents/optimize.md +156 -133
  40. package/assets/{commands → claude-code/commands}/agents/pattern-analyzer.md +150 -126
  41. package/assets/{commands → claude-code/commands}/agents/prompt-quality.md +155 -134
  42. package/assets/claude-code/commands/agents/prompt-validate.md +155 -0
  43. package/assets/{commands → claude-code/commands}/agents/public-interface.md +156 -134
  44. package/assets/{commands → claude-code/commands}/agents/release.md +156 -135
  45. package/assets/{commands → claude-code/commands}/agents/security.md +156 -137
  46. package/assets/{commands → claude-code/commands}/agents/test-review.md +156 -136
  47. package/assets/{commands → claude-code/commands}/agents/type-safety.md +156 -135
  48. package/assets/{commands → claude-code/commands}/agents/validate.md +156 -134
  49. package/assets/claude-code/commands/agents/workflow-synthesis.md +157 -0
  50. package/assets/claude-code/commands/pipelines/aristotle.md +143 -0
  51. package/assets/claude-code/commands/pipelines/ship.md +188 -0
  52. package/assets/claude-code/commands/workflows/post-implementation.md +60 -0
  53. package/assets/claude-code/commands/workflows/pre-implementation.md +46 -0
  54. package/assets/claude-code/commands/workflows/prompt-audit.md +44 -0
  55. package/assets/codex/agents/anxiety-reader-agent.toml +462 -0
  56. package/assets/codex/agents/api-contract-validator-agent.toml +738 -0
  57. package/assets/codex/agents/aristotle-analyst-agent.toml +750 -0
  58. package/assets/codex/agents/aristotle-explorer-agent.toml +155 -0
  59. package/assets/codex/agents/aristotle-forecaster-agent.toml +449 -0
  60. package/assets/codex/agents/aristotle-validator-agent.toml +424 -0
  61. package/assets/codex/agents/assumption-excavator-agent.toml +1126 -0
  62. package/assets/codex/agents/code-auditor-agent.toml +815 -0
  63. package/assets/codex/agents/code-optimizer-agent.toml +652 -0
  64. package/assets/codex/agents/code-validator-agent.toml +573 -0
  65. package/assets/codex/agents/docs-validator-agent.toml +468 -0
  66. package/assets/codex/agents/frontend-validator-agent.toml +598 -0
  67. package/assets/codex/agents/mcp-validator-agent.toml +580 -0
  68. package/assets/codex/agents/pre-implementation-architect-agent.toml +817 -0
  69. package/assets/codex/agents/prompt-engineer-agent.toml +922 -0
  70. package/assets/codex/agents/prompt-pattern-analyzer-agent.toml +689 -0
  71. package/assets/codex/agents/prompt-quality-validator-agent.toml +777 -0
  72. package/assets/codex/agents/public-interface-validator-agent.toml +695 -0
  73. package/assets/codex/agents/release-readiness-agent.toml +491 -0
  74. package/assets/codex/agents/security-analyst-agent.toml +847 -0
  75. package/assets/codex/agents/test-architect-agent.toml +615 -0
  76. package/assets/codex/agents/type-safety-validator-agent.toml +686 -0
  77. package/assets/codex/agents/workflow-synthesis-agent.toml +631 -0
  78. package/assets/gemini-cli/agents/anxiety-reader-agent.md +470 -0
  79. package/assets/gemini-cli/agents/api-contract-validator-agent.md +747 -0
  80. package/assets/gemini-cli/agents/aristotle-analyst-agent.md +758 -0
  81. package/assets/gemini-cli/agents/aristotle-explorer-agent.md +163 -0
  82. package/assets/gemini-cli/agents/aristotle-forecaster-agent.md +457 -0
  83. package/assets/gemini-cli/agents/aristotle-validator-agent.md +432 -0
  84. package/assets/gemini-cli/agents/assumption-excavator-agent.md +1134 -0
  85. package/assets/gemini-cli/agents/code-auditor-agent.md +827 -0
  86. package/assets/gemini-cli/agents/code-optimizer-agent.md +661 -0
  87. package/assets/gemini-cli/agents/code-validator-agent.md +582 -0
  88. package/assets/gemini-cli/agents/docs-validator-agent.md +477 -0
  89. package/assets/gemini-cli/agents/frontend-validator-agent.md +610 -0
  90. package/assets/gemini-cli/agents/mcp-validator-agent.md +589 -0
  91. package/assets/gemini-cli/agents/pre-implementation-architect-agent.md +826 -0
  92. package/assets/gemini-cli/agents/prompt-engineer-agent.md +931 -0
  93. package/assets/gemini-cli/agents/prompt-pattern-analyzer-agent.md +698 -0
  94. package/assets/gemini-cli/agents/prompt-quality-validator-agent.md +786 -0
  95. package/assets/gemini-cli/agents/public-interface-validator-agent.md +707 -0
  96. package/assets/gemini-cli/agents/release-readiness-agent.md +500 -0
  97. package/assets/gemini-cli/agents/security-analyst-agent.md +859 -0
  98. package/assets/gemini-cli/agents/test-architect-agent.md +624 -0
  99. package/assets/gemini-cli/agents/type-safety-validator-agent.md +695 -0
  100. package/assets/gemini-cli/agents/workflow-synthesis-agent.md +639 -0
  101. package/assets/gemini-cli/commands/agents/anxiety-reader.toml +155 -0
  102. package/assets/gemini-cli/commands/agents/api-contract.toml +154 -0
  103. package/assets/gemini-cli/commands/agents/architect.toml +154 -0
  104. package/assets/gemini-cli/commands/agents/aristotle-analyst.toml +155 -0
  105. package/assets/gemini-cli/commands/agents/aristotle-explorer.toml +155 -0
  106. package/assets/gemini-cli/commands/agents/aristotle-forecaster.toml +155 -0
  107. package/assets/gemini-cli/commands/agents/aristotle-validator.toml +155 -0
  108. package/assets/gemini-cli/commands/agents/assumption-excavator.toml +155 -0
  109. package/assets/gemini-cli/commands/agents/audit.toml +154 -0
  110. package/assets/gemini-cli/commands/agents/docs-validate.toml +154 -0
  111. package/assets/gemini-cli/commands/agents/frontend.toml +154 -0
  112. package/assets/gemini-cli/commands/agents/mcp-validate.toml +154 -0
  113. package/assets/gemini-cli/commands/agents/optimize.toml +154 -0
  114. package/assets/gemini-cli/commands/agents/pattern-analyzer.toml +148 -0
  115. package/assets/gemini-cli/commands/agents/prompt-quality.toml +153 -0
  116. package/assets/gemini-cli/commands/agents/prompt-validate.toml +153 -0
  117. package/assets/gemini-cli/commands/agents/public-interface.toml +154 -0
  118. package/assets/gemini-cli/commands/agents/release.toml +154 -0
  119. package/assets/gemini-cli/commands/agents/security.toml +154 -0
  120. package/assets/gemini-cli/commands/agents/test-review.toml +154 -0
  121. package/assets/gemini-cli/commands/agents/type-safety.toml +154 -0
  122. package/assets/gemini-cli/commands/agents/validate.toml +154 -0
  123. package/assets/gemini-cli/commands/agents/workflow-synthesis.toml +155 -0
  124. package/assets/gemini-cli/commands/pipelines/aristotle.toml +139 -0
  125. package/assets/gemini-cli/commands/pipelines/ship.toml +184 -0
  126. package/assets/gemini-cli/commands/workflows/post-implementation.toml +56 -0
  127. package/assets/gemini-cli/commands/workflows/pre-implementation.toml +42 -0
  128. package/assets/gemini-cli/commands/workflows/prompt-audit.toml +40 -0
  129. package/assets/opencode/agents/anxiety-reader-agent.md +472 -0
  130. package/assets/opencode/agents/api-contract-validator-agent.md +749 -0
  131. package/assets/opencode/agents/aristotle-analyst-agent.md +760 -0
  132. package/assets/opencode/agents/aristotle-explorer-agent.md +164 -0
  133. package/assets/opencode/agents/aristotle-forecaster-agent.md +459 -0
  134. package/assets/opencode/agents/aristotle-validator-agent.md +434 -0
  135. package/assets/opencode/agents/assumption-excavator-agent.md +1136 -0
  136. package/assets/opencode/agents/code-auditor-agent.md +826 -0
  137. package/assets/opencode/agents/code-optimizer-agent.md +663 -0
  138. package/assets/opencode/agents/code-validator-agent.md +584 -0
  139. package/assets/opencode/agents/docs-validator-agent.md +479 -0
  140. package/assets/opencode/agents/frontend-validator-agent.md +609 -0
  141. package/assets/opencode/agents/mcp-validator-agent.md +591 -0
  142. package/assets/opencode/agents/pre-implementation-architect-agent.md +828 -0
  143. package/assets/opencode/agents/prompt-engineer-agent.md +933 -0
  144. package/assets/opencode/agents/prompt-pattern-analyzer-agent.md +700 -0
  145. package/assets/opencode/agents/prompt-quality-validator-agent.md +788 -0
  146. package/assets/opencode/agents/public-interface-validator-agent.md +706 -0
  147. package/assets/opencode/agents/release-readiness-agent.md +502 -0
  148. package/assets/opencode/agents/security-analyst-agent.md +858 -0
  149. package/assets/opencode/agents/test-architect-agent.md +626 -0
  150. package/assets/opencode/agents/type-safety-validator-agent.md +697 -0
  151. package/assets/opencode/agents/workflow-synthesis-agent.md +641 -0
  152. package/dist/cli.js +22 -380
  153. package/dist/commands/helpers.d.ts +73 -0
  154. package/dist/commands/helpers.js +274 -0
  155. package/dist/commands/setup.d.ts +13 -0
  156. package/dist/commands/setup.js +93 -0
  157. package/dist/commands/uninstall.d.ts +3 -0
  158. package/dist/commands/uninstall.js +126 -0
  159. package/dist/commands/verify.d.ts +1 -0
  160. package/dist/commands/verify.js +28 -0
  161. package/dist/harnesses/claude-code.d.ts +8 -0
  162. package/dist/harnesses/claude-code.js +74 -0
  163. package/dist/harnesses/codex.d.ts +15 -0
  164. package/dist/harnesses/codex.js +54 -0
  165. package/dist/harnesses/gemini-cli.d.ts +12 -0
  166. package/dist/harnesses/gemini-cli.js +80 -0
  167. package/dist/harnesses/index.d.ts +27 -0
  168. package/dist/harnesses/index.js +54 -0
  169. package/dist/harnesses/opencode.d.ts +14 -0
  170. package/dist/harnesses/opencode.js +139 -0
  171. package/dist/harnesses/types.d.ts +106 -0
  172. package/dist/harnesses/types.js +26 -0
  173. package/dist/lib/agent-transform.d.ts +12 -0
  174. package/dist/lib/agent-transform.js +129 -0
  175. package/dist/lib/asset-catalog.d.ts +9 -0
  176. package/dist/lib/asset-catalog.js +56 -0
  177. package/dist/lib/atomic-write.d.ts +11 -0
  178. package/dist/lib/atomic-write.js +28 -0
  179. package/dist/lib/config-merger.d.ts +9 -2
  180. package/dist/lib/config-merger.js +44 -7
  181. package/dist/lib/display.d.ts +14 -0
  182. package/dist/lib/display.js +66 -0
  183. package/dist/lib/file-ops.d.ts +11 -0
  184. package/dist/lib/file-ops.js +40 -4
  185. package/dist/lib/hash.d.ts +1 -0
  186. package/dist/lib/hash.js +2 -1
  187. package/dist/lib/health.d.ts +2 -0
  188. package/dist/lib/health.js +10 -0
  189. package/dist/lib/manifest.d.ts +51 -5
  190. package/dist/lib/manifest.js +146 -13
  191. package/dist/lib/paths.d.ts +30 -3
  192. package/dist/lib/paths.js +98 -12
  193. package/dist/lib/settings-merger.d.ts +31 -8
  194. package/dist/lib/settings-merger.js +87 -24
  195. package/dist/lib/version.d.ts +2 -0
  196. package/dist/lib/version.js +10 -0
  197. package/dist/steps/agents.d.ts +4 -1
  198. package/dist/steps/agents.js +48 -9
  199. package/dist/steps/auth.js +26 -10
  200. package/dist/steps/cli.d.ts +53 -0
  201. package/dist/steps/cli.js +90 -0
  202. package/dist/steps/commands.d.ts +6 -1
  203. package/dist/steps/commands.js +36 -9
  204. package/dist/steps/detect.d.ts +3 -0
  205. package/dist/steps/detect.js +11 -0
  206. package/dist/steps/mcp.d.ts +6 -2
  207. package/dist/steps/mcp.js +39 -22
  208. package/dist/steps/metrics.d.ts +26 -10
  209. package/dist/steps/metrics.js +108 -108
  210. package/dist/steps/shell.d.ts +2 -0
  211. package/dist/steps/shell.js +26 -9
  212. package/dist/steps/signup.d.ts +7 -4
  213. package/dist/steps/signup.js +29 -20
  214. package/dist/steps/verify.d.ts +2 -2
  215. package/dist/steps/verify.js +118 -112
  216. package/package.json +40 -14
  217. package/assets/agents/docs-validator-agent.md +0 -490
  218. package/assets/agents/release-readiness-agent.md +0 -482
  219. package/assets/commands/agents/aristotle-analyst.md +0 -115
  220. package/assets/commands/agents/aristotle-explorer.md +0 -92
  221. package/assets/commands/agents/aristotle-forecaster.md +0 -114
  222. package/assets/commands/agents/aristotle-validator.md +0 -114
  223. package/assets/commands/agents/prompt-validate.md +0 -135
  224. package/assets/commands/agents/workflow-synthesis.md +0 -101
  225. package/assets/commands/workflows/aristotle.md +0 -543
  226. package/assets/commands/workflows/post-implementation.md +0 -577
  227. package/assets/commands/workflows/pre-implementation.md +0 -670
  228. package/assets/commands/workflows/prompt-audit.md +0 -754
  229. package/assets/commands/workflows/ship.md +0 -721
  230. package/dist/test/auth.test.d.ts +0 -1
  231. package/dist/test/auth.test.js +0 -43
  232. package/dist/test/config-io.test.d.ts +0 -1
  233. package/dist/test/config-io.test.js +0 -56
  234. package/dist/test/config-merger.test.d.ts +0 -1
  235. package/dist/test/config-merger.test.js +0 -94
  236. package/dist/test/detect.test.d.ts +0 -1
  237. package/dist/test/detect.test.js +0 -25
  238. package/dist/test/file-ops.test.d.ts +0 -1
  239. package/dist/test/file-ops.test.js +0 -100
  240. package/dist/test/hash.test.d.ts +0 -1
  241. package/dist/test/hash.test.js +0 -14
  242. package/dist/test/manifest.test.d.ts +0 -1
  243. package/dist/test/manifest.test.js +0 -78
  244. package/dist/test/paths.test.d.ts +0 -1
  245. package/dist/test/paths.test.js +0 -30
  246. package/dist/test/settings-merger.test.d.ts +0 -1
  247. package/dist/test/settings-merger.test.js +0 -167
  248. package/dist/test/shell-profile.test.d.ts +0 -1
  249. package/dist/test/shell-profile.test.js +0 -40
  250. package/dist/test/shell.test.d.ts +0 -1
  251. package/dist/test/shell.test.js +0 -71
  252. package/dist/test/signup.test.d.ts +0 -1
  253. package/dist/test/signup.test.js +0 -83
@@ -1 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- import { describe, it, expect, vi, afterEach } from "vitest";
2
- import { resolveApiKey } from "../steps/auth.js";
3
- afterEach(() => {
4
- vi.unstubAllEnvs();
5
- });
6
- describe("resolveApiKey", () => {
7
- it("uses --api-key flag when provided", async () => {
8
- const result = await resolveApiKey({
9
- apiKeyFlag: "ulr_test123",
10
- skipValidation: true,
11
- });
12
- expect(result.apiKey).toBe("ulr_test123");
13
- expect(result.email).toBeNull();
14
- });
15
- it("uses ULUOPS_API_KEY env var when no flag", async () => {
16
- vi.stubEnv("ULUOPS_API_KEY", "ulr_envkey");
17
- const result = await resolveApiKey({
18
- skipValidation: true,
19
- });
20
- expect(result.apiKey).toBe("ulr_envkey");
21
- });
22
- it("throws when key does not start with ulr_", async () => {
23
- await expect(resolveApiKey({
24
- apiKeyFlag: "bad_key",
25
- skipValidation: true,
26
- })).rejects.toThrow("API keys start with ulr_");
27
- });
28
- it("throws when no key found and not interactive", async () => {
29
- vi.stubEnv("ULUOPS_API_KEY", "");
30
- await expect(resolveApiKey({
31
- interactive: false,
32
- skipValidation: true,
33
- })).rejects.toThrow("No API key found");
34
- });
35
- it("flag takes priority over env var", async () => {
36
- vi.stubEnv("ULUOPS_API_KEY", "ulr_envkey");
37
- const result = await resolveApiKey({
38
- apiKeyFlag: "ulr_flagkey",
39
- skipValidation: true,
40
- });
41
- expect(result.apiKey).toBe("ulr_flagkey");
42
- });
43
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,56 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { writeFile, unlink, mkdtemp, readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { readConfig, writeConfig } from "../lib/config-merger.js";
6
- let tmpDir;
7
- beforeEach(async () => {
8
- tmpDir = await mkdtemp(join(tmpdir(), "uluops-config-io-"));
9
- });
10
- afterEach(async () => {
11
- try {
12
- const { readdir } = await import("node:fs/promises");
13
- for (const f of await readdir(tmpDir)) {
14
- await unlink(join(tmpDir, f));
15
- }
16
- }
17
- catch {
18
- // cleanup best-effort
19
- }
20
- });
21
- describe("readConfig", () => {
22
- it("returns empty object when file does not exist", async () => {
23
- const result = await readConfig(join(tmpDir, "nonexistent.json"));
24
- expect(result).toEqual({});
25
- });
26
- it("returns empty object on malformed JSON", async () => {
27
- const path = join(tmpDir, "bad.json");
28
- await writeFile(path, "{ invalid }");
29
- const result = await readConfig(path);
30
- expect(result).toEqual({});
31
- });
32
- it("parses valid JSON correctly", async () => {
33
- const path = join(tmpDir, "good.json");
34
- await writeFile(path, JSON.stringify({ mcpServers: {}, numStartups: 5 }));
35
- const result = await readConfig(path);
36
- expect(result.mcpServers).toEqual({});
37
- expect(result.numStartups).toBe(5);
38
- });
39
- });
40
- describe("writeConfig", () => {
41
- it("writes formatted JSON with trailing newline", async () => {
42
- const path = join(tmpDir, "output.json");
43
- await writeConfig(path, { mcpServers: {}, foo: "bar" });
44
- const raw = await readFile(path, "utf-8");
45
- expect(raw).toMatch(/\n$/);
46
- const parsed = JSON.parse(raw);
47
- expect(parsed.foo).toBe("bar");
48
- });
49
- it("round-trips through readConfig", async () => {
50
- const path = join(tmpDir, "roundtrip.json");
51
- const config = { mcpServers: { test: { command: "echo", args: [], env: {} } }, extra: true };
52
- await writeConfig(path, config);
53
- const loaded = await readConfig(path);
54
- expect(loaded).toEqual(config);
55
- });
56
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,94 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { mergeUluopsMcp, removeUluopsMcp } from "../lib/config-merger.js";
3
- describe("mergeUluopsMcp", () => {
4
- it("adds both MCP servers to empty config", () => {
5
- const result = mergeUluopsMcp({}, "ulr_test123");
6
- expect(result.mcpServers).toBeDefined();
7
- expect(result.mcpServers["uluops-tracker"]).toMatchObject({
8
- command: "npx",
9
- args: ["-y", "uluops-tracker-mcp-client"],
10
- env: {
11
- ULUOPS_TRACKER_API_URL: "https://api.uluops.ai/api/v1",
12
- ULUOPS_TRACKER_API_KEY: "ulr_test123",
13
- },
14
- });
15
- expect(result.mcpServers["uluops-registry"]).toMatchObject({
16
- command: "npx",
17
- args: ["-y", "uluops-registry-mcp-client"],
18
- env: {
19
- ULUOPS_REGISTRY_URL: "https://api.uluops.ai/api/v1/registry",
20
- ULUOPS_API_KEY: "ulr_test123",
21
- },
22
- });
23
- });
24
- it("preserves existing non-UluOps MCP servers", () => {
25
- const config = {
26
- mcpServers: {
27
- "other-server": { command: "node", args: ["./other.js"], env: {} },
28
- },
29
- };
30
- const result = mergeUluopsMcp(config, "ulr_abc");
31
- expect(result.mcpServers["other-server"]).toBeDefined();
32
- expect(result.mcpServers["uluops-tracker"]).toBeDefined();
33
- });
34
- it("preserves all top-level non-mcpServers keys", () => {
35
- const config = {
36
- numStartups: 42,
37
- tipsHistory: ["tip1"],
38
- mcpServers: {},
39
- };
40
- const result = mergeUluopsMcp(config, "ulr_abc");
41
- expect(result.numStartups).toBe(42);
42
- expect(result.tipsHistory).toEqual(["tip1"]);
43
- });
44
- it("overwrites existing UluOps servers with new API key", () => {
45
- const config = {
46
- mcpServers: {
47
- "uluops-registry": {
48
- command: "npx",
49
- args: ["-y", "uluops-registry-mcp-client"],
50
- env: { ULUOPS_API_KEY: "ulr_old", ULUOPS_REGISTRY_URL: "https://api.uluops.ai/api/v1/registry" },
51
- },
52
- },
53
- };
54
- const result = mergeUluopsMcp(config, "ulr_new");
55
- expect(result.mcpServers["uluops-registry"].env["ULUOPS_API_KEY"]).toBe("ulr_new");
56
- });
57
- });
58
- describe("removeUluopsMcp", () => {
59
- it("removes both UluOps servers", () => {
60
- const config = {
61
- mcpServers: {
62
- "uluops-tracker": { command: "npx", args: [], env: {} },
63
- "uluops-registry": { command: "npx", args: [], env: {} },
64
- },
65
- };
66
- const result = removeUluopsMcp(config);
67
- expect(result.mcpServers).toBeUndefined();
68
- });
69
- it("preserves non-UluOps servers when removing", () => {
70
- const config = {
71
- mcpServers: {
72
- "uluops-tracker": { command: "npx", args: [], env: {} },
73
- "other-server": { command: "node", args: [], env: {} },
74
- },
75
- };
76
- const result = removeUluopsMcp(config);
77
- expect(result.mcpServers["other-server"]).toBeDefined();
78
- expect(result.mcpServers["uluops-tracker"]).toBeUndefined();
79
- });
80
- it("preserves top-level keys other than mcpServers", () => {
81
- const config = {
82
- numStartups: 5,
83
- mcpServers: {
84
- "uluops-tracker": { command: "npx", args: [], env: {} },
85
- },
86
- };
87
- const result = removeUluopsMcp(config);
88
- expect(result.numStartups).toBe(5);
89
- });
90
- it("handles config with no mcpServers key", () => {
91
- const result = removeUluopsMcp({ numStartups: 1 });
92
- expect(result.mcpServers).toBeUndefined();
93
- });
94
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { detect } from "../steps/detect.js";
3
- describe("detect", () => {
4
- it("returns a valid Environment object", async () => {
5
- const env = await detect();
6
- expect(env).toHaveProperty("os");
7
- expect(env).toHaveProperty("isWsl");
8
- expect(env).toHaveProperty("shell");
9
- expect(env).toHaveProperty("shellProfile");
10
- expect(env).toHaveProperty("nodeVersion");
11
- expect(env).toHaveProperty("claudeHomeExists");
12
- });
13
- it("nodeVersion matches process.version", async () => {
14
- const env = await detect();
15
- expect(env.nodeVersion).toBe(process.version);
16
- });
17
- it("os is one of the supported platforms", async () => {
18
- const env = await detect();
19
- expect(["linux", "darwin", "win32"]).toContain(env.os);
20
- });
21
- it("claudeHomeExists is a boolean", async () => {
22
- const env = await detect();
23
- expect(typeof env.claudeHomeExists).toBe("boolean");
24
- });
25
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,100 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { writeFile, readFile, mkdir, mkdtemp, readdir } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { copyIfChanged, unlinkFiles, syncAssets } from "../lib/file-ops.js";
6
- let tmpDir;
7
- let srcDir;
8
- let destDir;
9
- beforeEach(async () => {
10
- tmpDir = await mkdtemp(join(tmpdir(), "uluops-fileops-"));
11
- srcDir = join(tmpDir, "src");
12
- destDir = join(tmpDir, "dest");
13
- await mkdir(srcDir, { recursive: true });
14
- await mkdir(destDir, { recursive: true });
15
- });
16
- describe("copyIfChanged", () => {
17
- it("copies file when destination does not exist", async () => {
18
- await writeFile(join(srcDir, "a.md"), "content A");
19
- const result = await copyIfChanged(join(srcDir, "a.md"), join(destDir, "a.md"), false);
20
- expect(result).toBe("copied");
21
- expect(await readFile(join(destDir, "a.md"), "utf-8")).toBe("content A");
22
- });
23
- it("skips file when content is identical", async () => {
24
- await writeFile(join(srcDir, "a.md"), "same");
25
- await writeFile(join(destDir, "a.md"), "same");
26
- const result = await copyIfChanged(join(srcDir, "a.md"), join(destDir, "a.md"), false);
27
- expect(result).toBe("skipped");
28
- });
29
- it("copies file when content differs", async () => {
30
- await writeFile(join(srcDir, "a.md"), "new content");
31
- await writeFile(join(destDir, "a.md"), "old content");
32
- const result = await copyIfChanged(join(srcDir, "a.md"), join(destDir, "a.md"), false);
33
- expect(result).toBe("copied");
34
- expect(await readFile(join(destDir, "a.md"), "utf-8")).toBe("new content");
35
- });
36
- it("does not write in dry-run mode", async () => {
37
- await writeFile(join(srcDir, "a.md"), "content");
38
- const result = await copyIfChanged(join(srcDir, "a.md"), join(destDir, "a.md"), true);
39
- expect(result).toBe("copied");
40
- // File should NOT exist
41
- const files = await readdir(destDir);
42
- expect(files).not.toContain("a.md");
43
- });
44
- });
45
- describe("unlinkFiles", () => {
46
- it("removes listed files and returns count", async () => {
47
- await writeFile(join(destDir, "a.md"), "a");
48
- await writeFile(join(destDir, "b.md"), "b");
49
- const removed = await unlinkFiles(destDir, ["a.md", "b.md"]);
50
- expect(removed).toBe(2);
51
- expect(await readdir(destDir)).toEqual([]);
52
- });
53
- it("returns 0 for already-missing files", async () => {
54
- const removed = await unlinkFiles(destDir, ["nonexistent.md"]);
55
- expect(removed).toBe(0);
56
- });
57
- });
58
- describe("syncAssets", () => {
59
- it("copies all .md files from source to destination", async () => {
60
- await writeFile(join(srcDir, "one.md"), "one");
61
- await writeFile(join(srcDir, "two.md"), "two");
62
- await writeFile(join(srcDir, "skip.txt"), "not md");
63
- const result = await syncAssets({ srcDir, destDir, dryRun: false });
64
- expect(result.copied).toBe(2);
65
- expect(result.files).toContain("one.md");
66
- expect(result.files).toContain("two.md");
67
- expect(result.files).not.toContain("skip.txt");
68
- });
69
- it("skips unchanged files on second run", async () => {
70
- await writeFile(join(srcDir, "one.md"), "content");
71
- await syncAssets({ srcDir, destDir, dryRun: false });
72
- const result = await syncAssets({ srcDir, destDir, dryRun: false });
73
- expect(result.copied).toBe(0);
74
- expect(result.skipped).toBe(1);
75
- });
76
- it("removes old manifest files no longer in source", async () => {
77
- await writeFile(join(srcDir, "keep.md"), "keep");
78
- await writeFile(join(destDir, "removed.md"), "old");
79
- const result = await syncAssets({
80
- srcDir,
81
- destDir,
82
- dryRun: false,
83
- oldManifestFiles: ["keep.md", "removed.md"],
84
- });
85
- expect(result.removed).toBe(1);
86
- expect(await readdir(destDir)).toContain("keep.md");
87
- expect(await readdir(destDir)).not.toContain("removed.md");
88
- });
89
- it("creates destination directory if missing", async () => {
90
- const newDest = join(tmpDir, "newdir");
91
- await writeFile(join(srcDir, "a.md"), "a");
92
- const result = await syncAssets({
93
- srcDir,
94
- destDir: newDest,
95
- dryRun: false,
96
- });
97
- expect(result.copied).toBe(1);
98
- expect(await readdir(newDest)).toContain("a.md");
99
- });
100
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,14 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { fileHash } from "../lib/hash.js";
3
- describe("fileHash", () => {
4
- it("returns a 12-character hex string", () => {
5
- const hash = fileHash("hello world");
6
- expect(hash).toMatch(/^[0-9a-f]{12}$/);
7
- });
8
- it("returns same hash for same content", () => {
9
- expect(fileHash("test")).toBe(fileHash("test"));
10
- });
11
- it("returns different hash for different content", () => {
12
- expect(fileHash("a")).not.toBe(fileHash("b"));
13
- });
14
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,78 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import { writeFile, unlink, mkdir } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- // We need to control the manifest path, so we mock paths.js
6
- const tmpDir = join(tmpdir(), "uluops-manifest-test-" + Date.now());
7
- const manifestPath = join(tmpDir, "uluops-manifest.json");
8
- vi.mock("../lib/paths.js", async (importOriginal) => {
9
- const original = await importOriginal();
10
- return {
11
- ...original,
12
- getManifestPath: () => manifestPath,
13
- };
14
- });
15
- // Import after mock is set up
16
- const { loadManifest, saveManifest, deleteManifest } = await import("../lib/manifest.js");
17
- const sampleManifest = {
18
- version: "0.1.0",
19
- installedAt: "2026-03-08T00:00:00.000Z",
20
- mcpScope: "global",
21
- mcpConfigPath: "/home/user/.claude.json",
22
- defsScope: "global",
23
- defsPath: "/home/user/.claude",
24
- shellModified: false,
25
- agents: ["code-validator-agent.md"],
26
- commands: ["agents/validate.md"],
27
- };
28
- beforeEach(async () => {
29
- await mkdir(tmpDir, { recursive: true });
30
- });
31
- afterEach(async () => {
32
- try {
33
- await unlink(manifestPath);
34
- }
35
- catch {
36
- // may not exist
37
- }
38
- });
39
- describe("loadManifest", () => {
40
- it("returns null when manifest does not exist", async () => {
41
- const result = await loadManifest();
42
- expect(result).toBeNull();
43
- });
44
- it("returns parsed manifest when file exists", async () => {
45
- await writeFile(manifestPath, JSON.stringify(sampleManifest, null, 2));
46
- const result = await loadManifest();
47
- expect(result).toEqual(sampleManifest);
48
- });
49
- it("returns null on malformed JSON", async () => {
50
- await writeFile(manifestPath, "{ invalid json }");
51
- const result = await loadManifest();
52
- expect(result).toBeNull();
53
- });
54
- });
55
- describe("saveManifest", () => {
56
- it("writes manifest as formatted JSON", async () => {
57
- await saveManifest(sampleManifest);
58
- const raw = await import("node:fs/promises").then((fs) => fs.readFile(manifestPath, "utf-8"));
59
- const parsed = JSON.parse(raw);
60
- expect(parsed).toEqual(sampleManifest);
61
- });
62
- it("round-trips correctly through save and load", async () => {
63
- await saveManifest(sampleManifest);
64
- const loaded = await loadManifest();
65
- expect(loaded).toEqual(sampleManifest);
66
- });
67
- });
68
- describe("deleteManifest", () => {
69
- it("deletes existing manifest", async () => {
70
- await writeFile(manifestPath, JSON.stringify(sampleManifest));
71
- await deleteManifest();
72
- const result = await loadManifest();
73
- expect(result).toBeNull();
74
- });
75
- it("does not throw if manifest does not exist", async () => {
76
- await expect(deleteManifest()).resolves.toBeUndefined();
77
- });
78
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,30 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { getClaudeHome, getClaudeJsonPath, getLocalMcpPath, getManifestPath, getAgentsDir, getCommandsDir, } from "../lib/paths.js";
5
- describe("path resolution", () => {
6
- it("getClaudeHome returns ~/.claude", () => {
7
- expect(getClaudeHome()).toBe(join(homedir(), ".claude"));
8
- });
9
- it("getClaudeJsonPath returns ~/.claude.json", () => {
10
- expect(getClaudeJsonPath()).toBe(join(homedir(), ".claude.json"));
11
- });
12
- it("getLocalMcpPath returns .mcp.json in cwd", () => {
13
- expect(getLocalMcpPath()).toBe(join(process.cwd(), ".mcp.json"));
14
- });
15
- it("getManifestPath returns ~/.claude/uluops-manifest.json", () => {
16
- expect(getManifestPath()).toBe(join(homedir(), ".claude", "uluops-manifest.json"));
17
- });
18
- it("getAgentsDir returns ~/.claude/agents when not local", () => {
19
- expect(getAgentsDir(false)).toBe(join(homedir(), ".claude", "agents"));
20
- });
21
- it("getAgentsDir returns ./uluops/agents when local", () => {
22
- expect(getAgentsDir(true)).toBe(join(process.cwd(), "uluops", "agents"));
23
- });
24
- it("getCommandsDir returns ~/.claude/commands when not local", () => {
25
- expect(getCommandsDir(false)).toBe(join(homedir(), ".claude", "commands"));
26
- });
27
- it("getCommandsDir returns ./uluops/commands when local", () => {
28
- expect(getCommandsDir(true)).toBe(join(process.cwd(), "uluops", "commands"));
29
- });
30
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,167 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { mergeUluopsHook, removeUluopsHook, hasUluopsHook, } from "../lib/settings-merger.js";
3
- describe("settings-merger", () => {
4
- describe("mergeUluopsHook", () => {
5
- it("should add hook to empty settings", () => {
6
- const result = mergeUluopsHook({}, "node ~/.claude/tools/agent-metrics/dist/hook.js");
7
- expect(result.hooks).toBeDefined();
8
- expect(result.hooks["SubagentStop"]).toHaveLength(1);
9
- expect(result.hooks["SubagentStop"][0].hooks[0].command).toContain("tools/agent-metrics");
10
- expect(result.hooks["SubagentStop"][0].hooks[0].timeout).toBe(30);
11
- });
12
- it("should preserve existing permissions", () => {
13
- const settings = {
14
- permissions: { allow: ["Bash(curl:*)"] },
15
- };
16
- const result = mergeUluopsHook(settings, "node ~/.claude/tools/agent-metrics/dist/hook.js");
17
- expect(result.permissions).toEqual({ allow: ["Bash(curl:*)"] });
18
- expect(result.hooks["SubagentStop"]).toHaveLength(1);
19
- });
20
- it("should preserve non-UluOps hooks", () => {
21
- const settings = {
22
- hooks: {
23
- SubagentStop: [
24
- {
25
- hooks: [
26
- { type: "command", command: "echo custom-hook" },
27
- ],
28
- },
29
- ],
30
- },
31
- };
32
- const result = mergeUluopsHook(settings, "node ~/.claude/tools/agent-metrics/dist/hook.js");
33
- // Should have both: custom + UluOps
34
- expect(result.hooks["SubagentStop"]).toHaveLength(2);
35
- expect(result.hooks["SubagentStop"][0].hooks[0].command).toBe("echo custom-hook");
36
- expect(result.hooks["SubagentStop"][1].hooks[0].command).toContain("tools/agent-metrics");
37
- });
38
- it("should replace existing UluOps hook on re-run", () => {
39
- const settings = {
40
- hooks: {
41
- SubagentStop: [
42
- {
43
- hooks: [
44
- {
45
- type: "command",
46
- command: "node /old/path/tools/agent-metrics/dist/hook.js",
47
- },
48
- ],
49
- },
50
- ],
51
- },
52
- };
53
- const result = mergeUluopsHook(settings, "node ~/.claude/tools/agent-metrics/dist/hook.js");
54
- // Should replace, not duplicate
55
- expect(result.hooks["SubagentStop"]).toHaveLength(1);
56
- expect(result.hooks["SubagentStop"][0].hooks[0].command).toContain("~/.claude/tools/agent-metrics");
57
- });
58
- it("should preserve other hook event types", () => {
59
- const settings = {
60
- hooks: {
61
- PreToolUse: [{ hooks: [{ type: "command", command: "echo pre" }] }],
62
- },
63
- };
64
- const result = mergeUluopsHook(settings, "node ~/.claude/tools/agent-metrics/dist/hook.js");
65
- expect(result.hooks["PreToolUse"]).toHaveLength(1);
66
- expect(result.hooks["SubagentStop"]).toHaveLength(1);
67
- });
68
- });
69
- describe("removeUluopsHook", () => {
70
- it("should remove UluOps hook and preserve permissions", () => {
71
- const settings = {
72
- permissions: { allow: ["Bash(curl:*)"] },
73
- hooks: {
74
- SubagentStop: [
75
- {
76
- hooks: [
77
- {
78
- type: "command",
79
- command: "node ~/.claude/tools/agent-metrics/dist/hook.js",
80
- timeout: 30,
81
- },
82
- ],
83
- },
84
- ],
85
- },
86
- };
87
- const result = removeUluopsHook(settings);
88
- expect(result.permissions).toEqual({ allow: ["Bash(curl:*)"] });
89
- expect(result.hooks).toBeUndefined();
90
- });
91
- it("should preserve non-UluOps hooks", () => {
92
- const settings = {
93
- hooks: {
94
- SubagentStop: [
95
- { hooks: [{ type: "command", command: "echo custom" }] },
96
- {
97
- hooks: [
98
- {
99
- type: "command",
100
- command: "node ~/.claude/tools/agent-metrics/dist/hook.js",
101
- },
102
- ],
103
- },
104
- ],
105
- },
106
- };
107
- const result = removeUluopsHook(settings);
108
- expect(result.hooks["SubagentStop"]).toHaveLength(1);
109
- expect(result.hooks["SubagentStop"][0].hooks[0].command).toBe("echo custom");
110
- });
111
- it("should handle settings with no hooks", () => {
112
- const settings = { permissions: { allow: [] } };
113
- const result = removeUluopsHook(settings);
114
- expect(result).toEqual({ permissions: { allow: [] } });
115
- });
116
- it("should clean up empty hooks object", () => {
117
- const settings = {
118
- hooks: {
119
- SubagentStop: [
120
- {
121
- hooks: [
122
- {
123
- type: "command",
124
- command: "node /some/tools/agent-metrics/hook.js",
125
- },
126
- ],
127
- },
128
- ],
129
- },
130
- };
131
- const result = removeUluopsHook(settings);
132
- expect(result.hooks).toBeUndefined();
133
- });
134
- });
135
- describe("hasUluopsHook", () => {
136
- it("should return true when UluOps hook is present", () => {
137
- const settings = {
138
- hooks: {
139
- SubagentStop: [
140
- {
141
- hooks: [
142
- {
143
- type: "command",
144
- command: "node ~/.claude/tools/agent-metrics/dist/hook.js",
145
- },
146
- ],
147
- },
148
- ],
149
- },
150
- };
151
- expect(hasUluopsHook(settings)).toBe(true);
152
- });
153
- it("should return false when no hooks exist", () => {
154
- expect(hasUluopsHook({})).toBe(false);
155
- });
156
- it("should return false when only non-UluOps hooks exist", () => {
157
- const settings = {
158
- hooks: {
159
- SubagentStop: [
160
- { hooks: [{ type: "command", command: "echo custom" }] },
161
- ],
162
- },
163
- };
164
- expect(hasUluopsHook(settings)).toBe(false);
165
- });
166
- });
167
- });
@@ -1 +0,0 @@
1
- export {};