feat: monorepo consolidation — forge pipeline, MACP protocol, framework plugin, profiles/guides/skills
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed

Work packages completed:
- WP1: packages/forge — pipeline runner, stage adapter, board tasks, brief classifier,
  persona loader with project-level overrides. 89 tests, 95.62% coverage.
- WP2: packages/macp — credential resolver, gate runner, event emitter, protocol types.
  65 tests, 96.24% coverage. Full Python-to-TS port preserving all behavior.
- WP3: plugins/mosaic-framework — OC rails injection plugin (before_agent_start +
  subagent_spawning hooks for Mosaic contract enforcement).
- WP4: profiles/ (domains, tech-stacks, workflows), guides/ (17 docs),
  skills/ (5 universal skills), forge pipeline assets (48 markdown files).

Board deliberation: docs/reviews/consolidation-board-memo.md
Brief: briefs/monorepo-consolidation.md

Consolidates mosaic/stack (forge, MACP, bootstrap framework) into mosaic/mosaic-stack.
154 new tests total. Zero Python — all TypeScript/ESM.
This commit is contained in:
Mos (Agent)
2026-03-30 19:43:24 +00:00
parent 40c068fcbc
commit 10689a30d2
123 changed files with 18166 additions and 11 deletions

View File

@@ -0,0 +1,307 @@
import { mkdirSync, writeFileSync, chmodSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
extractProvider,
parseDotenv,
stripJSON5Extensions,
checkOCConfigPermissions,
isValidCredential,
resolveCredentials,
REDACTED_MARKER,
PROVIDER_REGISTRY,
} from '../src/credential-resolver.js';
import { CredentialError } from '../src/types.js';
function makeTmpDir(): string {
const dir = join(tmpdir(), `macp-test-${randomUUID()}`);
mkdirSync(dir, { recursive: true });
return dir;
}
describe('extractProvider', () => {
it('extracts provider from model reference', () => {
expect(extractProvider('anthropic/claude-3')).toBe('anthropic');
expect(extractProvider('openai/gpt-4')).toBe('openai');
expect(extractProvider('zai/model-x')).toBe('zai');
});
it('handles whitespace and casing', () => {
expect(extractProvider(' Anthropic/claude-3 ')).toBe('anthropic');
});
it('throws on empty model reference', () => {
expect(() => extractProvider('')).toThrow(CredentialError);
expect(() => extractProvider(' ')).toThrow(CredentialError);
});
it('throws on unsupported provider', () => {
expect(() => extractProvider('unknown/model')).toThrow(CredentialError);
expect(() => extractProvider('unknown/model')).toThrow('Unsupported credential provider');
});
});
describe('parseDotenv', () => {
it('parses key=value pairs', () => {
expect(parseDotenv('FOO=bar\nBAZ=qux')).toEqual({ FOO: 'bar', BAZ: 'qux' });
});
it('strips single and double quotes', () => {
expect(parseDotenv('A="hello"\nB=\'world\'')).toEqual({ A: 'hello', B: 'world' });
});
it('skips comments and blank lines', () => {
expect(parseDotenv('# comment\n\nFOO=bar\n # another\n')).toEqual({ FOO: 'bar' });
});
it('skips lines without =', () => {
expect(parseDotenv('NOEQUALS\nFOO=bar')).toEqual({ FOO: 'bar' });
});
it('skips lines with empty key', () => {
expect(parseDotenv('=value\nFOO=bar')).toEqual({ FOO: 'bar' });
});
it('handles value with = in it', () => {
expect(parseDotenv('KEY=val=ue')).toEqual({ KEY: 'val=ue' });
});
});
describe('stripJSON5Extensions', () => {
it('removes trailing commas', () => {
const input = '{"a": 1, "b": 2,}';
const result = JSON.parse(stripJSON5Extensions(input));
expect(result).toEqual({ a: 1, b: 2 });
});
it('quotes unquoted keys', () => {
const input = '{foo: "bar", baz: 42}';
const result = JSON.parse(stripJSON5Extensions(input));
expect(result).toEqual({ foo: 'bar', baz: 42 });
});
it('removes full-line comments', () => {
const input = '{\n // this is a comment\n "key": "value"\n}';
const result = JSON.parse(stripJSON5Extensions(input));
expect(result).toEqual({ key: 'value' });
});
it('handles single-quoted strings', () => {
const input = "{key: 'value'}";
const result = JSON.parse(stripJSON5Extensions(input));
expect(result).toEqual({ key: 'value' });
});
it('preserves URLs and timestamps inside string values', () => {
const input = '{"url": "https://example.com/path?q=1", "ts": "2024-01-01T00:00:00Z"}';
const result = JSON.parse(stripJSON5Extensions(input));
expect(result.url).toBe('https://example.com/path?q=1');
expect(result.ts).toBe('2024-01-01T00:00:00Z');
});
it('handles complex JSON5 with mixed features', () => {
const input = `{
// comment
apiKey: 'sk-abc123',
url: "https://api.example.com/v1",
nested: {
value: "hello",
flag: true,
},
}`;
const result = JSON.parse(stripJSON5Extensions(input));
expect(result.apiKey).toBe('sk-abc123');
expect(result.url).toBe('https://api.example.com/v1');
expect(result.nested.value).toBe('hello');
expect(result.nested.flag).toBe(true);
});
});
describe('isValidCredential', () => {
it('returns true for normal values', () => {
expect(isValidCredential('sk-abc123')).toBe(true);
});
it('returns false for empty/whitespace', () => {
expect(isValidCredential('')).toBe(false);
expect(isValidCredential(' ')).toBe(false);
});
it('returns false for redacted marker', () => {
expect(isValidCredential(REDACTED_MARKER)).toBe(false);
});
});
describe('checkOCConfigPermissions', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('returns false for non-existent file', () => {
expect(checkOCConfigPermissions(join(tmp, 'missing.json'))).toBe(false);
});
it('returns true for file owned by current user', () => {
const p = join(tmp, 'config.json');
writeFileSync(p, '{}');
chmodSync(p, 0o600);
expect(checkOCConfigPermissions(p)).toBe(true);
});
it('returns true with warning for world-readable file', () => {
const p = join(tmp, 'config.json');
writeFileSync(p, '{}');
chmodSync(p, 0o644);
expect(checkOCConfigPermissions(p)).toBe(true);
});
it('returns false when uid does not match', () => {
const p = join(tmp, 'config.json');
writeFileSync(p, '{}');
expect(checkOCConfigPermissions(p, { getuid: () => 99999 })).toBe(false);
});
});
describe('resolveCredentials', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
delete process.env['ANTHROPIC_API_KEY'];
delete process.env['OPENAI_API_KEY'];
delete process.env['ZAI_API_KEY'];
delete process.env['CUSTOM_KEY'];
});
it('resolves from credential file', () => {
writeFileSync(join(tmp, 'anthropic.env'), 'ANTHROPIC_API_KEY=sk-file-key\n');
const result = resolveCredentials('anthropic/claude-3', { credentialsDir: tmp });
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-file-key' });
});
it('resolves from ambient environment', () => {
process.env['ANTHROPIC_API_KEY'] = 'sk-ambient-key';
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-ambient-key' });
});
it('resolves from OC config env block', () => {
const ocPath = join(tmp, 'openclaw.json');
writeFileSync(ocPath, JSON.stringify({ env: { ANTHROPIC_API_KEY: 'sk-oc-env' } }));
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: ocPath,
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-oc-env' });
});
it('resolves from OC config provider apiKey', () => {
const ocPath = join(tmp, 'openclaw.json');
writeFileSync(
ocPath,
JSON.stringify({
env: {},
models: { providers: { anthropic: { apiKey: 'sk-oc-provider' } } },
}),
);
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: ocPath,
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-oc-provider' });
});
it('mosaic credential file wins over OC config', () => {
writeFileSync(join(tmp, 'anthropic.env'), 'ANTHROPIC_API_KEY=sk-file-wins\n');
const ocPath = join(tmp, 'openclaw.json');
writeFileSync(ocPath, JSON.stringify({ env: { ANTHROPIC_API_KEY: 'sk-oc-loses' } }));
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: tmp,
ocConfigPath: ocPath,
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-file-wins' });
});
it('gracefully falls back when OC config is missing', () => {
process.env['ANTHROPIC_API_KEY'] = 'sk-fallback';
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: join(tmp, 'nonexistent.json'),
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-fallback' });
});
it('skips redacted values in OC config', () => {
const ocPath = join(tmp, 'openclaw.json');
writeFileSync(ocPath, JSON.stringify({ env: { ANTHROPIC_API_KEY: REDACTED_MARKER } }));
process.env['ANTHROPIC_API_KEY'] = 'sk-ambient';
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: ocPath,
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-ambient' });
});
it('throws CredentialError when nothing resolves', () => {
expect(() =>
resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: join(tmp, 'nonexistent.json'),
}),
).toThrow(CredentialError);
});
it('supports task-level credential env var override', () => {
process.env['CUSTOM_KEY'] = 'sk-custom';
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: join(tmp, 'nonexistent.json'),
taskConfig: { credentials: { provider_key_env: 'CUSTOM_KEY' } },
});
expect(result).toEqual({ CUSTOM_KEY: 'sk-custom' });
});
it('handles JSON5 OC config syntax', () => {
const ocPath = join(tmp, 'openclaw.json');
writeFileSync(
ocPath,
`{
// OC config with JSON5 features
env: {
ANTHROPIC_API_KEY: 'sk-json5-key',
},
}`,
);
const result = resolveCredentials('anthropic/claude-3', {
credentialsDir: join(tmp, 'empty'),
ocConfigPath: ocPath,
});
expect(result).toEqual({ ANTHROPIC_API_KEY: 'sk-json5-key' });
});
});
describe('PROVIDER_REGISTRY', () => {
it('has entries for anthropic, openai, zai', () => {
expect(Object.keys(PROVIDER_REGISTRY)).toEqual(['anthropic', 'openai', 'zai']);
for (const meta of Object.values(PROVIDER_REGISTRY)) {
expect(meta).toHaveProperty('credential_file');
expect(meta).toHaveProperty('env_var');
expect(meta).toHaveProperty('oc_env_key');
expect(meta).toHaveProperty('oc_provider_path');
}
});
});

View File

@@ -0,0 +1,141 @@
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { nowISO, appendEvent, emitEvent } from '../src/event-emitter.js';
import type { MACPEvent } from '../src/types.js';
function makeTmpDir(): string {
const dir = join(tmpdir(), `macp-event-${randomUUID()}`);
mkdirSync(dir, { recursive: true });
return dir;
}
describe('nowISO', () => {
it('returns a valid ISO timestamp', () => {
const ts = nowISO();
expect(() => new Date(ts)).not.toThrow();
expect(new Date(ts).toISOString()).toBe(ts);
});
});
describe('appendEvent', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('appends event as ndjson line', () => {
const eventsPath = join(tmp, 'events.ndjson');
const event: MACPEvent = {
event_id: 'evt-1',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: 'Test event',
metadata: {},
};
appendEvent(eventsPath, event);
const content = readFileSync(eventsPath, 'utf-8');
const lines = content.trim().split('\n');
expect(lines).toHaveLength(1);
const parsed = JSON.parse(lines[0]!);
expect(parsed.event_id).toBe('evt-1');
expect(parsed.event_type).toBe('task.started');
expect(parsed.task_id).toBe('task-1');
});
it('appends multiple events', () => {
const eventsPath = join(tmp, 'events.ndjson');
const base: MACPEvent = {
event_id: '',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: '',
metadata: {},
};
appendEvent(eventsPath, { ...base, event_id: 'evt-1', message: 'first' });
appendEvent(eventsPath, { ...base, event_id: 'evt-2', message: 'second' });
const lines = readFileSync(eventsPath, 'utf-8').trim().split('\n');
expect(lines).toHaveLength(2);
});
it('creates parent directories', () => {
const eventsPath = join(tmp, 'nested', 'deep', 'events.ndjson');
const event: MACPEvent = {
event_id: 'evt-1',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: 'nested',
metadata: {},
};
appendEvent(eventsPath, event);
expect(readFileSync(eventsPath, 'utf-8')).toContain('nested');
});
});
describe('emitEvent', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('creates event with all required fields', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.completed', 'task-42', 'completed', 'controller', 'Task done');
const content = readFileSync(eventsPath, 'utf-8');
const event = JSON.parse(content.trim());
expect(event.event_id).toBeTruthy();
expect(event.event_type).toBe('task.completed');
expect(event.task_id).toBe('task-42');
expect(event.status).toBe('completed');
expect(event.source).toBe('controller');
expect(event.message).toBe('Task done');
expect(event.timestamp).toBeTruthy();
expect(event.metadata).toEqual({});
});
it('includes metadata when provided', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.failed', 'task-1', 'failed', 'worker', 'err', {
exit_code: 1,
});
const event = JSON.parse(readFileSync(eventsPath, 'utf-8').trim());
expect(event.metadata).toEqual({ exit_code: 1 });
});
it('generates unique event_ids', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.started', 'task-1', 'running', 'test', 'a');
emitEvent(eventsPath, 'task.started', 'task-1', 'running', 'test', 'b');
const events = readFileSync(eventsPath, 'utf-8')
.trim()
.split('\n')
.map((l) => JSON.parse(l));
expect(events[0].event_id).not.toBe(events[1].event_id);
});
});

View File

@@ -0,0 +1,253 @@
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { normalizeGate, countAIFindings, runGate, runGates } from '../src/gate-runner.js';
function makeTmpDir(): string {
const dir = join(tmpdir(), `macp-gate-${randomUUID()}`);
mkdirSync(dir, { recursive: true });
return dir;
}
describe('normalizeGate', () => {
it('normalizes a string to mechanical gate', () => {
expect(normalizeGate('echo test')).toEqual({
command: 'echo test',
type: 'mechanical',
fail_on: 'blocker',
});
});
it('normalizes an object gate with defaults', () => {
expect(normalizeGate({ command: 'lint' })).toEqual({
command: 'lint',
type: 'mechanical',
fail_on: 'blocker',
});
});
it('preserves explicit type and fail_on', () => {
expect(normalizeGate({ command: 'review', type: 'ai-review', fail_on: 'any' })).toEqual({
command: 'review',
type: 'ai-review',
fail_on: 'any',
});
});
it('handles non-string/non-object input', () => {
expect(normalizeGate(42)).toEqual({ command: '', type: 'mechanical', fail_on: 'blocker' });
expect(normalizeGate(null)).toEqual({ command: '', type: 'mechanical', fail_on: 'blocker' });
});
});
describe('countAIFindings', () => {
it('returns zeros for non-object', () => {
expect(countAIFindings(null)).toEqual({ blockers: 0, total: 0 });
expect(countAIFindings('string')).toEqual({ blockers: 0, total: 0 });
expect(countAIFindings([])).toEqual({ blockers: 0, total: 0 });
});
it('counts from stats block', () => {
const output = { stats: { blockers: 2, should_fix: 3, suggestions: 1 } };
expect(countAIFindings(output)).toEqual({ blockers: 2, total: 6 });
});
it('counts from findings array when stats has no blockers', () => {
const output = {
stats: { blockers: 0 },
findings: [{ severity: 'blocker' }, { severity: 'warning' }, { severity: 'blocker' }],
};
expect(countAIFindings(output)).toEqual({ blockers: 2, total: 3 });
});
it('uses stats blockers over findings array when stats has blockers', () => {
const output = {
stats: { blockers: 5 },
findings: [{ severity: 'blocker' }, { severity: 'warning' }],
};
// stats.blockers = 5, total from stats = 5+0+0 = 5, findings not used for total since stats total is non-zero
expect(countAIFindings(output)).toEqual({ blockers: 5, total: 5 });
});
it('counts findings length as total when stats has zero total', () => {
const output = {
findings: [{ severity: 'warning' }, { severity: 'info' }],
};
expect(countAIFindings(output)).toEqual({ blockers: 0, total: 2 });
});
});
describe('runGate', () => {
let tmp: string;
let logPath: string;
beforeEach(() => {
tmp = makeTmpDir();
logPath = join(tmp, 'gate.log');
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('passes mechanical gate on exit 0', () => {
const result = runGate('echo hello', tmp, logPath, 30);
expect(result.passed).toBe(true);
expect(result.exit_code).toBe(0);
expect(result.type).toBe('mechanical');
expect(result.output).toContain('hello');
});
it('fails mechanical gate on non-zero exit', () => {
const result = runGate('exit 1', tmp, logPath, 30);
expect(result.passed).toBe(false);
expect(result.exit_code).toBe(1);
});
it('ci-pipeline always passes', () => {
const result = runGate({ command: 'anything', type: 'ci-pipeline' }, tmp, logPath, 30);
expect(result.passed).toBe(true);
expect(result.type).toBe('ci-pipeline');
expect(result.output).toBe('CI pipeline gate placeholder');
});
it('empty command passes', () => {
const result = runGate({ command: '' }, tmp, logPath, 30);
expect(result.passed).toBe(true);
});
it('ai-review gate parses JSON output', () => {
const json = JSON.stringify({ stats: { blockers: 0, should_fix: 1 } });
const result = runGate({ command: `echo '${json}'`, type: 'ai-review' }, tmp, logPath, 30);
expect(result.passed).toBe(true);
expect(result.blockers).toBe(0);
expect(result.findings).toBe(1);
});
it('ai-review gate fails on blockers', () => {
const json = JSON.stringify({ stats: { blockers: 2 } });
const result = runGate({ command: `echo '${json}'`, type: 'ai-review' }, tmp, logPath, 30);
expect(result.passed).toBe(false);
expect(result.blockers).toBe(2);
});
it('ai-review gate with fail_on=any fails on any findings', () => {
const json = JSON.stringify({ stats: { blockers: 0, should_fix: 1 } });
const result = runGate(
{ command: `echo '${json}'`, type: 'ai-review', fail_on: 'any' },
tmp,
logPath,
30,
);
expect(result.passed).toBe(false);
expect(result.fail_on).toBe('any');
});
it('ai-review gate fails on invalid JSON output', () => {
const result = runGate({ command: 'echo "not json"', type: 'ai-review' }, tmp, logPath, 30);
expect(result.passed).toBe(false);
expect(result.parse_error).toBeDefined();
});
it('writes to log file', () => {
runGate('echo logged', tmp, logPath, 30);
const log = readFileSync(logPath, 'utf-8');
expect(log).toContain('COMMAND: echo logged');
expect(log).toContain('logged');
expect(log).toContain('EXIT:');
});
});
describe('runGates', () => {
let tmp: string;
let logPath: string;
let eventsPath: string;
beforeEach(() => {
tmp = makeTmpDir();
logPath = join(tmp, 'gates.log');
eventsPath = join(tmp, 'events.ndjson');
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('runs multiple gates and returns results', () => {
const { allPassed, gateResults } = runGates(
['echo one', 'echo two'],
tmp,
logPath,
30,
eventsPath,
'task-1',
);
expect(allPassed).toBe(true);
expect(gateResults).toHaveLength(2);
});
it('reports failure when any gate fails', () => {
const { allPassed, gateResults } = runGates(
['echo ok', 'exit 1'],
tmp,
logPath,
30,
eventsPath,
'task-2',
);
expect(allPassed).toBe(false);
expect(gateResults[0]!.passed).toBe(true);
expect(gateResults[1]!.passed).toBe(false);
});
it('emits events for each gate', () => {
runGates(['echo test'], tmp, logPath, 30, eventsPath, 'task-3');
const events = readFileSync(eventsPath, 'utf-8')
.trim()
.split('\n')
.map((l) => JSON.parse(l));
expect(events).toHaveLength(2); // started + passed
expect(events[0].event_type).toBe('rail.check.started');
expect(events[1].event_type).toBe('rail.check.passed');
});
it('skips gates with empty command (non ci-pipeline)', () => {
const { gateResults } = runGates(
[{ command: '', type: 'mechanical' }, 'echo real'],
tmp,
logPath,
30,
eventsPath,
'task-4',
);
expect(gateResults).toHaveLength(1);
});
it('does not skip ci-pipeline even with empty command', () => {
const { gateResults } = runGates(
[{ command: '', type: 'ci-pipeline' }],
tmp,
logPath,
30,
eventsPath,
'task-5',
);
expect(gateResults).toHaveLength(1);
expect(gateResults[0]!.passed).toBe(true);
});
it('emits failed event with correct message', () => {
runGates(['exit 42'], tmp, logPath, 30, eventsPath, 'task-6');
const events = readFileSync(eventsPath, 'utf-8')
.trim()
.split('\n')
.map((l) => JSON.parse(l));
const failEvent = events.find(
(e: Record<string, unknown>) => e.event_type === 'rail.check.failed',
);
expect(failEvent).toBeDefined();
expect(failEvent.message).toContain('Gate failed (');
});
});