P0-P8: Full V1.0.0 Alpha implementation + audit reports

Implements 123 tasks across 9 phases (T-001..T-809) totaling 146 source files.

Monorepo (P0):
- 7-package Bun + Turborepo + TypeScript monorepo
- dependency-cruiser enforcing 7 forbidden edges + 5 deep-import rules

Contracts (P0):
- 16 type files (ids/error/event/runtime/ipc/task/worker-result/tool/artifact/evidence/project/provider/permission/ui/capability/platform)

Storage & Events (P1):
- DatabaseManager + MigrationRunner (19 tables, 22 indexes, 5 schema_meta seeds)
- 16 repositories (Repository<T,I,U> pattern, INV-1 status columns via EventStore.project only)
- EventSchemaRegistry (54 durable + 7 ephemeral), EventStore, EventBus, EventIngestor
- Project/Session/Artifact/Evidence stores + 8-step Recovery

Tools & Permission (P2):
- PathClassifier (8 categories), CommandRiskAnalyzer (10 categories), SecretRedactor
- PermissionEngine 6-layer evaluation (capability→profile→task_scope→risk→credential→user_prompt)
- ToolRegistry with 20+ tools across fs/shell/git/project/artifact/context/permission/doctor
- CapabilityManifestValidator + CapabilityRegistry

LLM & Context (P3):
- ModelConfigLoader, CapabilityMatrix, AnthropicCanonicalConverter
- AnthropicAdapter + OpenAICompatibleAdapter
- ProviderManager facade
- PromptLayerLoader (L0/L1/L3/L5), CompactionPolicy, ContextAssembler

Worker IPC & Scheduler (P4):
- WorkerProtocol (NDJSON), WorkerProcess (exit codes 0-5), WorkerManager (spawn/handshake)
- WorkerRuntime (INV-3: IPC only, no direct fs/shell/SQLite)
- 5 worker roles (Executor/Reviewer/Debugger/Compactor/ExperienceMiner)
- TaskGraph, WavePlanner, RetryPlanner, AgentMonitor, WorkspaceManager
- Scheduler (state machine), 8-step Recovery

C++ Toolchain (P5):
- DiagnosticParser, CppProjectDetector, CMakeConfigurator, CppBuilder
- CppTestRunner, CppcheckRunner, ClangdClient
- CppToolRegistrar + capability manifest

Projection & TUI (P6):
- ProjectionStore (hydrate/apply/snapshot/subscribe)
- TuiApp + 8 components (Session/Task/Agent/Tool/Diff/Evidence/Permission/Blocker/Hud)
- ProjectionClient in-process ref

Agents & Knowledge (P7):
- MainAgent, ArchitectureDesigner
- DebugKnowledgeStore + LearnedMemoryStore (single-writer, outbox model)
- Role integration wiring

CLI & Doctor & Release (P8):
- Logger + DeveloperLogEncryptor (AES-256-GCM)
- DoctorService (self_bootstrap first)
- RuntimeApp + ServiceRegistry
- 11 CLI commands: run/init/doctor/provider/resume/compact/history/session/restore/e2e/release
- CliEntrypoint + air<TODO>

Audit (in AirPlan/docs/):
- Deepseek开发阶段审计.md (97 findings)
- Opus开发阶段审计.md (140+ findings, 18 P0 blockers)
- MiniMaxM3开发阶段审计.md (18 P0 blockers, focuses on executability)
- AirPlan/TODO.md (technical debt + 42 TODOs by phase)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-02 19:19:55 +08:00
parent 071283df8f
commit a773bac28c
179 changed files with 21855 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
/**
* CompactionPolicy - Token budget management and compaction decisions
*
* Implements contracts §16; DD §10.3.
*
* @module packages/runtime/src/context/CompactionPolicy
*/
import type { PromptLayer, BudgetFitResult } from '@aircoding/contracts'
export interface CompactionConfig {
max_tokens: number
compaction_threshold: number // fraction of max_tokens that triggers compaction
min_compact_tokens: number // minimum tokens to free to consider compaction successful
immutable_layers: string[] // layers that can never be dropped
}
const DEFAULT_CONFIG: CompactionConfig = {
max_tokens: 200000,
compaction_threshold: 0.85, // compact when 85% full
min_compact_tokens: 40000, // must free at least 40K tokens
immutable_layers: ['runtime_invariant', 'role']
}
export interface CompactionDecision {
should_compact: boolean
reason?: string
layers_to_compact?: PromptLayer[]
estimated_tokens_freed?: number
}
export interface CompactionResult {
ok: boolean
compacted_layers: PromptLayer[] // layers that were compacted/summarized
summary_content: string // compaction summary
tokens_freed: number
remaining_tokens: number
warnings: string[]
}
export class CompactionPolicy {
private config: CompactionConfig
constructor(config?: Partial<CompactionConfig>) {
this.config = { ...DEFAULT_CONFIG, ...config }
}
/**
* Check if compaction should be triggered.
* Returns decision with recommendation.
*/
should_compact(layers: PromptLayer[], current_token_count: number): CompactionDecision {
const threshold = this.config.max_tokens * this.config.compaction_threshold
if (current_token_count < threshold) {
return { should_compact: false, reason: `Tokens (${current_token_count}) below threshold (${threshold})` }
}
// Find compactable layers (not immutable, not L0/L1)
const compactable = layers.filter(l => !this.config.immutable_layers.includes(l.level))
if (compactable.length === 0) {
return { should_compact: false, reason: 'No compactable layers found' }
}
// Estimate how many tokens we could free
// Target compactable layers from highest level (most recent / least important)
const sorted = [...compactable].sort((a, b) => {
const level_order: Record<string, number> = {
runtime_invariant: 0, role: 1, safety: 2, project_rules: 3,
architecture: 4, task_spec: 5, evidence: 6, conversation: 7,
tool_output: 8, user_override: 9, system_debug: 10
}
return level_order[b.level] - level_order[a.level]
})
let estimated_freed = 0
const to_compact: PromptLayer[] = []
const target = current_token_count - (this.config.max_tokens * 0.6) // Compact to 60%
for (const layer of sorted) {
if (estimated_freed >= target) break
estimated_freed += layer.token_estimate || 0
to_compact.push(layer)
}
if (to_compact.length === 0 || estimated_freed < this.config.min_compact_tokens) {
return {
should_compact: false,
reason: `Insufficient tokens to free: ${estimated_freed} < ${this.config.min_compact_tokens}`
}
}
return {
should_compact: true,
reason: `Token count (${current_token_count}) exceeds threshold (${threshold})`,
layers_to_compact: to_compact,
estimated_tokens_freed: estimated_freed
}
}
/**
* Execute compaction on the given layers.
* Returns compacted result with summary.
*/
compact(layers_to_compact: PromptLayer[], all_layers: PromptLayer[]): CompactionResult {
const warnings: string[] = []
let tokens_freed = 0
// Check immutable layers are preserved
const immutable_preserved = all_layers.filter(l => this.config.immutable_layers.includes(l.level))
if (immutable_preserved.length < this.config.immutable_layers.length) {
warnings.push('Some immutable layers were in the compaction set - preserving them')
}
// Generate summary of compacted layers
const summary_parts: string[] = []
for (const layer of layers_to_compact) {
const level = layer.level
const tokens = layer.token_estimate || 0
tokens_freed += tokens
summary_parts.push(`- ${level}: ~${Math.round(tokens)} tokens (source: ${layer.source_ref || 'inline'})`)
}
const summary_content = [
'# Compaction Summary',
'',
`Compact ${new Date().toISOString()}: removed ${layers_to_compact.length} layers, freed ~${Math.round(tokens_freed)} tokens`,
'',
'## Compacted Layers',
...summary_parts,
'',
'## Preserved Layers',
...all_layers
.filter(l => !layers_to_compact.includes(l))
.map(l => `- ${l.level}: ~${Math.round(l.token_estimate || 0)} tokens`)
].join('\n')
const remaining_tokens = all_layers
.filter(l => !layers_to_compact.includes(l))
.reduce((sum, l) => sum + (l.token_estimate || 0), 0)
return {
ok: true,
compacted_layers: layers_to_compact,
summary_content,
tokens_freed,
remaining_tokens,
warnings
}
}
/**
* Fit layers into a token budget.
* Returns fitted layers, omitted layers, and omissions report.
*/
fit_to_budget(layers: PromptLayer[], budget: number): BudgetFitResult {
// Sort by priority (lower = higher priority)
const sorted = [...layers].sort((a, b) => a.priority - b.priority)
const fitted: PromptLayer[] = []
const omitted: PromptLayer[] = []
const omissions: string[] = []
let total_tokens = 0
for (const layer of sorted) {
const tokens = layer.token_estimate || 0
if (this.config.immutable_layers.includes(layer.level)) {
// Never omit immutable layers
fitted.push(layer)
total_tokens += tokens
if (total_tokens > budget) {
omissions.push(`WARNING: Budget exceeded by immutable layer: ${layer.level}`)
}
continue
}
if (total_tokens + tokens <= budget) {
fitted.push(layer)
total_tokens += tokens
} else {
omitted.push(layer)
omissions.push(`Omitted ${layer.level}: would exceed budget (${total_tokens} + ${Math.round(tokens)} > ${budget})`)
}
}
return { fitted, omitted, omissions, total_tokens }
}
}
export function createCompactionPolicy(config?: Partial<CompactionConfig>): CompactionPolicy {
return new CompactionPolicy(config)
}

View File

@@ -0,0 +1,212 @@
/**
* ContextAssembler - Assembles prompt layers into Anthropic-canonical context
*
* Implements contracts §16; DD §10.1 + §10.2 layer-assembly table.
*
* @module packages/runtime/src/context/ContextAssembler
*/
import type {
AgentType, PromptLayer, BudgetFitResult,
SessionID, ProjectID, AgentID, TaskID, ArtifactID, ISOTimeString
} from '@aircoding/contracts'
import { PromptLayerLoader, createPromptLayerLoader } from './PromptLayerLoader.js'
import { CompactionPolicy, createCompactionPolicy } from './CompactionPolicy.js'
export interface AssembledContext {
messages: AssembledMessage[]
metadata: AssemblyMetadata
}
export interface AssembledMessage {
role: 'system' | 'user' | 'assistant'
content: string
layer?: string
}
export interface AssemblyMetadata {
total_tokens: number
fitted_layers: string[]
omitted_layers: string[]
compaction_requested: boolean
layers_compacted: boolean
omissions: string[]
messages_artifact_id?: string
assembled_at: ISOTimeString
}
export interface AssemblyContext {
session_id: SessionID
project_id: ProjectID
project_root: string
agent_id: AgentID
agent_type: AgentType
task_id?: TaskID
token_budget?: number
additional_layers?: PromptLayer[]
}
export class ContextAssembler {
private loader: PromptLayerLoader
private policy: CompactionPolicy
constructor(loader?: PromptLayerLoader, policy?: CompactionPolicy) {
this.loader = loader || createPromptLayerLoader()
this.policy = policy || createCompactionPolicy()
}
/**
* Assemble context from all layers.
* Returns Anthropic-canonical AssembledContext.
*/
assemble(context: AssemblyContext): AssembledContext {
const token_budget = context.token_budget || 200000
const warnings: string[] = []
// Collect all layers
const layers = this.collect_layers(context)
// Fit into budget
const fit_result = this.policy.fit_to_budget(layers, token_budget)
if (fit_result.omissions.length > 0) {
warnings.push(...fit_result.omissions)
}
// Build messages from fitted layers
const messages = this.build_messages(fit_result.fitted, context)
// Check if compaction is needed
const compaction_check = this.policy.should_compact(layers, fit_result.total_tokens)
return {
messages,
metadata: {
total_tokens: fit_result.total_tokens,
fitted_layers: fit_result.fitted.map(l => l.level),
omitted_layers: fit_result.omitted.map(l => l.level),
compaction_requested: compaction_check.should_compact,
layers_compacted: false,
omissions: fit_result.omissions,
assembled_at: new Date().toISOString() as ISOTimeString
}
}
}
/**
* Collect all layers in order L0-L9.
*/
private collect_layers(context: AssemblyContext): PromptLayer[] {
const layers: PromptLayer[] = []
// L0: Runtime invariants (ALWAYS FIRST, never omitted)
const l0 = this.loader.load_runtime_invariant()
layers.push(l0)
// L1: Role (worker AgentType)
const l1 = this.loader.load_role(context.agent_type)
layers.push(l1)
// L2: Safety - built-in
layers.push({
level: 'safety' as any,
priority: 2,
content: '# Safety Rules\n- Never execute destructive commands\n- Always validate inputs\n- Report errors immediately',
token_estimate: 50
})
// L3: Project rules
const project_rules = this.loader.load_project_rules({
project_id: context.project_id,
project_root: context.project_root
})
layers.push(...project_rules)
// L4: Architecture (if available)
if (context.additional_layers) {
const arch_layers = context.additional_layers.filter(l => l.level === 'architecture')
layers.push(...arch_layers)
}
// L5: Task spec (if task_id provided)
if (context.task_id) {
const task_layers = this.loader.load_task_context(
{
id: context.task_id,
type: 'execute',
title: 'Current Task',
description: 'Task from session context',
acceptance_criteria: ['Task completed successfully']
},
{}
)
layers.push(...task_layers)
}
// TODO(P3): L6 Evidence - load from EvidenceStore (read-only)
// TODO(P3): L7 Conversation - load from SessionStore message history
// TODO(P3): L8 Tool output - load recent tool results from SessionStore
// TODO(P3): L9 User override - load user directives/additional layers
// Add any additional layers
if (context.additional_layers) {
const others = context.additional_layers.filter(l => l.level !== 'architecture')
layers.push(...others)
}
return layers
}
/**
* Build assembled messages from fitted layers.
*/
private build_messages(layers: PromptLayer[], context: AssemblyContext): AssembledMessage[] {
const messages: AssembledMessage[] = []
// System message: L0 + L1 + L2 + L3
const system_content = layers
.filter(l => ['runtime_invariant', 'role', 'safety', 'project_rules'].includes(l.level))
.map(l => l.content)
.join('\n\n---\n\n')
if (system_content) {
messages.push({ role: 'system', content: system_content, layer: 'system' })
}
// Architecture context
const arch_layers = layers.filter(l => l.level === 'architecture')
for (const l of arch_layers) {
messages.push({ role: 'user', content: String(l.content), layer: 'architecture' })
}
// Task spec
const task_layers = layers.filter(l => l.level === 'task_spec')
for (const l of task_layers) {
messages.push({ role: 'user', content: String(l.content), layer: 'task_spec' })
}
// Evidence
const evidence_layers = layers.filter(l => l.level === 'evidence')
for (const l of evidence_layers) {
messages.push({ role: 'user', content: String(l.content), layer: 'evidence' })
}
// Conversation (L7)
const conv_layers = layers.filter(l => l.level === 'conversation')
for (const l of conv_layers) {
messages.push({ role: 'assistant', content: String(l.content), layer: 'conversation' })
}
// Tool output (L8)
const tool_layers = layers.filter(l => l.level === 'tool_output')
for (const l of tool_layers) {
messages.push({ role: 'user', content: `[Tool Output]\n${l.content}`, layer: 'tool_output' })
}
return messages
}
}
export function createContextAssembler(loader?: PromptLayerLoader, policy?: CompactionPolicy): ContextAssembler {
return new ContextAssembler(loader, policy)
}

View File

@@ -0,0 +1,320 @@
/**
* PromptLayerLoader - Loads prompt layers from external resources
*
* Implements contracts §16; DD §10.2.
* 4 methods: load_runtime_invariant (L0), load_role (L1, worker AgentType only),
* load_project_rules (L3), load_task_context (L5).
*
* @module packages/runtime/src/context/PromptLayerLoader
*/
import { readFileSync, existsSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import type { PromptLayer, PromptLayerLevel, AgentType } from '@aircoding/contracts'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const BUILTIN_PROMPTS_DIR = join(__dirname, '..', 'context', 'prompts')
export class PromptLayerLoader {
private prompts_dir: string
constructor(prompts_dir?: string) {
this.prompts_dir = prompts_dir || BUILTIN_PROMPTS_DIR
}
/**
* Load L0: Runtime invariant prompt.
* Includes INV-1..5, core safety rules.
*/
load_runtime_invariant(): PromptLayer {
const path = join(this.prompts_dir, 'runtime_invariant.md')
let content = this.read_or_default(path, DEFAULT_L0_INVARIANT)
return {
level: 'runtime_invariant',
priority: 0,
content,
token_estimate: content.length / 4,
source_ref: path,
immutable: true
}
}
/**
* Load L1: Role-specific prompt.
* Accepts only worker AgentType (executor/reviewer/debugger/compactor/experience_miner).
* Runtime roles (main/architecture_designer/scheduler) load built-in directly.
*/
load_role(role: AgentType): PromptLayer {
const path = join(this.prompts_dir, 'roles', `${role}.md`)
const content = this.read_or_default(path, default_role_prompt(role))
return {
level: 'role',
priority: 1,
content,
token_estimate: content.length / 4,
source_ref: path,
immutable: false
}
}
/**
* Load L3: Project rules from .air/ directory.
*/
load_project_rules(project: { project_id: string; project_root: string }): PromptLayer[] {
const layers: PromptLayer[] = []
// Load .air/shared/rules.md
const shared_rules = join(project.project_root, '.air', 'shared', 'rules.md')
if (existsSync(shared_rules)) {
const content = readFileSync(shared_rules, 'utf-8')
layers.push({
level: 'project_rules',
priority: 3,
content,
token_estimate: content.length / 4,
source_ref: shared_rules
})
}
// Load .air/local/rules.md (overrides)
const local_rules = join(project.project_root, '.air', 'local', 'rules.md')
if (existsSync(local_rules)) {
const content = readFileSync(local_rules, 'utf-8')
layers.push({
level: 'project_rules',
priority: 2, // higher than shared
content,
token_estimate: content.length / 4,
source_ref: local_rules
})
}
return layers
}
/**
* Load L5: Task context (plan refs, arc refs, artifacts).
*/
load_task_context(
spec: {
id: string
type: string
title: string
description: string
acceptance_criteria: string[]
},
context_refs: { plan_ref?: string; arc_ref?: string; artifacts?: string[] }
): PromptLayer[] {
const layers: PromptLayer[] = []
// Task spec layer
const task_content = [
`# Task: ${spec.title}`,
`ID: ${spec.id}`,
`Type: ${spec.type}`,
'',
`## Description`,
spec.description,
'',
'## Acceptance Criteria',
...spec.acceptance_criteria.map((c, i) => `${i + 1}. ${c}`)
].join('\n')
layers.push({
level: 'task_spec',
priority: 5,
content: task_content,
token_estimate: task_content.length / 4
})
// Plan reference
if (context_refs.plan_ref) {
const content = `# Implementation Plan\nRef: ${context_refs.plan_ref}`
layers.push({
level: 'task_spec',
priority: 6,
content,
token_estimate: content.length / 4,
source_ref: context_refs.plan_ref
})
}
// Architecture reference
if (context_refs.arc_ref) {
const content = `# Architecture\nRef: ${context_refs.arc_ref}`
layers.push({
level: 'architecture',
priority: 4,
content,
token_estimate: content.length / 4,
source_ref: context_refs.arc_ref
})
}
return layers
}
private read_or_default(path: string, default_content: string): string {
if (existsSync(path)) {
return readFileSync(path, 'utf-8')
}
return default_content
}
}
// ============================================================================
// Default prompts (embedded as fallback when files not found)
// ============================================================================
const DEFAULT_L0_INVARIANT = `# Runtime Invariants (L0)
You are an AI coding assistant operating within the AirCoding v1.0.0 runtime.
## Core Invariants (INV-1..5)
### INV-1: Status Columns
Status columns are written ONLY by EventStore.project(). Repositories store data; they do NOT set status.
Never use repository.update() to change status — always emit an event instead.
### INV-2: Cross-DB Writes
Cross-DB writes MUST use the outbox model with a single writer.
Never write directly to tables in another database.
### INV-3: Side Effects
All side effects MUST go through ToolRegistry.call() → PermissionEngine.evaluate().
Never execute commands, write files, or access network directly.
### INV-4: Import Direction
Imports are one-way: contracts → runtime → other packages.
Never import from runtime into contracts.
### INV-5: EventBus is Transport
EventBus is a transport layer, NEVER a source of truth.
EventStore is the authoritative source. Never query EventBus for state.
## Safety Rules
- Never execute destructive commands without explicit confirmation
- Never access files outside the project workspace
- Never expose credentials, API keys, or secrets in output
- Always validate tool inputs before execution
`
function default_role_prompt(role: AgentType): string {
const prompts: Record<AgentType, string> = {
executor: `# Executor Role (L1)
You are an Executor agent responsible for implementing task specifications.
## Your responsibilities:
1. Read and understand the task specification
2. Implement the required changes
3. Run tests to verify correctness
4. Report completion status
## Rules:
- Follow the architecture defined in the implementation plan
- Use the FileSystem tools for code changes (read-before-edit enforced)
- Use Shell tools for building and testing
- Report any issues or blockers immediately
- NEVER make changes outside the project scope
## Output:
- Code changes with clear diffs
- Build/test results
- Completion status (pass/fail/blocked)
`,
reviewer: `# Reviewer Role (L1)
You are a Reviewer agent responsible for code review and quality assurance.
## Your responsibilities:
1. Review code changes for correctness and style
2. Check for security vulnerabilities
3. Verify architecture compliance
4. Identify potential issues
## Rules:
- Check for INV-1..5 compliance
- Verify no direct side effects
- Check import direction compliance
- Flag any dropped or lost semantic information
## Output:
- Review findings with severity levels
- Suggested fixes for each issue
- Overall pass/fail verdict
`,
debugger: `# Debugger Role (L1)
You are a Debugger agent responsible for diagnosing and fixing issues.
## Your responsibilities:
1. Analyze error reports and stack traces
2. Reproduce the issue in a controlled environment
3. Identify root cause
4. Propose and apply fixes
## Rules:
- Collect evidence (logs, traces, diagnostics)
- Verify fixes don't introduce regressions
- Use Debug tools for deep inspection
- Document findings for future reference
## Output:
- Root cause analysis
- Applied fix with explanation
- Evidence references
`,
compactor: `# Compactor Role (L1)
You are a Compactor agent responsible for context compaction and memory management.
## Your responsibilities:
1. Monitor token usage and trigger compaction when needed
2. Generate concise summaries of conversation history
3. Archive old context while preserving critical information
4. Maintain referential integrity during compaction
## Rules:
- Never drop L0 (runtime_invariant) or L1 (role) layers
- Preserve all task specifications
- Keep evidence references intact
- Document what was compacted and why
## Output:
- Compaction summary
- Archived context references
- Updated context state
`,
experience_miner: `# Experience Miner Role (L1)
You are an Experience Miner agent responsible for extracting patterns and learnings.
## Your responsibilities:
1. Analyze completed tasks for reusable patterns
2. Extract common failure modes and fixes
3. Identify architectural insights
4. Generate experience artifacts for future reference
## Rules:
- Only mine from completed/verified tasks
- Anonymize sensitive information
- Link to source tasks and evidence
- Categorize findings for easy lookup
## Output:
- Experience entries with categories
- Pattern descriptions
- Source references
`
}
return prompts[role] || `# ${role}\n\nRole prompt not yet defined.`
}
export function createPromptLayerLoader(prompts_dir?: string): PromptLayerLoader {
return new PromptLayerLoader(prompts_dir)
}

View File

@@ -0,0 +1,10 @@
/**
* Context module exports
* @module packages/runtime/src/context
*/
export { PromptLayerLoader, createPromptLayerLoader } from './PromptLayerLoader.js'
export { CompactionPolicy, createCompactionPolicy } from './CompactionPolicy.js'
export type { CompactionConfig, CompactionDecision, CompactionResult } from './CompactionPolicy.js'
export { ContextAssembler, createContextAssembler } from './ContextAssembler.js'
export type { AssembledContext, AssembledMessage, AssemblyMetadata, AssemblyContext } from './ContextAssembler.js'

View File

@@ -0,0 +1,21 @@
# Compactor Role (L1)
You are a **Compactor** — a context management agent. Your job is to compress conversation state.
## Workflow
1. Detect: check if context exceeds compaction threshold
2. Select: identify compactable layers (L6-L8 are safe targets)
3. Summarize: create concise summaries preserving key info
4. Archive: store summaries, update references
5. Verify: ensure no critical context was lost
## Rules
- NEVER drop layers L0 (runtime_invariant) or L1 (role)
- Preserve all task specifications and acceptance criteria
- Document what was compacted
- Maintain referential integrity
## Output
- Compaction summary
- Archived context references
- Updated token counts

View File

@@ -0,0 +1,22 @@
# Debugger Role (L1)
You are a **Debugger** — a diagnostic and repair agent. Your job is to find root causes.
## Workflow
1. Gather evidence: error reports, stack traces, logs
2. Reproduce: recreate the failure in a controlled way
3. Diagnose: trace from symptom to root cause
4. Fix: apply the minimal fix
5. Verify: confirm the fix resolves the issue
## Rules
- Always collect evidence before diagnosing
- Document your diagnosis chain
- Verify fixes don't break other features
- Reference evidence files in your report
## Output
- Root cause analysis
- Applied fix with explanation
- Evidence references
- Verification results

View File

@@ -0,0 +1,23 @@
# Executor Role (L1)
You are an **Executor** — the primary implementation agent. Your job is to take a task specification and produce working code.
## Workflow
1. Read the task specification and requirements
2. Understand the architecture constraints (check IMPLEMENTATION-PLAN.md)
3. Plan your implementation approach
4. Implement changes using tools (`fs.read`, `fs.edit`, `fs.write`)
5. Verify with `shell.run` (build, test)
6. Report completion with evidence
## Rules
- **Read-before-edit**: Always `fs.read` a file before `fs.edit`
- **Exact-edit**: Provide the exact text to find/replace
- Scope: Stay within the task boundaries
- Report blockers immediately — don't guess or skip
- All side effects through `ToolRegistry`
## Output
- File changes (diffs)
- Build/test results
- Task completion status (pass/fail/blocked)

View File

@@ -0,0 +1,21 @@
# Experience Miner Role (L1)
You are an **Experience Miner** — a knowledge extraction agent. Your job is to find patterns in completed work.
## Workflow
1. Scan: review completed tasks and their outcomes
2. Extract: identify reusable patterns, common failures, insights
3. Categorize: tag findings by domain (code, debug, security, arch)
4. Store: create experience artifacts
5. Link: connect findings to source tasks
## Rules
- Only mine from completed and verified tasks
- Anonymize sensitive info in extracted patterns
- Categorize clearly for searchability
- Include source references
## Output
- Experience entries with categories/tags
- Pattern descriptions
- Source task references

View File

@@ -0,0 +1,20 @@
# Reviewer Role (L1)
You are a **Reviewer** — a code quality and security auditor. Your job is to inspect code changes.
## Workflow
1. Read the changes (via `git.diff` or `fs.read`)
2. Review for correctness, style, and architecture compliance
3. Check against invariants (INV-1..5)
4. Report findings with severity
## Review Dimensions
- **Correctness**: Does the code do what it says?
- **Security**: Any vulnerabilities or unsafe patterns?
- **Architecture**: Does it comply with the architecture design?
- **Style**: Follows project conventions?
## Output
- Findings list with severity (info/warning/error/fatal)
- Suggested fixes for each
- Overall verdict (pass/fail/needs_work)

View File

@@ -0,0 +1,30 @@
# Runtime Invariants (L0)
## AirCoding V1.0.0 Alpha
You are an AI coding agent running in the AirCoding local AI runtime. Follow these rules at all times.
### INV-1: Status Columns
Status columns (`status`, `agent_status`, `run_status`, `attempt_status`, `state`) MUST be written ONLY by `EventStore.project()`. Repository classes write data, NOT status. To change status, emit an event — never call `repository.update({status: ...})`.
### INV-2: Cross-DB Writes
All writes spanning multiple databases MUST use the **outbox model** with a single writer. The `event_outbox` table is the transport. Never open a second database handle for direct writes.
### INV-3: Side Effects
ALL side effects (filesystem writes, shell commands, network calls) MUST go through `ToolRegistry.call()``PermissionEngine.evaluate()`. Tools are the ONLY path to side effects.
### INV-4: Import Direction
Imports are one-way only:
```
contracts → runtime → workers/llm/tools
```
Never import from a higher layer downward.
### INV-5: EventBus
`EventBus` is a transport layer. It is NEVER a source of truth. The `EventStore` is the single authoritative event log. Never query EventBus for state or recovery.
## Safety
- Never execute `rm -rf`, `dd`, `mkfs`, or similar destructive commands
- Never expose API keys, tokens, or passwords in output
- Validate all inputs before use
- Report all errors with their semantic signatures