@foundation0/api 1.1.8 → 1.1.11

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.
@@ -204,7 +204,8 @@ describe("createExampleMcpServer request handling", () => {
204
204
  }
205
205
  });
206
206
 
207
- it("accepts repoName for projects tools (preferred over legacy repoRoot/processRoot)", async () => {
207
+ it("accepts repoName selectors in short and owner/repo format", async () => {
208
+ const originalCwd = process.cwd();
208
209
  const tempDir = await fs.mkdtemp(
209
210
  path.join(os.tmpdir(), "f0-mcp-server-reponame-"),
210
211
  );
@@ -216,85 +217,75 @@ describe("createExampleMcpServer request handling", () => {
216
217
  await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
217
218
  recursive: true,
218
219
  });
220
+ await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
221
+ await fs.writeFile(
222
+ path.join(tempDir, ".git", "config"),
223
+ [
224
+ '[remote "origin"]',
225
+ "\turl = https://example.com/F0/adl.git",
226
+ "",
227
+ ].join("\n"),
228
+ );
219
229
 
220
- const instance = createExampleMcpServer({ repoRoot: tempDir, repoName: "test" });
230
+ process.chdir(tempDir);
231
+ const instance = createExampleMcpServer();
221
232
  const handler = getToolHandler(instance);
222
233
 
223
- const result = await handler(
234
+ const shortSelector = await handler(
224
235
  {
225
236
  method: "tools/call",
226
237
  params: {
227
238
  name: "projects.listProjects",
228
239
  arguments: {
229
- repoName: "test",
240
+ repoName: "adl",
230
241
  },
231
242
  },
232
243
  },
233
244
  {},
234
245
  );
246
+ expect(shortSelector.isError).toBe(false);
247
+ const shortPayload = JSON.parse(shortSelector.content?.[0]?.text ?? "{}");
248
+ expect(shortPayload.ok).toBe(true);
249
+ expect(shortPayload.result).toContain("adl");
250
+ expect(shortPayload.result).toContain("beta");
235
251
 
236
- expect(result.isError).toBe(false);
237
- const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
238
- expect(payload.ok).toBe(true);
239
- expect(payload.result).toContain("adl");
240
- expect(payload.result).toContain("beta");
241
- } finally {
242
- await fs.rm(tempDir, { recursive: true, force: true });
243
- }
244
- });
245
-
246
- it("auto-detects repoRoot from process.cwd() when repoName is omitted", async () => {
247
- const tempDir = await fs.mkdtemp(
248
- path.join(os.tmpdir(), "f0-mcp-server-autoreporoot-"),
249
- );
250
- const originalCwd = process.cwd();
251
-
252
- try {
253
- await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
254
- await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
255
- recursive: true,
256
- });
257
- await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
258
- recursive: true,
259
- });
260
-
261
- process.chdir(tempDir);
262
-
263
- const instance = createExampleMcpServer();
264
- const handler = getToolHandler(instance);
265
-
266
- const result = await handler(
252
+ const fullSelector = await handler(
267
253
  {
268
254
  method: "tools/call",
269
255
  params: {
270
256
  name: "projects.listProjects",
271
- arguments: {},
257
+ arguments: {
258
+ repoName: "F0/adl",
259
+ },
272
260
  },
273
261
  },
274
262
  {},
275
263
  );
276
-
277
- expect(result.isError).toBe(false);
278
- const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
279
- expect(payload.ok).toBe(true);
280
- expect(payload.result).toContain("adl");
281
- expect(payload.result).toContain("beta");
264
+ expect(fullSelector.isError).toBe(false);
265
+ const fullPayload = JSON.parse(fullSelector.content?.[0]?.text ?? "{}");
266
+ expect(fullPayload.ok).toBe(true);
267
+ expect(fullPayload.result).toContain("adl");
268
+ expect(fullPayload.result).toContain("beta");
282
269
  } finally {
283
270
  process.chdir(originalCwd);
284
271
  await fs.rm(tempDir, { recursive: true, force: true });
285
272
  }
286
273
  });
287
274
 
288
- it("still accepts legacy processRoot for backwards compatibility", async () => {
275
+ it("parses owner/repo repoName into repo segment even without full-name metadata", async () => {
276
+ const originalCwd = process.cwd();
289
277
  const tempDir = await fs.mkdtemp(
290
- path.join(os.tmpdir(), "f0-mcp-server-processroot-"),
278
+ path.join(os.tmpdir(), "f0-mcp-server-reponame-segment-fallback-"),
291
279
  );
292
280
  try {
293
281
  await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
294
282
  await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
295
283
  recursive: true,
296
284
  });
285
+ await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
286
+ await fs.writeFile(path.join(tempDir, ".git", "config"), "", "utf8");
297
287
 
288
+ process.chdir(tempDir);
298
289
  const instance = createExampleMcpServer();
299
290
  const handler = getToolHandler(instance);
300
291
 
@@ -304,7 +295,7 @@ describe("createExampleMcpServer request handling", () => {
304
295
  params: {
305
296
  name: "projects.listProjects",
306
297
  arguments: {
307
- processRoot: tempDir,
298
+ repoName: "F0/adl",
308
299
  },
309
300
  },
310
301
  },
@@ -316,14 +307,17 @@ describe("createExampleMcpServer request handling", () => {
316
307
  expect(payload.ok).toBe(true);
317
308
  expect(payload.result).toContain("adl");
318
309
  } finally {
310
+ process.chdir(originalCwd);
319
311
  await fs.rm(tempDir, { recursive: true, force: true });
320
312
  }
321
313
  });
322
314
 
323
- it("normalizes legacy repoRoot when caller accidentally passes a project root", async () => {
315
+ it("auto-detects Git workspace from process.cwd() when repoName is omitted", async () => {
324
316
  const tempDir = await fs.mkdtemp(
325
- path.join(os.tmpdir(), "f0-mcp-server-reporoot-normalize-"),
317
+ path.join(os.tmpdir(), "f0-mcp-server-autoworkspace-"),
326
318
  );
319
+ const originalCwd = process.cwd();
320
+
327
321
  try {
328
322
  await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
329
323
  await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
@@ -332,6 +326,17 @@ describe("createExampleMcpServer request handling", () => {
332
326
  await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
333
327
  recursive: true,
334
328
  });
329
+ await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
330
+ await fs.writeFile(
331
+ path.join(tempDir, ".git", "config"),
332
+ [
333
+ '[remote "origin"]',
334
+ "\turl = https://example.com/F0/adl.git",
335
+ "",
336
+ ].join("\n"),
337
+ );
338
+
339
+ process.chdir(tempDir);
335
340
 
336
341
  const instance = createExampleMcpServer();
337
342
  const handler = getToolHandler(instance);
@@ -341,9 +346,7 @@ describe("createExampleMcpServer request handling", () => {
341
346
  method: "tools/call",
342
347
  params: {
343
348
  name: "projects.listProjects",
344
- arguments: {
345
- repoRoot: path.join(tempDir, "projects", "adl"),
346
- },
349
+ arguments: {},
347
350
  },
348
351
  },
349
352
  {},
@@ -355,24 +358,41 @@ describe("createExampleMcpServer request handling", () => {
355
358
  expect(payload.result).toContain("adl");
356
359
  expect(payload.result).toContain("beta");
357
360
  } finally {
361
+ process.chdir(originalCwd);
358
362
  await fs.rm(tempDir, { recursive: true, force: true });
359
363
  }
360
364
  });
361
365
 
362
- it("uses server default repoRoot when repoName is omitted", async () => {
363
- const tempDir = await fs.mkdtemp(
364
- path.join(os.tmpdir(), "f0-mcp-server-default-reporoot-"),
366
+ it("ignores legacy processRoot/repoRoot payload fields and uses Git workspace", async () => {
367
+ const originalCwd = process.cwd();
368
+ const workspaceA = await fs.mkdtemp(
369
+ path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-a-"),
370
+ );
371
+ const workspaceB = await fs.mkdtemp(
372
+ path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-b-"),
365
373
  );
366
374
  try {
367
- await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
368
- await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
375
+ await fs.mkdir(path.join(workspaceA, "api"), { recursive: true });
376
+ await fs.mkdir(path.join(workspaceA, "projects", "adl", "docs"), {
369
377
  recursive: true,
370
378
  });
371
- await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
379
+ await fs.mkdir(path.join(workspaceA, ".git"), { recursive: true });
380
+ await fs.writeFile(
381
+ path.join(workspaceA, ".git", "config"),
382
+ [
383
+ '[remote "origin"]',
384
+ "\turl = https://example.com/F0/adl.git",
385
+ "",
386
+ ].join("\n"),
387
+ );
388
+
389
+ await fs.mkdir(path.join(workspaceB, "api"), { recursive: true });
390
+ await fs.mkdir(path.join(workspaceB, "projects", "beta", "docs"), {
372
391
  recursive: true,
373
392
  });
374
393
 
375
- const instance = createExampleMcpServer({ repoRoot: tempDir });
394
+ process.chdir(workspaceA);
395
+ const instance = createExampleMcpServer();
376
396
  const handler = getToolHandler(instance);
377
397
 
378
398
  const result = await handler(
@@ -380,7 +400,10 @@ describe("createExampleMcpServer request handling", () => {
380
400
  method: "tools/call",
381
401
  params: {
382
402
  name: "projects.listProjects",
383
- arguments: {},
403
+ arguments: {
404
+ processRoot: workspaceB,
405
+ repoRoot: workspaceB,
406
+ },
384
407
  },
385
408
  },
386
409
  {},
@@ -390,13 +413,16 @@ describe("createExampleMcpServer request handling", () => {
390
413
  const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
391
414
  expect(payload.ok).toBe(true);
392
415
  expect(payload.result).toContain("adl");
393
- expect(payload.result).toContain("beta");
416
+ expect(payload.result).not.toContain("beta");
394
417
  } finally {
395
- await fs.rm(tempDir, { recursive: true, force: true });
418
+ process.chdir(originalCwd);
419
+ await fs.rm(workspaceA, { recursive: true, force: true });
420
+ await fs.rm(workspaceB, { recursive: true, force: true });
396
421
  }
397
422
  });
398
423
 
399
- it("exposes mcp.workspace to explain server filesystem context", async () => {
424
+ it("exposes mcp.workspace with Git workspace context", async () => {
425
+ const originalCwd = process.cwd();
400
426
  const tempDir = await fs.mkdtemp(
401
427
  path.join(os.tmpdir(), "f0-mcp-server-workspace-"),
402
428
  );
@@ -405,8 +431,18 @@ describe("createExampleMcpServer request handling", () => {
405
431
  await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
406
432
  recursive: true,
407
433
  });
434
+ await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
435
+ await fs.writeFile(
436
+ path.join(tempDir, ".git", "config"),
437
+ [
438
+ '[remote "origin"]',
439
+ "\turl = https://example.com/F0/adl.git",
440
+ "",
441
+ ].join("\n"),
442
+ );
408
443
 
409
- const instance = createExampleMcpServer({ repoRoot: tempDir });
444
+ process.chdir(tempDir);
445
+ const instance = createExampleMcpServer();
410
446
  const handler = getToolHandler(instance);
411
447
 
412
448
  const result = await handler(
@@ -423,16 +459,21 @@ describe("createExampleMcpServer request handling", () => {
423
459
  expect(result.isError).toBe(false);
424
460
  const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
425
461
  expect(payload.ok).toBe(true);
426
- expect(payload.result.repoRoot).toBe(path.resolve(tempDir));
427
- expect(payload.result.defaultRepoRoot).toBe(path.resolve(tempDir));
462
+ expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
463
+ expect(payload.result.processRoot).toBe(path.resolve(tempDir));
464
+ expect(payload.result.defaultRepoName).toBe("adl");
465
+ expect(payload.result.defaultRepoFullName).toBe("F0/adl");
466
+ expect(payload.result.availableRepoNames).toContain("adl");
467
+ expect(payload.result.availableRepoNames).toContain("F0/adl");
428
468
  expect(payload.result.hasProjectsDir).toBe(true);
429
469
  expect(Array.isArray(payload.result.projects)).toBe(true);
430
470
  } finally {
471
+ process.chdir(originalCwd);
431
472
  await fs.rm(tempDir, { recursive: true, force: true });
432
473
  }
433
474
  });
434
475
 
435
- it("auto-detects repo root/name from .git/config when no repoRoot/repoName are provided", async () => {
476
+ it("auto-detects repo identity from .git/config when no repoName is provided", async () => {
436
477
  const originalCwd = process.cwd();
437
478
  const tempDir = await fs.mkdtemp(
438
479
  path.join(os.tmpdir(), "f0-mcp-server-git-detect-"),
@@ -470,11 +511,13 @@ describe("createExampleMcpServer request handling", () => {
470
511
  expect(workspace.isError).toBe(false);
471
512
  const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
472
513
  expect(payload.ok).toBe(true);
473
- expect(payload.result.defaultRepoRoot).toBe(path.resolve(tempDir));
474
- expect(payload.result.repoRoot).toBe(path.resolve(tempDir));
514
+ expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
515
+ expect(payload.result.processRoot).toBe(path.resolve(tempDir));
475
516
  expect(payload.result.defaultRepoName).toBe("adl");
517
+ expect(payload.result.defaultRepoFullName).toBe("F0/adl");
476
518
  expect(payload.result.repoName).toBe("adl");
477
519
  expect(payload.result.availableRepoNames).toContain("adl");
520
+ expect(payload.result.availableRepoNames).toContain("F0/adl");
478
521
 
479
522
  const list = await handler(
480
523
  {
@@ -538,6 +581,8 @@ describe("createExampleMcpServer request handling", () => {
538
581
  const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
539
582
  expect(payload.ok).toBe(true);
540
583
  expect(payload.result.defaultRepoName).toBe("adl");
584
+ expect(payload.result.defaultRepoFullName).toBe("F0/adl");
585
+ expect(payload.result.availableRepoNames).toContain("F0/adl");
541
586
  } finally {
542
587
  process.chdir(originalCwd);
543
588
  await fs.rm(tempDir, { recursive: true, force: true });