mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-06 18:52:03 +08:00
411f5e71df
Agent templates: folder-only templates under templates/ (context/instructions.md + optional context extras, .mcp.json, skills/). Stamping via ncl groups create --template writes the provider-neutral instructions.prepend.md (inlined at the top of CLAUDE.md/AGENTS.md every spawn), copies context extras preserving their template-relative layout, writes MCP servers to container config, and installs the per-group skills overlay. Includes docs (docs/templates.md, templates/README.md). Setup-wizard wiring ships separately on top of this. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const TEST_ROOT = '/tmp/nanoclaw-claude-md-compose-test';
|
|
const GROUPS_DIR = path.join(TEST_ROOT, 'groups');
|
|
|
|
vi.mock('./config.js', async (importOriginal) => ({
|
|
...(await importOriginal<typeof import('./config.js')>()),
|
|
GROUPS_DIR: '/tmp/nanoclaw-claude-md-compose-test/groups',
|
|
}));
|
|
|
|
vi.mock('./log.js', () => ({
|
|
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), fatal: vi.fn() },
|
|
}));
|
|
|
|
import { composeGroupClaudeMd } from './claude-md-compose.js';
|
|
import { ensureContainerConfig } from './db/container-configs.js';
|
|
import { closeDb, createAgentGroup, initTestDb, runMigrations } from './db/index.js';
|
|
import { PERSONA_PREPEND_FILE } from './group-persona.js';
|
|
import type { AgentGroup } from './types.js';
|
|
|
|
function group(id: string, folder: string): AgentGroup {
|
|
return { id, name: folder, folder, agent_provider: null, created_at: new Date().toISOString() } as AgentGroup;
|
|
}
|
|
|
|
function seed(ag: AgentGroup): void {
|
|
createAgentGroup(ag);
|
|
ensureContainerConfig(ag.id);
|
|
}
|
|
|
|
function writePersona(folder: string, text: string): void {
|
|
const dir = path.join(GROUPS_DIR, folder);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, PERSONA_PREPEND_FILE), text);
|
|
}
|
|
|
|
function importsOf(folder: string): string[] {
|
|
const md = fs.readFileSync(path.join(GROUPS_DIR, folder, 'CLAUDE.md'), 'utf-8');
|
|
return md.split('\n').filter((line) => line.startsWith('@'));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
fs.mkdirSync(TEST_ROOT, { recursive: true });
|
|
runMigrations(initTestDb());
|
|
});
|
|
|
|
afterEach(() => {
|
|
closeDb();
|
|
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('composeGroupClaudeMd persona prepend', () => {
|
|
it('imports the persona fragment FIRST, before the shared base', () => {
|
|
const ag = group('ag-persona', 'persona-group');
|
|
seed(ag);
|
|
writePersona(ag.folder, 'You are an SDR agent.\n');
|
|
|
|
composeGroupClaudeMd(ag);
|
|
|
|
const imports = importsOf(ag.folder);
|
|
expect(imports[0]).toBe('@./.claude-fragments/persona.md');
|
|
expect(imports[1]).toBe('@./.claude-shared.md');
|
|
expect(fs.readFileSync(path.join(GROUPS_DIR, ag.folder, '.claude-fragments', 'persona.md'), 'utf-8')).toBe(
|
|
'You are an SDR agent.',
|
|
);
|
|
});
|
|
|
|
it('keeps the persona across a second compose (not pruned)', () => {
|
|
const ag = group('ag-persona-2', 'persona-group-2');
|
|
seed(ag);
|
|
writePersona(ag.folder, 'persona body');
|
|
|
|
composeGroupClaudeMd(ag);
|
|
composeGroupClaudeMd(ag);
|
|
|
|
expect(fs.existsSync(path.join(GROUPS_DIR, ag.folder, '.claude-fragments', 'persona.md'))).toBe(true);
|
|
expect(importsOf(ag.folder)[0]).toBe('@./.claude-fragments/persona.md');
|
|
});
|
|
|
|
it('is inert when no persona file is present (non-template groups)', () => {
|
|
const ag = group('ag-no-persona', 'no-persona-group');
|
|
seed(ag);
|
|
|
|
composeGroupClaudeMd(ag);
|
|
|
|
const imports = importsOf(ag.folder);
|
|
expect(imports[0]).toBe('@./.claude-shared.md');
|
|
expect(imports).not.toContain('@./.claude-fragments/persona.md');
|
|
expect(fs.existsSync(path.join(GROUPS_DIR, ag.folder, '.claude-fragments', 'persona.md'))).toBe(false);
|
|
});
|
|
});
|