swarmkit 0.0.5 → 0.0.6
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/packages/setup.js +79 -53
- package/dist/packages/setup.test.js +26 -29
- package/package.json +1 -1
package/dist/packages/setup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync,
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
@@ -63,9 +63,9 @@ function ensureProjectRoot(cwd) {
|
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* After init, relocate a package's config dir into .swarm/ if the CLI
|
|
66
|
-
* created it at the legacy top-level location
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* created it at the legacy top-level location.
|
|
67
|
+
* All supported packages natively check .swarm/<pkg> before .<pkg>,
|
|
68
|
+
* so no symlink is needed.
|
|
69
69
|
*/
|
|
70
70
|
function relocate(cwd, legacyName, targetName) {
|
|
71
71
|
const src = join(cwd, legacyName);
|
|
@@ -76,26 +76,9 @@ function relocate(cwd, legacyName, targetName) {
|
|
|
76
76
|
renameSync(src, dest);
|
|
77
77
|
}
|
|
78
78
|
catch {
|
|
79
|
-
|
|
79
|
+
// Non-fatal — leave in legacy location
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
// Create a symlink at the legacy location so packages find their data
|
|
83
|
-
ensureSymlink(cwd, legacyName, targetName);
|
|
84
|
-
}
|
|
85
|
-
/** Create a relative symlink: cwd/<legacyName> → .swarm/<targetName> */
|
|
86
|
-
function ensureSymlink(cwd, legacyName, targetName) {
|
|
87
|
-
const link = join(cwd, legacyName);
|
|
88
|
-
const target = join(PROJECT_ROOT, targetName); // relative path
|
|
89
|
-
if (isSymlink(link))
|
|
90
|
-
return; // Already symlinked
|
|
91
|
-
if (existsSync(link))
|
|
92
|
-
return; // Real directory exists — don't overwrite
|
|
93
|
-
try {
|
|
94
|
-
symlinkSync(target, link);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
// Non-fatal — package can still be configured via env var
|
|
98
|
-
}
|
|
99
82
|
}
|
|
100
83
|
function isSymlink(path) {
|
|
101
84
|
try {
|
|
@@ -119,6 +102,10 @@ export async function initProjectPackage(pkg, ctx) {
|
|
|
119
102
|
// for packages that create directly in .swarm/, this is a no-op.
|
|
120
103
|
if (ctx.usePrefix)
|
|
121
104
|
relocate(ctx.cwd, ".opentasks", "opentasks");
|
|
105
|
+
// Older opentasks versions write .gitattributes at the project root.
|
|
106
|
+
// Newer versions write it inside the opentasks dir. Clean up the
|
|
107
|
+
// root-level file as a backwards-compat fallback.
|
|
108
|
+
cleanupGitattributes(ctx.cwd);
|
|
122
109
|
return result;
|
|
123
110
|
}
|
|
124
111
|
case "minimem":
|
|
@@ -206,40 +193,52 @@ async function shellInit(command, args, cwd, envOverrides) {
|
|
|
206
193
|
}
|
|
207
194
|
async function initMinimem(ctx) {
|
|
208
195
|
const targetDir = ctx.usePrefix
|
|
209
|
-
? join(
|
|
210
|
-
:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
196
|
+
? join(PROJECT_ROOT, "minimem")
|
|
197
|
+
: ".minimem";
|
|
198
|
+
const absTarget = join(ctx.cwd, targetDir);
|
|
199
|
+
// Try CLI init first (creates contained layout in newer versions)
|
|
200
|
+
const result = await shellInit("minimem", ["init", targetDir], ctx.cwd);
|
|
201
|
+
// Verify the CLI created the contained layout (config.json at root).
|
|
202
|
+
// Older minimem versions create a nested .minimem/ subdir instead.
|
|
203
|
+
if (!existsSync(join(absTarget, "config.json"))) {
|
|
204
|
+
initMinimemInline(absTarget);
|
|
205
|
+
}
|
|
206
|
+
// Patch embedding provider if the wizard chose one
|
|
207
|
+
const provider = ctx.embeddingProvider && ctx.embeddingProvider !== "local"
|
|
208
|
+
? ctx.embeddingProvider
|
|
209
|
+
: null;
|
|
210
|
+
if (provider) {
|
|
211
|
+
const configPath = join(absTarget, "config.json");
|
|
212
|
+
try {
|
|
213
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
214
|
+
config.embedding = { ...config.embedding, provider };
|
|
215
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
223
216
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const configPath = join(targetDir, "config.json");
|
|
227
|
-
if (existsSync(configPath)) {
|
|
228
|
-
try {
|
|
229
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
230
|
-
config.embedding = config.embedding || {};
|
|
231
|
-
config.embedding.provider = ctx.embeddingProvider;
|
|
232
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// Non-fatal — user can configure manually
|
|
236
|
-
}
|
|
237
|
-
}
|
|
217
|
+
catch {
|
|
218
|
+
// Non-fatal
|
|
238
219
|
}
|
|
239
|
-
return { package: "minimem", success: true };
|
|
240
220
|
}
|
|
241
|
-
|
|
242
|
-
|
|
221
|
+
return { package: "minimem", success: true };
|
|
222
|
+
}
|
|
223
|
+
/** Fallback: create contained layout inline when CLI is unavailable or outdated */
|
|
224
|
+
function initMinimemInline(targetDir) {
|
|
225
|
+
mkdirSync(targetDir, { recursive: true });
|
|
226
|
+
mkdirSync(join(targetDir, "memory"), { recursive: true });
|
|
227
|
+
const configPath = join(targetDir, "config.json");
|
|
228
|
+
if (!existsSync(configPath)) {
|
|
229
|
+
writeFileSync(configPath, JSON.stringify({
|
|
230
|
+
embedding: { provider: "auto" },
|
|
231
|
+
hybrid: { enabled: true, vectorWeight: 0.7, textWeight: 0.3 },
|
|
232
|
+
query: { maxResults: 10, minScore: 0.3 },
|
|
233
|
+
}, null, 2) + "\n");
|
|
234
|
+
}
|
|
235
|
+
const memoryPath = join(targetDir, "MEMORY.md");
|
|
236
|
+
if (!existsSync(memoryPath)) {
|
|
237
|
+
writeFileSync(memoryPath, "# Memory\n\nAdd notes, decisions, and context here.\n");
|
|
238
|
+
}
|
|
239
|
+
const gitignorePath = join(targetDir, ".gitignore");
|
|
240
|
+
if (!existsSync(gitignorePath)) {
|
|
241
|
+
writeFileSync(gitignorePath, "index.db\nindex.db-*\n");
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
async function initSdr(ctx) {
|
|
@@ -446,6 +445,33 @@ function hasContent(dir) {
|
|
|
446
445
|
return false;
|
|
447
446
|
}
|
|
448
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Remove root-level .gitattributes created by older opentasks versions.
|
|
450
|
+
* Newer versions write .gitattributes inside the opentasks directory.
|
|
451
|
+
*/
|
|
452
|
+
function cleanupGitattributes(cwd) {
|
|
453
|
+
const attrPath = join(cwd, ".gitattributes");
|
|
454
|
+
if (!existsSync(attrPath))
|
|
455
|
+
return;
|
|
456
|
+
try {
|
|
457
|
+
const content = readFileSync(attrPath, "utf-8");
|
|
458
|
+
const cleaned = content
|
|
459
|
+
.split("\n")
|
|
460
|
+
.filter((line) => !line.includes("merge=opentasks") &&
|
|
461
|
+
!line.includes("OpenTasks merge driver"))
|
|
462
|
+
.join("\n")
|
|
463
|
+
.trim();
|
|
464
|
+
if (!cleaned) {
|
|
465
|
+
unlinkSync(attrPath);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
writeFileSync(attrPath, cleaned + "\n");
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
// Non-fatal
|
|
473
|
+
}
|
|
474
|
+
}
|
|
449
475
|
function getProjectName(cwd) {
|
|
450
476
|
const pkgPath = join(cwd, "package.json");
|
|
451
477
|
if (existsSync(pkgPath)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mkdirSync, rmSync, existsSync,
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync, realpathSync, } from "node:fs";
|
|
3
3
|
import { join, basename } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -152,7 +152,7 @@ describe("isGlobalInit", () => {
|
|
|
152
152
|
// ─── Project: opentasks (real CLI) ───────────────────────────────────────────
|
|
153
153
|
describe("initProjectPackage — opentasks (real CLI)", async () => {
|
|
154
154
|
const installed = await hasCliInstalled("opentasks");
|
|
155
|
-
it.skipIf(!installed)("runs opentasks init
|
|
155
|
+
it.skipIf(!installed)("runs opentasks init and relocates to .swarm/", async () => {
|
|
156
156
|
createProject("test-opentasks");
|
|
157
157
|
const result = await initProjectPackage("opentasks", projectCtx({ cwd: testDir, packages: ["opentasks"] }));
|
|
158
158
|
expect(result.success).toBe(true);
|
|
@@ -162,12 +162,10 @@ describe("initProjectPackage — opentasks (real CLI)", async () => {
|
|
|
162
162
|
expect(existsSync(join(testDir, ".swarm", "opentasks", "config.json"))).toBe(true);
|
|
163
163
|
expect(existsSync(join(testDir, ".swarm", "opentasks", "graph.jsonl"))).toBe(true);
|
|
164
164
|
expect(existsSync(join(testDir, ".swarm", "opentasks", ".gitignore"))).toBe(true);
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
expect(
|
|
169
|
-
// Accessible via symlink
|
|
170
|
-
expect(existsSync(join(testDir, ".opentasks", "config.json"))).toBe(true);
|
|
165
|
+
// No symlink at legacy location (packages natively check .swarm/)
|
|
166
|
+
expect(existsSync(join(testDir, ".opentasks"))).toBe(false);
|
|
167
|
+
// .gitattributes should be cleaned up (merge driver creates it at root)
|
|
168
|
+
expect(existsSync(join(testDir, ".gitattributes"))).toBe(false);
|
|
171
169
|
// Verify config content
|
|
172
170
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "opentasks", "config.json"), "utf-8"));
|
|
173
171
|
expect(config.version).toBe("1.0");
|
|
@@ -189,24 +187,30 @@ describe("initProjectPackage — opentasks (real CLI)", async () => {
|
|
|
189
187
|
expect(config.location.name).toBe(basename(testDir));
|
|
190
188
|
});
|
|
191
189
|
});
|
|
192
|
-
// ─── Project: minimem (
|
|
193
|
-
describe("initProjectPackage — minimem
|
|
194
|
-
|
|
195
|
-
it.skipIf(!installed)("runs minimem init and creates .minimem/", async () => {
|
|
190
|
+
// ─── Project: minimem (CLI with inline fallback) ─────────────────────────────
|
|
191
|
+
describe("initProjectPackage — minimem", () => {
|
|
192
|
+
it("creates all artifacts inside .swarm/minimem/", async () => {
|
|
196
193
|
createProject("test-minimem");
|
|
197
194
|
const result = await initProjectPackage("minimem", projectCtx({ cwd: testDir, packages: ["minimem"] }));
|
|
198
195
|
expect(result.success).toBe(true);
|
|
199
|
-
//
|
|
196
|
+
// Core artifacts inside .swarm/minimem/
|
|
200
197
|
expect(existsSync(join(testDir, ".swarm", "minimem"))).toBe(true);
|
|
201
198
|
expect(existsSync(join(testDir, ".swarm", "minimem", "config.json"))).toBe(true);
|
|
202
199
|
expect(existsSync(join(testDir, ".swarm", "minimem", ".gitignore"))).toBe(true);
|
|
203
|
-
expect(existsSync(join(testDir, "MEMORY.md"))).toBe(true);
|
|
200
|
+
expect(existsSync(join(testDir, ".swarm", "minimem", "MEMORY.md"))).toBe(true);
|
|
201
|
+
expect(existsSync(join(testDir, ".swarm", "minimem", "memory"))).toBe(true);
|
|
202
|
+
// Nothing at project root
|
|
203
|
+
expect(existsSync(join(testDir, "MEMORY.md"))).toBe(false);
|
|
204
|
+
expect(existsSync(join(testDir, "memory"))).toBe(false);
|
|
204
205
|
// Verify default config
|
|
205
206
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
206
207
|
expect(config.embedding.provider).toBe("auto");
|
|
207
208
|
expect(config.hybrid.enabled).toBe(true);
|
|
209
|
+
// Verify .gitignore content
|
|
210
|
+
const gitignore = readFileSync(join(testDir, ".swarm", "minimem", ".gitignore"), "utf-8");
|
|
211
|
+
expect(gitignore).toContain("index.db");
|
|
208
212
|
});
|
|
209
|
-
it
|
|
213
|
+
it("sets embedding.provider to openai", async () => {
|
|
210
214
|
createProject("test-embed");
|
|
211
215
|
await initProjectPackage("minimem", projectCtx({
|
|
212
216
|
cwd: testDir,
|
|
@@ -215,11 +219,8 @@ describe("initProjectPackage — minimem (real CLI)", async () => {
|
|
|
215
219
|
}));
|
|
216
220
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
217
221
|
expect(config.embedding.provider).toBe("openai");
|
|
218
|
-
// Other fields preserved
|
|
219
|
-
expect(config.hybrid.enabled).toBe(true);
|
|
220
|
-
expect(config.query.maxResults).toBe(10);
|
|
221
222
|
});
|
|
222
|
-
it
|
|
223
|
+
it("sets embedding.provider to gemini", async () => {
|
|
223
224
|
createProject("test-embed-gemini");
|
|
224
225
|
await initProjectPackage("minimem", projectCtx({
|
|
225
226
|
cwd: testDir,
|
|
@@ -229,7 +230,7 @@ describe("initProjectPackage — minimem (real CLI)", async () => {
|
|
|
229
230
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
230
231
|
expect(config.embedding.provider).toBe("gemini");
|
|
231
232
|
});
|
|
232
|
-
it
|
|
233
|
+
it("uses 'auto' when provider is local", async () => {
|
|
233
234
|
createProject("test-local");
|
|
234
235
|
await initProjectPackage("minimem", projectCtx({
|
|
235
236
|
cwd: testDir,
|
|
@@ -239,7 +240,7 @@ describe("initProjectPackage — minimem (real CLI)", async () => {
|
|
|
239
240
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
240
241
|
expect(config.embedding.provider).toBe("auto");
|
|
241
242
|
});
|
|
242
|
-
it
|
|
243
|
+
it("uses 'auto' when provider is null", async () => {
|
|
243
244
|
createProject("test-null");
|
|
244
245
|
await initProjectPackage("minimem", projectCtx({
|
|
245
246
|
cwd: testDir,
|
|
@@ -552,8 +553,7 @@ describe("initGlobalPackage — unknown", () => {
|
|
|
552
553
|
// ─── E2E: full project init flow (solo bundle) ──────────────────────────────
|
|
553
554
|
describe("e2e: project init — solo bundle", async () => {
|
|
554
555
|
const opentasksOk = await hasCliInstalled("opentasks");
|
|
555
|
-
const
|
|
556
|
-
const allOk = opentasksOk && minimemOk;
|
|
556
|
+
const allOk = opentasksOk; // minimem init is inline (no CLI needed)
|
|
557
557
|
it.skipIf(!allOk)("initializes opentasks → minimem with cross-wiring", async () => {
|
|
558
558
|
createProject("solo-e2e");
|
|
559
559
|
const ctx = projectCtx({
|
|
@@ -614,9 +614,8 @@ describe("e2e: project init — solo bundle", async () => {
|
|
|
614
614
|
// ─── E2E: full project init flow (team bundle — init order) ─────────────────
|
|
615
615
|
describe("e2e: project init — team bundle (init order)", async () => {
|
|
616
616
|
const opentasksOk = await hasCliInstalled("opentasks");
|
|
617
|
-
const minimemOk = await hasCliInstalled("minimem");
|
|
618
617
|
const ccOk = await hasCliInstalled("cognitive-core");
|
|
619
|
-
const allOk = opentasksOk &&
|
|
618
|
+
const allOk = opentasksOk && ccOk; // minimem init is inline (no CLI needed)
|
|
620
619
|
it.skipIf(!allOk)("cognitive-core runs after minimem (can detect .minimem/)", async () => {
|
|
621
620
|
createProject("team-e2e");
|
|
622
621
|
const ctx = projectCtx({
|
|
@@ -845,9 +844,8 @@ describe("flow: plugin bootstrap — init project dirs via swarmkit", () => {
|
|
|
845
844
|
});
|
|
846
845
|
});
|
|
847
846
|
// ─── E2E: embedding provider propagation ─────────────────────────────────────
|
|
848
|
-
describe("e2e: embedding provider propagation",
|
|
849
|
-
|
|
850
|
-
it.skipIf(!minimemOk)("openai embedding flows through to real minimem config", async () => {
|
|
847
|
+
describe("e2e: embedding provider propagation", () => {
|
|
848
|
+
it("openai embedding flows through to minimem config", async () => {
|
|
851
849
|
createProject("embed-e2e");
|
|
852
850
|
await initProjectPackage("minimem", projectCtx({
|
|
853
851
|
cwd: testDir,
|
|
@@ -857,7 +855,6 @@ describe("e2e: embedding provider propagation", async () => {
|
|
|
857
855
|
}));
|
|
858
856
|
const config = JSON.parse(readFileSync(join(testDir, ".swarm", "minimem", "config.json"), "utf-8"));
|
|
859
857
|
expect(config.embedding.provider).toBe("openai");
|
|
860
|
-
// Real minimem config has these fields too
|
|
861
858
|
expect(config.hybrid).toBeDefined();
|
|
862
859
|
expect(config.query).toBeDefined();
|
|
863
860
|
});
|