feat(lib): bundle MCP servers from .common/lib into factory/lib/
Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for: - projects-meta-mcp v2.25.0 (TypeScript/Node) - wiki-graph v0.3.1 (TypeScript/Node) - interns-mcp v0.3.3 (Python/FastMCP) .gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc) bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
153
lib/projects-meta-mcp/tests/tools/meta.test.ts
Normal file
153
lib/projects-meta-mcp/tests/tools/meta.test.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { writeCache } from '../../src/lib/cache.js';
|
||||
import { makeMetaTools } from '../../src/tools/meta.js';
|
||||
import type { Paths } from '../../src/lib/config.js';
|
||||
|
||||
function mkPaths(home: string): Paths {
|
||||
const cacheDir = join(home, '.cache');
|
||||
return {
|
||||
cacheDir,
|
||||
cacheFile: join(cacheDir, 'tasks.json'),
|
||||
syncLog: join(cacheDir, 'sync.log'),
|
||||
authFile: join(home, '.config', 'auth.toml'),
|
||||
sharedWikiClone: join(home, 'wiki'),
|
||||
agendaTasksDir: join(home, 'tasks'),
|
||||
};
|
||||
}
|
||||
|
||||
describe('meta.status', () => {
|
||||
let paths: Paths;
|
||||
|
||||
beforeEach(async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), 'meta-'));
|
||||
paths = mkPaths(home);
|
||||
await mkdir(paths.sharedWikiClone, { recursive: true });
|
||||
await mkdir(join(paths.sharedWikiClone, 'node'), { recursive: true });
|
||||
await writeFile(
|
||||
join(paths.sharedWikiClone, 'node', 'one.md'),
|
||||
'---\ntitle: t\ndomain: node\n---\n\nbody',
|
||||
);
|
||||
});
|
||||
|
||||
it('reports cache_missing when no cache', async () => {
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.cache_missing).toBe(true);
|
||||
expect(parsed.wiki_pages_count).toBe(1);
|
||||
expect(parsed.agenda_present).toBe(false);
|
||||
expect(parsed.agenda_tasks_dir).toBe(paths.agendaTasksDir);
|
||||
});
|
||||
|
||||
it('reports age and stale=false for fresh cache', async () => {
|
||||
await writeCache(paths.cacheFile, {
|
||||
schemaVersion: 3,
|
||||
synced_at: new Date().toISOString(),
|
||||
synced_from: 'https://g',
|
||||
machine: 'm',
|
||||
projects: [],
|
||||
errors: [],
|
||||
});
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.stale).toBe(false);
|
||||
expect(parsed.age_seconds).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it('reports stale=true for >24h cache', async () => {
|
||||
const old = new Date(Date.now() - 25 * 3600 * 1000).toISOString();
|
||||
await writeCache(paths.cacheFile, {
|
||||
schemaVersion: 3,
|
||||
synced_at: old,
|
||||
synced_from: 'https://g',
|
||||
machine: 'm',
|
||||
projects: [{ name: 'p', default_branch: 'main', fetched_at: old, active_tasks: [], all_tasks_count: 0, raw: '' }],
|
||||
errors: [{ project: 'q', reason: 'x' }],
|
||||
});
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.stale).toBe(true);
|
||||
expect(parsed.projects_count).toBe(1);
|
||||
expect(parsed.errors_count).toBe(1);
|
||||
});
|
||||
|
||||
it('exposes agenda-project counts and source=local when agenda default_branch is "local"', async () => {
|
||||
await writeCache(paths.cacheFile, {
|
||||
schemaVersion: 3,
|
||||
synced_at: new Date().toISOString(),
|
||||
synced_from: 'https://g',
|
||||
machine: 'm',
|
||||
projects: [
|
||||
{ name: 'p', default_branch: 'main', fetched_at: new Date().toISOString(), active_tasks: [], all_tasks_count: 0, raw: '' },
|
||||
{
|
||||
name: 'agenda',
|
||||
default_branch: 'local',
|
||||
fetched_at: new Date().toISOString(),
|
||||
active_tasks: [
|
||||
{ slug: 'm1', status: 'active', next: 'do x' },
|
||||
{ slug: 'm2', status: 'paused', next: 'do y' },
|
||||
],
|
||||
all_tasks_count: 3,
|
||||
raw: '...',
|
||||
},
|
||||
],
|
||||
errors: [],
|
||||
});
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.agenda_present).toBe(true);
|
||||
expect(parsed.agenda_tasks_active).toBe(2);
|
||||
expect(parsed.agenda_tasks_total).toBe(3);
|
||||
expect(parsed.agenda_source).toBe('local');
|
||||
expect(parsed.agenda_branch).toBe('local');
|
||||
});
|
||||
|
||||
it('reports agenda_source=gitea when agenda default_branch is a real branch', async () => {
|
||||
await writeCache(paths.cacheFile, {
|
||||
schemaVersion: 3,
|
||||
synced_at: new Date().toISOString(),
|
||||
synced_from: 'https://g',
|
||||
machine: 'm',
|
||||
projects: [
|
||||
{
|
||||
name: 'agenda',
|
||||
default_branch: 'main',
|
||||
fetched_at: new Date().toISOString(),
|
||||
active_tasks: [{ slug: 'g1', status: 'active', next: 'x' }],
|
||||
all_tasks_count: 1,
|
||||
raw: '...',
|
||||
},
|
||||
],
|
||||
errors: [],
|
||||
});
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.agenda_present).toBe(true);
|
||||
expect(parsed.agenda_source).toBe('gitea');
|
||||
expect(parsed.agenda_branch).toBe('main');
|
||||
});
|
||||
|
||||
it('reports agenda_source=none when agenda absent', async () => {
|
||||
await writeCache(paths.cacheFile, {
|
||||
schemaVersion: 3,
|
||||
synced_at: new Date().toISOString(),
|
||||
synced_from: 'https://g',
|
||||
machine: 'm',
|
||||
projects: [{ name: 'p', default_branch: 'main', fetched_at: new Date().toISOString(), active_tasks: [], all_tasks_count: 0, raw: '' }],
|
||||
errors: [],
|
||||
});
|
||||
const tools = makeMetaTools({ paths });
|
||||
const r = await (tools.find((t) => t.name === 'meta.status')!.handler({}));
|
||||
const parsed = JSON.parse(r.content[0].text);
|
||||
expect(parsed.agenda_present).toBe(false);
|
||||
expect(parsed.agenda_source).toBe('none');
|
||||
expect(parsed.agenda_branch).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user