chore: consolidate new foundation and archive v1 (#1495)

This commit is contained in:
2026-09-07 12:32:57 -05:00
3511 changed files with 727899 additions and 10 deletions
@@ -0,0 +1,13 @@
# Mosaic Runtime Adapter — Claude Code
## MANDATORY — Read Before Any Response
BEFORE responding to any user message, READ:
1. `~/.config/mosaic/AGENTS.md`
2. `~/.config/mosaic/runtime/claude/RUNTIME.md`
Do NOT respond until both files are loaded.
This file (`~/.claude/CLAUDE.md`) exists only as a fallback for direct `claude` launches.
For full injection, use `mosaic claude`.
@@ -0,0 +1,61 @@
# Claude Runtime Reference
Claude-runtime behavior only. Global rules win if anything here conflicts.
## Required Actions
1. Follow the Session Start load order in `~/.config/mosaic/AGENTS.md`.
2. Runtime config lives in `~/.claude/settings.json` (hooks, model, plugins, permissions) and
`~/.claude/hooks-config.json`.
3. Structured reasoning (Constitution) binds to the sequential-thinking MCP on this harness; it is REQUIRED — if unavailable, report the failure and stop planning-intensive execution.
4. First response MUST declare mode per the global contract.
5. Git wrappers first for issue/PR/milestone ops; runtime-default confirmation prompts do NOT
override Mosaic hard gates (push/merge/issue-close without routine confirmation).
## Subagent Model Selection (Claude Code syntax)
The Task tool takes `model`: `"haiku"` | `"sonnet"` | `"opus"`. You MUST set it per the tier rule
in AGENTS.md — omitting it defaults to the parent (usually opus) and wastes budget.
```
Task(subagent_type="Explore", model="haiku", prompt="Find all API route handlers")
Task(subagent_type="feature-dev:code-reviewer", model="sonnet", prompt="Review src/auth/ changes")
Task(subagent_type="Plan", model="opus", prompt="Design the multi-tenant isolation strategy")
```
## Memory Policy (Hard Gate)
OpenBrain is the primary cross-agent memory layer — capture learnings/gotchas/decisions there
(`capture` MCP tool or REST). `~/.claude/projects/*/memory/MEMORY.md` is **write-blocked** by the
`prevent-memory-write.sh` PreToolUse hook (the rule alone proved insufficient — the hook is the
hard gate). At session start, `search(topic)` + `recent()` to load prior context. Full protocol:
`~/.config/mosaic/guides/MEMORY.md`.
Quick placement: discoveries/decisions → OpenBrain; active task state → `docs/TASKS.md` or
`docs/scratchpads/`; Mosaic framework notes → `~/.config/mosaic/memory/`.
## MCP Configuration
MCP servers are configured in `~/.claude.json` (key `mcpServers`) — NOT `~/.claude/settings.json`,
where that key is ignored. `settings.json` controls hooks/model/plugins/permissions.
```bash
claude mcp add --scope user --transport http <name> <url> --header "Authorization: Bearer <token>"
claude mcp add --scope user <name> -- npx -y <package> # stdio
```
`--scope user``~/.claude.json` (global); `project``.claude/settings.json`; `local` (default)
→ not committed.
## Required Settings (launcher-audited, advisory)
`mosaic claude` warns if `~/.claude/settings.json` is missing these (session still launches):
- **Hooks** — PreToolUse `prevent-memory-write.sh` (Write|Edit|MultiEdit); PostToolUse
`qa-hook-stdin.sh` + `typecheck-hook.sh` (Edit|MultiEdit|Write).
- **Plugins** — `feature-dev`, `pr-review-toolkit`, `code-review`.
- **Settings** — `enableAllMcpTools: true`; `model: "opus"` (orchestrator default; workers use
tiered models via the Task `model` param).
Note: PostToolUse hook plain stdout on exit 0 goes to the debug log, not model context — only
`hookSpecificOutput.additionalContext` (or exit-2 stderr) enters context.
@@ -0,0 +1 @@
Mosaic lease promotion was processed mechanically; no action is needed.
@@ -0,0 +1,301 @@
# Context7 Integration for Atomic Code Implementer
## Overview
The atomic-code-implementer agent uses Context7 MCP server to dynamically fetch up-to-date documentation for libraries and frameworks. This integration provides real-time access to the latest API documentation, best practices, and code examples.
## Integration Points
### 1. Preset-Driven Documentation Lookup
Each preset configuration includes a `context7Libraries` array that specifies which libraries to fetch documentation for:
```json
{
"context7Libraries": [
"@nestjs/common",
"@nestjs/core",
"@nestjs/typeorm",
"typeorm",
"class-validator"
]
}
```
When a preset is loaded, the agent automatically resolves and fetches documentation for all specified libraries.
### 2. Error-Driven Documentation Lookup
When build errors, type errors, or runtime issues occur, the agent can automatically lookup documentation for:
- Error resolution patterns
- API migration guides
- Breaking change documentation
- Best practice guidelines
### 3. Implementation-Driven Lookup
During atomic task implementation, the agent can fetch:
- Framework-specific implementation patterns
- Library-specific configuration examples
- Performance optimization techniques
- Security best practices
## Context7 Usage Patterns
### Library Resolution
```javascript
// Resolve library ID from preset configuration
const libraryId = await mcp__context7__resolve_library_id({
libraryName: '@nestjs/common',
});
```
### Documentation Retrieval
```javascript
// Get comprehensive documentation
const docs = await mcp__context7__get_library_docs({
context7CompatibleLibraryID: '/nestjs/nest',
topic: 'controllers',
tokens: 8000,
});
```
### Error-Specific Lookups
```javascript
// Look up specific error patterns
const errorDocs = await mcp__context7__get_library_docs({
context7CompatibleLibraryID: '/typescript/typescript',
topic: 'type errors',
tokens: 5000,
});
```
## Automatic Lookup Triggers
### 1. Preset Loading Phase
When an atomic task is started:
1. Detect tech stack from file extensions
2. Load appropriate preset configuration
3. Extract `context7Libraries` array
4. Resolve all library IDs
5. Fetch relevant documentation based on task context
### 2. Error Detection Phase
When quality hooks detect issues:
1. Parse error messages for library/framework references
2. Resolve documentation for problematic libraries
3. Look up error-specific resolution patterns
4. Apply common fixes based on documentation
### 3. Implementation Phase
During code implementation:
1. Detect new library imports or API usage
2. Automatically fetch documentation for unknown patterns
3. Provide implementation examples and best practices
4. Validate against latest API specifications
## Context7 Library Mappings
### NestJS Backend
```json
{
"@nestjs/common": "/nestjs/nest",
"@nestjs/typeorm": "/nestjs/typeorm",
"typeorm": "/typeorm/typeorm",
"class-validator": "/typestack/class-validator",
"bcrypt": "/kelektiv/node.bcrypt.js"
}
```
### React Frontend
```json
{
"react": "/facebook/react",
"react-dom": "/facebook/react",
"@tanstack/react-query": "/tanstack/query",
"tailwindcss": "/tailwindlabs/tailwindcss",
"@testing-library/react": "/testing-library/react-testing-library"
}
```
### Python FastAPI
```json
{
"fastapi": "/tiangolo/fastapi",
"sqlalchemy": "/sqlalchemy/sqlalchemy",
"pydantic": "/samuelcolvin/pydantic",
"pytest": "/pytest-dev/pytest"
}
```
## Integration Workflow
### Sequential Thinking Enhanced Lookup
```markdown
1. **Preset Analysis Phase**
- Use sequential thinking to determine optimal documentation needs
- Analyze task requirements for specific library features
- Prioritize documentation lookup based on complexity
2. **Dynamic Documentation Loading**
- Load core framework documentation first
- Fetch specialized library docs based on task specifics
- Cache documentation for session reuse
3. **Implementation Guidance**
- Use retrieved docs to guide implementation decisions
- Apply documented best practices and patterns
- Validate implementation against official examples
```
### Error Resolution Workflow
```markdown
1. **Error Detection**
- Parse error messages for library/API references
- Identify deprecated or changed APIs
- Extract relevant context from error stack traces
2. **Documentation Lookup**
- Resolve library documentation for error context
- Fetch migration guides for breaking changes
- Look up troubleshooting and FAQ sections
3. **Automated Remediation**
- Apply documented fixes and workarounds
- Update code to use current APIs
- Add proper error handling based on docs
```
## Configuration Examples
### Preset Configuration with Context7
```json
{
"name": "NestJS HIPAA Healthcare",
"techStack": {
"framework": "NestJS",
"database": "TypeORM + PostgreSQL"
},
"context7Libraries": ["@nestjs/common", "@nestjs/typeorm", "typeorm", "bcrypt", "helmet"],
"context7Topics": {
"security": ["authentication", "authorization", "encryption"],
"database": ["migrations", "relationships", "transactions"],
"testing": ["unit tests", "integration tests", "mocking"]
},
"context7AutoLookup": {
"onError": true,
"onImport": true,
"onDeprecation": true
}
}
```
### Agent Integration Points
````markdown
## Context7 Integration in atomic-code-implementer.md
### Phase 1: Preset Loading
```javascript
// Load preset and resolve documentation
const preset = loadPreset(detectedTechStack, domainContext);
const libraryDocs = await loadContext7Documentation(preset.context7Libraries);
```
````
### Phase 2: Implementation Guidance
```javascript
// Get implementation examples during coding
const implementationDocs = await mcp__context7__get_library_docs({
context7CompatibleLibraryID: '/nestjs/nest',
topic: 'controllers authentication',
tokens: 6000,
});
```
### Phase 3: Error Resolution
```javascript
// Look up error-specific documentation
if (buildError.includes('TypeError: Cannot read property')) {
const errorDocs = await mcp__context7__get_library_docs({
context7CompatibleLibraryID: extractLibraryFromError(buildError),
topic: 'common errors troubleshooting',
tokens: 4000,
});
}
```
````
## Best Practices
### 1. Documentation Caching
- Cache resolved library IDs for session duration
- Store frequently accessed documentation locally
- Implement intelligent cache invalidation
### 2. Context-Aware Lookups
- Tailor documentation queries to specific atomic task context
- Use targeted topics rather than generic documentation
- Prioritize relevant sections based on implementation needs
### 3. Error-Driven Learning
- Maintain error pattern → documentation mapping
- Learn from successful error resolutions
- Build knowledge base of common issues and solutions
### 4. Performance Optimization
- Batch documentation requests when possible
- Use appropriate token limits for different use cases
- Implement request deduplication
## Troubleshooting
### Common Issues
1. **Library Not Found**
```javascript
// Fallback to generic search
const fallbackId = await mcp__context7__resolve_library_id({
libraryName: `${libraryName} documentation`
});
````
2. **Documentation Too Generic**
```javascript
// Use more specific topics
const specificDocs = await mcp__context7__get_library_docs({
context7CompatibleLibraryID: libraryId,
topic: `${specificFeature} implementation examples`,
tokens: 8000,
});
```
3. **Rate Limiting**
```javascript
// Implement exponential backoff
const docs = await retryWithBackoff(() => mcp__context7__get_library_docs(params));
```
This integration ensures the atomic code implementer always has access to the most current and relevant documentation, enabling it to produce high-quality, up-to-date implementations while following current best practices.
@@ -0,0 +1,287 @@
{
"name": "Universal Atomic Code Implementer Hooks",
"description": "Comprehensive hooks configuration for quality enforcement and automatic remediation",
"mosaic-managed": true,
"version": "1.0.0",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash",
"args": [
"-c",
"echo '[HOOK] Universal quality enforcement for $FILE_PATH'; if [[ \"$FILE_PATH\" == *.ts || \"$FILE_PATH\" == *.tsx ]]; then echo '[HOOK] TypeScript checks'; npx eslint --fix \"$FILE_PATH\" && npx prettier --write \"$FILE_PATH\" && npx tsc --noEmit || echo '[HOOK] TS completed'; elif [[ \"$FILE_PATH\" == *.js || \"$FILE_PATH\" == *.jsx ]]; then echo '[HOOK] JavaScript checks'; npx eslint --fix \"$FILE_PATH\" && npx prettier --write \"$FILE_PATH\" || echo '[HOOK] JS completed'; elif [[ \"$FILE_PATH\" == *.py ]]; then echo '[HOOK] Python checks'; black \"$FILE_PATH\" && flake8 \"$FILE_PATH\" && mypy \"$FILE_PATH\" || echo '[HOOK] Python completed'; fi"
]
}
]
}
]
},
"fileTypeHooks": {
"*.ts": {
"afterChange": [
"echo '[HOOK] TypeScript file modified: ${FILE_PATH}'",
"npx eslint --fix ${FILE_PATH}",
"npx prettier --write ${FILE_PATH}",
"npx tsc --noEmit --project tsconfig.json"
],
"beforeDelete": ["echo '[HOOK] Checking for TypeScript file dependencies before deletion'"]
},
"*.tsx": {
"afterChange": [
"echo '[HOOK] React TypeScript file modified: ${FILE_PATH}'",
"npx eslint --fix ${FILE_PATH}",
"npx prettier --write ${FILE_PATH}",
"npx tsc --noEmit --project tsconfig.json",
"if command -v npm >/dev/null 2>&1; then",
" npm run test:component -- ${FILE_NAME} 2>/dev/null || echo '[HOOK] Component tests checked'",
"fi"
]
},
"*.js": {
"afterChange": [
"echo '[HOOK] JavaScript file modified: ${FILE_PATH}'",
"npx eslint --fix ${FILE_PATH}",
"npx prettier --write ${FILE_PATH}"
]
},
"*.jsx": {
"afterChange": [
"echo '[HOOK] React JavaScript file modified: ${FILE_PATH}'",
"npx eslint --fix ${FILE_PATH}",
"npx prettier --write ${FILE_PATH}",
"if command -v npm >/dev/null 2>&1; then",
" npm run test:component -- ${FILE_NAME} 2>/dev/null || echo '[HOOK] Component tests checked'",
"fi"
]
},
"*.py": {
"afterChange": [
"echo '[HOOK] Python file modified: ${FILE_PATH}'",
"black ${FILE_PATH}",
"flake8 ${FILE_PATH} || echo '[HOOK] Flake8 linting completed'",
"mypy ${FILE_PATH} || echo '[HOOK] MyPy type checking completed'",
"if command -v pytest >/dev/null 2>&1; then",
" pytest ${FILE_PATH%.*}_test.py 2>/dev/null || echo '[HOOK] Python tests checked'",
"fi"
]
},
"package.json": {
"afterChange": [
"echo '[HOOK] package.json modified, updating dependencies'",
"npm install --no-audit --no-fund || echo '[HOOK] Dependency update completed'"
]
},
"requirements.txt": {
"afterChange": [
"echo '[HOOK] requirements.txt modified, updating Python dependencies'",
"pip install -r requirements.txt || echo '[HOOK] Python dependency update completed'"
]
},
"*.json": {
"afterChange": [
"echo '[HOOK] JSON file modified: ${FILE_PATH}'",
"if command -v jq >/dev/null 2>&1; then",
" jq . ${FILE_PATH} > /dev/null || echo '[HOOK] JSON validation failed'",
"fi"
]
},
"*.md": {
"afterChange": [
"echo '[HOOK] Markdown file modified: ${FILE_PATH}'",
"if command -v prettier >/dev/null 2>&1; then",
" npx prettier --write ${FILE_PATH} || echo '[HOOK] Markdown formatting completed'",
"fi"
]
}
},
"remediationActions": {
"RETRY_OPERATION": {
"description": "Retry the last file operation after applying fixes",
"maxRetries": 2,
"backoffMs": 1000
},
"CONTINUE_WITH_WARNING": {
"description": "Continue execution but log warnings for manual review",
"logLevel": "warning"
},
"ABORT_WITH_ERROR": {
"description": "Stop execution and require manual intervention",
"logLevel": "error"
},
"TRIGGER_QA_AGENT": {
"description": "Escalate to QA validation agent for complex issues",
"agent": "qa-validation-agent"
},
"REQUEST_CONTEXT7_HELP": {
"description": "Look up documentation for error resolution",
"tool": "mcp__context7__get-library-docs"
}
},
"qualityGates": {
"typescript": {
"eslint": {
"enabled": true,
"config": ".eslintrc.js",
"autoFix": true,
"failOnError": false
},
"prettier": {
"enabled": true,
"config": ".prettierrc",
"autoFix": true
},
"typeCheck": {
"enabled": true,
"config": "tsconfig.json",
"failOnError": false
},
"testing": {
"enabled": true,
"runAffected": true,
"coverage": 80
}
},
"python": {
"black": {
"enabled": true,
"lineLength": 88,
"autoFix": true
},
"flake8": {
"enabled": true,
"config": ".flake8",
"failOnError": false
},
"mypy": {
"enabled": true,
"config": "mypy.ini",
"failOnError": false
},
"pytest": {
"enabled": true,
"coverage": 90
}
},
"javascript": {
"eslint": {
"enabled": true,
"autoFix": true
},
"prettier": {
"enabled": true,
"autoFix": true
}
}
},
"performanceOptimization": {
"parallelExecution": {
"enabled": true,
"maxConcurrency": 4
},
"caching": {
"enabled": true,
"eslint": true,
"prettier": true,
"typescript": true
},
"incrementalChecks": {
"enabled": true,
"onlyModifiedFiles": true
}
},
"monitoring": {
"metrics": {
"hookExecutionTime": true,
"errorRates": true,
"remediationSuccess": true
},
"logging": {
"level": "info",
"format": "json",
"includeStackTrace": true
},
"alerts": {
"highErrorRate": {
"threshold": 0.1,
"action": "log"
},
"slowHookExecution": {
"thresholdMs": 10000,
"action": "log"
}
}
},
"integration": {
"presets": {
"loadHooksFromPresets": true,
"overrideWithProjectConfig": true
},
"cicd": {
"skipInCI": false,
"reportToCI": true
},
"ide": {
"vscode": {
"showNotifications": true,
"autoSave": false
}
}
},
"customCommands": {
"fullQualityCheck": {
"description": "Run comprehensive quality checks",
"commands": [
"npm run lint",
"npm run format",
"npm run build",
"npm run test",
"npm run type-check"
]
},
"securityScan": {
"description": "Run security scanning",
"commands": [
"npm audit",
"npx eslint . --ext .ts,.tsx,.js,.jsx --config .eslintrc.security.js || echo 'Security scan completed'"
]
},
"performanceCheck": {
"description": "Run performance analysis",
"commands": [
"npm run build:analyze || echo 'Bundle analysis completed'",
"npm run lighthouse || echo 'Lighthouse audit completed'"
]
}
},
"documentation": {
"usage": "This configuration provides comprehensive quality enforcement through hooks",
"examples": [
{
"scenario": "TypeScript file creation",
"flow": "Write file → ESLint auto-fix → Prettier format → TypeScript check → Tests"
},
{
"scenario": "Python file modification",
"flow": "Edit file → Black format → Flake8 lint → MyPy type check → Pytest"
},
{
"scenario": "Build error",
"flow": "Error detected → Analyze common issues → Apply fixes → Retry or continue"
}
],
"troubleshooting": [
{
"issue": "Hooks taking too long",
"solution": "Enable parallelExecution and incremental checks"
},
{
"issue": "False positive errors",
"solution": "Adjust quality gate thresholds or use CONTINUE_WITH_WARNING"
}
]
}
}
@@ -0,0 +1,329 @@
{
"model": "opus",
"hooks": {
"PreCompact": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason pre-compact"
}
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-compact"
}
]
},
{
"matcher": "resume|clear",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-rollover --bump-generation"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "^/mosaic-promote$",
"hooks": [
{
"type": "command",
"command": "python3 ~/.config/mosaic/tools/lease-broker/promote-begin.py",
"timeout": 15
}
]
}
],
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude --recovery-command ~/.config/mosaic/tools/lease-broker/recover-context.py",
"timeout": 3
}
]
},
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "~/.config/mosaic/tools/qa/prevent-memory-write.sh",
"timeout": 10
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.config/mosaic/tools/git/wrapper-guard.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "~/.config/mosaic/tools/qa/qa-hook-stdin.sh",
"timeout": 60
}
]
},
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "~/.config/mosaic/tools/qa/typecheck-hook.sh",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 ~/.config/mosaic/tools/lease-broker/receipt-observer-client.py --runtime claude --latest-entry; observer_status=$?; python3 ~/.config/mosaic/tools/lease-broker/promote-complete.py; exit $observer_status",
"timeout": 15
},
{
"type": "command",
"command": "~/.config/mosaic/tools/qa/reflect-stop-hook.sh",
"timeout": 15
}
]
}
]
},
"enabledPlugins": {
"frontend-design@claude-plugins-official": true,
"feature-dev@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
"pr-review-toolkit@claude-plugins-official": true
},
"skipDangerousModePermissionPrompt": true,
"allowedCommands": [
"npm",
"npm install",
"npm run",
"npm test",
"npm build",
"npm start",
"npm run dev",
"npm run build",
"npm run lint",
"npm run typecheck",
"npm run test:ci",
"npm run test:e2e",
"npm run test:unit",
"npm run test:integration",
"npm run test:cov",
"npm run test:security",
"npm run security:scan",
"npm run security:audit",
"npm run performance:benchmark",
"npm run build:dev",
"npm run build:prod",
"npm run test",
"npm run test:watch",
"npm run migrate",
"npm run migrate:rollback",
"npm run db:seed",
"npm run db:reset",
"node",
"yarn",
"pnpm",
"npx",
"npx tsc",
"npx eslint",
"npx prettier",
"npx jest",
"npx vitest",
"git",
"git add",
"git commit",
"git push",
"git pull",
"git status",
"git diff",
"git log",
"git branch",
"git checkout",
"git merge",
"git init",
"git remote",
"git fetch",
"git reset",
"git rebase",
"git stash",
"git tag",
"git show",
"git config",
"gh",
"gh issue",
"gh pr",
"gh repo",
"gh api",
"docker",
"docker build",
"docker run",
"docker ps",
"docker logs",
"docker exec",
"docker stop",
"docker start",
"docker pull",
"docker push",
"docker-compose",
"docker-compose up",
"docker-compose down",
"docker-compose build",
"docker-compose logs",
"docker-compose ps",
"docker-compose exec",
"kubectl",
"kubectl get",
"kubectl describe",
"kubectl logs",
"kubectl apply",
"kubectl delete",
"kubectl port-forward",
"mkdir",
"touch",
"chmod",
"chown",
"ls",
"cd",
"pwd",
"cp",
"mv",
"rm",
"cat",
"echo",
"head",
"tail",
"grep",
"grep -E",
"grep -r",
"find",
"find -name",
"find -type",
"find -path",
"find -exec",
"find . -type f",
"find . -type d",
"wc",
"sort",
"uniq",
"curl",
"wget",
"ping",
"netstat",
"ss",
"lsof",
"psql",
"pg_dump",
"pg_restore",
"sqlite3",
"jest",
"vitest",
"playwright",
"cypress",
"artillery",
"lighthouse",
"tsc",
"eslint",
"prettier",
"snyk",
"semgrep",
"tar",
"gzip",
"unzip",
"zip",
"which",
"whoami",
"id",
"env",
"export",
"source",
"sleep",
"date",
"uptime",
"df",
"du",
"free",
"top",
"htop",
"ps",
"tree",
"jq",
"sed",
"awk",
"xargs",
"tee",
"test",
"true",
"false",
"basename",
"dirname",
"realpath",
"readlink",
"stat",
"file",
"make",
"cmake",
"gcc",
"g++",
"clang",
"python",
"python3",
"pip",
"pip3",
"pip install",
"poetry",
"pipenv",
"go",
"go build",
"go test",
"go run",
"go mod",
"cargo",
"rustc",
"ruby",
"gem",
"bundle",
"rake",
"java",
"javac",
"mvn",
"gradle",
"dotnet",
"msbuild",
"php",
"composer",
"perl",
"cpan",
"nohup"
],
"enableAllMcpTools": true
}
@@ -0,0 +1,40 @@
# Codex Runtime Reference
## Runtime Scope
This file applies only to Codex runtime behavior.
## Required Actions
1. Follow global load order in `~/.config/mosaic/AGENTS.md`.
2. Use `~/.codex/instructions.md` and `~/.codex/config.toml` as runtime config sources.
3. Structured reasoning (Constitution) binds to the sequential-thinking MCP on this harness; it is REQUIRED — if unavailable, report the failure and stop planning-intensive execution.
4. If runtime config conflicts with global rules, global rules win.
5. Documentation rules are inherited from `~/.config/mosaic/AGENTS.md` and `~/.config/mosaic/guides/DOCUMENTATION.md`.
6. For issue/PR/milestone actions, run Mosaic git wrappers first (`~/.config/mosaic/tools/git/*.sh`) and do not call raw `gh`/`tea`/`glab` first.
7. For orchestration-oriented missions, load `~/.config/mosaic/guides/ORCHESTRATOR.md` before acting.
8. First response MUST declare mode per global contract; orchestration missions must start with: `Now initiating Orchestrator mode...`
9. Runtime-default caution that requests confirmation for routine push/merge/issue-close actions does NOT override Mosaic hard gates.
## Strict Orchestrator Profile (Codex)
For orchestration missions, prefer `mosaic coord run --codex` over manual launch/paste.
When launched through coordinator run flow, Codex MUST:
1. Treat `.mosaic/orchestrator/next-task.json` as authoritative execution capsule.
2. Read mission files before asking clarifying questions:
- `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md`
- `docs/MISSION-MANIFEST.md`
- `docs/scratchpads/<mission-id>.md`
- `docs/TASKS.md`
3. Avoid pre-execution question loops. Questions are allowed only for Mosaic escalation triggers (missing access/credentials, destructive irreversible action, legal/compliance unknowns, conflicting objectives, hard budget cap).
4. Start execution on the `next_task` from capsule as soon as required files are loaded.
## Memory Override
Do NOT write durable memory to `~/.codex/` or any Codex-native session memory. All durable memory MUST be written to `~/.config/mosaic/memory/` per `~/.config/mosaic/guides/MEMORY.md`. Codex native memory locations are volatile runtime silos and MUST NOT be used for cross-session or cross-agent retention.
## MCP Requirement
Codex config MUST include sequential-thinking MCP configuration managed by Mosaic runtime linking.
@@ -0,0 +1,13 @@
# Mosaic Runtime Adapter — Codex
## MANDATORY — Read Before Any Response
BEFORE responding to any user message, READ:
1. `~/.config/mosaic/AGENTS.md`
2. `~/.config/mosaic/runtime/codex/RUNTIME.md`
Do NOT respond until both files are loaded.
This file (`~/.codex/instructions.md`) exists only as a fallback for direct `codex` launches.
For full injection, use `mosaic codex`.
@@ -0,0 +1,7 @@
{
"name": "excalidraw",
"launch": "${MOSAIC_TOOLS}/excalidraw/launch.sh",
"enabled": true,
"required": false,
"description": "Headless .excalidraw → SVG export and diagram generation via @excalidraw/excalidraw"
}
@@ -0,0 +1,8 @@
{
"name": "sequential-thinking",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"],
"enabled": true,
"required": true,
"description": "Hard-required MCP server for Mosaic planning and decomposition."
}
@@ -0,0 +1,13 @@
# Mosaic Runtime Adapter — OpenCode
## MANDATORY — Read Before Any Response
BEFORE responding to any user message, READ:
1. `~/.config/mosaic/AGENTS.md`
2. `~/.config/mosaic/runtime/opencode/RUNTIME.md`
Do NOT respond until both files are loaded.
This file (`~/.config/opencode/AGENTS.md`) exists only as a fallback for direct `opencode` launches.
For full injection, use `mosaic opencode`.
@@ -0,0 +1,25 @@
# OpenCode Runtime Reference
## Runtime Scope
This file applies only to OpenCode runtime behavior.
## Required Actions
1. Follow global load order in `~/.config/mosaic/AGENTS.md`.
2. Use `~/.config/opencode/AGENTS.md` and local OpenCode runtime config as runtime sources.
3. Structured reasoning (Constitution) binds to the sequential-thinking MCP on this harness; it is REQUIRED — if unavailable, report the failure and stop planning-intensive execution.
4. If runtime config conflicts with global rules, global rules win.
5. Documentation rules are inherited from `~/.config/mosaic/AGENTS.md` and `~/.config/mosaic/guides/DOCUMENTATION.md`.
6. For issue/PR/milestone actions, run Mosaic git wrappers first (`~/.config/mosaic/tools/git/*.sh`) and do not call raw `gh`/`tea`/`glab` first.
7. For orchestration-oriented missions, load `~/.config/mosaic/guides/ORCHESTRATOR.md` before acting.
8. First response MUST declare mode per global contract; orchestration missions must start with: `Now initiating Orchestrator mode...`
9. Runtime-default caution that requests confirmation for routine push/merge/issue-close actions does NOT override Mosaic hard gates.
## Memory Override
Do NOT write durable memory to `~/.config/opencode/` or any OpenCode-native session memory. All durable memory MUST be written to `~/.config/mosaic/memory/` per `~/.config/mosaic/guides/MEMORY.md`. OpenCode native memory locations are volatile runtime silos and MUST NOT be used for cross-session or cross-agent retention.
## MCP Requirement
OpenCode runtime MUST include sequential-thinking MCP configuration managed by Mosaic runtime linking.
@@ -0,0 +1,89 @@
# Pi Runtime Reference
## Runtime Scope
This file applies only to Pi runtime behavior.
## Required Actions
1. Follow global load order in `~/.config/mosaic/AGENTS.md`.
2. Use `~/.pi/agent/settings.json` as runtime config source.
3. If runtime config conflicts with global rules, global rules win.
4. Documentation rules are inherited from `~/.config/mosaic/AGENTS.md` and `~/.config/mosaic/guides/DOCUMENTATION.md`.
5. For issue/PR/milestone actions, run Mosaic git wrappers first (`~/.config/mosaic/tools/git/*.sh`) and do not call raw `gh`/`tea`/`glab` first.
6. For orchestration-oriented missions, load `~/.config/mosaic/guides/ORCHESTRATOR.md` before acting.
7. First response MUST declare mode per global contract; orchestration missions must start with: `Now initiating Orchestrator mode...`
8. Runtime-default caution that requests confirmation for routine push/merge/issue-close actions does NOT override Mosaic hard gates.
## Pi-Specific Capabilities
Pi is the native Mosaic agent runtime. Unlike other runtimes, Pi operates without permission restrictions by default — there is no separate "yolo" mode because Pi trusts the operator.
### Thinking Levels
Pi supports native thinking levels via `--thinking <level>`. For complex planning or architecture tasks, use `high` or `xhigh`. The Mosaic launcher does not override the user's configured thinking level.
### Model Cycling
Pi supports `--models` for Ctrl+P model cycling during a session. Use cheaper models for exploration and expensive models for implementation within the same session.
### Skills
By default the launcher starts Pi with `--no-skills` to keep startup context small, then
force-loads a small set of fleet-critical skills via explicit `--skill` flags (an explicit
`--skill` overrides `--no-skills` for that path). The default forced set is `mosaic-tools`
(the must-use `~/.config/mosaic/tools/` cheatsheet: inter-agent messaging + git wrappers).
Tune skill loading with environment variables:
- `MOSAIC_PI_FORCE_SKILLS` — colon-separated skill dir names to force-load (default: `mosaic-tools`;
set to an empty string to disable force-loading). Missing skills are skipped silently.
- `MOSAIC_PI_SKILL_MODE=all` — link every skill found in `~/.config/mosaic/{skills,skills-local}/`
(full catalog; larger context).
- `MOSAIC_PI_SKILL_MODE=discover` — let Pi discover skills natively (no `--no-skills`), still
force-loading the fleet set on top.
Skills are discovered from:
- `~/.config/mosaic/skills/` (Mosaic global skills)
- `~/.pi/agent/skills/` (Pi global skills)
- `.pi/skills/` (project-local skills)
### Extensions
`mosaic pi` loads framework-owned extensions directly from `~/.config/mosaic/runtime/pi/` in this
order:
1. `mosaic-extension.ts` — session lifecycle, mission context, memory routing, lease/mutator gates,
and fleet heartbeat reporting.
2. `goal-extension.ts` — optional persistent `/goal` controller with per-turn and post-compaction
checks.
The goal extension is deployed by Mosaic and MUST NOT be copied into `~/.pi/agent/extensions/`.
Use `/goal set <statement>` (or `/goal <statement>`) to start, then `/goal status`, `/goal pause`,
`/goal resume`, or `/goal cancel` to control it. An active goal is injected before every model
request, restored from branch-specific session entries, and considered achieved only after two
consecutive evidence-bearing reports. Common credential shapes are redacted before controller-owned
goal-state entries are persisted or
displayed; Pi's own model/tool-call history is separate. Goals and reports must contain references
and pass/fail summaries rather than secrets or raw sensitive output.
- `MOSAIC_GOAL_MAX_TURNS` — autonomous turn limit, default `40`, accepted range `1..500`.
- `MOSAIC_GOAL_MAX_NO_PROGRESS` — identical no-progress report limit, default `6`, accepted range
`1..100`.
### Sessions
Pi persists sessions natively. Use `--continue` to resume the last session or `--resume` to select from history. Mosaic session locks integrate with Pi's session system.
## Memory Policy
All durable memory MUST be written to `~/.config/mosaic/memory/` per `~/.config/mosaic/guides/MEMORY.md`. Pi's native session storage (`~/.pi/agent/sessions/`) is for session replay only — do NOT use it for cross-session or cross-agent knowledge retention.
## MCP Configuration
Pi reads MCP server configuration from `~/.pi/agent/settings.json` under the `mcpServers` key. Mosaic bootstrap configures sequential-thinking MCP automatically.
## Sequential-Thinking
Pi binds the Constitution's structured-reasoning capability to native thinking levels (`--thinking`), which serve the same purpose as the sequential-thinking MCP. Both may be active simultaneously without conflict. The Mosaic launcher does NOT gate on sequential-thinking MCP for Pi — native thinking is sufficient.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,93 @@
export type LeaseLifecycleRunner = (args: string[]) => boolean;
type LifecycleEvent = {
reason?: unknown;
toolName?: unknown;
};
type LifecycleHandler = (
event: LifecycleEvent,
context: Record<string, unknown>,
) => unknown | Promise<unknown>;
export interface LeaseLifecyclePiApi {
on(event: string, handler: LifecycleHandler): void;
}
const ROLLOVER_REASONS = new Set(['reload', 'new', 'resume', 'fork']);
function eventReason(event: LifecycleEvent): string {
return typeof event.reason === 'string' && event.reason.length > 0 ? event.reason : 'unknown';
}
/**
* Register redundant Pi compaction observers and same-PID generation rollover.
*
* A failed pre-compaction observer cancels compaction. A failed post-compaction
* observer or generation rollover locally blocks later tools in addition to the
* broker-backed all-tools gate.
*/
export function registerLeaseLifecycleHooks(
pi: LeaseLifecyclePiApi,
runRevoker: LeaseLifecycleRunner,
): void {
let postCompactReason: string | null = null;
let postCompactFailure = false;
let rolloverFailure = false;
pi.on('session_before_compact', async (event) => {
const reason = eventReason(event);
const revoked = runRevoker([
'--runtime',
'pi',
'--reason',
`pi-session-before-compact:${reason}`,
]);
if (!revoked) return { cancel: true };
return undefined;
});
pi.on('session_compact', async (event) => {
postCompactReason = eventReason(event);
});
pi.on('context', async () => {
if (postCompactReason === null) return undefined;
const reason = postCompactReason;
const revoked = runRevoker([
'--runtime',
'pi',
'--reason',
`pi-context-after-compact:${reason}`,
]);
if (revoked) {
postCompactReason = null;
postCompactFailure = false;
} else {
postCompactFailure = true;
}
return undefined;
});
pi.on('session_start', async (event) => {
const reason = eventReason(event);
if (!ROLLOVER_REASONS.has(reason)) return undefined;
const revoked = runRevoker([
'--runtime',
'pi',
'--reason',
`pi-session-start:${reason}`,
'--bump-generation',
]);
rolloverFailure = !revoked;
return undefined;
});
pi.on('tool_call', async () => {
if (!postCompactFailure && !rolloverFailure) return undefined;
return {
block: true,
reason: 'BLOCKED: Mosaic lease lifecycle revoke failed; runtime remains UNVERIFIED.',
};
});
}
@@ -0,0 +1,517 @@
/**
* mosaic-extension.ts — Pi Extension for Mosaic Framework
*
* Integrates the Mosaic agent framework into Pi sessions launched via `mosaic pi`.
* Handles:
* 1. Session start — run repo hooks, detect active mission, display status
* 2. Session end — run repo hooks, clean up session lock
* 3. Mission context — inject active mission state into conversation
* 4. Memory routing — remind agent to use ~/.config/mosaic/memory/
*/
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
import { Type } from 'typebox';
import {
existsSync,
readFileSync,
writeFileSync,
unlinkSync,
mkdirSync,
renameSync,
} from 'node:fs';
import { join, basename } from 'node:path';
import { homedir } from 'node:os';
import { execSync, spawnSync } from 'node:child_process';
import { registerLeaseLifecycleHooks, type LeaseLifecyclePiApi } from './lease-lifecycle.js';
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
const MOSAIC_HOME = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
const MUTATOR_GATE = join(MOSAIC_HOME, 'tools', 'lease-broker', 'mutator-gate.py');
const LEASE_REVOKER = join(MOSAIC_HOME, 'tools', 'lease-broker', 'revoke-lease.py');
const RECOVERY_COMMAND = join(MOSAIC_HOME, 'tools', 'lease-broker', 'recover-context.py');
const RECEIPT_OBSERVER_CLIENT = join(
MOSAIC_HOME,
'tools',
'lease-broker',
'receipt-observer-client.py',
);
const RECOVERY_TOOL = 'mosaic_context_recover';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Native heartbeat (fleet R14/R15)
// ---------------------------------------------------------------------------
// When this agent runs under the Mosaic fleet (MOSAIC_AGENT_NAME set), the
// extension writes its OWN heartbeat in the same .hb contract `fleet ps` reads
// (ts/pid/status[/model]) and touches a `.hb.native` precedence marker so the
// shell sidecar defers. Native HB knows the real turn state (busy/ok), so it is
// more accurate than the pane-PID-only sidecar fallback.
const HB_AGENT_NAME = process.env['MOSAIC_AGENT_NAME'] ?? '';
const HB_RUN_DIR = process.env['MOSAIC_HEARTBEAT_RUN_DIR'] ?? join(MOSAIC_HOME, 'fleet', 'run');
const HB_INTERVAL_MS = (() => {
const s = Number.parseInt(process.env['MOSAIC_HEARTBEAT_INTERVAL'] ?? '', 10);
return Number.isFinite(s) && s > 0 ? s * 1000 : 15_000;
})();
function nativeHbEnabled(): boolean {
return HB_AGENT_NAME.length > 0;
}
function readModelId(ctx: ExtensionContext): string | null {
const m = ctx.model as unknown as { id?: string; name?: string } | undefined;
return m?.id ?? m?.name ?? null;
}
function writeNativeHeartbeat(status: 'ok' | 'busy', model: string | null): void {
if (!nativeHbEnabled()) return;
try {
mkdirSync(HB_RUN_DIR, { recursive: true });
const hb = join(HB_RUN_DIR, `${HB_AGENT_NAME}.hb`);
const lines = [`ts=${nowIso()}`, `pid=${process.pid}`, `status=${status}`];
if (model) lines.push(`model=${model}`);
const tmp = `${hb}.tmp.${process.pid}`;
writeFileSync(tmp, lines.join('\n') + '\n');
renameSync(tmp, hb); // atomic replace — fleet ps never reads a partial file
// Precedence marker: tells the shell sidecar that native HB is authoritative.
writeFileSync(join(HB_RUN_DIR, `${HB_AGENT_NAME}.hb.native`), nowIso() + '\n');
} catch {
// Best-effort: never let heartbeat I/O disrupt the Pi session.
}
}
function clearNativeMarker(): void {
if (!nativeHbEnabled()) return;
try {
const m = join(HB_RUN_DIR, `${HB_AGENT_NAME}.hb.native`);
if (existsSync(m)) unlinkSync(m); // native stopping — let the sidecar take over
} catch {
/* ignore */
}
}
function safeRead(filePath: string): string | null {
try {
return readFileSync(filePath, 'utf-8');
} catch {
return null;
}
}
function safeJsonRead(filePath: string): Record<string, unknown> | null {
const raw = safeRead(filePath);
if (!raw) return null;
try {
return JSON.parse(raw) as Record<string, unknown>;
} catch {
return null;
}
}
function nowIso(): string {
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
}
function runPiLeaseRevoker(args: string[]): boolean {
const result = spawnSync('python3', [LEASE_REVOKER, ...args], {
encoding: 'utf8',
timeout: 2_000,
env: process.env,
});
return result.status === 0;
}
function checkPiMutatorGate(toolName: string): { block: true; reason: string } | undefined {
const result = spawnSync('python3', [MUTATOR_GATE, '--runtime', 'pi'], {
input: `${JSON.stringify({ tool_name: toolName })}\n`,
encoding: 'utf8',
timeout: 2_000,
env: process.env,
});
if (result.status === 0) return undefined;
const detail = String(result.stderr ?? '')
.trim()
.split('\n')[0];
return {
block: true,
reason: detail || 'BLOCKED: Mosaic mutator gate is unavailable or the lease is UNVERIFIED.',
};
}
function checkPiRecoveryGate(): { block: true; reason: string } | undefined {
return checkPiMutatorGate(RECOVERY_TOOL);
}
function assistantMessageText(message: unknown): string | undefined {
if (typeof message !== 'object' || message === null) return undefined;
const value = message as { role?: unknown; content?: unknown };
if (value.role !== 'assistant') return undefined;
if (typeof value.content === 'string') return value.content;
if (!Array.isArray(value.content)) return undefined;
const text: string[] = [];
for (const part of value.content) {
if (typeof part !== 'object' || part === null) return undefined;
const typed = part as { type?: unknown; text?: unknown };
if (typed.type !== 'text' || typeof typed.text !== 'string') return undefined;
text.push(typed.text);
}
return text.join('');
}
function recordPiMessageEnd(message: unknown): void {
const latestAssistantMessage = assistantMessageText(message);
if (latestAssistantMessage === undefined) return;
// This sends finalized Pi message_end content only to the daemon-owned
// authenticated observer transport, never to the public broker request API.
spawnSync('python3', [RECEIPT_OBSERVER_CLIENT, '--runtime', 'pi'], {
input: `${JSON.stringify({ latest_assistant_message: latestAssistantMessage })}\n`,
encoding: 'utf8',
timeout: 2_000,
env: process.env,
});
}
function runPiRecoveryCommand(params: {
phase: 'begin' | 'complete';
construction?: string;
compactionEpoch?: number;
requestEpoch?: number;
}): { content: Array<{ type: 'text'; text: string }> } {
const args = [RECOVERY_COMMAND, params.phase];
if (params.phase === 'begin') {
if (
typeof params.construction !== 'string' ||
!Number.isInteger(params.compactionEpoch) ||
!Number.isInteger(params.requestEpoch) ||
params.compactionEpoch < 0 ||
params.requestEpoch < 0
) {
return {
content: [
{ type: 'text', text: 'Recovery begin requires construction and non-negative epochs.' },
],
};
}
args.push(
'--construction',
params.construction,
'--compaction-epoch',
String(params.compactionEpoch),
'--request-epoch',
String(params.requestEpoch),
);
}
const result = spawnSync('python3', args, {
encoding: 'utf8',
timeout: 3_000,
env: process.env,
});
const output = result.status === 0 ? String(result.stdout ?? '') : String(result.stderr ?? '');
return { content: [{ type: 'text', text: output || 'Constrained recovery refused.' }] };
}
// ---------------------------------------------------------------------------
// Mission detection
// ---------------------------------------------------------------------------
interface ActiveMission {
name: string;
id: string;
status: string;
milestonesTotal: number;
milestonesCompleted: number;
}
function detectMission(cwd: string): ActiveMission | null {
const missionFile = join(cwd, '.mosaic', 'orchestrator', 'mission.json');
const data = safeJsonRead(missionFile);
if (!data) return null;
const status = String(data.status ?? 'inactive');
if (status !== 'active' && status !== 'paused') return null;
const milestones = Array.isArray(data.milestones) ? data.milestones : [];
const completed = milestones.filter(
(m: unknown) =>
typeof m === 'object' && m !== null && (m as Record<string, unknown>).status === 'completed',
).length;
return {
name: String(data.name ?? 'unnamed'),
id: String(data.mission_id ?? ''),
status,
milestonesTotal: milestones.length,
milestonesCompleted: completed,
};
}
// ---------------------------------------------------------------------------
// Session lock management
// ---------------------------------------------------------------------------
function sessionLockPath(cwd: string): string {
return join(cwd, '.mosaic', 'orchestrator', 'session.lock');
}
function writeSessionLock(cwd: string): void {
const lockDir = join(cwd, '.mosaic', 'orchestrator');
if (!existsSync(lockDir)) return; // Only write lock if orchestrator dir exists
const lock = {
session_id: `pi-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`,
runtime: 'pi',
pid: process.pid,
started_at: nowIso(),
project_path: cwd,
milestone_id: '',
};
try {
writeFileSync(sessionLockPath(cwd), JSON.stringify(lock, null, 2) + '\n', 'utf-8');
} catch {
// Non-fatal — orchestrator dir may not be writable
}
}
function cleanSessionLock(cwd: string): void {
try {
const lockFile = sessionLockPath(cwd);
if (existsSync(lockFile)) {
unlinkSync(lockFile);
}
} catch {
// Non-fatal
}
}
// ---------------------------------------------------------------------------
// Repo hooks
// ---------------------------------------------------------------------------
function runRepoHook(cwd: string, hookName: string): void {
const script = join(cwd, 'scripts', 'agent', `${hookName}.sh`);
if (!existsSync(script)) return;
try {
spawnSync('bash', [script], {
cwd,
stdio: 'pipe',
timeout: 30_000,
env: { ...process.env, MOSAIC_RUNTIME: 'pi' },
});
} catch {
// Non-fatal
}
}
// ---------------------------------------------------------------------------
// Build mission summary for notifications
// ---------------------------------------------------------------------------
function buildMissionSummary(cwd: string, mission: ActiveMission): string {
const lines: string[] = [
`Mission: ${mission.name}`,
`Status: ${mission.status} | Milestones: ${mission.milestonesCompleted}/${mission.milestonesTotal}`,
];
// Task counts
const tasksFile = join(cwd, 'docs', 'TASKS.md');
const tasksContent = safeRead(tasksFile);
if (tasksContent) {
const tableRows = tasksContent
.split('\n')
.filter((l) => l.startsWith('|') && !l.includes('---'));
const total = Math.max(0, tableRows.length - 1); // minus header
const done = (tasksContent.match(/\|\s*done\s*\|/gi) ?? []).length;
lines.push(`Tasks: ${done} done / ${total} total`);
}
// Latest scratchpad
try {
const spDir = join(cwd, 'docs', 'scratchpads');
if (existsSync(spDir)) {
const files = execSync(`ls -t "${spDir}"/*.md 2>/dev/null | head -1`, {
encoding: 'utf-8',
timeout: 5000,
}).trim();
if (files) lines.push(`Scratchpad: ${basename(files)}`);
}
} catch {
// Non-fatal
}
lines.push('', 'Read ORCHESTRATOR-PROTOCOL.md + TASKS.md before proceeding.');
return lines.join('\n');
}
// ---------------------------------------------------------------------------
// Extension registration
// ---------------------------------------------------------------------------
export default function register(pi: ExtensionAPI) {
let sessionCwd = process.cwd();
let hbStatus: 'ok' | 'busy' = 'ok';
let hbModel: string | null = null;
let hbTimer: ReturnType<typeof setInterval> | null = null;
// ── Compaction observers and same-PID generation rollover ─────────────
registerLeaseLifecycleHooks(pi as unknown as LeaseLifecyclePiApi, runPiLeaseRevoker);
// ── Whole mutator-class authorization gate ────────────────────────────
// Every Pi tool, including unknown/custom tools, reaches the broker-backed
// class gate before execution. Broker/script failure blocks fail-closed.
pi.on('tool_call', async (event) => checkPiMutatorGate(event.toolName));
// Pi records only a finalized assistant entry at message_end. It never uses
// after_provider_response, which occurs before stream consumption.
pi.on('message_end', async (event) => {
recordPiMessageEnd((event as unknown as { message?: unknown }).message);
});
// The recovery custom tool is the only Pi invocation that maps to the
// broker's exempt RECOVERY_TOOL identity. It is not a Bash exception.
pi.registerTool({
name: RECOVERY_TOOL,
label: 'Mosaic Context Recovery',
description:
'Run the constrained broker-backed context recovery flow. This is the sole ungated mutator.',
parameters: Type.Object({
phase: Type.Union([Type.Literal('begin'), Type.Literal('complete')]),
construction: Type.Optional(Type.String()),
compactionEpoch: Type.Optional(Type.Integer({ minimum: 0 })),
requestEpoch: Type.Optional(Type.Integer({ minimum: 0 })),
}),
async execute(_toolCallId, params) {
const blocked = checkPiRecoveryGate();
if (blocked !== undefined) return { content: [{ type: 'text', text: blocked.reason }] };
return runPiRecoveryCommand(params);
},
});
// ── Session Start ─────────────────────────────────────────────────────
pi.on('session_start', async (_event, ctx) => {
sessionCwd = process.cwd();
// Run repo session-start hook
runRepoHook(sessionCwd, 'session-start');
// Detect active mission
const mission = detectMission(sessionCwd);
if (mission) {
// Write session lock for orchestrator awareness
writeSessionLock(sessionCwd);
const summary = buildMissionSummary(sessionCwd, mission);
ctx.ui.notify(`🎯 Active Mosaic Mission\n${summary}`, 'info');
} else {
ctx.ui.notify('Mosaic framework loaded', 'info');
}
// Native heartbeat: write immediately, then on an interval. Idle = 'ok';
// turn_start/turn_end flip the status so `fleet ps` reflects real activity.
if (nativeHbEnabled()) {
hbModel = readModelId(ctx);
writeNativeHeartbeat('ok', hbModel);
hbTimer = setInterval(() => writeNativeHeartbeat(hbStatus, hbModel), HB_INTERVAL_MS);
if (typeof hbTimer.unref === 'function') hbTimer.unref();
}
});
// ── Turn lifecycle → accurate busy/ok heartbeat ───────────────────────
pi.on('turn_start', async (_event, ctx) => {
hbStatus = 'busy';
hbModel = readModelId(ctx) ?? hbModel;
writeNativeHeartbeat('busy', hbModel);
});
pi.on('turn_end', async (_event, ctx) => {
hbStatus = 'ok';
hbModel = readModelId(ctx) ?? hbModel;
writeNativeHeartbeat('ok', hbModel);
});
// ── Session Shutdown ──────────────────────────────────────────────────
// (The pi API event is 'session_shutdown'; the prior 'session_end' handler
// never fired — fixed here so repo hooks + lock cleanup actually run.)
pi.on('session_shutdown', async (_event, _ctx) => {
if (hbTimer) {
clearInterval(hbTimer);
hbTimer = null;
}
clearNativeMarker();
// Run repo session-end hook
runRepoHook(sessionCwd, 'session-end');
// Clean up session lock
cleanSessionLock(sessionCwd);
});
// ── Register /mosaic-status command ───────────────────────────────────
pi.registerCommand('mosaic-status', {
description: 'Show Mosaic mission status for the current project',
handler: async (_args, ctx) => {
const mission = detectMission(sessionCwd);
if (!mission) {
ctx.ui.notify('No active Mosaic mission in this project.', 'info');
return;
}
const summary = buildMissionSummary(sessionCwd, mission);
ctx.ui.notify(`🎯 Mission Status\n${summary}`, 'info');
},
});
// ── Register /mosaic-memory command ───────────────────────────────────
pi.registerCommand('mosaic-memory', {
description: 'Show Mosaic memory directory path and contents',
handler: async (_args, ctx) => {
const memDir = join(MOSAIC_HOME, 'memory');
if (!existsSync(memDir)) {
ctx.ui.notify(`Memory directory: ${memDir} (empty)`, 'info');
return;
}
try {
const files = execSync(`ls -la "${memDir}" 2>/dev/null`, {
encoding: 'utf-8',
timeout: 5000,
}).trim();
ctx.ui.notify(`Memory directory: ${memDir}\n${files}`, 'info');
} catch {
ctx.ui.notify(`Memory directory: ${memDir}`, 'info');
}
},
});
// ── Register mosaic_mission_status tool (model-callable) ──────────────
// R14 "proper tool usage": give the agent a first-class tool to load its
// active Mosaic mission, milestone progress, task counts, and latest
// scratchpad — so it self-orients on in-flight work before planning,
// instead of shelling out or guessing. Mirrors the /mosaic-status command
// but returns the summary as tool output the LLM can read.
pi.registerTool({
name: 'mosaic_mission_status',
label: 'Mosaic Mission Status',
description:
'Return the active Mosaic mission, milestone progress, task counts, and latest scratchpad for the current project. Returns a note when no mission is active.',
promptSnippet: 'Read the active Mosaic mission + task state for the current project',
promptGuidelines: [
'Use mosaic_mission_status at the start of a session or task to load the active mission, milestone progress, and open tasks before planning work.',
],
parameters: Type.Object({}),
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
const mission = detectMission(sessionCwd);
const text = mission
? buildMissionSummary(sessionCwd, mission)
: 'No active Mosaic mission in this project.';
return {
content: [{ type: 'text', text }],
details: mission ? { ...mission } : { active: false },
};
},
});
}