Compare commits
11 Commits
15830e2f2a
...
fix/idempo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a08da9164a | ||
|
|
e83674ac51 | ||
|
|
a6e59bf829 | ||
| e46f0641f6 | |||
|
|
07efaa9580 | ||
|
|
361fece023 | ||
| 80e69016b0 | |||
|
|
e084a88a9d | ||
| 990a88362f | |||
|
|
ea9782b2dc | ||
| 8efbaf100e |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/gateway",
|
"name": "@mosaic/gateway",
|
||||||
"version": "0.0.0",
|
"version": "0.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/web",
|
"name": "@mosaic/web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/agent",
|
"name": "@mosaic/agent",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/auth",
|
"name": "@mosaic/auth",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/brain",
|
"name": "@mosaic/brain",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/cli",
|
"name": "@mosaic/cli",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Command } from 'commander';
|
|||||||
import { createQualityRailsCli } from '@mosaic/quality-rails';
|
import { createQualityRailsCli } from '@mosaic/quality-rails';
|
||||||
import { registerAgentCommand } from './commands/agent.js';
|
import { registerAgentCommand } from './commands/agent.js';
|
||||||
import { registerMissionCommand } from './commands/mission.js';
|
import { registerMissionCommand } from './commands/mission.js';
|
||||||
import { registerPrdyCommand } from './commands/prdy.js';
|
// prdy is registered via launch.ts
|
||||||
import { registerLaunchCommands } from './commands/launch.js';
|
import { registerLaunchCommands } from './commands/launch.js';
|
||||||
|
|
||||||
const _require = createRequire(import.meta.url);
|
const _require = createRequire(import.meta.url);
|
||||||
@@ -298,10 +298,6 @@ registerAgentCommand(program);
|
|||||||
|
|
||||||
registerMissionCommand(program);
|
registerMissionCommand(program);
|
||||||
|
|
||||||
// ─── prdy ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
registerPrdyCommand(program);
|
|
||||||
|
|
||||||
// ─── quality-rails ──────────────────────────────────────────────────────
|
// ─── quality-rails ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
const qrWrapper = createQualityRailsCli();
|
const qrWrapper = createQualityRailsCli();
|
||||||
|
|||||||
@@ -54,12 +54,24 @@ function checkRuntime(cmd: string): void {
|
|||||||
function checkSoul(): void {
|
function checkSoul(): void {
|
||||||
const soulPath = join(MOSAIC_HOME, 'SOUL.md');
|
const soulPath = join(MOSAIC_HOME, 'SOUL.md');
|
||||||
if (!existsSync(soulPath)) {
|
if (!existsSync(soulPath)) {
|
||||||
console.log('[mosaic] SOUL.md not found. Running mosaic init...');
|
console.log('[mosaic] SOUL.md not found. Running setup wizard...');
|
||||||
|
|
||||||
|
// Prefer the TypeScript wizard (idempotent, detects existing files)
|
||||||
|
try {
|
||||||
|
const result = spawnSync(process.execPath, [process.argv[1]!, 'wizard'], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
if (result.status === 0 && existsSync(soulPath)) return;
|
||||||
|
} catch {
|
||||||
|
// Fall through to legacy init
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: legacy bash mosaic-init
|
||||||
const initBin = join(MOSAIC_HOME, 'tools', '_scripts', 'mosaic-init');
|
const initBin = join(MOSAIC_HOME, 'tools', '_scripts', 'mosaic-init');
|
||||||
if (existsSync(initBin)) {
|
if (existsSync(initBin)) {
|
||||||
spawnSync(initBin, [], { stdio: 'inherit' });
|
spawnSync(initBin, [], { stdio: 'inherit' });
|
||||||
} else {
|
} else {
|
||||||
console.error('[mosaic] mosaic-init not found. Run: mosaic wizard');
|
console.error('[mosaic] Setup failed. Run: mosaic wizard');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,23 +473,191 @@ function execRuntime(cmd: string, args: string[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Framework script delegation (for tools that remain in bash) ─────────────
|
// ─── Framework script/tool delegation ───────────────────────────────────────
|
||||||
|
|
||||||
function delegateToFrameworkScript(script: string, args: string[]): never {
|
function delegateToScript(scriptPath: string, args: string[], env?: Record<string, string>): never {
|
||||||
const scriptPath = join(MOSAIC_HOME, 'tools', '_scripts', script);
|
|
||||||
if (!existsSync(scriptPath)) {
|
if (!existsSync(scriptPath)) {
|
||||||
console.error(`[mosaic] Script not found: ${scriptPath}`);
|
console.error(`[mosaic] Script not found: ${scriptPath}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
execFileSync(scriptPath, args, { stdio: 'inherit' });
|
execFileSync('bash', [scriptPath, ...args], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: { ...process.env, ...env },
|
||||||
|
});
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.exit((err as { status?: number }).status ?? 1);
|
process.exit((err as { status?: number }).status ?? 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Commander registration ──────────────────────────────────────────────────
|
function fwScript(name: string): string {
|
||||||
|
return join(MOSAIC_HOME, 'tools', '_scripts', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toolScript(toolDir: string, name: string): string {
|
||||||
|
return join(MOSAIC_HOME, 'tools', toolDir, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Coord (mission orchestrator) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
const COORD_SUBCMDS: Record<string, string> = {
|
||||||
|
status: 'session-status.sh',
|
||||||
|
session: 'session-status.sh',
|
||||||
|
init: 'mission-init.sh',
|
||||||
|
mission: 'mission-status.sh',
|
||||||
|
progress: 'mission-status.sh',
|
||||||
|
continue: 'continue-prompt.sh',
|
||||||
|
next: 'continue-prompt.sh',
|
||||||
|
run: 'session-run.sh',
|
||||||
|
start: 'session-run.sh',
|
||||||
|
smoke: 'smoke-test.sh',
|
||||||
|
test: 'smoke-test.sh',
|
||||||
|
resume: 'session-resume.sh',
|
||||||
|
recover: 'session-resume.sh',
|
||||||
|
};
|
||||||
|
|
||||||
|
function runCoord(args: string[]): never {
|
||||||
|
checkMosaicHome();
|
||||||
|
let runtime = 'claude';
|
||||||
|
let yoloFlag = '';
|
||||||
|
const coordArgs: string[] = [];
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
if (arg === '--claude' || arg === '--codex' || arg === '--pi') {
|
||||||
|
runtime = arg.slice(2);
|
||||||
|
} else if (arg === '--yolo') {
|
||||||
|
yoloFlag = '--yolo';
|
||||||
|
} else {
|
||||||
|
coordArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcmd = coordArgs[0] ?? 'help';
|
||||||
|
const subArgs = coordArgs.slice(1);
|
||||||
|
const script = COORD_SUBCMDS[subcmd];
|
||||||
|
|
||||||
|
if (!script) {
|
||||||
|
console.log(`mosaic coord — mission coordinator tools
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
init --name <name> [opts] Initialize a new mission
|
||||||
|
mission [--project <path>] Show mission progress dashboard
|
||||||
|
status [--project <path>] Check agent session health
|
||||||
|
continue [--project <path>] Generate continuation prompt
|
||||||
|
run [--project <path>] Launch runtime with mission context
|
||||||
|
smoke Run orchestration smoke checks
|
||||||
|
resume [--project <path>] Crash recovery
|
||||||
|
|
||||||
|
Runtime: --claude (default) | --codex | --pi | --yolo`);
|
||||||
|
process.exit(subcmd === 'help' ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yoloFlag) subArgs.unshift(yoloFlag);
|
||||||
|
delegateToScript(toolScript('orchestrator', script), subArgs, {
|
||||||
|
MOSAIC_COORD_RUNTIME: runtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Prdy (PRD tools via framework scripts) ─────────────────────────────────
|
||||||
|
|
||||||
|
const PRDY_SUBCMDS: Record<string, string> = {
|
||||||
|
init: 'prdy-init.sh',
|
||||||
|
update: 'prdy-update.sh',
|
||||||
|
validate: 'prdy-validate.sh',
|
||||||
|
check: 'prdy-validate.sh',
|
||||||
|
status: 'prdy-status.sh',
|
||||||
|
};
|
||||||
|
|
||||||
|
function runPrdyLocal(args: string[]): never {
|
||||||
|
checkMosaicHome();
|
||||||
|
let runtime = 'claude';
|
||||||
|
const prdyArgs: string[] = [];
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
if (arg === '--claude' || arg === '--codex' || arg === '--pi') {
|
||||||
|
runtime = arg.slice(2);
|
||||||
|
} else {
|
||||||
|
prdyArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcmd = prdyArgs[0] ?? 'help';
|
||||||
|
const subArgs = prdyArgs.slice(1);
|
||||||
|
const script = PRDY_SUBCMDS[subcmd];
|
||||||
|
|
||||||
|
if (!script) {
|
||||||
|
console.log(`mosaic prdy — PRD creation and validation
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
init [--project <path>] [--name <feature>] Create docs/PRD.md
|
||||||
|
update [--project <path>] Update existing PRD
|
||||||
|
validate [--project <path>] Check PRD completeness
|
||||||
|
status [--project <path>] Quick PRD health check
|
||||||
|
|
||||||
|
Runtime: --claude (default) | --codex | --pi`);
|
||||||
|
process.exit(subcmd === 'help' ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateToScript(toolScript('prdy', script), subArgs, {
|
||||||
|
MOSAIC_PRDY_RUNTIME: runtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Seq (sequential-thinking MCP) ──────────────────────────────────────────
|
||||||
|
|
||||||
|
function runSeq(args: string[]): never {
|
||||||
|
checkMosaicHome();
|
||||||
|
const action = args[0] ?? 'check';
|
||||||
|
const rest = args.slice(1);
|
||||||
|
const checker = fwScript('mosaic-ensure-sequential-thinking');
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'check':
|
||||||
|
delegateToScript(checker, ['--check', ...rest]);
|
||||||
|
break; // unreachable
|
||||||
|
case 'fix':
|
||||||
|
case 'apply':
|
||||||
|
delegateToScript(checker, rest);
|
||||||
|
break;
|
||||||
|
case 'start': {
|
||||||
|
console.log('[mosaic] Starting sequential-thinking MCP server...');
|
||||||
|
try {
|
||||||
|
execFileSync('npx', ['-y', '@modelcontextprotocol/server-sequential-thinking', ...rest], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
process.exit(0);
|
||||||
|
} catch (err) {
|
||||||
|
process.exit((err as { status?: number }).status ?? 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.error(`[mosaic] Unknown seq subcommand '${action}'. Use: check|fix|start`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Upgrade ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function runUpgrade(args: string[]): never {
|
||||||
|
checkMosaicHome();
|
||||||
|
const subcmd = args[0];
|
||||||
|
|
||||||
|
if (!subcmd || subcmd === 'release') {
|
||||||
|
delegateToScript(fwScript('mosaic-release-upgrade'), args.slice(subcmd === 'release' ? 1 : 0));
|
||||||
|
} else if (subcmd === 'check') {
|
||||||
|
delegateToScript(fwScript('mosaic-release-upgrade'), ['--dry-run', ...args.slice(1)]);
|
||||||
|
} else if (subcmd === 'project') {
|
||||||
|
delegateToScript(fwScript('mosaic-upgrade'), args.slice(1));
|
||||||
|
} else if (subcmd.startsWith('-')) {
|
||||||
|
delegateToScript(fwScript('mosaic-release-upgrade'), args);
|
||||||
|
} else {
|
||||||
|
delegateToScript(fwScript('mosaic-upgrade'), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Commander registration ─────────────────────────────────────────────────
|
||||||
|
|
||||||
export function registerLaunchCommands(program: Command): void {
|
export function registerLaunchCommands(program: Command): void {
|
||||||
// Runtime launchers
|
// Runtime launchers
|
||||||
@@ -509,15 +689,58 @@ export function registerLaunchCommands(program: Command): void {
|
|||||||
launchRuntime(runtime as RuntimeName, cmd.args, true);
|
launchRuntime(runtime as RuntimeName, cmd.args, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Framework management commands (delegate to bash scripts)
|
// Coord (mission orchestrator)
|
||||||
const frameworkCommands: Record<string, { desc: string; script: string }> = {
|
program
|
||||||
|
.command('coord')
|
||||||
|
.description('Mission coordinator tools (init, status, run, continue, resume)')
|
||||||
|
.allowUnknownOption(true)
|
||||||
|
.allowExcessArguments(true)
|
||||||
|
.action((_opts: unknown, cmd: Command) => {
|
||||||
|
runCoord(cmd.args);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prdy (PRD tools via local framework scripts)
|
||||||
|
program
|
||||||
|
.command('prdy')
|
||||||
|
.description('PRD creation and validation (init, update, validate, status)')
|
||||||
|
.allowUnknownOption(true)
|
||||||
|
.allowExcessArguments(true)
|
||||||
|
.action((_opts: unknown, cmd: Command) => {
|
||||||
|
runPrdyLocal(cmd.args);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seq (sequential-thinking MCP management)
|
||||||
|
program
|
||||||
|
.command('seq')
|
||||||
|
.description('sequential-thinking MCP management (check/fix/start)')
|
||||||
|
.allowUnknownOption(true)
|
||||||
|
.allowExcessArguments(true)
|
||||||
|
.action((_opts: unknown, cmd: Command) => {
|
||||||
|
runSeq(cmd.args);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upgrade (release + project)
|
||||||
|
program
|
||||||
|
.command('upgrade')
|
||||||
|
.description('Upgrade Mosaic release or project files')
|
||||||
|
.allowUnknownOption(true)
|
||||||
|
.allowExcessArguments(true)
|
||||||
|
.action((_opts: unknown, cmd: Command) => {
|
||||||
|
runUpgrade(cmd.args);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Direct framework script delegates
|
||||||
|
const directCommands: Record<string, { desc: string; script: string }> = {
|
||||||
init: { desc: 'Generate SOUL.md (agent identity contract)', script: 'mosaic-init' },
|
init: { desc: 'Generate SOUL.md (agent identity contract)', script: 'mosaic-init' },
|
||||||
doctor: { desc: 'Health audit — detect drift and missing files', script: 'mosaic-doctor' },
|
doctor: { desc: 'Health audit — detect drift and missing files', script: 'mosaic-doctor' },
|
||||||
sync: { desc: 'Sync skills from canonical source', script: 'mosaic-sync-skills' },
|
sync: { desc: 'Sync skills from canonical source', script: 'mosaic-sync-skills' },
|
||||||
bootstrap: { desc: 'Bootstrap a repo with Mosaic standards', script: 'mosaic-bootstrap-repo' },
|
bootstrap: {
|
||||||
|
desc: 'Bootstrap a repo with Mosaic standards',
|
||||||
|
script: 'mosaic-bootstrap-repo',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, { desc, script }] of Object.entries(frameworkCommands)) {
|
for (const [name, { desc, script }] of Object.entries(directCommands)) {
|
||||||
program
|
program
|
||||||
.command(name)
|
.command(name)
|
||||||
.description(desc)
|
.description(desc)
|
||||||
@@ -525,7 +748,7 @@ export function registerLaunchCommands(program: Command): void {
|
|||||||
.allowExcessArguments(true)
|
.allowExcessArguments(true)
|
||||||
.action((_opts: unknown, cmd: Command) => {
|
.action((_opts: unknown, cmd: Command) => {
|
||||||
checkMosaicHome();
|
checkMosaicHome();
|
||||||
delegateToFrameworkScript(script, cmd.args);
|
delegateToScript(fwScript(script), cmd.args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/coord",
|
"name": "@mosaic/coord",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/db",
|
"name": "@mosaic/db",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/design-tokens",
|
"name": "@mosaic/design-tokens",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/forge",
|
"name": "@mosaic/forge",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/log",
|
"name": "@mosaic/log",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/macp",
|
"name": "@mosaic/macp",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/memory",
|
"name": "@mosaic/memory",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
88
packages/mosaic/__tests__/platform/file-ops.test.ts
Normal file
88
packages/mosaic/__tests__/platform/file-ops.test.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
mkdtempSync,
|
||||||
|
mkdirSync,
|
||||||
|
writeFileSync,
|
||||||
|
readFileSync,
|
||||||
|
existsSync,
|
||||||
|
chmodSync,
|
||||||
|
rmSync,
|
||||||
|
} from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { syncDirectory } from '../../src/platform/file-ops.js';
|
||||||
|
|
||||||
|
describe('syncDirectory', () => {
|
||||||
|
let tmpDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-file-ops-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a no-op when source and target are the same path', () => {
|
||||||
|
const dir = join(tmpDir, 'same');
|
||||||
|
mkdirSync(dir, { recursive: true });
|
||||||
|
writeFileSync(join(dir, 'file.txt'), 'hello');
|
||||||
|
// Should not throw even with read-only files
|
||||||
|
const gitDir = join(dir, '.git', 'objects', 'pack');
|
||||||
|
mkdirSync(gitDir, { recursive: true });
|
||||||
|
const packFile = join(gitDir, 'pack-abc.idx');
|
||||||
|
writeFileSync(packFile, 'data');
|
||||||
|
chmodSync(packFile, 0o444);
|
||||||
|
|
||||||
|
expect(() => syncDirectory(dir, dir)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips nested .git directories when excludeGit is true', () => {
|
||||||
|
const src = join(tmpDir, 'src');
|
||||||
|
const dest = join(tmpDir, 'dest');
|
||||||
|
|
||||||
|
// Create source with a nested .git
|
||||||
|
mkdirSync(join(src, 'sources', 'skills', '.git', 'objects'), { recursive: true });
|
||||||
|
writeFileSync(join(src, 'sources', 'skills', '.git', 'objects', 'pack.idx'), 'git-data');
|
||||||
|
writeFileSync(join(src, 'sources', 'skills', 'SKILL.md'), 'skill content');
|
||||||
|
writeFileSync(join(src, 'README.md'), 'readme');
|
||||||
|
|
||||||
|
syncDirectory(src, dest, { excludeGit: true });
|
||||||
|
|
||||||
|
// .git contents should NOT be copied
|
||||||
|
expect(existsSync(join(dest, 'sources', 'skills', '.git'))).toBe(false);
|
||||||
|
// Normal files should be copied
|
||||||
|
expect(readFileSync(join(dest, 'sources', 'skills', 'SKILL.md'), 'utf-8')).toBe(
|
||||||
|
'skill content',
|
||||||
|
);
|
||||||
|
expect(readFileSync(join(dest, 'README.md'), 'utf-8')).toBe('readme');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('copies nested .git directories when excludeGit is false', () => {
|
||||||
|
const src = join(tmpDir, 'src');
|
||||||
|
const dest = join(tmpDir, 'dest');
|
||||||
|
|
||||||
|
mkdirSync(join(src, 'sub', '.git'), { recursive: true });
|
||||||
|
writeFileSync(join(src, 'sub', '.git', 'HEAD'), 'ref: refs/heads/main');
|
||||||
|
|
||||||
|
syncDirectory(src, dest, { excludeGit: false });
|
||||||
|
|
||||||
|
expect(readFileSync(join(dest, 'sub', '.git', 'HEAD'), 'utf-8')).toBe('ref: refs/heads/main');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects preserve option', () => {
|
||||||
|
const src = join(tmpDir, 'src');
|
||||||
|
const dest = join(tmpDir, 'dest');
|
||||||
|
|
||||||
|
mkdirSync(src, { recursive: true });
|
||||||
|
mkdirSync(dest, { recursive: true });
|
||||||
|
writeFileSync(join(src, 'SOUL.md'), 'new soul');
|
||||||
|
writeFileSync(join(dest, 'SOUL.md'), 'old soul');
|
||||||
|
writeFileSync(join(src, 'README.md'), 'new readme');
|
||||||
|
|
||||||
|
syncDirectory(src, dest, { preserve: ['SOUL.md'] });
|
||||||
|
|
||||||
|
expect(readFileSync(join(dest, 'SOUL.md'), 'utf-8')).toBe('old soul');
|
||||||
|
expect(readFileSync(join(dest, 'README.md'), 'utf-8')).toBe('new readme');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -65,4 +65,36 @@ describe('detectInstallStage', () => {
|
|||||||
expect(state.installAction).toBe('keep');
|
expect(state.installAction).toBe('keep');
|
||||||
expect(state.soul.agentName).toBe('TestAgent');
|
expect(state.soul.agentName).toBe('TestAgent');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('pre-populates state when reconfiguring', async () => {
|
||||||
|
mkdirSync(join(tmpDir, 'bin'), { recursive: true });
|
||||||
|
writeFileSync(join(tmpDir, 'SOUL.md'), 'You are **Jarvis** in this session.');
|
||||||
|
writeFileSync(join(tmpDir, 'USER.md'), '**Name:** TestUser');
|
||||||
|
|
||||||
|
const p = new HeadlessPrompter({
|
||||||
|
'What would you like to do?': 'reconfigure',
|
||||||
|
});
|
||||||
|
const state = createState(tmpDir);
|
||||||
|
await detectInstallStage(p, state, mockConfig);
|
||||||
|
|
||||||
|
expect(state.installAction).toBe('reconfigure');
|
||||||
|
// Existing values loaded as defaults for reconfiguration
|
||||||
|
expect(state.soul.agentName).toBe('TestAgent');
|
||||||
|
expect(state.user.userName).toBe('TestUser');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not pre-populate state on fresh reset', async () => {
|
||||||
|
mkdirSync(join(tmpDir, 'bin'), { recursive: true });
|
||||||
|
writeFileSync(join(tmpDir, 'SOUL.md'), 'You are **Jarvis** in this session.');
|
||||||
|
|
||||||
|
const p = new HeadlessPrompter({
|
||||||
|
'What would you like to do?': 'reset',
|
||||||
|
});
|
||||||
|
const state = createState(tmpDir);
|
||||||
|
await detectInstallStage(p, state, mockConfig);
|
||||||
|
|
||||||
|
expect(state.installAction).toBe('reset');
|
||||||
|
// Reset should NOT load existing values
|
||||||
|
expect(state.soul.agentName).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,12 +60,14 @@ Options:
|
|||||||
--timezone <tz> Your timezone (e.g., "America/Chicago")
|
--timezone <tz> Your timezone (e.g., "America/Chicago")
|
||||||
--non-interactive Fail if any required value is missing (no prompts)
|
--non-interactive Fail if any required value is missing (no prompts)
|
||||||
--soul-only Only generate SOUL.md
|
--soul-only Only generate SOUL.md
|
||||||
|
--force Overwrite existing files without prompting
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
NON_INTERACTIVE=0
|
NON_INTERACTIVE=0
|
||||||
SOUL_ONLY=0
|
SOUL_ONLY=0
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -79,6 +81,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--timezone) TIMEZONE="$2"; shift 2 ;;
|
--timezone) TIMEZONE="$2"; shift 2 ;;
|
||||||
--non-interactive) NON_INTERACTIVE=1; shift ;;
|
--non-interactive) NON_INTERACTIVE=1; shift ;;
|
||||||
--soul-only) SOUL_ONLY=1; shift ;;
|
--soul-only) SOUL_ONLY=1; shift ;;
|
||||||
|
--force) FORCE=1; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
|
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -139,6 +142,134 @@ prompt_multiline() {
|
|||||||
eval "$var_name=\"$value\""
|
eval "$var_name=\"$value\""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Existing file detection ────────────────────────────────────
|
||||||
|
|
||||||
|
detect_existing_config() {
|
||||||
|
local found=0
|
||||||
|
local existing_files=()
|
||||||
|
|
||||||
|
[[ -f "$SOUL_OUTPUT" ]] && { found=1; existing_files+=("SOUL.md"); }
|
||||||
|
[[ -f "$USER_OUTPUT" ]] && { found=1; existing_files+=("USER.md"); }
|
||||||
|
[[ -f "$TOOLS_OUTPUT" ]] && { found=1; existing_files+=("TOOLS.md"); }
|
||||||
|
|
||||||
|
if [[ $found -eq 0 || $FORCE -eq 1 ]]; then
|
||||||
|
return 0 # No existing files or --force: proceed with fresh install
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[mosaic-init] Existing configuration detected:"
|
||||||
|
for f in "${existing_files[@]}"; do
|
||||||
|
echo " ✓ $f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show current agent name if SOUL.md exists
|
||||||
|
if [[ -f "$SOUL_OUTPUT" ]]; then
|
||||||
|
local current_name
|
||||||
|
current_name=$(grep -oP 'You are \*\*\K[^*]+' "$SOUL_OUTPUT" 2>/dev/null || true)
|
||||||
|
if [[ -n "$current_name" ]]; then
|
||||||
|
echo " Agent: $current_name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ $NON_INTERACTIVE -eq 1 ]]; then
|
||||||
|
echo "[mosaic-init] Existing config found. Use --force to overwrite in non-interactive mode."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "What would you like to do?"
|
||||||
|
echo " 1) keep — Keep existing files, skip init (default)"
|
||||||
|
echo " 2) import — Import values from existing files as defaults, then regenerate"
|
||||||
|
echo " 3) overwrite — Start fresh, overwrite all files"
|
||||||
|
printf "Choose [1/2/3]: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
case "${choice:-1}" in
|
||||||
|
1|keep)
|
||||||
|
echo "[mosaic-init] Keeping existing configuration."
|
||||||
|
# Still push to runtime adapters in case framework was updated
|
||||||
|
if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" ]]; then
|
||||||
|
echo "[mosaic-init] Updating runtime adapters..."
|
||||||
|
"$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets"
|
||||||
|
fi
|
||||||
|
echo "[mosaic-init] Done. Launch with: mosaic claude"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
2|import)
|
||||||
|
echo "[mosaic-init] Importing values from existing files as defaults..."
|
||||||
|
import_existing_values
|
||||||
|
;;
|
||||||
|
3|overwrite)
|
||||||
|
echo "[mosaic-init] Starting fresh install..."
|
||||||
|
# Back up existing files
|
||||||
|
local ts
|
||||||
|
ts=$(date +%Y%m%d%H%M%S)
|
||||||
|
for f in "${existing_files[@]}"; do
|
||||||
|
local src="$MOSAIC_HOME/$f"
|
||||||
|
if [[ -f "$src" ]]; then
|
||||||
|
cp "$src" "${src}.bak.${ts}"
|
||||||
|
echo " Backed up $f → ${f}.bak.${ts}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[mosaic-init] Invalid choice. Keeping existing configuration."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
import_existing_values() {
|
||||||
|
# Import SOUL.md values
|
||||||
|
if [[ -f "$SOUL_OUTPUT" ]]; then
|
||||||
|
local content
|
||||||
|
content=$(cat "$SOUL_OUTPUT")
|
||||||
|
|
||||||
|
if [[ -z "$AGENT_NAME" ]]; then
|
||||||
|
AGENT_NAME=$(echo "$content" | grep -oP 'You are \*\*\K[^*]+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
if [[ -z "$ROLE_DESCRIPTION" ]]; then
|
||||||
|
ROLE_DESCRIPTION=$(echo "$content" | grep -oP 'Role identity: \K.+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
if [[ -z "$STYLE" ]]; then
|
||||||
|
if echo "$content" | grep -q 'Be direct, concise'; then
|
||||||
|
STYLE="direct"
|
||||||
|
elif echo "$content" | grep -q 'Be warm and conversational'; then
|
||||||
|
STYLE="friendly"
|
||||||
|
elif echo "$content" | grep -q 'Use professional, structured'; then
|
||||||
|
STYLE="formal"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Import USER.md values
|
||||||
|
if [[ -f "$USER_OUTPUT" ]]; then
|
||||||
|
local content
|
||||||
|
content=$(cat "$USER_OUTPUT")
|
||||||
|
|
||||||
|
if [[ -z "$USER_NAME" ]]; then
|
||||||
|
USER_NAME=$(echo "$content" | grep -oP '\*\*Name:\*\* \K.+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
if [[ -z "$PRONOUNS" ]]; then
|
||||||
|
PRONOUNS=$(echo "$content" | grep -oP '\*\*Pronouns:\*\* \K.+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
if [[ -z "$TIMEZONE" ]]; then
|
||||||
|
TIMEZONE=$(echo "$content" | grep -oP '\*\*Timezone:\*\* \K.+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Import TOOLS.md values
|
||||||
|
if [[ -f "$TOOLS_OUTPUT" ]]; then
|
||||||
|
local content
|
||||||
|
content=$(cat "$TOOLS_OUTPUT")
|
||||||
|
|
||||||
|
if [[ -z "$CREDENTIALS_LOCATION" ]]; then
|
||||||
|
CREDENTIALS_LOCATION=$(echo "$content" | grep -oP '\*\*Location:\*\* \K.+' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_existing_config
|
||||||
|
|
||||||
# ── SOUL.md Generation ────────────────────────────────────────
|
# ── SOUL.md Generation ────────────────────────────────────────
|
||||||
echo "[mosaic-init] Generating SOUL.md — agent identity contract"
|
echo "[mosaic-init] Generating SOUL.md — agent identity contract"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -55,7 +55,26 @@ mkdir -p "$MOSAIC_HOME" "$MOSAIC_SKILLS_DIR" "$MOSAIC_LOCAL_SKILLS_DIR"
|
|||||||
if [[ $fetch -eq 1 ]]; then
|
if [[ $fetch -eq 1 ]]; then
|
||||||
if [[ -d "$SKILLS_REPO_DIR/.git" ]]; then
|
if [[ -d "$SKILLS_REPO_DIR/.git" ]]; then
|
||||||
echo "[mosaic-skills] Updating skills source: $SKILLS_REPO_DIR"
|
echo "[mosaic-skills] Updating skills source: $SKILLS_REPO_DIR"
|
||||||
git -C "$SKILLS_REPO_DIR" pull --rebase
|
|
||||||
|
# Stash any local changes (dirty index or worktree) before pulling
|
||||||
|
local_changes=0
|
||||||
|
if ! git -C "$SKILLS_REPO_DIR" diff --quiet 2>/dev/null || \
|
||||||
|
! git -C "$SKILLS_REPO_DIR" diff --cached --quiet 2>/dev/null; then
|
||||||
|
local_changes=1
|
||||||
|
echo "[mosaic-skills] Stashing local changes..."
|
||||||
|
git -C "$SKILLS_REPO_DIR" stash push -q -m "mosaic-sync-skills auto-stash"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git -C "$SKILLS_REPO_DIR" pull --rebase; then
|
||||||
|
echo "[mosaic-skills] WARN: pull failed — continuing with existing checkout" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore stashed changes
|
||||||
|
if [[ $local_changes -eq 1 ]]; then
|
||||||
|
echo "[mosaic-skills] Restoring local changes..."
|
||||||
|
git -C "$SKILLS_REPO_DIR" stash pop -q 2>/dev/null || \
|
||||||
|
echo "[mosaic-skills] WARN: stash pop had conflicts — check $SKILLS_REPO_DIR" >&2
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "[mosaic-skills] Cloning skills source to: $SKILLS_REPO_DIR"
|
echo "[mosaic-skills] Cloning skills source to: $SKILLS_REPO_DIR"
|
||||||
mkdir -p "$(dirname "$SKILLS_REPO_DIR")"
|
mkdir -p "$(dirname "$SKILLS_REPO_DIR")"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/mosaic",
|
"name": "@mosaic/mosaic",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.4",
|
||||||
"description": "Mosaic agent framework — installation wizard and meta package",
|
"description": "Mosaic agent framework — installation wizard and meta package",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
export const VERSION = '0.1.0';
|
export const VERSION = '0.0.2';
|
||||||
|
|
||||||
export const DEFAULT_MOSAIC_HOME = join(homedir(), '.config', 'mosaic');
|
export const DEFAULT_MOSAIC_HOME = join(homedir(), '.config', 'mosaic');
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
unlinkSync,
|
unlinkSync,
|
||||||
statSync,
|
statSync,
|
||||||
} from 'node:fs';
|
} from 'node:fs';
|
||||||
import { dirname, join, relative } from 'node:path';
|
import { dirname, join, relative, resolve } from 'node:path';
|
||||||
|
|
||||||
const MAX_BACKUPS = 3;
|
const MAX_BACKUPS = 3;
|
||||||
|
|
||||||
@@ -68,6 +68,9 @@ export function syncDirectory(
|
|||||||
target: string,
|
target: string,
|
||||||
options: { preserve?: string[]; excludeGit?: boolean } = {},
|
options: { preserve?: string[]; excludeGit?: boolean } = {},
|
||||||
): void {
|
): void {
|
||||||
|
// Guard: source and target are the same directory — nothing to sync
|
||||||
|
if (resolve(source) === resolve(target)) return;
|
||||||
|
|
||||||
const preserveSet = new Set(options.preserve ?? []);
|
const preserveSet = new Set(options.preserve ?? []);
|
||||||
|
|
||||||
// Collect files from source
|
// Collect files from source
|
||||||
@@ -77,9 +80,10 @@ export function syncDirectory(
|
|||||||
const stat = statSync(src);
|
const stat = statSync(src);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
const relPath = relative(relBase, src);
|
const relPath = relative(relBase, src);
|
||||||
|
const dirName = relPath.split('/').pop() ?? '';
|
||||||
|
|
||||||
// Skip .git
|
// Skip any .git directory (top-level or nested, e.g. sources/agent-skills/.git)
|
||||||
if (options.excludeGit && relPath === '.git') return;
|
if (options.excludeGit && (dirName === '.git' || relPath.includes('/.git'))) return;
|
||||||
|
|
||||||
// Skip preserved paths at top level
|
// Skip preserved paths at top level
|
||||||
if (preserveSet.has(relPath) && existsSync(dest)) return;
|
if (preserveSet.has(relPath) && existsSync(dest)) return;
|
||||||
@@ -91,6 +95,9 @@ export function syncDirectory(
|
|||||||
} else {
|
} else {
|
||||||
const relPath = relative(relBase, src);
|
const relPath = relative(relBase, src);
|
||||||
|
|
||||||
|
// Skip files inside .git directories
|
||||||
|
if (options.excludeGit && relPath.includes('/.git/')) return;
|
||||||
|
|
||||||
// Skip preserved files at top level
|
// Skip preserved files at top level
|
||||||
if (preserveSet.has(relPath) && existsSync(dest)) return;
|
if (preserveSet.has(relPath) && existsSync(dest)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ export async function detectInstallStage(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (state.installAction === 'keep') {
|
if (state.installAction === 'keep' || state.installAction === 'reconfigure') {
|
||||||
|
// Load existing values — for 'keep' they're final, for 'reconfigure'
|
||||||
|
// they become pre-populated defaults so the user can tweak them.
|
||||||
state.soul = await config.readSoul();
|
state.soul = await config.readSoul();
|
||||||
state.user = await config.readUser();
|
state.user = await config.readUser();
|
||||||
state.tools = await config.readTools();
|
state.tools = await config.readTools();
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ export async function soulSetupStage(p: WizardPrompter, state: WizardState): Pro
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else if (state.installAction === 'reconfigure') {
|
||||||
|
// Show existing value as default so the user can accept or change it
|
||||||
|
state.soul.agentName = await p.text({
|
||||||
|
message: 'What name should agents use?',
|
||||||
|
placeholder: state.soul.agentName,
|
||||||
|
defaultValue: state.soul.agentName,
|
||||||
|
validate: (v) => {
|
||||||
|
if (v.length === 0) return 'Name cannot be empty';
|
||||||
|
if (v.length > 50) return 'Name must be under 50 characters';
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.mode === 'advanced') {
|
if (state.mode === 'advanced') {
|
||||||
@@ -38,7 +50,7 @@ export async function soulSetupStage(p: WizardPrompter, state: WizardState): Pro
|
|||||||
state.soul.roleDescription ??= DEFAULTS.roleDescription;
|
state.soul.roleDescription ??= DEFAULTS.roleDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.soul.communicationStyle) {
|
if (!state.soul.communicationStyle || state.installAction === 'reconfigure') {
|
||||||
state.soul.communicationStyle = await p.select<CommunicationStyle>({
|
state.soul.communicationStyle = await p.select<CommunicationStyle>({
|
||||||
message: 'Communication style',
|
message: 'Communication style',
|
||||||
options: [
|
options: [
|
||||||
@@ -46,7 +58,7 @@ export async function soulSetupStage(p: WizardPrompter, state: WizardState): Pro
|
|||||||
{ value: 'friendly', label: 'Friendly', hint: 'Warm but efficient, conversational' },
|
{ value: 'friendly', label: 'Friendly', hint: 'Warm but efficient, conversational' },
|
||||||
{ value: 'formal', label: 'Formal', hint: 'Professional, structured, thorough' },
|
{ value: 'formal', label: 'Formal', hint: 'Professional, structured, thorough' },
|
||||||
],
|
],
|
||||||
initialValue: 'direct',
|
initialValue: state.soul.communicationStyle ?? 'direct',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/prdy",
|
"name": "@mosaic/prdy",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/quality-rails",
|
"name": "@mosaic/quality-rails",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/queue",
|
"name": "@mosaic/queue",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/types",
|
"name": "@mosaic/types",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/discord-plugin",
|
"name": "@mosaic/discord-plugin",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/oc-macp-plugin",
|
"name": "@mosaic/oc-macp-plugin",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"description": "OpenClaw ACP runtime backend that routes sessions_spawn(runtime:\"macp\") to the Pi MACP runner.",
|
"description": "OpenClaw ACP runtime backend that routes sessions_spawn(runtime:\"macp\") to the Pi MACP runner.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/oc-framework-plugin",
|
"name": "@mosaic/oc-framework-plugin",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"description": "Injects Mosaic framework rails, runtime contract, and active mission context into all OpenClaw agent sessions and ACP subagent spawns.",
|
"description": "Injects Mosaic framework rails, runtime contract, and active mission context into all OpenClaw agent sessions and ACP subagent spawns.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/telegram-plugin",
|
"name": "@mosaic/telegram-plugin",
|
||||||
"version": "0.0.1-alpha.2",
|
"version": "0.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
Reference in New Issue
Block a user