feat(mosaic): P5 — overlay composer (compose-contract + *.local overlays) (#605)
Some checks are pending
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #605.
This commit is contained in:
2026-06-22 02:16:05 +00:00
committed by jason.woltje
parent c8b2dab0ca
commit 23343bb7f0
6 changed files with 274 additions and 45 deletions

View File

@@ -291,12 +291,23 @@ function buildPrdBlock(): string {
// ─── Runtime prompt builder ──────────────────────────────────────────────────
function buildRuntimePrompt(runtime: RuntimeName): string {
/**
* Compose the full runtime contract for a harness: the resident-by-value core
* (CONSTITUTION + AGENTS + USER + TOOLS + runtime) plus operator overlays
* (`*.local.md` deltas), merged in precedence order so the model gets one
* pre-merged blob (DESIGN §3.2 / R7). Overlays are injected as deltas by value;
* base files keep their existing residency (USER injected; SOUL/STANDARDS are
* load-on-demand, so only their small `.local` deltas are injected here).
*
* `mosaicHome` is parameterized for testability; production callers use the
* module-level default.
*/
export function composeContract(runtime: RuntimeName, mosaicHome: string = MOSAIC_HOME): string {
const runtimeContractPaths: Record<RuntimeName, string> = {
claude: join(MOSAIC_HOME, 'runtime', 'claude', 'RUNTIME.md'),
codex: join(MOSAIC_HOME, 'runtime', 'codex', 'RUNTIME.md'),
opencode: join(MOSAIC_HOME, 'runtime', 'opencode', 'RUNTIME.md'),
pi: join(MOSAIC_HOME, 'runtime', 'pi', 'RUNTIME.md'),
claude: join(mosaicHome, 'runtime', 'claude', 'RUNTIME.md'),
codex: join(mosaicHome, 'runtime', 'codex', 'RUNTIME.md'),
opencode: join(mosaicHome, 'runtime', 'opencode', 'RUNTIME.md'),
pi: join(mosaicHome, 'runtime', 'pi', 'RUNTIME.md'),
};
const runtimeFile = runtimeContractPaths[runtime];
@@ -331,27 +342,55 @@ For required push/merge/issue-close/release actions, execute without routine con
`);
// CONSTITUTION.md (L0 — the non-negotiable law; lead with it). Tolerant of
// pre-constitution installs that have not been re-seeded yet.
const constitution = readOptional(join(MOSAIC_HOME, 'CONSTITUTION.md'));
// pre-constitution installs that have not been re-seeded yet. Injected by
// value verbatim so the bare-launch fallback read is byte-equal (R8).
const constitution = readOptional(join(mosaicHome, 'CONSTITUTION.md'));
if (constitution) parts.push(constitution);
// AGENTS.md
parts.push(readFileSync(join(MOSAIC_HOME, 'AGENTS.md'), 'utf-8'));
parts.push(readFileSync(join(mosaicHome, 'AGENTS.md'), 'utf-8'));
// USER.md
const user = readOptional(join(MOSAIC_HOME, 'USER.md'));
// USER.md (+ USER.local.md operator overlay, appended directly under the
// profile its base owns).
const user = readOptional(join(mosaicHome, 'USER.md'));
if (user) parts.push('\n\n# User Profile\n\n' + user);
const userLocal = readOptional(join(mosaicHome, 'USER.local.md'));
if (userLocal.trim()) {
parts.push('\n\n## Operator Overlay (USER.local.md)\n\n' + userLocal);
}
// TOOLS.md
const tools = readOptional(join(MOSAIC_HOME, 'TOOLS.md'));
const tools = readOptional(join(mosaicHome, 'TOOLS.md'));
if (tools) parts.push('\n\n# Machine Tools\n\n' + tools);
// Operator overlays whose base layers are load-on-demand (SOUL, STANDARDS):
// inject only the small `.local` delta by value so the customization reaches
// the model without re-injecting the full base prose (preserves the byte
// budget). Absent `.local` files → base-only, automatically (R7 §3.2).
const overlayBlocks: string[] = [];
const soulLocal = readOptional(join(mosaicHome, 'SOUL.local.md'));
if (soulLocal.trim()) {
overlayBlocks.push('## Persona Overlay (SOUL.local.md)\n\n' + soulLocal.trim());
}
const standardsLocal = readOptional(join(mosaicHome, 'STANDARDS.local.md'));
if (standardsLocal.trim()) {
overlayBlocks.push('## Standards Overlay (STANDARDS.local.md)\n\n' + standardsLocal.trim());
}
if (overlayBlocks.length > 0) {
parts.push('\n\n# Operator Overlays\n\n' + overlayBlocks.join('\n\n'));
}
// Runtime-specific contract
parts.push('\n\n# Runtime-Specific Contract\n\n' + readFileSync(runtimeFile, 'utf-8'));
return parts.join('\n');
}
/** @deprecated internal alias — use composeContract. Retained for call-site clarity. */
function buildRuntimePrompt(runtime: RuntimeName): string {
return composeContract(runtime);
}
// ─── Session lock ────────────────────────────────────────────────────────────
function writeSessionLock(runtime: string): void {
@@ -976,6 +1015,22 @@ export function registerLaunchCommands(program: Command): void {
launchRuntime(runtime, extraArgs, yolo);
});
// compose-contract — emit the composed runtime contract (base + operator
// overlays) for a harness to stdout, without launching. For inspection,
// `mosaic doctor`, diffing, and the composer test (R7).
program
.command('compose-contract <harness>')
.description('Print the composed runtime contract (base + *.local overlays) for a harness')
.action((harness: string) => {
const valid: RuntimeName[] = ['claude', 'codex', 'opencode', 'pi'];
if (!valid.includes(harness as RuntimeName)) {
console.error(`Unknown harness '${harness}'. Expected one of: ${valid.join(', ')}.`);
process.exitCode = 64;
return;
}
process.stdout.write(composeContract(harness as RuntimeName));
});
// Coord (mission orchestrator)
program
.command('coord')