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.
@@ -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, symlinkSync, writeFileSync, } from "node:fs";
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, then leave a symlink at
67
- * the old path so the package can still find its data at runtime.
68
- * This is a no-op when the package already respects its env-var override.
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
- return; // Non-fatal — leave in legacy location
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(ctx.cwd, PROJECT_ROOT, "minimem")
210
- : join(ctx.cwd, ".minimem");
211
- try {
212
- await execFileAsync("minimem", ["init"], {
213
- cwd: ctx.cwd,
214
- timeout: 30_000,
215
- env: ctx.usePrefix
216
- ? { ...cleanEnv(), MINIMEM_CONFIG_DIR: join(PROJECT_ROOT, "minimem") }
217
- : cleanEnv(),
218
- });
219
- // relocate handles packages that don't yet respect the env var;
220
- // for packages that create directly in .swarm/, this is a no-op.
221
- if (ctx.usePrefix) {
222
- relocate(ctx.cwd, ".minimem", "minimem");
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
- // Patch embedding provider if configured
225
- if (ctx.embeddingProvider && ctx.embeddingProvider !== "local") {
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
- catch (err) {
242
- return { package: "minimem", success: false, message: formatError(err) };
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, lstatSync, readFileSync, readlinkSync, writeFileSync, realpathSync, } from "node:fs";
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, relocates to .swarm/, and creates symlink", async () => {
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
- // Verify symlink at legacy location
166
- const link = join(testDir, ".opentasks");
167
- expect(lstatSync(link).isSymbolicLink()).toBe(true);
168
- expect(readlinkSync(link)).toBe(".swarm/opentasks");
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 (real CLI) + embedding patching ────────────────────────
193
- describe("initProjectPackage — minimem (real CLI)", async () => {
194
- const installed = await hasCliInstalled("minimem");
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
- // Verify real filesystem artifacts
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.skipIf(!installed)("patches embedding.provider to openai", async () => {
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.skipIf(!installed)("patches embedding.provider to gemini", async () => {
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.skipIf(!installed)("does NOT patch when provider is local", async () => {
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.skipIf(!installed)("does NOT patch when provider is null", async () => {
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 minimemOk = await hasCliInstalled("minimem");
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 && minimemOk && ccOk;
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", async () => {
849
- const minimemOk = await hasCliInstalled("minimem");
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarmkit",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Multi-agent infa toolkit",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",