chore: consolidate new foundation and archive v1 (#1495)
This commit is contained in:
@@ -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 },
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user