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,240 @@
import { spawnSync } from 'node:child_process';
import { appendFileSync, mkdirSync } from 'node:fs';
import { dirname } from 'node:path';
import { emitEvent } from './event-emitter.js';
import { nowISO } from './event-emitter.js';
import type { GateResult } from './types.js';
export interface NormalizedGate {
command: string;
type: string;
fail_on: string;
}
export function normalizeGate(gate: unknown): NormalizedGate {
if (typeof gate === 'string') {
return { command: gate, type: 'mechanical', fail_on: 'blocker' };
}
if (typeof gate === 'object' && gate !== null && !Array.isArray(gate)) {
const g = gate as Record<string, unknown>;
return {
command: String(g['command'] ?? ''),
type: String(g['type'] ?? 'mechanical'),
fail_on: String(g['fail_on'] ?? 'blocker'),
};
}
return { command: '', type: 'mechanical', fail_on: 'blocker' };
}
export function runShell(
command: string,
cwd: string,
logPath: string,
timeoutSec: number,
): { exitCode: number; output: string; timedOut: boolean } {
mkdirSync(dirname(logPath), { recursive: true });
const header = `\n[${nowISO()}] COMMAND: ${command}\n`;
appendFileSync(logPath, header, 'utf-8');
let exitCode: number;
let output = '';
let timedOut = false;
try {
const result = spawnSync('bash', ['-lc', command], {
cwd,
timeout: Math.max(1, timeoutSec) * 1000,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
output = (result.stdout ?? '') + (result.stderr ?? '');
if (result.error && (result.error as NodeJS.ErrnoException).code === 'ETIMEDOUT') {
timedOut = true;
exitCode = 124;
appendFileSync(logPath, `[${nowISO()}] TIMEOUT: exceeded ${timeoutSec}s\n`, 'utf-8');
} else {
exitCode = result.status ?? 1;
}
} catch {
exitCode = 1;
}
if (output) appendFileSync(logPath, output, 'utf-8');
appendFileSync(logPath, `[${nowISO()}] EXIT: ${exitCode}\n`, 'utf-8');
return { exitCode, output, timedOut };
}
export function countAIFindings(parsedOutput: unknown): { blockers: number; total: number } {
if (typeof parsedOutput !== 'object' || parsedOutput === null || Array.isArray(parsedOutput)) {
return { blockers: 0, total: 0 };
}
const obj = parsedOutput as Record<string, unknown>;
const stats = obj['stats'];
let blockers = 0;
let total = 0;
if (typeof stats === 'object' && stats !== null && !Array.isArray(stats)) {
const s = stats as Record<string, unknown>;
blockers = Number(s['blockers']) || 0;
total = blockers + (Number(s['should_fix']) || 0) + (Number(s['suggestions']) || 0);
}
const findings = obj['findings'];
if (Array.isArray(findings)) {
if (blockers === 0) {
blockers = findings.filter(
(f) =>
typeof f === 'object' &&
f !== null &&
(f as Record<string, unknown>)['severity'] === 'blocker',
).length;
}
if (total === 0) {
total = findings.length;
}
}
return { blockers, total };
}
export function runGate(
gate: unknown,
cwd: string,
logPath: string,
timeoutSec: number,
): GateResult {
const gateEntry = normalizeGate(gate);
const gateType = gateEntry.type;
const command = gateEntry.command;
if (gateType === 'ci-pipeline') {
return {
command,
exit_code: 0,
type: gateType,
output: 'CI pipeline gate placeholder',
timed_out: false,
passed: true,
};
}
if (!command) {
return {
command: '',
exit_code: 0,
type: gateType,
output: '',
timed_out: false,
passed: true,
};
}
const { exitCode, output, timedOut } = runShell(command, cwd, logPath, timeoutSec);
const result: GateResult = {
command,
exit_code: exitCode,
type: gateType,
output,
timed_out: timedOut,
passed: false,
};
if (gateType !== 'ai-review') {
result.passed = exitCode === 0;
return result;
}
const failOn = gateEntry.fail_on || 'blocker';
let parsedOutput: unknown = undefined;
let blockers = 0;
let findingsCount = 0;
let parseError: string | undefined;
try {
parsedOutput = output.trim() ? JSON.parse(output) : {};
const counts = countAIFindings(parsedOutput);
blockers = counts.blockers;
findingsCount = counts.total;
} catch (exc) {
parseError = String(exc instanceof Error ? exc.message : exc);
}
if (failOn === 'any') {
result.passed = exitCode === 0 && findingsCount === 0 && !timedOut && parseError === undefined;
} else {
result.passed = exitCode === 0 && blockers === 0 && !timedOut && parseError === undefined;
}
result.fail_on = failOn;
result.blockers = blockers;
result.findings = findingsCount;
if (parsedOutput !== undefined) {
result.parsed_output = parsedOutput;
}
if (parseError !== undefined) {
result.parse_error = parseError;
}
return result;
}
export function runGates(
gates: unknown[],
cwd: string,
logPath: string,
timeoutSec: number,
eventsPath: string,
taskId: string,
): { allPassed: boolean; gateResults: GateResult[] } {
let allPassed = true;
const gateResults: GateResult[] = [];
for (const gate of gates) {
const gateEntry = normalizeGate(gate);
const gateCmd = gateEntry.command;
if (!gateCmd && gateEntry.type !== 'ci-pipeline') continue;
const label = gateCmd || gateEntry.type;
emitEvent(
eventsPath,
'rail.check.started',
taskId,
'gated',
'quality-gate',
`Running gate: ${label}`,
);
const result = runGate(gate, cwd, logPath, timeoutSec);
gateResults.push(result);
if (result.passed) {
emitEvent(
eventsPath,
'rail.check.passed',
taskId,
'gated',
'quality-gate',
`Gate passed: ${label}`,
);
continue;
}
allPassed = false;
let message: string;
if (result.timed_out) {
message = `Gate timed out after ${timeoutSec}s: ${label}`;
} else if (result.type === 'ai-review' && result.parse_error) {
message = `AI review gate output was not valid JSON: ${label}`;
} else {
message = `Gate failed (${result.exit_code}): ${label}`;
}
emitEvent(eventsPath, 'rail.check.failed', taskId, 'gated', 'quality-gate', message);
}
return { allPassed, gateResults };
}