@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.
- package/LICENSE +21 -0
- package/README.md +109 -89
- package/assets/auto-tracker-save.mjs +142 -0
- package/assets/claude-code/agents/anxiety-reader-agent.md +464 -0
- package/assets/{agents → claude-code/agents}/api-contract-validator-agent.md +9 -228
- package/assets/{agents → claude-code/agents}/aristotle-analyst-agent.md +51 -4
- package/assets/{agents → claude-code/agents}/aristotle-explorer-agent.md +6 -2
- package/assets/{agents → claude-code/agents}/aristotle-forecaster-agent.md +15 -230
- package/assets/{agents → claude-code/agents}/aristotle-validator-agent.md +12 -252
- package/assets/{agents → claude-code/agents}/assumption-excavator-agent.md +21 -247
- package/assets/{agents → claude-code/agents}/code-auditor-agent.md +12 -255
- package/assets/{agents → claude-code/agents}/code-optimizer-agent.md +15 -236
- package/assets/{agents → claude-code/agents}/code-validator-agent.md +31 -300
- package/assets/claude-code/agents/docs-validator-agent.md +472 -0
- package/assets/{agents → claude-code/agents}/frontend-validator-agent.md +15 -258
- package/assets/{agents → claude-code/agents}/mcp-validator-agent.md +8 -252
- package/assets/{agents → claude-code/agents}/pre-implementation-architect-agent.md +8 -224
- package/assets/{agents → claude-code/agents}/prompt-engineer-agent.md +57 -290
- package/assets/{agents → claude-code/agents}/prompt-pattern-analyzer-agent.md +10 -225
- package/assets/{agents → claude-code/agents}/prompt-quality-validator-agent.md +11 -249
- package/assets/{agents → claude-code/agents}/public-interface-validator-agent.md +15 -268
- package/assets/claude-code/agents/release-readiness-agent.md +495 -0
- package/assets/{agents → claude-code/agents}/security-analyst-agent.md +236 -480
- package/assets/{agents → claude-code/agents}/test-architect-agent.md +16 -259
- package/assets/{agents → claude-code/agents}/type-safety-validator-agent.md +23 -266
- package/assets/{agents → claude-code/agents}/workflow-synthesis-agent.md +23 -226
- package/assets/claude-code/commands/agents/anxiety-reader.md +157 -0
- package/assets/{commands → claude-code/commands}/agents/api-contract.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/architect.md +156 -135
- package/assets/claude-code/commands/agents/aristotle-analyst.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-explorer.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-forecaster.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-validator.md +157 -0
- package/assets/{commands → claude-code/commands}/agents/assumption-excavator.md +49 -6
- package/assets/{commands → claude-code/commands}/agents/audit.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/docs-validate.md +156 -133
- package/assets/{commands → claude-code/commands}/agents/frontend.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/mcp-validate.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/optimize.md +156 -133
- package/assets/{commands → claude-code/commands}/agents/pattern-analyzer.md +150 -126
- package/assets/{commands → claude-code/commands}/agents/prompt-quality.md +155 -134
- package/assets/claude-code/commands/agents/prompt-validate.md +155 -0
- package/assets/{commands → claude-code/commands}/agents/public-interface.md +156 -134
- package/assets/{commands → claude-code/commands}/agents/release.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/security.md +156 -137
- package/assets/{commands → claude-code/commands}/agents/test-review.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/type-safety.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/validate.md +156 -134
- package/assets/claude-code/commands/agents/workflow-synthesis.md +157 -0
- package/assets/claude-code/commands/pipelines/aristotle.md +143 -0
- package/assets/claude-code/commands/pipelines/ship.md +188 -0
- package/assets/claude-code/commands/workflows/post-implementation.md +60 -0
- package/assets/claude-code/commands/workflows/pre-implementation.md +46 -0
- package/assets/claude-code/commands/workflows/prompt-audit.md +44 -0
- package/assets/codex/agents/anxiety-reader-agent.toml +462 -0
- package/assets/codex/agents/api-contract-validator-agent.toml +738 -0
- package/assets/codex/agents/aristotle-analyst-agent.toml +750 -0
- package/assets/codex/agents/aristotle-explorer-agent.toml +155 -0
- package/assets/codex/agents/aristotle-forecaster-agent.toml +449 -0
- package/assets/codex/agents/aristotle-validator-agent.toml +424 -0
- package/assets/codex/agents/assumption-excavator-agent.toml +1126 -0
- package/assets/codex/agents/code-auditor-agent.toml +815 -0
- package/assets/codex/agents/code-optimizer-agent.toml +652 -0
- package/assets/codex/agents/code-validator-agent.toml +573 -0
- package/assets/codex/agents/docs-validator-agent.toml +468 -0
- package/assets/codex/agents/frontend-validator-agent.toml +598 -0
- package/assets/codex/agents/mcp-validator-agent.toml +580 -0
- package/assets/codex/agents/pre-implementation-architect-agent.toml +817 -0
- package/assets/codex/agents/prompt-engineer-agent.toml +922 -0
- package/assets/codex/agents/prompt-pattern-analyzer-agent.toml +689 -0
- package/assets/codex/agents/prompt-quality-validator-agent.toml +777 -0
- package/assets/codex/agents/public-interface-validator-agent.toml +695 -0
- package/assets/codex/agents/release-readiness-agent.toml +491 -0
- package/assets/codex/agents/security-analyst-agent.toml +847 -0
- package/assets/codex/agents/test-architect-agent.toml +615 -0
- package/assets/codex/agents/type-safety-validator-agent.toml +686 -0
- package/assets/codex/agents/workflow-synthesis-agent.toml +631 -0
- package/assets/gemini-cli/agents/anxiety-reader-agent.md +470 -0
- package/assets/gemini-cli/agents/api-contract-validator-agent.md +747 -0
- package/assets/gemini-cli/agents/aristotle-analyst-agent.md +758 -0
- package/assets/gemini-cli/agents/aristotle-explorer-agent.md +163 -0
- package/assets/gemini-cli/agents/aristotle-forecaster-agent.md +457 -0
- package/assets/gemini-cli/agents/aristotle-validator-agent.md +432 -0
- package/assets/gemini-cli/agents/assumption-excavator-agent.md +1134 -0
- package/assets/gemini-cli/agents/code-auditor-agent.md +827 -0
- package/assets/gemini-cli/agents/code-optimizer-agent.md +661 -0
- package/assets/gemini-cli/agents/code-validator-agent.md +582 -0
- package/assets/gemini-cli/agents/docs-validator-agent.md +477 -0
- package/assets/gemini-cli/agents/frontend-validator-agent.md +610 -0
- package/assets/gemini-cli/agents/mcp-validator-agent.md +589 -0
- package/assets/gemini-cli/agents/pre-implementation-architect-agent.md +826 -0
- package/assets/gemini-cli/agents/prompt-engineer-agent.md +931 -0
- package/assets/gemini-cli/agents/prompt-pattern-analyzer-agent.md +698 -0
- package/assets/gemini-cli/agents/prompt-quality-validator-agent.md +786 -0
- package/assets/gemini-cli/agents/public-interface-validator-agent.md +707 -0
- package/assets/gemini-cli/agents/release-readiness-agent.md +500 -0
- package/assets/gemini-cli/agents/security-analyst-agent.md +859 -0
- package/assets/gemini-cli/agents/test-architect-agent.md +624 -0
- package/assets/gemini-cli/agents/type-safety-validator-agent.md +695 -0
- package/assets/gemini-cli/agents/workflow-synthesis-agent.md +639 -0
- package/assets/gemini-cli/commands/agents/anxiety-reader.toml +155 -0
- package/assets/gemini-cli/commands/agents/api-contract.toml +154 -0
- package/assets/gemini-cli/commands/agents/architect.toml +154 -0
- package/assets/gemini-cli/commands/agents/aristotle-analyst.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-explorer.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-forecaster.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-validator.toml +155 -0
- package/assets/gemini-cli/commands/agents/assumption-excavator.toml +155 -0
- package/assets/gemini-cli/commands/agents/audit.toml +154 -0
- package/assets/gemini-cli/commands/agents/docs-validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/frontend.toml +154 -0
- package/assets/gemini-cli/commands/agents/mcp-validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/optimize.toml +154 -0
- package/assets/gemini-cli/commands/agents/pattern-analyzer.toml +148 -0
- package/assets/gemini-cli/commands/agents/prompt-quality.toml +153 -0
- package/assets/gemini-cli/commands/agents/prompt-validate.toml +153 -0
- package/assets/gemini-cli/commands/agents/public-interface.toml +154 -0
- package/assets/gemini-cli/commands/agents/release.toml +154 -0
- package/assets/gemini-cli/commands/agents/security.toml +154 -0
- package/assets/gemini-cli/commands/agents/test-review.toml +154 -0
- package/assets/gemini-cli/commands/agents/type-safety.toml +154 -0
- package/assets/gemini-cli/commands/agents/validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/workflow-synthesis.toml +155 -0
- package/assets/gemini-cli/commands/pipelines/aristotle.toml +139 -0
- package/assets/gemini-cli/commands/pipelines/ship.toml +184 -0
- package/assets/gemini-cli/commands/workflows/post-implementation.toml +56 -0
- package/assets/gemini-cli/commands/workflows/pre-implementation.toml +42 -0
- package/assets/gemini-cli/commands/workflows/prompt-audit.toml +40 -0
- package/assets/opencode/agents/anxiety-reader-agent.md +472 -0
- package/assets/opencode/agents/api-contract-validator-agent.md +749 -0
- package/assets/opencode/agents/aristotle-analyst-agent.md +760 -0
- package/assets/opencode/agents/aristotle-explorer-agent.md +164 -0
- package/assets/opencode/agents/aristotle-forecaster-agent.md +459 -0
- package/assets/opencode/agents/aristotle-validator-agent.md +434 -0
- package/assets/opencode/agents/assumption-excavator-agent.md +1136 -0
- package/assets/opencode/agents/code-auditor-agent.md +826 -0
- package/assets/opencode/agents/code-optimizer-agent.md +663 -0
- package/assets/opencode/agents/code-validator-agent.md +584 -0
- package/assets/opencode/agents/docs-validator-agent.md +479 -0
- package/assets/opencode/agents/frontend-validator-agent.md +609 -0
- package/assets/opencode/agents/mcp-validator-agent.md +591 -0
- package/assets/opencode/agents/pre-implementation-architect-agent.md +828 -0
- package/assets/opencode/agents/prompt-engineer-agent.md +933 -0
- package/assets/opencode/agents/prompt-pattern-analyzer-agent.md +700 -0
- package/assets/opencode/agents/prompt-quality-validator-agent.md +788 -0
- package/assets/opencode/agents/public-interface-validator-agent.md +706 -0
- package/assets/opencode/agents/release-readiness-agent.md +502 -0
- package/assets/opencode/agents/security-analyst-agent.md +858 -0
- package/assets/opencode/agents/test-architect-agent.md +626 -0
- package/assets/opencode/agents/type-safety-validator-agent.md +697 -0
- package/assets/opencode/agents/workflow-synthesis-agent.md +641 -0
- package/dist/cli.js +22 -380
- package/dist/commands/helpers.d.ts +73 -0
- package/dist/commands/helpers.js +274 -0
- package/dist/commands/setup.d.ts +13 -0
- package/dist/commands/setup.js +93 -0
- package/dist/commands/uninstall.d.ts +3 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.js +28 -0
- package/dist/harnesses/claude-code.d.ts +8 -0
- package/dist/harnesses/claude-code.js +74 -0
- package/dist/harnesses/codex.d.ts +15 -0
- package/dist/harnesses/codex.js +54 -0
- package/dist/harnesses/gemini-cli.d.ts +12 -0
- package/dist/harnesses/gemini-cli.js +80 -0
- package/dist/harnesses/index.d.ts +27 -0
- package/dist/harnesses/index.js +54 -0
- package/dist/harnesses/opencode.d.ts +14 -0
- package/dist/harnesses/opencode.js +139 -0
- package/dist/harnesses/types.d.ts +106 -0
- package/dist/harnesses/types.js +26 -0
- package/dist/lib/agent-transform.d.ts +12 -0
- package/dist/lib/agent-transform.js +129 -0
- package/dist/lib/asset-catalog.d.ts +9 -0
- package/dist/lib/asset-catalog.js +56 -0
- package/dist/lib/atomic-write.d.ts +11 -0
- package/dist/lib/atomic-write.js +28 -0
- package/dist/lib/config-merger.d.ts +9 -2
- package/dist/lib/config-merger.js +44 -7
- package/dist/lib/display.d.ts +14 -0
- package/dist/lib/display.js +66 -0
- package/dist/lib/file-ops.d.ts +11 -0
- package/dist/lib/file-ops.js +40 -4
- package/dist/lib/hash.d.ts +1 -0
- package/dist/lib/hash.js +2 -1
- package/dist/lib/health.d.ts +2 -0
- package/dist/lib/health.js +10 -0
- package/dist/lib/manifest.d.ts +51 -5
- package/dist/lib/manifest.js +146 -13
- package/dist/lib/paths.d.ts +30 -3
- package/dist/lib/paths.js +98 -12
- package/dist/lib/settings-merger.d.ts +31 -8
- package/dist/lib/settings-merger.js +87 -24
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.js +10 -0
- package/dist/steps/agents.d.ts +4 -1
- package/dist/steps/agents.js +48 -9
- package/dist/steps/auth.js +26 -10
- package/dist/steps/cli.d.ts +53 -0
- package/dist/steps/cli.js +90 -0
- package/dist/steps/commands.d.ts +6 -1
- package/dist/steps/commands.js +36 -9
- package/dist/steps/detect.d.ts +3 -0
- package/dist/steps/detect.js +11 -0
- package/dist/steps/mcp.d.ts +6 -2
- package/dist/steps/mcp.js +39 -22
- package/dist/steps/metrics.d.ts +26 -10
- package/dist/steps/metrics.js +108 -108
- package/dist/steps/shell.d.ts +2 -0
- package/dist/steps/shell.js +26 -9
- package/dist/steps/signup.d.ts +7 -4
- package/dist/steps/signup.js +29 -20
- package/dist/steps/verify.d.ts +2 -2
- package/dist/steps/verify.js +118 -112
- package/package.json +40 -14
- package/assets/agents/docs-validator-agent.md +0 -490
- package/assets/agents/release-readiness-agent.md +0 -482
- package/assets/commands/agents/aristotle-analyst.md +0 -115
- package/assets/commands/agents/aristotle-explorer.md +0 -92
- package/assets/commands/agents/aristotle-forecaster.md +0 -114
- package/assets/commands/agents/aristotle-validator.md +0 -114
- package/assets/commands/agents/prompt-validate.md +0 -135
- package/assets/commands/agents/workflow-synthesis.md +0 -101
- package/assets/commands/workflows/aristotle.md +0 -543
- package/assets/commands/workflows/post-implementation.md +0 -577
- package/assets/commands/workflows/pre-implementation.md +0 -670
- package/assets/commands/workflows/prompt-audit.md +0 -754
- package/assets/commands/workflows/ship.md +0 -721
- package/dist/test/auth.test.d.ts +0 -1
- package/dist/test/auth.test.js +0 -43
- package/dist/test/config-io.test.d.ts +0 -1
- package/dist/test/config-io.test.js +0 -56
- package/dist/test/config-merger.test.d.ts +0 -1
- package/dist/test/config-merger.test.js +0 -94
- package/dist/test/detect.test.d.ts +0 -1
- package/dist/test/detect.test.js +0 -25
- package/dist/test/file-ops.test.d.ts +0 -1
- package/dist/test/file-ops.test.js +0 -100
- package/dist/test/hash.test.d.ts +0 -1
- package/dist/test/hash.test.js +0 -14
- package/dist/test/manifest.test.d.ts +0 -1
- package/dist/test/manifest.test.js +0 -78
- package/dist/test/paths.test.d.ts +0 -1
- package/dist/test/paths.test.js +0 -30
- package/dist/test/settings-merger.test.d.ts +0 -1
- package/dist/test/settings-merger.test.js +0 -167
- package/dist/test/shell-profile.test.d.ts +0 -1
- package/dist/test/shell-profile.test.js +0 -40
- package/dist/test/shell.test.d.ts +0 -1
- package/dist/test/shell.test.js +0 -71
- package/dist/test/signup.test.d.ts +0 -1
- package/dist/test/signup.test.js +0 -83
package/dist/test/auth.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/test/auth.test.js
DELETED
|
@@ -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 {};
|
package/dist/test/detect.test.js
DELETED
|
@@ -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
|
-
});
|
package/dist/test/hash.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/test/hash.test.js
DELETED
|
@@ -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 {};
|
package/dist/test/paths.test.js
DELETED
|
@@ -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 {};
|