feat(framework): superpowers enforcement, typecheck hook, file-ownership rules (#451)
All checks were successful
ci/woodpecker/manual/ci Pipeline was successful
ci/woodpecker/manual/publish Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #451.
This commit is contained in:
2026-04-07 00:44:22 +00:00
committed by jason.woltje
parent 1bfd8570d6
commit 026382325c
7 changed files with 267 additions and 9 deletions

View File

@@ -78,6 +78,82 @@ function checkSoul(): void {
}
}
// ─── Claude settings validation ─────────────────────────────────────────────
interface SettingsAudit {
warnings: string[];
}
function auditClaudeSettings(): SettingsAudit {
const warnings: string[] = [];
const settingsPath = join(homedir(), '.claude', 'settings.json');
const settings = readJson(settingsPath);
if (!settings) {
warnings.push('~/.claude/settings.json not found — hooks and plugins will be missing');
return { warnings };
}
// Check required hooks
const hooks = settings['hooks'] as Record<string, unknown[]> | undefined;
const requiredPreToolUse = ['prevent-memory-write.sh'];
const requiredPostToolUse = ['qa-hook-stdin.sh', 'typecheck-hook.sh'];
const preHooks = (hooks?.['PreToolUse'] ?? []) as Array<Record<string, unknown>>;
const postHooks = (hooks?.['PostToolUse'] ?? []) as Array<Record<string, unknown>>;
const preCommands = preHooks.flatMap((h) => {
const inner = (h['hooks'] ?? []) as Array<Record<string, unknown>>;
return inner.map((ih) => String(ih['command'] ?? ''));
});
const postCommands = postHooks.flatMap((h) => {
const inner = (h['hooks'] ?? []) as Array<Record<string, unknown>>;
return inner.map((ih) => String(ih['command'] ?? ''));
});
for (const script of requiredPreToolUse) {
if (!preCommands.some((c) => c.includes(script))) {
warnings.push(`Missing PreToolUse hook: ${script}`);
}
}
for (const script of requiredPostToolUse) {
if (!postCommands.some((c) => c.includes(script))) {
warnings.push(`Missing PostToolUse hook: ${script}`);
}
}
// Check required plugins
const plugins = (settings['enabledPlugins'] ?? {}) as Record<string, boolean>;
const requiredPlugins = ['feature-dev', 'pr-review-toolkit', 'code-review'];
for (const plugin of requiredPlugins) {
const found = Object.keys(plugins).some((k) => k.startsWith(plugin) && plugins[k]);
if (!found) {
warnings.push(`Missing plugin: ${plugin}`);
}
}
// Check enableAllMcpTools
if (!settings['enableAllMcpTools']) {
warnings.push('enableAllMcpTools is not true — MCP tools may require per-tool approval');
}
return { warnings };
}
function printSettingsWarnings(audit: SettingsAudit): void {
if (audit.warnings.length === 0) return;
console.log('\n[mosaic] Claude Code settings audit:');
for (const w of audit.warnings) {
console.log(`${w}`);
}
console.log(
'[mosaic] Run: mosaic doctor — or see ~/.config/mosaic/runtime/claude/RUNTIME.md for required settings.\n',
);
}
function checkSequentialThinking(runtime: string): void {
const checker = fwScript('mosaic-ensure-sequential-thinking');
if (!existsSync(checker)) return; // Skip if checker doesn't exist
@@ -407,6 +483,10 @@ function launchRuntime(runtime: RuntimeName, args: string[], yolo: boolean): nev
switch (runtime) {
case 'claude': {
// Audit Claude Code settings and warn about missing hooks/plugins
const settingsAudit = auditClaudeSettings();
printSettingsWarnings(settingsAudit);
const prompt = buildRuntimePrompt('claude');
const cliArgs = yolo ? ['--dangerously-skip-permissions'] : [];
cliArgs.push('--append-system-prompt', prompt);