feat(framework): superpowers enforcement, typecheck hook, file-ownership rules #451
@@ -59,9 +59,9 @@ pnpm typecheck && pnpm lint && pnpm format:check # Quality gates
|
|||||||
The `agent` column specifies the required model for each task. **This is set at task creation by the orchestrator and must not be changed by workers.**
|
The `agent` column specifies the required model for each task. **This is set at task creation by the orchestrator and must not be changed by workers.**
|
||||||
|
|
||||||
| Value | When to use | Budget |
|
| Value | When to use | Budget |
|
||||||
| -------- | ----------------------------------------------------------- | -------------------------- |
|
| --------- | ----------------------------------------------------------- | -------------------------- |
|
||||||
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
|
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
|
||||||
| `glm-5` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
|
| `glm-5.1` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
|
||||||
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
|
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
|
||||||
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
|
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
|
||||||
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
|
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
|
||||||
|
|||||||
@@ -73,6 +73,27 @@ Spawn a worker instead. No exceptions. No "quick fixes."
|
|||||||
- Wait for at least one worker to complete before spawning more
|
- Wait for at least one worker to complete before spawning more
|
||||||
- This optimizes token usage and reduces context pressure
|
- This optimizes token usage and reduces context pressure
|
||||||
|
|
||||||
|
## File Ownership & Partitioning (Hard Rule for Parallel Workers)
|
||||||
|
|
||||||
|
When dispatching parallel workers, the orchestrator MUST assign **non-overlapping file scopes** to each worker. File collisions between parallel workers cause merge conflicts, lost edits, and wasted tokens.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
|
||||||
|
1. **Exclusive file ownership.** Each file may be assigned to at most one active worker. The orchestrator records ownership in the worker dispatch (prompt or task definition).
|
||||||
|
2. **Partition by directory or module.** Prefer assigning entire directories/modules to one worker rather than splitting files within a directory across workers.
|
||||||
|
3. **Shared files are serialized.** If two tasks must modify the same file (e.g., a shared types file, a barrel export), they MUST run sequentially — never in parallel. Mark the second task with `depends_on` pointing to the first.
|
||||||
|
4. **Test files follow source ownership.** If Worker A owns `src/auth/login.ts`, Worker A also owns `src/auth/__tests__/login.test.ts`. Do not split source and test across workers.
|
||||||
|
5. **Config files are orchestrator-reserved.** Files like `package.json`, `tsconfig.json`, and CI config are owned by the orchestrator and modified only between worker cycles, never during parallel execution.
|
||||||
|
6. **Document ownership in dispatch.** When spawning a worker, include an explicit `Files:` section listing owned paths/globs. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Files (exclusive — do not touch files outside this scope):
|
||||||
|
- apps/web/src/components/auth/**
|
||||||
|
- apps/web/src/lib/auth.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Conflict recovery.** If a worker edits a file outside its scope, the orchestrator MUST flag the violation, assess the diff, and either revert the out-of-scope change or re-run the affected worker with the corrected file.
|
||||||
|
|
||||||
## Delegation Mode Selection
|
## Delegation Mode Selection
|
||||||
|
|
||||||
Choose one delegation mode at session start:
|
Choose one delegation mode at session start:
|
||||||
|
|||||||
@@ -151,11 +151,68 @@ When delegating work to subagents, you MUST select the cheapest model capable of
|
|||||||
|
|
||||||
**Runtime-specific syntax**: See the runtime reference for how to specify model tier when spawning subagents (e.g., Claude Code Task tool `model` parameter).
|
**Runtime-specific syntax**: See the runtime reference for how to specify model tier when spawning subagents (e.g., Claude Code Task tool `model` parameter).
|
||||||
|
|
||||||
|
## Superpowers Enforcement (Hard Rule)
|
||||||
|
|
||||||
|
Mosaic provides capabilities beyond basic code editing: **skills**, **hooks**, **MCP tools**, and **plugins**. These are not optional extras — they are force multipliers that agents MUST actively use when applicable. Under-utilization of superpowers is a framework violation.
|
||||||
|
|
||||||
|
### Skills
|
||||||
|
|
||||||
|
Skills are domain-specific instruction sets in `~/.config/mosaic/skills/` that encode best practices, patterns, and guardrails. They are loaded into agents via the runtime's skill mechanism (e.g., Claude Code slash commands, Pi `--skill` flag).
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
|
||||||
|
1. Before starting implementation, scan available skills (`ls ~/.config/mosaic/skills/`) and load any that match the task domain.
|
||||||
|
2. When a skill exists for the technology being used (e.g., `nestjs-best-practices` for NestJS work), you MUST load it.
|
||||||
|
3. When spawning workers, include skill loading in the kickstart prompt.
|
||||||
|
4. If you complete a task without loading a relevant available skill, that is a quality gap.
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
|
||||||
|
Hooks provide automated quality gates (lint, format, typecheck) that fire on file edits. They are configured in the runtime settings and run automatically.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
|
||||||
|
1. Do NOT bypass or suppress hook output. If a hook reports errors, fix them before proceeding.
|
||||||
|
2. Hook failures are immediate feedback — treat them like failing tests.
|
||||||
|
3. If a hook is consistently failing on valid code, report it as a framework issue rather than working around it.
|
||||||
|
|
||||||
|
### MCP Tools
|
||||||
|
|
||||||
|
MCP servers extend agent capabilities with external integrations (sequential-thinking, web search, memory, browser automation, etc.). Available MCP tools are listed at session start.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
|
||||||
|
1. **sequential-thinking** is REQUIRED for planning, architecture, and multi-step reasoning. Use it — do not skip structured thinking for complex decisions.
|
||||||
|
2. **OpenBrain** (`capture`, `search`, `recent`) is the cross-agent memory layer. Capture discoveries and search for prior context at session start.
|
||||||
|
3. When a task involves web research, browser testing, or external data, use the available MCP tools (web-search, chrome-devtools, web-reader) rather than asking the user to look things up.
|
||||||
|
4. Check available MCP tools at session start and use them proactively throughout the session.
|
||||||
|
|
||||||
|
### Plugins (Runtime-Specific)
|
||||||
|
|
||||||
|
Runtime plugins (e.g., Claude Code's `feature-dev`, `pr-review-toolkit`, `code-review`) provide specialized agent capabilities like code review, architecture analysis, and test coverage analysis.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
|
||||||
|
1. After completing a significant code change, use code review plugins proactively — do not wait for the user to ask.
|
||||||
|
2. Before creating a PR, use PR review plugins to catch issues early.
|
||||||
|
3. When designing architecture, use planning/architecture plugins for structured analysis.
|
||||||
|
|
||||||
|
### Self-Evolution
|
||||||
|
|
||||||
|
The Mosaic framework should improve over time based on usage patterns:
|
||||||
|
|
||||||
|
1. When you discover a recurring pattern that should be codified, capture it to OpenBrain with `type: "framework-improvement"`.
|
||||||
|
2. When a hook, skill, or tool is missing for a common task, capture the gap to OpenBrain with `type: "tooling-gap"`.
|
||||||
|
3. When a framework rule causes friction without adding value, capture the observation to OpenBrain with `type: "framework-friction"`.
|
||||||
|
|
||||||
|
These captures feed the framework's continuous improvement cycle.
|
||||||
|
|
||||||
## Skills Policy
|
## Skills Policy
|
||||||
|
|
||||||
- Use only the minimum required skills for the active task.
|
- Load skills that match the active task domain before starting implementation.
|
||||||
- Do not load unrelated skills.
|
- Do not load unrelated skills.
|
||||||
- Follow skill trigger rules from the active runtime instruction layer.
|
- Follow skill trigger rules from the active runtime instruction layer.
|
||||||
|
- Actively check `~/.config/mosaic/skills/` for applicable skills rather than passively waiting for them to be mentioned.
|
||||||
|
|
||||||
## Session Closure Requirement
|
## Session Closure Requirement
|
||||||
|
|
||||||
|
|||||||
@@ -102,3 +102,30 @@ claude mcp add --scope user <name> -- npx -y <package>
|
|||||||
`--scope local` = default, local-only (not committed).
|
`--scope local` = default, local-only (not committed).
|
||||||
|
|
||||||
Do NOT add `mcpServers` to `~/.claude/settings.json` — that key is ignored for MCP loading.
|
Do NOT add `mcpServers` to `~/.claude/settings.json` — that key is ignored for MCP loading.
|
||||||
|
|
||||||
|
## Required Claude Code Settings (Enforced by Launcher)
|
||||||
|
|
||||||
|
The `mosaic claude` launcher validates that `~/.claude/settings.json` contains the required Mosaic configuration. Missing or outdated settings trigger a warning at launch.
|
||||||
|
|
||||||
|
**Required hooks:**
|
||||||
|
|
||||||
|
| Event | Matcher | Script | Purpose |
|
||||||
|
| ----------- | ------------------------ | ------------------------- | ---------------------------------------------- |
|
||||||
|
| PreToolUse | `Write\|Edit\|MultiEdit` | `prevent-memory-write.sh` | Block writes to `~/.claude/projects/*/memory/` |
|
||||||
|
| PostToolUse | `Edit\|MultiEdit\|Write` | `qa-hook-stdin.sh` | QA report generation after code edits |
|
||||||
|
| PostToolUse | `Edit\|MultiEdit\|Write` | `typecheck-hook.sh` | Inline TypeScript type checking |
|
||||||
|
|
||||||
|
**Required plugins:**
|
||||||
|
|
||||||
|
| Plugin | Purpose |
|
||||||
|
| ------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `feature-dev` | Subagent architecture: code-reviewer, code-architect, code-explorer |
|
||||||
|
| `pr-review-toolkit` | PR review: code-simplifier, comment-analyzer, test-analyzer, silent-failure-hunter, type-design-analyzer |
|
||||||
|
| `code-review` | Standalone code review capabilities |
|
||||||
|
|
||||||
|
**Required settings:**
|
||||||
|
|
||||||
|
- `enableAllMcpTools: true` — Allow all configured MCP tools without per-tool approval
|
||||||
|
- `model: "opus"` — Default to opus for orchestrator-level sessions (workers use tiered models via Task tool)
|
||||||
|
|
||||||
|
If `mosaic claude` detects missing hooks or plugins, it will print a warning with the exact settings to add. The session will still launch — enforcement is advisory, not blocking — but agents operating without these settings are running degraded.
|
||||||
|
|||||||
@@ -23,6 +23,16 @@
|
|||||||
"timeout": 60
|
"timeout": 60
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Edit|MultiEdit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.config/mosaic/tools/qa/typecheck-hook.sh",
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
63
packages/mosaic/framework/tools/qa/typecheck-hook.sh
Executable file
63
packages/mosaic/framework/tools/qa/typecheck-hook.sh
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Lightweight PostToolUse typecheck hook for TypeScript files.
|
||||||
|
# Runs tsc --noEmit on the nearest tsconfig after TS/TSX edits.
|
||||||
|
# Returns non-zero with diagnostic output so the agent sees type errors immediately.
|
||||||
|
# Location: ~/.config/mosaic/tools/qa/typecheck-hook.sh
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# Read JSON from stdin (Claude Code PostToolUse payload)
|
||||||
|
JSON_INPUT=$(cat)
|
||||||
|
|
||||||
|
# Extract file path
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
FILE_PATH=$(echo "$JSON_INPUT" | jq -r '.tool_input.file_path // .tool_response.filePath // .file_path // empty' 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
FILE_PATH=$(echo "$JSON_INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)"$/\1/' | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only check TypeScript files
|
||||||
|
if ! [[ "$FILE_PATH" =~ \.(ts|tsx)$ ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Must be a real file
|
||||||
|
if [ ! -f "$FILE_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find nearest tsconfig.json by walking up from the file
|
||||||
|
DIR=$(dirname "$FILE_PATH")
|
||||||
|
TSCONFIG=""
|
||||||
|
while [ "$DIR" != "/" ] && [ "$DIR" != "." ]; do
|
||||||
|
if [ -f "$DIR/tsconfig.json" ]; then
|
||||||
|
TSCONFIG="$DIR/tsconfig.json"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
DIR=$(dirname "$DIR")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$TSCONFIG" ]; then
|
||||||
|
# No tsconfig found — skip silently
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run tsc --noEmit from the tsconfig directory
|
||||||
|
# Use --pretty for readable output, limit to 10 errors to keep output short
|
||||||
|
TSCONFIG_DIR=$(dirname "$TSCONFIG")
|
||||||
|
cd "$TSCONFIG_DIR"
|
||||||
|
|
||||||
|
# Run typecheck — capture output and exit code
|
||||||
|
OUTPUT=$(npx tsc --noEmit --pretty --maxNodeModuleJsDepth 0 2>&1) || STATUS=$?
|
||||||
|
|
||||||
|
if [ "${STATUS:-0}" -ne 0 ]; then
|
||||||
|
# Filter output to only show errors related to the edited file (if possible)
|
||||||
|
BASENAME=$(basename "$FILE_PATH")
|
||||||
|
RELEVANT=$(echo "$OUTPUT" | grep -A2 "$BASENAME" 2>/dev/null || echo "$OUTPUT" | head -20)
|
||||||
|
|
||||||
|
echo "TypeScript type errors detected after editing $FILE_PATH:"
|
||||||
|
echo "$RELEVANT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -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 {
|
function checkSequentialThinking(runtime: string): void {
|
||||||
const checker = fwScript('mosaic-ensure-sequential-thinking');
|
const checker = fwScript('mosaic-ensure-sequential-thinking');
|
||||||
if (!existsSync(checker)) return; // Skip if checker doesn't exist
|
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) {
|
switch (runtime) {
|
||||||
case 'claude': {
|
case 'claude': {
|
||||||
|
// Audit Claude Code settings and warn about missing hooks/plugins
|
||||||
|
const settingsAudit = auditClaudeSettings();
|
||||||
|
printSettingsWarnings(settingsAudit);
|
||||||
|
|
||||||
const prompt = buildRuntimePrompt('claude');
|
const prompt = buildRuntimePrompt('claude');
|
||||||
const cliArgs = yolo ? ['--dangerously-skip-permissions'] : [];
|
const cliArgs = yolo ? ['--dangerously-skip-permissions'] : [];
|
||||||
cliArgs.push('--append-system-prompt', prompt);
|
cliArgs.push('--append-system-prompt', prompt);
|
||||||
|
|||||||
Reference in New Issue
Block a user