swarmkit 0.0.6 → 0.0.7
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/dist/cli.js +2 -0
- package/dist/commands/configure/configure.d.ts +5 -0
- package/dist/commands/configure/configure.js +354 -0
- package/dist/commands/configure/configure.test.d.ts +1 -0
- package/dist/commands/configure/configure.test.js +539 -0
- package/dist/commands/configure/read-config.d.ts +12 -0
- package/dist/commands/configure/read-config.js +81 -0
- package/dist/commands/configure.d.ts +2 -0
- package/dist/commands/configure.js +14 -0
- package/dist/commands/init/phases/configure.js +0 -21
- package/dist/commands/init/phases/global-setup.d.ts +1 -1
- package/dist/commands/init/phases/global-setup.js +22 -44
- package/dist/commands/init/phases/integrations.d.ts +16 -0
- package/dist/commands/init/phases/integrations.js +172 -0
- package/dist/commands/init/phases/package-config.d.ts +10 -0
- package/dist/commands/init/phases/package-config.js +117 -0
- package/dist/commands/init/phases/phases.test.d.ts +1 -0
- package/dist/commands/init/phases/phases.test.js +711 -0
- package/dist/commands/init/phases/project.js +17 -0
- package/dist/commands/init/phases/review.d.ts +8 -0
- package/dist/commands/init/phases/review.js +79 -0
- package/dist/commands/init/phases/use-case.js +41 -27
- package/dist/commands/init/phases/wizard-flow.test.d.ts +1 -0
- package/dist/commands/init/phases/wizard-flow.test.js +657 -0
- package/dist/commands/init/phases/wizard-modes.test.d.ts +1 -0
- package/dist/commands/init/phases/wizard-modes.test.js +270 -0
- package/dist/commands/init/state.d.ts +31 -1
- package/dist/commands/init/state.js +4 -0
- package/dist/commands/init/state.test.js +7 -0
- package/dist/commands/init/wizard.d.ts +1 -0
- package/dist/commands/init/wizard.js +31 -23
- package/dist/commands/init.js +2 -0
- package/dist/packages/registry.d.ts +66 -0
- package/dist/packages/registry.js +258 -0
- package/dist/packages/setup.d.ts +42 -0
- package/dist/packages/setup.js +244 -15
- package/dist/packages/setup.test.js +520 -13
- package/package.json +1 -1
|
@@ -19,7 +19,8 @@ vi.mock("node:os", async () => {
|
|
|
19
19
|
const actual = await import("node:os");
|
|
20
20
|
return { ...actual, homedir: () => testHome };
|
|
21
21
|
});
|
|
22
|
-
const { PROJECT_CONFIG_DIRS, FLAT_PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, GLOBAL_CONFIG_DIRS, isProjectInit, isGlobalInit, initProjectPackage, initGlobalPackage, } = await import("./setup.js");
|
|
22
|
+
const { PROJECT_CONFIG_DIRS, FLAT_PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, GLOBAL_CONFIG_DIRS, isProjectInit, isGlobalInit, initProjectPackage, initGlobalPackage, runPackageWizard, } = await import("./setup.js");
|
|
23
|
+
import { createEmptyState } from "../commands/init/state.js";
|
|
23
24
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
24
25
|
let testDir;
|
|
25
26
|
beforeEach(() => {
|
|
@@ -59,6 +60,7 @@ function projectCtx(overrides = {}) {
|
|
|
59
60
|
embeddingProvider: overrides.embeddingProvider ?? null,
|
|
60
61
|
apiKeys: overrides.apiKeys ?? {},
|
|
61
62
|
usePrefix: overrides.usePrefix ?? true,
|
|
63
|
+
packageConfigs: overrides.packageConfigs,
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
function globalCtx(overrides = {}) {
|
|
@@ -66,6 +68,7 @@ function globalCtx(overrides = {}) {
|
|
|
66
68
|
packages: overrides.packages ?? [],
|
|
67
69
|
embeddingProvider: overrides.embeddingProvider ?? null,
|
|
68
70
|
apiKeys: overrides.apiKeys ?? {},
|
|
71
|
+
packageConfigs: overrides.packageConfigs,
|
|
69
72
|
};
|
|
70
73
|
}
|
|
71
74
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -88,7 +91,7 @@ describe("constants", () => {
|
|
|
88
91
|
}
|
|
89
92
|
});
|
|
90
93
|
it("GLOBAL_CONFIG_DIRS maps all global packages", () => {
|
|
91
|
-
expect(Object.keys(GLOBAL_CONFIG_DIRS).sort()).toEqual(["openhive", "skill-tree"]);
|
|
94
|
+
expect(Object.keys(GLOBAL_CONFIG_DIRS).sort()).toEqual(["claude-code-swarm", "openhive", "skill-tree"]);
|
|
92
95
|
});
|
|
93
96
|
it("no overlap between project and global config dirs", () => {
|
|
94
97
|
const projectDirs = new Set(Object.values(PROJECT_CONFIG_DIRS));
|
|
@@ -258,12 +261,12 @@ describe("initProjectPackage — cognitive-core (real CLI)", async () => {
|
|
|
258
261
|
createProject("test-cc");
|
|
259
262
|
const result = await initProjectPackage("cognitive-core", projectCtx({ cwd: testDir, packages: ["cognitive-core"] }));
|
|
260
263
|
expect(result.success).toBe(true);
|
|
261
|
-
// Verify directory structure
|
|
264
|
+
// Verify directory structure (cognitive-core v2 uses team-* prefixed names)
|
|
262
265
|
expect(existsSync(join(testDir, ".swarm", "cognitive-core"))).toBe(true);
|
|
263
|
-
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "experiences"))).toBe(true);
|
|
264
|
-
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "playbooks"))).toBe(true);
|
|
265
|
-
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "meta-strategies"))).toBe(true);
|
|
266
|
-
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "meta-observations"))).toBe(true);
|
|
266
|
+
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "team-experiences"))).toBe(true);
|
|
267
|
+
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "team-playbooks"))).toBe(true);
|
|
268
|
+
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "team-meta-strategies"))).toBe(true);
|
|
269
|
+
expect(existsSync(join(testDir, ".swarm", "cognitive-core", "team-meta-observations"))).toBe(true);
|
|
267
270
|
});
|
|
268
271
|
});
|
|
269
272
|
// ─── Project: self-driving-repo ──────────────────────────────────────────────
|
|
@@ -528,16 +531,43 @@ describe("initGlobalPackage — sessionlog", () => {
|
|
|
528
531
|
});
|
|
529
532
|
});
|
|
530
533
|
});
|
|
531
|
-
// ─── Global: claude-code-swarm (
|
|
534
|
+
// ─── Global: claude-code-swarm (global config directory) ─────────────────────
|
|
532
535
|
describe("initGlobalPackage — claude-code-swarm", () => {
|
|
533
|
-
it("
|
|
536
|
+
it("creates ~/.swarmkit/claude-swarm/ with default config.json", async () => {
|
|
534
537
|
const result = await initGlobalPackage("claude-code-swarm", globalCtx({ packages: ["claude-code-swarm"] }));
|
|
535
|
-
expect(result).
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
538
|
+
expect(result.success).toBe(true);
|
|
539
|
+
expect(result.package).toBe("claude-code-swarm");
|
|
540
|
+
const configDir = join(testHome, ".claude-swarm");
|
|
541
|
+
expect(existsSync(configDir)).toBe(true);
|
|
542
|
+
const configPath = join(configDir, "config.json");
|
|
543
|
+
expect(existsSync(configPath)).toBe(true);
|
|
544
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
545
|
+
expect(config).toEqual({
|
|
546
|
+
map: {
|
|
547
|
+
server: "",
|
|
548
|
+
sidecar: "session",
|
|
549
|
+
},
|
|
550
|
+
sessionlog: {
|
|
551
|
+
enabled: false,
|
|
552
|
+
sync: "off",
|
|
553
|
+
},
|
|
539
554
|
});
|
|
540
555
|
});
|
|
556
|
+
it("does not overwrite existing global config.json", async () => {
|
|
557
|
+
const configDir = join(testHome, ".claude-swarm");
|
|
558
|
+
mkdirSync(configDir, { recursive: true });
|
|
559
|
+
writeFileSync(join(configDir, "config.json"), JSON.stringify({ map: { server: "ws://my-server:9090" } }));
|
|
560
|
+
await initGlobalPackage("claude-code-swarm", globalCtx({ packages: ["claude-code-swarm"] }));
|
|
561
|
+
const config = JSON.parse(readFileSync(join(configDir, "config.json"), "utf-8"));
|
|
562
|
+
expect(config.map.server).toBe("ws://my-server:9090");
|
|
563
|
+
});
|
|
564
|
+
it("isGlobalInit returns true when global config exists", async () => {
|
|
565
|
+
await initGlobalPackage("claude-code-swarm", globalCtx({ packages: ["claude-code-swarm"] }));
|
|
566
|
+
expect(isGlobalInit("claude-code-swarm")).toBe(true);
|
|
567
|
+
});
|
|
568
|
+
it("isGlobalInit returns false when no global config", () => {
|
|
569
|
+
expect(isGlobalInit("claude-code-swarm")).toBe(false);
|
|
570
|
+
});
|
|
541
571
|
});
|
|
542
572
|
// ─── Global: unknown ─────────────────────────────────────────────────────────
|
|
543
573
|
describe("initGlobalPackage — unknown", () => {
|
|
@@ -859,3 +889,480 @@ describe("e2e: embedding provider propagation", () => {
|
|
|
859
889
|
expect(config.query).toBeDefined();
|
|
860
890
|
});
|
|
861
891
|
});
|
|
892
|
+
// ─── Package wizard delegation: runPackageWizard ─────────────────────────────
|
|
893
|
+
describe("runPackageWizard — opentasks (real CLI)", async () => {
|
|
894
|
+
const installed = await hasCliInstalled("opentasks");
|
|
895
|
+
it.skipIf(!installed)("delegates to opentasks init via spawn", async () => {
|
|
896
|
+
createProject("wizard-ot");
|
|
897
|
+
const state = createEmptyState();
|
|
898
|
+
state.selectedPackages = ["opentasks"];
|
|
899
|
+
state.usePrefix = true;
|
|
900
|
+
const result = await runPackageWizard("opentasks", {
|
|
901
|
+
command: "opentasks",
|
|
902
|
+
args: ["init", "--name", "wizard-ot"],
|
|
903
|
+
}, state, testDir);
|
|
904
|
+
expect(result.success).toBe(true);
|
|
905
|
+
expect(result.package).toBe("opentasks");
|
|
906
|
+
// opentasks creates .opentasks/ at cwd
|
|
907
|
+
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(true);
|
|
908
|
+
// Verify config content
|
|
909
|
+
const config = JSON.parse(readFileSync(join(testDir, ".opentasks", "config.json"), "utf-8"));
|
|
910
|
+
expect(config.location.name).toBe("wizard-ot");
|
|
911
|
+
});
|
|
912
|
+
it.skipIf(!installed)("passes SWARMKIT_EMBEDDING_PROVIDER env var", async () => {
|
|
913
|
+
createProject("wizard-env");
|
|
914
|
+
const state = createEmptyState();
|
|
915
|
+
state.selectedPackages = ["opentasks"];
|
|
916
|
+
state.embeddingProvider = "openai";
|
|
917
|
+
state.apiKeys = { openai: "sk-test-key" };
|
|
918
|
+
// The env vars are passed but opentasks doesn't use them — we just
|
|
919
|
+
// verify the wizard completes successfully with them set.
|
|
920
|
+
const result = await runPackageWizard("opentasks", {
|
|
921
|
+
command: "opentasks",
|
|
922
|
+
args: ["init", "--name", "wizard-env"],
|
|
923
|
+
}, state, testDir);
|
|
924
|
+
expect(result.success).toBe(true);
|
|
925
|
+
});
|
|
926
|
+
it.skipIf(!installed)("passes custom env overrides from cliWizard config", async () => {
|
|
927
|
+
createProject("wizard-env-override");
|
|
928
|
+
const state = createEmptyState();
|
|
929
|
+
state.selectedPackages = ["opentasks"];
|
|
930
|
+
const result = await runPackageWizard("opentasks", {
|
|
931
|
+
command: "opentasks",
|
|
932
|
+
args: ["init", "--name", "wizard-env-override"],
|
|
933
|
+
env: {
|
|
934
|
+
OPENTASKS_PROJECT_DIR: ".swarm/opentasks",
|
|
935
|
+
},
|
|
936
|
+
}, state, testDir);
|
|
937
|
+
expect(result.success).toBe(true);
|
|
938
|
+
// With OPENTASKS_PROJECT_DIR set, opentasks creates in .swarm/opentasks/
|
|
939
|
+
expect(existsSync(join(testDir, ".swarm", "opentasks", "config.json"))).toBe(true);
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
describe("runPackageWizard — skill-tree (real CLI)", async () => {
|
|
943
|
+
const installed = await hasCliInstalled("skill-tree");
|
|
944
|
+
it.skipIf(!installed)("delegates to skill-tree config init via spawn", async () => {
|
|
945
|
+
const state = createEmptyState();
|
|
946
|
+
state.selectedPackages = ["skill-tree"];
|
|
947
|
+
// Clean existing skill-tree config created by earlier tests in this run
|
|
948
|
+
rmSync(join(testHome, ".skill-tree"), { recursive: true, force: true });
|
|
949
|
+
const result = await runPackageWizard("skill-tree", {
|
|
950
|
+
command: "skill-tree",
|
|
951
|
+
args: ["config", "init"],
|
|
952
|
+
}, state, testDir);
|
|
953
|
+
expect(result.success).toBe(true);
|
|
954
|
+
expect(result.package).toBe("skill-tree");
|
|
955
|
+
expect(existsSync(join(testHome, ".skill-tree", "config.yaml"))).toBe(true);
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
describe("runPackageWizard — opentasks with prefix relocation", async () => {
|
|
959
|
+
const installed = await hasCliInstalled("opentasks");
|
|
960
|
+
it.skipIf(!installed)("relocates .opentasks/ to .swarm/opentasks/ after wizard", async () => {
|
|
961
|
+
createProject("wizard-relocate");
|
|
962
|
+
const { relocateAfterWizard } = await import("./setup.js");
|
|
963
|
+
const state = createEmptyState();
|
|
964
|
+
state.selectedPackages = ["opentasks"];
|
|
965
|
+
state.usePrefix = true;
|
|
966
|
+
// Run wizard without prefix env var — creates at .opentasks/
|
|
967
|
+
const result = await runPackageWizard("opentasks", {
|
|
968
|
+
command: "opentasks",
|
|
969
|
+
args: ["init", "--name", "wizard-relocate"],
|
|
970
|
+
}, state, testDir);
|
|
971
|
+
expect(result.success).toBe(true);
|
|
972
|
+
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(true);
|
|
973
|
+
// Now relocate
|
|
974
|
+
relocateAfterWizard(testDir, "opentasks", true);
|
|
975
|
+
// Should have moved to .swarm/opentasks/
|
|
976
|
+
expect(existsSync(join(testDir, ".swarm", "opentasks", "config.json"))).toBe(true);
|
|
977
|
+
// Flat location should be gone
|
|
978
|
+
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(false);
|
|
979
|
+
});
|
|
980
|
+
it.skipIf(!installed)("does nothing when usePrefix is false", async () => {
|
|
981
|
+
createProject("wizard-no-prefix");
|
|
982
|
+
const { relocateAfterWizard } = await import("./setup.js");
|
|
983
|
+
const state = createEmptyState();
|
|
984
|
+
state.selectedPackages = ["opentasks"];
|
|
985
|
+
state.usePrefix = false;
|
|
986
|
+
const result = await runPackageWizard("opentasks", {
|
|
987
|
+
command: "opentasks",
|
|
988
|
+
args: ["init", "--name", "wizard-no-prefix"],
|
|
989
|
+
}, state, testDir);
|
|
990
|
+
expect(result.success).toBe(true);
|
|
991
|
+
// No relocation when prefix is false
|
|
992
|
+
relocateAfterWizard(testDir, "opentasks", false);
|
|
993
|
+
// Files stay at flat location
|
|
994
|
+
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(true);
|
|
995
|
+
expect(existsSync(join(testDir, ".swarm", "opentasks"))).toBe(false);
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
describe("runPackageWizard — nonexistent CLI", () => {
|
|
999
|
+
it("returns failure for a command that does not exist", async () => {
|
|
1000
|
+
const state = createEmptyState();
|
|
1001
|
+
state.selectedPackages = ["fake-package"];
|
|
1002
|
+
const result = await runPackageWizard("fake-package", {
|
|
1003
|
+
command: "nonexistent-cli-binary-swarmkit-test",
|
|
1004
|
+
args: ["init"],
|
|
1005
|
+
}, state);
|
|
1006
|
+
expect(result.success).toBe(false);
|
|
1007
|
+
expect(result.package).toBe("fake-package");
|
|
1008
|
+
expect(result.message).toBeDefined();
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
describe("runPackageWizard — failing command", () => {
|
|
1012
|
+
it("returns failure with exit code when command fails", async () => {
|
|
1013
|
+
const state = createEmptyState();
|
|
1014
|
+
state.selectedPackages = ["test-pkg"];
|
|
1015
|
+
const result = await runPackageWizard("test-pkg", {
|
|
1016
|
+
command: "node",
|
|
1017
|
+
args: ["-e", "process.exit(1)"],
|
|
1018
|
+
}, state);
|
|
1019
|
+
expect(result.success).toBe(false);
|
|
1020
|
+
expect(result.package).toBe("test-pkg");
|
|
1021
|
+
expect(result.message).toContain("exited with code 1");
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
describe("runPackageWizard — successful command", () => {
|
|
1025
|
+
it("returns success when command exits with 0", async () => {
|
|
1026
|
+
const state = createEmptyState();
|
|
1027
|
+
state.selectedPackages = ["test-pkg"];
|
|
1028
|
+
const result = await runPackageWizard("test-pkg", {
|
|
1029
|
+
command: "node",
|
|
1030
|
+
args: ["-e", "process.exit(0)"],
|
|
1031
|
+
}, state);
|
|
1032
|
+
expect(result.success).toBe(true);
|
|
1033
|
+
expect(result.package).toBe("test-pkg");
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
// ─── packageConfigs overrides ────────────────────────────────────────────────
|
|
1037
|
+
describe("packageConfigs — minimem overrides", () => {
|
|
1038
|
+
it("applies custom vector weights from packageConfigs", async () => {
|
|
1039
|
+
createProject("mm-override");
|
|
1040
|
+
const pkgConfigs = {
|
|
1041
|
+
minimem: {
|
|
1042
|
+
values: {
|
|
1043
|
+
"hybrid.vectorWeight": "0.9",
|
|
1044
|
+
"hybrid.textWeight": "0.1",
|
|
1045
|
+
"query.maxResults": "25",
|
|
1046
|
+
"query.minScore": "0.5",
|
|
1047
|
+
},
|
|
1048
|
+
usedCliWizard: false,
|
|
1049
|
+
},
|
|
1050
|
+
};
|
|
1051
|
+
await initProjectPackage("minimem", projectCtx({
|
|
1052
|
+
cwd: testDir,
|
|
1053
|
+
packages: ["minimem"],
|
|
1054
|
+
packageConfigs: pkgConfigs,
|
|
1055
|
+
}));
|
|
1056
|
+
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
1057
|
+
expect(config.hybrid.vectorWeight).toBe(0.9);
|
|
1058
|
+
expect(config.hybrid.textWeight).toBe(0.1);
|
|
1059
|
+
expect(config.query.maxResults).toBe(25);
|
|
1060
|
+
expect(config.query.minScore).toBe(0.5);
|
|
1061
|
+
});
|
|
1062
|
+
it("preserves defaults when packageConfigs has no minimem entry", async () => {
|
|
1063
|
+
createProject("mm-default");
|
|
1064
|
+
await initProjectPackage("minimem", projectCtx({
|
|
1065
|
+
cwd: testDir,
|
|
1066
|
+
packages: ["minimem"],
|
|
1067
|
+
packageConfigs: {},
|
|
1068
|
+
}));
|
|
1069
|
+
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
1070
|
+
expect(config.hybrid.vectorWeight).toBe(0.7);
|
|
1071
|
+
expect(config.hybrid.textWeight).toBe(0.3);
|
|
1072
|
+
expect(config.query.maxResults).toBe(10);
|
|
1073
|
+
expect(config.query.minScore).toBe(0.3);
|
|
1074
|
+
});
|
|
1075
|
+
it("combines embedding provider with packageConfigs overrides", async () => {
|
|
1076
|
+
createProject("mm-combined");
|
|
1077
|
+
await initProjectPackage("minimem", projectCtx({
|
|
1078
|
+
cwd: testDir,
|
|
1079
|
+
packages: ["minimem"],
|
|
1080
|
+
embeddingProvider: "gemini",
|
|
1081
|
+
packageConfigs: {
|
|
1082
|
+
minimem: {
|
|
1083
|
+
values: { "query.maxResults": "50" },
|
|
1084
|
+
usedCliWizard: false,
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
}));
|
|
1088
|
+
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
1089
|
+
expect(config.embedding.provider).toBe("gemini");
|
|
1090
|
+
expect(config.query.maxResults).toBe(50);
|
|
1091
|
+
// Defaults preserved for untouched fields
|
|
1092
|
+
expect(config.hybrid.vectorWeight).toBe(0.7);
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
describe("packageConfigs — sessionlog overrides", () => {
|
|
1096
|
+
it("applies custom settings from packageConfigs", async () => {
|
|
1097
|
+
const pkgConfigs = {
|
|
1098
|
+
sessionlog: {
|
|
1099
|
+
values: {
|
|
1100
|
+
enabled: true,
|
|
1101
|
+
strategy: "auto-commit",
|
|
1102
|
+
summarizationEnabled: true,
|
|
1103
|
+
},
|
|
1104
|
+
usedCliWizard: false,
|
|
1105
|
+
},
|
|
1106
|
+
};
|
|
1107
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1108
|
+
cwd: testDir,
|
|
1109
|
+
packages: ["sessionlog"],
|
|
1110
|
+
packageConfigs: pkgConfigs,
|
|
1111
|
+
}));
|
|
1112
|
+
const settings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1113
|
+
expect(settings.enabled).toBe(true);
|
|
1114
|
+
expect(settings.strategy).toBe("auto-commit");
|
|
1115
|
+
expect(settings.summarizationEnabled).toBe(true);
|
|
1116
|
+
// Defaults preserved for untouched fields
|
|
1117
|
+
expect(settings.logLevel).toBe("warn");
|
|
1118
|
+
expect(settings.telemetryEnabled).toBe(false);
|
|
1119
|
+
});
|
|
1120
|
+
it("uses defaults when sessionlog not in packageConfigs", async () => {
|
|
1121
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1122
|
+
cwd: testDir,
|
|
1123
|
+
packages: ["sessionlog"],
|
|
1124
|
+
packageConfigs: {},
|
|
1125
|
+
}));
|
|
1126
|
+
const settings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1127
|
+
expect(settings).toEqual({
|
|
1128
|
+
enabled: false,
|
|
1129
|
+
strategy: "manual-commit",
|
|
1130
|
+
logLevel: "warn",
|
|
1131
|
+
telemetryEnabled: false,
|
|
1132
|
+
summarizationEnabled: false,
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
describe("packageConfigs — sessionlog session repo config", () => {
|
|
1137
|
+
it("puts sessionRepo.remote in settings.json (committable)", async () => {
|
|
1138
|
+
const pkgConfigs = {
|
|
1139
|
+
sessionlog: {
|
|
1140
|
+
values: {
|
|
1141
|
+
enabled: true,
|
|
1142
|
+
"sessionRepo.remote": "git@github.com:org/sessions.git",
|
|
1143
|
+
"sessionRepo.directory": "my-project",
|
|
1144
|
+
},
|
|
1145
|
+
usedCliWizard: false,
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1149
|
+
cwd: testDir,
|
|
1150
|
+
packages: ["sessionlog"],
|
|
1151
|
+
packageConfigs: pkgConfigs,
|
|
1152
|
+
}));
|
|
1153
|
+
const settings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1154
|
+
expect(settings.sessionRepo).toBeDefined();
|
|
1155
|
+
expect(settings.sessionRepo.remote).toBe("git@github.com:org/sessions.git");
|
|
1156
|
+
expect(settings.sessionRepo.directory).toBe("my-project");
|
|
1157
|
+
expect(settings.enabled).toBe(true);
|
|
1158
|
+
// No local settings file — no machine-specific fields
|
|
1159
|
+
expect(existsSync(join(testDir, ".swarm", "sessionlog", "settings.local.json"))).toBe(false);
|
|
1160
|
+
});
|
|
1161
|
+
it("puts sessionRepo.localPath in settings.local.json (machine-specific)", async () => {
|
|
1162
|
+
const pkgConfigs = {
|
|
1163
|
+
sessionlog: {
|
|
1164
|
+
values: {
|
|
1165
|
+
enabled: true,
|
|
1166
|
+
"sessionRepo.remote": "git@github.com:org/sessions.git",
|
|
1167
|
+
"sessionRepo.localPath": "/Users/dev/GitHub/sessions",
|
|
1168
|
+
},
|
|
1169
|
+
usedCliWizard: false,
|
|
1170
|
+
},
|
|
1171
|
+
};
|
|
1172
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1173
|
+
cwd: testDir,
|
|
1174
|
+
packages: ["sessionlog"],
|
|
1175
|
+
packageConfigs: pkgConfigs,
|
|
1176
|
+
}));
|
|
1177
|
+
// Remote goes to settings.json
|
|
1178
|
+
const settings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1179
|
+
expect(settings.sessionRepo.remote).toBe("git@github.com:org/sessions.git");
|
|
1180
|
+
// localPath goes to settings.local.json
|
|
1181
|
+
const localSettings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.local.json"), "utf-8"));
|
|
1182
|
+
expect(localSettings.sessionRepo.localPath).toBe("/Users/dev/GitHub/sessions");
|
|
1183
|
+
});
|
|
1184
|
+
it("omits sessionRepo when remote is empty string", async () => {
|
|
1185
|
+
const pkgConfigs = {
|
|
1186
|
+
sessionlog: {
|
|
1187
|
+
values: {
|
|
1188
|
+
enabled: true,
|
|
1189
|
+
"sessionRepo.remote": "",
|
|
1190
|
+
},
|
|
1191
|
+
usedCliWizard: false,
|
|
1192
|
+
},
|
|
1193
|
+
};
|
|
1194
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1195
|
+
cwd: testDir,
|
|
1196
|
+
packages: ["sessionlog"],
|
|
1197
|
+
packageConfigs: pkgConfigs,
|
|
1198
|
+
}));
|
|
1199
|
+
const settings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1200
|
+
expect(settings.sessionRepo).toBeUndefined();
|
|
1201
|
+
expect(settings.enabled).toBe(true);
|
|
1202
|
+
});
|
|
1203
|
+
it("creates .gitignore that ignores settings.local.json", async () => {
|
|
1204
|
+
await initProjectPackage("sessionlog", projectCtx({
|
|
1205
|
+
cwd: testDir,
|
|
1206
|
+
packages: ["sessionlog"],
|
|
1207
|
+
}));
|
|
1208
|
+
const gitignore = readFileSync(join(testDir, ".swarm", "sessionlog", ".gitignore"), "utf-8");
|
|
1209
|
+
expect(gitignore).toContain("settings.local.json");
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
describe("packageConfigs — claude-code-swarm project overrides", () => {
|
|
1213
|
+
it("applies MAP and sessionlog overrides from packageConfigs", async () => {
|
|
1214
|
+
const pkgConfigs = {
|
|
1215
|
+
"claude-code-swarm": {
|
|
1216
|
+
values: {
|
|
1217
|
+
"map.enabled": true,
|
|
1218
|
+
"map.server": "ws://custom:9090",
|
|
1219
|
+
"map.scope": "my-project",
|
|
1220
|
+
"sessionlog.enabled": true,
|
|
1221
|
+
"sessionlog.sync": "live",
|
|
1222
|
+
},
|
|
1223
|
+
usedCliWizard: false,
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
await initProjectPackage("claude-code-swarm", projectCtx({
|
|
1227
|
+
cwd: testDir,
|
|
1228
|
+
packages: ["claude-code-swarm"],
|
|
1229
|
+
packageConfigs: pkgConfigs,
|
|
1230
|
+
}));
|
|
1231
|
+
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "claude-swarm", "config.json"), "utf-8"));
|
|
1232
|
+
expect(config.map.enabled).toBe(true);
|
|
1233
|
+
expect(config.map.server).toBe("ws://custom:9090");
|
|
1234
|
+
expect(config.map.scope).toBe("my-project");
|
|
1235
|
+
expect(config.sessionlog.enabled).toBe(true);
|
|
1236
|
+
expect(config.sessionlog.sync).toBe("live");
|
|
1237
|
+
// Defaults preserved
|
|
1238
|
+
expect(config.map.systemId).toBe("system-claude-swarm");
|
|
1239
|
+
expect(config.map.sidecar).toBe("session");
|
|
1240
|
+
expect(config.template).toBe("");
|
|
1241
|
+
});
|
|
1242
|
+
it("uses defaults when not in packageConfigs", async () => {
|
|
1243
|
+
await initProjectPackage("claude-code-swarm", projectCtx({
|
|
1244
|
+
cwd: testDir,
|
|
1245
|
+
packages: ["claude-code-swarm"],
|
|
1246
|
+
packageConfigs: {},
|
|
1247
|
+
}));
|
|
1248
|
+
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "claude-swarm", "config.json"), "utf-8"));
|
|
1249
|
+
expect(config.map.enabled).toBe(false);
|
|
1250
|
+
expect(config.map.server).toBe("ws://localhost:8080");
|
|
1251
|
+
expect(config.sessionlog.enabled).toBe(false);
|
|
1252
|
+
expect(config.sessionlog.sync).toBe("off");
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
1255
|
+
describe("packageConfigs — claude-code-swarm global overrides", () => {
|
|
1256
|
+
it("applies overrides to global config", async () => {
|
|
1257
|
+
const result = await initGlobalPackage("claude-code-swarm", globalCtx({
|
|
1258
|
+
packages: ["claude-code-swarm"],
|
|
1259
|
+
packageConfigs: {
|
|
1260
|
+
"claude-code-swarm": {
|
|
1261
|
+
values: {
|
|
1262
|
+
"map.server": "ws://production:8080",
|
|
1263
|
+
"sessionlog.enabled": true,
|
|
1264
|
+
"sessionlog.sync": "on-finish",
|
|
1265
|
+
},
|
|
1266
|
+
usedCliWizard: false,
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
}));
|
|
1270
|
+
expect(result.success).toBe(true);
|
|
1271
|
+
const config = JSON.parse(readFileSync(join(testHome, ".claude-swarm", "config.json"), "utf-8"));
|
|
1272
|
+
expect(config.map.server).toBe("ws://production:8080");
|
|
1273
|
+
expect(config.map.sidecar).toBe("session"); // default preserved
|
|
1274
|
+
expect(config.sessionlog.enabled).toBe(true);
|
|
1275
|
+
expect(config.sessionlog.sync).toBe("on-finish");
|
|
1276
|
+
});
|
|
1277
|
+
});
|
|
1278
|
+
describe("packageConfigs — self-driving-repo template override", async () => {
|
|
1279
|
+
const installed = await hasCliInstalled("sdr");
|
|
1280
|
+
it.skipIf(!installed)("uses template from packageConfigs", async () => {
|
|
1281
|
+
createProject("sdr-override");
|
|
1282
|
+
const pkgConfigs = {
|
|
1283
|
+
"self-driving-repo": {
|
|
1284
|
+
values: { template: "pr-review" },
|
|
1285
|
+
usedCliWizard: false,
|
|
1286
|
+
},
|
|
1287
|
+
};
|
|
1288
|
+
const result = await initProjectPackage("self-driving-repo", projectCtx({
|
|
1289
|
+
cwd: testDir,
|
|
1290
|
+
packages: ["self-driving-repo"],
|
|
1291
|
+
packageConfigs: pkgConfigs,
|
|
1292
|
+
}));
|
|
1293
|
+
expect(result.success).toBe(true);
|
|
1294
|
+
expect(existsSync(join(testDir, ".swarm", "self-driving"))).toBe(true);
|
|
1295
|
+
});
|
|
1296
|
+
it.skipIf(installed)("falls back to triage-only when no packageConfigs and CLI missing", async () => {
|
|
1297
|
+
const result = await initProjectPackage("self-driving-repo", projectCtx({
|
|
1298
|
+
cwd: testDir,
|
|
1299
|
+
packages: ["self-driving-repo"],
|
|
1300
|
+
packageConfigs: {},
|
|
1301
|
+
}));
|
|
1302
|
+
// sdr not installed → failure, but the template used was triage-only (default)
|
|
1303
|
+
expect(result.success).toBe(false);
|
|
1304
|
+
expect(result.package).toBe("self-driving-repo");
|
|
1305
|
+
});
|
|
1306
|
+
});
|
|
1307
|
+
// ─── E2E: packageConfigs in full project init flow ───────────────────────────
|
|
1308
|
+
describe("e2e: packageConfigs flow through full init", async () => {
|
|
1309
|
+
const opentasksOk = await hasCliInstalled("opentasks");
|
|
1310
|
+
it.skipIf(!opentasksOk)("packageConfigs overrides apply across all packages in order", async () => {
|
|
1311
|
+
createProject("full-override-e2e");
|
|
1312
|
+
const pkgConfigs = {
|
|
1313
|
+
minimem: {
|
|
1314
|
+
values: { "query.maxResults": "42" },
|
|
1315
|
+
usedCliWizard: false,
|
|
1316
|
+
},
|
|
1317
|
+
sessionlog: {
|
|
1318
|
+
values: { enabled: true, strategy: "auto-commit" },
|
|
1319
|
+
usedCliWizard: false,
|
|
1320
|
+
},
|
|
1321
|
+
"claude-code-swarm": {
|
|
1322
|
+
values: {
|
|
1323
|
+
"map.enabled": true,
|
|
1324
|
+
"sessionlog.enabled": true,
|
|
1325
|
+
},
|
|
1326
|
+
usedCliWizard: false,
|
|
1327
|
+
},
|
|
1328
|
+
};
|
|
1329
|
+
const ctx = projectCtx({
|
|
1330
|
+
cwd: testDir,
|
|
1331
|
+
packages: [
|
|
1332
|
+
"opentasks",
|
|
1333
|
+
"minimem",
|
|
1334
|
+
"sessionlog",
|
|
1335
|
+
"claude-code-swarm",
|
|
1336
|
+
],
|
|
1337
|
+
embeddingProvider: "openai",
|
|
1338
|
+
packageConfigs: pkgConfigs,
|
|
1339
|
+
});
|
|
1340
|
+
const results = [];
|
|
1341
|
+
for (const pkg of PROJECT_INIT_ORDER) {
|
|
1342
|
+
if (!ctx.packages.includes(pkg))
|
|
1343
|
+
continue;
|
|
1344
|
+
if (isProjectInit(ctx.cwd, pkg))
|
|
1345
|
+
continue;
|
|
1346
|
+
results.push(await initProjectPackage(pkg, ctx));
|
|
1347
|
+
}
|
|
1348
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
1349
|
+
expect(results.map((r) => r.package)).toEqual([
|
|
1350
|
+
"opentasks",
|
|
1351
|
+
"minimem",
|
|
1352
|
+
"sessionlog",
|
|
1353
|
+
"claude-code-swarm",
|
|
1354
|
+
]);
|
|
1355
|
+
// minimem: custom maxResults + openai embedding
|
|
1356
|
+
const mmConfig = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
1357
|
+
expect(mmConfig.query.maxResults).toBe(42);
|
|
1358
|
+
expect(mmConfig.embedding.provider).toBe("openai");
|
|
1359
|
+
// sessionlog: enabled + auto-commit
|
|
1360
|
+
const slSettings = JSON.parse(readFileSync(join(testDir, ".swarm", "sessionlog", "settings.json"), "utf-8"));
|
|
1361
|
+
expect(slSettings.enabled).toBe(true);
|
|
1362
|
+
expect(slSettings.strategy).toBe("auto-commit");
|
|
1363
|
+
// claude-code-swarm: MAP enabled + sessionlog bridging
|
|
1364
|
+
const csConfig = JSON.parse(readFileSync(join(testDir, ".swarm", "claude-swarm", "config.json"), "utf-8"));
|
|
1365
|
+
expect(csConfig.map.enabled).toBe(true);
|
|
1366
|
+
expect(csConfig.sessionlog.enabled).toBe(true);
|
|
1367
|
+
});
|
|
1368
|
+
});
|