P0.2 — α System Core — Agent-Ready Task Prompts
This document contains copy-paste-ready prompts for agents executing the four P0.2 sub-tasks: MCP Server Bootstrap, SQLite Initialization, Two-Phase Startup, and Health Check Tool.
These tasks implement the core MCP server infrastructure and database layer that form the foundation of the Colibri runtime.
For the canonical task specifications, see task-breakdown.md § P0.2.
For extraction references (schema, startup patterns), see docs/reference/extractions/alpha-system-core-extraction.md and docs/reference/extractions/gamma-server-lifecycle-extraction.md (if available).
P0.2 Group Summary
| Task ID | Title | Depends on | Effort | Unblocks | Status |
|---|---|---|---|---|---|
| P0.2.1 | MCP Server Bootstrap | P0.1.2 | M | P0.2.3 | spec-ready, code-not-started |
| P0.2.2 | SQLite Initialization | P0.1.4 | L | P0.2.3 | spec-ready, code-not-started |
| P0.2.3 | Two-Phase Startup | P0.2.1, P0.2.2 | M | P0.2.4 | spec-ready, code-not-started |
| P0.2.4 | Health Check Tool | P0.2.3 | S | (none in P0) | spec-ready, code-not-started |
P0.2.1 — MCP Server Bootstrap
Spec source: task-breakdown.md § P0.2.1
Extraction reference: alpha-system-core-extraction.md (server bootstrap section)
Worktree: feature/p0-2-1-mcp-bootstrap
Branch command:
git fetch origin
git worktree add .worktrees/claude/p0-2-1-mcp-bootstrap -b feature/p0-2-1-mcp-bootstrap origin/main
cd .worktrees/claude/p0-2-1-mcp-bootstrap
Estimated effort: Medium (M)
Depends on: P0.1.2 (Test Runner + Linter)
Unblocks: P0.2.3 (Two-Phase Startup)
Files to create
src/server.ts— MCP server initialization and tool registrationtests/server.test.ts— integration tests for server handshake and ping tool
Acceptance criteria (from spec)
McpServercreated withname: "colibri",versionfrompackage.json- stdio transport only —
StdioServerTransportfrom@modelcontextprotocol/sdk. No HTTP transport in Phase 0 (S17 §2 — stdio is the only transport; HTTP belongs to a later phase) - Server exports
registerTool(name, schema, handler)helper that wires every tool through the 5-stage middleware chain (α System Core): tool-lock → schema validate → audit enter → dispatch → audit exit - At least 1 registered tool:
server/pingreturns{ status: "ok", version }and is exercised through all five middleware stages in an integration test npm testpasses with MCP handshake integration test- No
AMS_*env vars are read — the donor namespace is not supported
Pre-flight reading (required before coding)
CLAUDE.md— worktree rulesdocs/guides/implementation/task-breakdown.md§ P0.2.1docs/reference/extractions/alpha-system-core-extraction.md(server bootstrap section)@modelcontextprotocol/sdkdocumentation (skim official MCP SDK docs)- Your completed P0.1.1 + P0.1.2 commits (reference package.json)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
Task: P0.2.1 — MCP Server Bootstrap
Your role: create an MCP server that listens on **stdio only** (S17 §2 — no HTTP in Phase 0), exposes a registerTool() helper
that wires every tool through the α five-stage middleware chain, and implements the server/ping tool as a proof-of-concept.
Required reading:
- CLAUDE.md (worktree rules)
- docs/guides/implementation/task-breakdown.md § P0.2.1
- docs/reference/extractions/alpha-system-core-extraction.md (server bootstrap section)
- Your P0.1.1 + P0.1.2 completions (package.json, jest.config.ts)
Setup:
1. Create or reuse worktree:
git fetch origin
git worktree add .worktrees/claude/p0-2-1-mcp-bootstrap -b feature/p0-2-1-mcp-bootstrap origin/main
cd .worktrees/claude/p0-2-1-mcp-bootstrap
2. Create these files:
- src/server.ts
- tests/server.test.ts
src/server.ts structure:
Import from @modelcontextprotocol/sdk:
- McpServer
- StdioServerTransport
Import from your config (P0.1.4):
- config (for COLIBRI_DB_PATH, COLIBRI_LOG_LEVEL, COLIBRI_STARTUP_TIMEOUT_MS, version)
Import the α middleware chain (P0.2.4) or stub it if not yet landed:
- toolLock, validateSchema, auditEnter, dispatch, auditExit
Export:
- createServer() function
- registerTool() helper that wires every handler through the five-stage middleware chain
- A start() function that connects the stdio transport
Pseudocode:
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { config } from './config.js';
import { runMiddlewareChain } from './middleware/index.js'; // P0.2.4
const server = new McpServer({
name: 'colibri',
version: '0.0.1', // read from package.json
});
// stdio only — no HTTP in Phase 0 per S17 §2
const transport = new StdioServerTransport();
// registerTool wires every handler through the 5-stage middleware chain
export function registerTool(name, schema, handler) {
server.tool(name, schema, async (args, extra) =>
runMiddlewareChain({ name, schema, handler, args, extra })
);
}
// Register the ping tool
registerTool('server/ping', z.object({}), async () => ({
status: 'ok',
version: '0.0.1',
}));
// Start
async function start() {
await server.connect(transport);
console.error('Colibri MCP server started on stdio');
}
export { server, start };
Key points:
- stdio only. No HTTP, no WebSocket, no dashboard — S17 §2.
- Every tool goes through the α five-stage chain. A tool that bypasses
runMiddlewareChainis not a valid Phase 0 tool. - The ping tool is proof-of-concept; returns
{ status: "ok", version }. server.connect(transport)is async; await it.- Log startup to
console.error(stdout is reserved for JSON-RPC traffic on stdio).
Test file (tests/server.test.ts):
- Test 1: Server initializes with name “colibri”
- Test 2: registerTool adds tool to server (inspect server’s internal tools list)
- Test 3: server/ping tool exists and returns { status: “ok”, version }
- Test 4: MCP handshake completes (use a stdio test client from the SDK)
- Test 5: Every tool call passes through all five α middleware stages (assert via audit records)
- Test 6: Zod-rejected call is still audited at stage 3 with
outcome: "schema_reject"
Example test structure:
describe('MCP Server', () => {
it('should initialize with name colibri', () => {
expect(server.name).toBe('colibri');
});
it('should have version from package.json', () => {
expect(server.version).toBeDefined();
});
it('should register server/ping tool', () => {
expect(server.tools.size).toBeGreaterThan(0);
});
it('server/ping should return status ok', async () => {
const result = await server.tools.get('server/ping')?.handler({});
expect(result).toEqual({ status: 'ok', version: expect.any(String) });
});
it('should use StdioServerTransport', () => {
expect(transport).toBeInstanceOf(StdioServerTransport);
});
it('should audit every call enter + exit', async () => {
await server.tools.get('server/ping')?.handler({});
const rows = db.prepare('SELECT * FROM actions WHERE tool_name = ?').all('server/ping');
expect(rows).toHaveLength(2); // enter + exit
});
});
Acceptance criteria to verify:
- src/server.ts exists and imports from @modelcontextprotocol/sdk
- McpServer instance created with name “colibri”
- Version read from package.json (or hardcoded as 0.0.1)
- registerTool() helper exported and wires through the α five-stage middleware chain
- stdio transport only — StdioServerTransport is the only transport instantiated (S17 §2)
- server/ping tool registered and responds with { status, version }
- npm test passes (server.test.ts runs)
- Integration test confirms MCP handshake completes and middleware chain is exercised
Writeback: After completion:
- task_update: task_id=”P0.2.1”, status=”done”, progress=100
- thought_record: task_id=”P0.2.1”, branch=”feature/p0-2-1-mcp-bootstrap”,
commit_sha=
, tests_run="npm test (server.test.ts: handshake, ping tool, transport selection)", summary="MCP server initialized. Transport-agnostic (stdio/http). server/ping tool registered.", blockers="none"
Do NOT edit main checkout. Do NOT commit to main. Work only in feature worktree.
Next step: P0.2.2 (SQLite Initialization) is independent; P0.2.3 depends on both P0.2.1 and P0.2.2.
### Verification checklist
- [ ] `src/server.ts` imports McpServer and `StdioServerTransport` from the SDK (no HTTP transport import)
- [ ] McpServer instance has name "colibri"
- [ ] Version read from package.json
- [ ] `registerTool()` helper exported and wires every tool through the α five-stage middleware chain
- [ ] **stdio transport only** — no `StreamableHTTPServerTransport`, no `AMS_TRANSPORT`
- [ ] `server/ping` tool registered with empty Zod schema
- [ ] `server/ping` returns `{ status: "ok", version }` and produces linked `actions` enter+exit rows
- [ ] `tests/server.test.ts` covers handshake, ping, middleware-chain traversal, schema-reject auditing
- [ ] `npm test` passes
### Writeback template
```yaml
task_update:
task_id: "P0.2.1"
status: "done"
progress: 100
notes: |
MCP server bootstrap complete.
- McpServer created (name: colibri, version from package.json)
- Transport: stdio only (StdioServerTransport) — no HTTP per S17 §2
- registerTool() helper wires every handler through the α five-stage middleware chain
- server/ping tool registered and audited enter+exit
thought_record:
task_id: "P0.2.1"
branch: "feature/p0-2-1-mcp-bootstrap"
commit_sha: "..."
tests_run: |
npm test (server.test.ts: handshake, ping tool, transport)
summary: |
P0.2.1 complete. MCP server with stdio/http transport.
registerTool() helper and server/ping proof-of-concept tool.
blockers: "none"
Common gotchas
- Package.json version: Reading version dynamically from package.json can be tricky in ESM. Options:
- Use
import.meta.resolve()(Node 18.13+) to locate package.json - Hardcode as “0.0.1” for now, refine later
- Use
readFileSync()with path resolution
- Use
-
Transport initialization: StdioServerTransport and StreamableHTTPServerTransport are initialized differently. Stdio is simple; HTTP needs host/port. Ensure config vars are set in .env or defaults.
-
Async startup: server.connect(transport) is async. If you export a start() function, ensure tests await it. Don’t start the server in module scope; do it in a main() block in src/index.ts (created in P0.2.3).
- Tool schema format: MCP SDK tools take a Zod schema or plain object. For ping, an empty
{}orz.object({})is fine. Ensure schema imports are correct.
P0.2.2 — SQLite Initialization
Spec source: task-breakdown.md § P0.2.2
Extraction reference: alpha-system-core-extraction.md (schema section)
Worktree: feature/p0-2-2-sqlite-init
Branch command:
git fetch origin
git worktree add .worktrees/claude/p0-2-2-sqlite-init -b feature/p0-2-2-sqlite-init origin/main
cd .worktrees/claude/p0-2-2-sqlite-init
Estimated effort: Large (L)
Depends on: P0.1.4 (Environment Validation)
Unblocks: P0.2.3 (Two-Phase Startup)
Files to create
src/db/index.ts— SQLite initialization and database handle (P0.2.2)src/db/schema.sql— empty schema header + the first migration slot only. Tables are earned per concept perdocs/architecture/data-model.md§2; none of them land heresrc/db/migrations/001_init.sql— the first migration; Phase 0 floor is just the schema header and pragmastests/db/init.test.ts— unit tests for DB init and the earning-rule behavior
Acceptance criteria (from spec)
- Uses
better-sqlite3(sync API) schema.sqlships an empty header; no tables are created here. Per-concept tables (β:tasks,task_transitions; ε:skills; ζ:thoughts,actions; η:merkle_nodes,merkle_roots; ν:sync_log) are added by their owning concept’s sub-task per the earning rule indocs/architecture/data-model.md§2initDb(path)creates DB if not exists, runs migrations in order, returnsDatabaseinstance- Idempotent: calling
initDb()twice does not fail or duplicate data - WAL mode enabled:
PRAGMA journal_mode=WAL - Foreign keys enabled:
PRAGMA foreign_keys=ON PRAGMA integrity_checkruns once at startup; failure → refuse to boot- A failed migration halts boot with a clear error (α System Core spec §”Configuration”)
- Test: fresh DB survives re-init with zero table count at the α floor; later concept tests add their own rows when they land
- No “78 tables” target. Any PR that asserts a hard-coded table count is a scope drift
Pre-flight reading (required before coding)
CLAUDE.md— worktree rulesdocs/guides/implementation/task-breakdown.md§ P0.2.2docs/architecture/data-model.md§2 — the earning rule. No table lands on disk before the owning concept’s sub-task earns it.docs/concepts/α-system-core.md— data ownership section (α owns the file, not the schema)docs/reference/extractions/alpha-system-core-extraction.md— heritage reference only. The donor DDL is for algorithmic study, not direct copy-paste.better-sqlite3documentation (skim for sync API, pragma usage)- Your completed P0.1.4 commit (reference config structure)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
Task: P0.2.2 — SQLite Initialization
Your role: implement a sync SQLite database layer that owns the file and the migration runner.
**You do not own the schema.** Phase 0 ships an empty schema header per the earning rule in
`docs/architecture/data-model.md` §2 — per-concept tables are added by their owning concept's
sub-task. Your job is idempotent init, WAL+FK pragmas, `integrity_check`, and ordered migration application.
Required reading:
- CLAUDE.md (worktree rules)
- docs/guides/implementation/task-breakdown.md § P0.2.2
- docs/architecture/data-model.md §2 (earning rule)
- docs/concepts/α-system-core.md (data ownership — α owns the file, not the schema)
- docs/reference/extractions/alpha-system-core-extraction.md (heritage reference only — do NOT copy-paste donor DDL)
- Your P0.1.4 completion (config structure)
Setup:
1. Create or reuse worktree:
git fetch origin
git worktree add .worktrees/claude/p0-2-2-sqlite-init -b feature/p0-2-2-sqlite-init origin/main
cd .worktrees/claude/p0-2-2-sqlite-init
2. Create these files:
- src/db/index.ts
- src/db/schema.sql ← empty header + pragma setup, no tables
- src/db/migrations/001_init.sql ← empty migration (header comment only; α is table-free)
- tests/db/init.test.ts
src/db/schema.sql structure:
The schema.sql file is **intentionally empty of tables**. Per the earning rule in
`docs/architecture/data-model.md` §2, α System Core owns the database **file** but not the schema.
Tables are earned by each concept's P0 sub-task:
- β (P0.3): `tasks`, `task_transitions`
- ε (P0.6): `skills`
- ζ (P0.7): `thoughts`, `actions`
- η (P0.8): `merkle_nodes`, `merkle_roots`
- ν (P0.9): `sync_log`
Each of those sub-tasks adds its own `migrations/NNN_<concept>.sql` file.
Key pragmas set from `src/db/index.ts` after `Database()` is constructed:
- `PRAGMA journal_mode = WAL`
- `PRAGMA foreign_keys = ON`
- `PRAGMA integrity_check` → abort boot on failure
Example `schema.sql` contents:
```sql
-- Colibri α System Core — database bootstrap
-- α owns the file, not the schema. Tables are added per concept
-- by their owning P0 sub-task (see docs/architecture/data-model.md §2).
-- Pragmas are set from src/db/index.ts, not from SQL, so WAL takes effect
-- before any migration runs.
Example migrations/001_init.sql contents:
-- 001_init: α System Core — empty migration slot.
-- Reserved so later concept migrations start at 002+.
src/db/index.ts structure:
import Database from 'better-sqlite3';
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { config } from '../config.js';
export function initDb(dbPath: string): Database.Database {
const db = new Database(dbPath);
// Pragmas — set BEFORE migrations run
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// Integrity check — abort boot on failure (α System Core spec)
const integrity = db.pragma('integrity_check', { simple: true });
if (integrity !== 'ok') {
throw new Error(`Database integrity check failed: ${integrity}`);
}
// Run migrations in order. Each concept's sub-task adds its own NNN_<concept>.sql.
const migrationsDir = join(import.meta.dirname, 'migrations');
const files = readdirSync(migrationsDir)
.filter(f => f.endsWith('.sql'))
.sort();
for (const file of files) {
const sql = readFileSync(join(migrationsDir, file), 'utf-8');
try {
db.exec(sql);
} catch (err) {
throw new Error(`Migration ${file} failed: ${(err as Error).message}`);
}
}
return db;
}
// Singleton handle — α owns it; no domain opens a raw handle of its own
let instance: Database.Database | null = null;
export function getDb(): Database.Database {
if (!instance) {
instance = initDb(config.COLIBRI_DB_PATH);
}
return instance;
}
export function closeDb(): void {
if (instance) {
instance.close();
instance = null;
}
}
Test file (tests/db/init.test.ts):
- Test 1: initDb creates file if not exists
- Test 2: initDb returns Database instance
- Test 3: At the α floor, fresh DB has zero user tables (the earning rule; concept tables land in later sub-tasks)
- Test 4: idempotent — calling initDb twice is safe (no error, no double-migration)
- Test 5:
foreign_keyspragma is ON - Test 6:
journal_modeis WAL - Test 7:
integrity_checkpasses on a fresh DB - Test 8: a deliberately corrupted DB file fails boot with a clear error
Example test structure:
describe('Database initialization', () => {
const testDbPath = './test-colibri.db';
afterEach(() => {
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath);
});
it('should create DB file if not exists', () => {
initDb(testDbPath);
expect(fs.existsSync(testDbPath)).toBe(true);
});
it('should have zero user tables at the α floor', () => {
const db = initDb(testDbPath);
const count = (db.prepare(
"SELECT COUNT(*) as c FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
).get() as { c: number }).c;
expect(count).toBe(0);
db.close();
});
it('should be idempotent', () => {
initDb(testDbPath).close();
expect(() => initDb(testDbPath).close()).not.toThrow();
});
it('should have foreign_keys pragma ON', () => {
const db = initDb(testDbPath);
expect(db.pragma('foreign_keys', { simple: true })).toBe(1);
db.close();
});
it('should have WAL mode enabled', () => {
const db = initDb(testDbPath);
expect(db.pragma('journal_mode', { simple: true })).toBe('wal');
db.close();
});
});
Acceptance criteria to verify:
src/db/index.tsexists and imports better-sqlite3initDb(path)creates DB file if not existsschema.sqlships with an empty header — no CREATE TABLE statementsmigrations/001_init.sqlships empty; later concepts add002_beta.sql,003_epsilon.sql, etc.- WAL pragma set (
PRAGMA journal_mode=WAL) - Foreign keys pragma set (
PRAGMA foreign_keys=ON) PRAGMA integrity_checkruns at startup and aborts boot on failure- Fresh DB has zero user tables at the α floor — no “78 tables” target
- Calling
initDbtwice is safe (no duplicate migrations) npm testpasses (all db tests pass)
Writeback: After completion:
- task_update: task_id=”P0.2.2”, status=”done”, progress=100
- thought_record: task_id=”P0.2.2”, branch=”feature/p0-2-2-sqlite-init”,
commit_sha=
, tests_run="npm test (db.init.test.ts: create, zero-table floor, idempotent, pragmas, integrity_check)", summary="SQLite DB layer: better-sqlite3 WAL+FK, integrity_check at boot, ordered migrations. α owns the file; schema is earned per concept.", blockers="none"
Do NOT edit main checkout. Do NOT commit to main. Work only in feature worktree.
Next step: P0.2.3 (Two-Phase Startup) depends on both P0.2.1 and P0.2.2.
### Verification checklist
- [ ] `src/db/schema.sql` exists with an **empty header only** (no CREATE TABLE)
- [ ] `src/db/migrations/001_init.sql` exists as an empty placeholder
- [ ] Pragmas set from `src/db/index.ts`: `journal_mode=WAL`, `foreign_keys=ON`, `integrity_check`
- [ ] `src/db/index.ts` imports better-sqlite3
- [ ] `initDb(path)` function creates DB file if not exists
- [ ] `initDb` returns Database instance after running ordered migrations
- [ ] `getDb()` and `closeDb()` singleton exported; no domain opens a raw handle
- [ ] **Fresh DB has zero user tables at the α floor** (per `data/colibri.db` earning rule)
- [ ] Calling `initDb` twice is safe (no error, no double-migration)
- [ ] `tests/db/init.test.ts` covers all criteria
- [ ] `npm test` passes
### Writeback template
```yaml
task_update:
task_id: "P0.2.2"
status: "done"
progress: 100
notes: |
SQLite database layer initialized.
- Empty schema header; per-concept tables earned per data-model.md §2
- WAL mode enabled
- Foreign keys enforced
- PRAGMA integrity_check at boot (fail fast)
- Ordered migration runner
thought_record:
task_id: "P0.2.2"
branch: "feature/p0-2-2-sqlite-init"
commit_sha: "..."
tests_run: |
npm test (db.init.test.ts: create, zero-table floor, idempotent, pragmas, integrity_check)
summary: |
P0.2.2 complete. α owns the database file, not the schema.
WAL + FK + integrity_check. Ordered migration runner. Singleton handle via getDb().
Zero user tables at the α floor; later concepts add their own migrations.
blockers: "none"
Common gotchas
-
Do not copy donor DDL. The donor
alpha-system-core-extraction.mdcontains 78 tables of heritage schema. None of it lands in Phase 0 α. Copy-pasting it is a scope-drift bug; reviewers will reject the PR. -
Pragmas first. Set
PRAGMA journal_mode=WALandPRAGMA foreign_keys=ONfromsrc/db/index.tsimmediately after opening the handle, before any migration runs.integrity_checkcomes next. -
import.meta.dirname: Node 20.11+ syntax. For older Node, usefileURLToPath(import.meta.url)+dirname(). -
Test cleanup: better-sqlite3 holds file locks. Close the handle in
afterEachor tests will leak file descriptors. -
Table count assertions are banned. Do not write a test that asserts a hard-coded table count. Each concept asserts its own tables when it lands.
P0.2.3 — Two-Phase Startup
Spec source: task-breakdown.md § P0.2.3
Extraction reference: alpha-system-core-extraction.md (startup section), gamma-server-lifecycle-extraction.md (if available)
Worktree: feature/p0-2-3-two-phase-startup
Branch command:
git fetch origin
git worktree add .worktrees/claude/p0-2-3-two-phase-startup -b feature/p0-2-3-two-phase-startup origin/main
cd .worktrees/claude/p0-2-3-two-phase-startup
Estimated effort: Medium (M)
Depends on: P0.2.1 (MCP Server Bootstrap), P0.2.2 (SQLite Initialization)
Unblocks: P0.2.4 (Health Check Tool)
Files to create
src/startup.ts— two-phase startup orchestrationsrc/index.ts— entry point that calls startup()tests/startup.test.ts— unit tests for phase 1/2, graceful shutdown
Acceptance criteria (from spec)
- Phase 1 (transport): MCP transport ready, health check responds, DB not yet loaded
- Phase 2 (heavy init): DB initialized, all tools registered, all domains loaded
startup()returns only after Phase 2 completes- If Phase 2 fails, server shuts down gracefully (no hanging process)
- Startup time logged:
console.error("Startup complete in {ms}ms") - Test: mock Phase 2 failure → verify clean shutdown
Pre-flight reading (required before coding)
CLAUDE.md— worktree rulesdocs/guides/implementation/task-breakdown.md§ P0.2.3docs/reference/extractions/alpha-system-core-extraction.md(startup section)- Your completed P0.2.1 + P0.2.2 commits (reference server and db modules)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
Task: P0.2.3 — Two-Phase Startup
Your role: orchestrate startup in two phases:
Phase 1 (fast): transport ready, health check responds, DB not loaded
Phase 2 (slow): DB initialized, all tools registered
Required reading:
- CLAUDE.md (worktree rules)
- docs/guides/implementation/task-breakdown.md § P0.2.3
- docs/reference/extractions/alpha-system-core-extraction.md (startup section)
- Your P0.2.1 (server.ts) and P0.2.2 (db/index.ts) completions
Setup:
1. Create or reuse worktree:
git fetch origin
git worktree add .worktrees/claude/p0-2-3-two-phase-startup -b feature/p0-2-3-two-phase-startup origin/main
cd .worktrees/claude/p0-2-3-two-phase-startup
2. Create these files:
- src/startup.ts
- src/index.ts (entry point)
- tests/startup.test.ts
src/startup.ts structure:
```typescript
import { server, registerTool, start as startServer } from './server.js';
import { initDb, closeDb } from './db/index.js';
import { config } from './config.js';
enum StartupPhase {
IDLE = 'idle',
PHASE_1 = 'phase1',
PHASE_2 = 'phase2',
}
let currentPhase = StartupPhase.IDLE;
async function startup(): Promise<void> {
const startTime = Date.now();
try {
// PHASE 1: Start transport, enable health check
console.error('[Startup] Phase 1: Starting transport...');
currentPhase = StartupPhase.PHASE_1;
await startServer(); // Assumes startServer() connects transport
console.error('[Startup] Phase 1 complete: transport ready');
// PHASE 2: Heavy initialization
console.error('[Startup] Phase 2: Initializing database and tools...');
currentPhase = StartupPhase.PHASE_2;
// Initialize DB
const dbPath = config.DATABASE_PATH || './data/ams.db';
const db = initDb(dbPath);
console.error('[Startup] Database initialized');
// Register additional tools (e.g., server/health in P0.2.4)
// For now, just the ping tool from P0.2.1
console.error('[Startup] Tools registered');
// Phase 2 complete
const elapsed = Date.now() - startTime;
console.error(`[Startup] Startup complete in ${elapsed}ms`);
} catch (error) {
console.error(`[Startup] Phase 2 failed, graceful shutdown:`, error);
await gracefulShutdown();
process.exit(1);
}
}
async function gracefulShutdown(): Promise<void> {
console.error('[Shutdown] Closing database...');
closeDb();
console.error('[Shutdown] Database closed');
// Could also close server/transport here if needed
}
export { startup, currentPhase, StartupPhase, gracefulShutdown };
src/index.ts (entry point):
import { startup } from './startup.js';
async function main() {
try {
await startup();
// After startup, server is running and listening
// Process runs indefinitely until SIGINT/SIGTERM
} catch (error) {
console.error('Fatal error during startup:', error);
process.exit(1);
}
}
main();
Test file (tests/startup.test.ts):
- Test 1: startup() completes successfully
- Test 2: currentPhase transitions: IDLE → PHASE_1 → PHASE_2
- Test 3: startup() returns only after Phase 2 completes
- Test 4: if Phase 2 fails (mock error), gracefulShutdown is called
- Test 5: startup time is logged
Example test structure:
describe('Startup', () => {
it('should transition through phases', async () => {
// Mock server.start(), db.initDb()
jest.mock('./server.js');
jest.mock('./db/index.js');
await startup();
expect(currentPhase).toBe(StartupPhase.PHASE_2);
});
it('should log startup time', async () => {
const consoleSpy = jest.spyOn(console, 'error');
await startup();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringMatching(/Startup complete in \d+ms/)
);
});
it('should gracefully shutdown if Phase 2 fails', async () => {
jest.mock('./server.js');
jest.mock('./db/index.js', () => ({
initDb: jest.fn().mockRejectedValue(new Error('DB init failed')),
closeDb: jest.fn(),
}));
await expect(startup()).rejects.toThrow();
expect(closeDb).toHaveBeenCalled();
});
});
Acceptance criteria to verify:
- src/startup.ts defines Phase 1 and Phase 2
- Phase 1: startServer() called, transport ready
- Phase 2: initDb() called, tools registered
- startup() returns only after Phase 2 completes
- Startup time logged with elapsed ms
- If Phase 2 fails, gracefulShutdown() called and process exits
- currentPhase exported and trackable
- src/index.ts calls startup() and handles errors
- npm test passes (startup.test.ts covers phase transitions)
Writeback: After completion:
- task_update: task_id=”P0.2.3”, status=”done”, progress=100
- thought_record: task_id=”P0.2.3”, branch=”feature/p0-2-3-two-phase-startup”,
commit_sha=
, tests_run="npm test (startup.test.ts: phase transitions, time logging, shutdown)", summary="Two-phase startup: Phase 1 (transport), Phase 2 (DB+tools). Graceful shutdown on error.", blockers="none"
Do NOT edit main checkout. Do NOT commit to main. Work only in feature worktree.
Next step: P0.2.4 (Health Check Tool) depends on your completion.
### Verification checklist
- [ ] `src/startup.ts` defines StartupPhase enum (IDLE, PHASE_1, PHASE_2)
- [ ] currentPhase exported and transitions correctly
- [ ] Phase 1: startServer() called
- [ ] Phase 2: initDb() called, tools registered
- [ ] startup() is async and returns Promise<void>
- [ ] Startup time logged with elapsed milliseconds
- [ ] gracefulShutdown() called if Phase 2 fails
- [ ] Process exits with code 1 on failure
- [ ] `src/index.ts` exists and calls startup() on main
- [ ] tests/startup.test.ts covers phase transitions and shutdown
- [ ] npm test passes
### Writeback template
```yaml
task_update:
task_id: "P0.2.3"
status: "done"
progress: 100
notes: |
Two-phase startup orchestration complete.
- Phase 1: transport ready
- Phase 2: DB + tools initialized
- Graceful shutdown on error
- Startup time logged
thought_record:
task_id: "P0.2.3"
branch: "feature/p0-2-3-two-phase-startup"
commit_sha: "..."
tests_run: |
npm test (startup.test.ts: phase transitions, time, shutdown)
summary: |
P0.2.3 complete. Two-phase startup with orchestration.
Phase 1 fast (transport), Phase 2 heavy (DB+tools).
Graceful shutdown on Phase 2 failure.
blockers: "none"
Common gotchas
-
Async/await: startup() must be async and return Promise
. Ensure all await calls are in place (startServer, initDb, etc.). Missing await = race condition. -
Phase transition timing: Phase 2 must NOT complete until DB is fully initialized. If you parallelize DB init with tool registration, ensure DB is ready BEFORE registration (order matters).
-
Shutdown signal handling: For P0.2, gracefulShutdown is called on error. Later phases may add SIGINT/SIGTERM handlers. Keep it simple for now.
-
Timing accuracy: The spec says “startup time logged”. Use Date.now() at start and calculate elapsed = Date.now() - startTime. Console.error is correct (stdout for logs, stderr for server events).
P0.2.4 — Health Check Tool
Spec source: task-breakdown.md § P0.2.4
Worktree: feature/p0-2-4-health-tool
Branch command:
git fetch origin
git worktree add .worktrees/claude/p0-2-4-health-tool -b feature/p0-2-4-health-tool origin/main
cd .worktrees/claude/p0-2-4-health-tool
Estimated effort: Small (S)
Depends on: P0.2.3 (Two-Phase Startup)
Unblocks: (none in Phase 0)
Files to create
src/tools/health.ts— health check tool implementationtests/tools/health.test.ts— unit tests for health check response
Acceptance criteria (from spec)
- Tool name:
server/health - Returns:
{ status, version, uptime_ms, db_tables, phase, mode } db_tables: count of SQLite tables (verifies schema loaded)-
[ ] phase:"phase1""phase2" mode: current runtime mode string- Response time < 100ms
Pre-flight reading (required before coding)
CLAUDE.md— worktree rulesdocs/guides/implementation/task-breakdown.md§ P0.2.4- Your completed P0.2.1 (server.registerTool), P0.2.3 (startup, currentPhase)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
Task: P0.2.4 — Health Check Tool
Your role: implement a health check MCP tool that reports server status, uptime, DB state, and current phase.
Required reading:
- CLAUDE.md (worktree rules)
- docs/guides/implementation/task-breakdown.md § P0.2.4
- Your P0.2.1 (server.registerTool), P0.2.3 (startup phases)
Setup:
1. Create or reuse worktree:
git fetch origin
git worktree add .worktrees/claude/p0-2-4-health-tool -b feature/p0-2-4-health-tool origin/main
cd .worktrees/claude/p0-2-4-health-tool
2. Create these files:
- src/tools/health.ts
- tests/tools/health.test.ts
src/tools/health.ts structure:
```typescript
import { getDb } from '../db/index.js';
import { currentPhase } from '../startup.js';
import { config } from '../config.js';
const startTime = Date.now(); // Track server uptime
export interface HealthResponse {
status: string;
version: string;
uptime_ms: number;
db_tables: number;
phase: 'phase1' | 'phase2';
mode: string;
}
export async function healthCheck(): Promise<HealthResponse> {
const startCheck = Date.now();
// Get DB table count
let dbTables = 0;
try {
const db = getDb();
const result = db.prepare(
"SELECT COUNT(*) as count FROM sqlite_master WHERE type='table'"
).get() as { count: number };
dbTables = result.count;
} catch (err) {
// DB not ready, count = 0
dbTables = 0;
}
const elapsed = Date.now() - startCheck;
return {
status: 'ok',
version: '0.0.1', // from package.json
uptime_ms: Date.now() - startTime,
db_tables: dbTables,
phase: currentPhase as 'phase1' | 'phase2',
mode: config.NODE_ENV || 'unknown',
};
}
Integration in src/server.ts (or startup.ts):
Register the health tool in the server:
import { healthCheck } from './tools/health.js';
registerTool('server/health', {}, async () => {
return await healthCheck();
});
Test file (tests/tools/health.test.ts):
- Test 1: healthCheck() returns expected fields
- Test 2: Response time < 100ms
- Test 3: db_tables > 0 when DB initialized
- Test 4: phase reflects current startup phase
- Test 5: status is “ok”
- Test 6: version matches package.json
Example test structure:
describe('Health Check Tool', () => {
it('should return all required fields', async () => {
const result = await healthCheck();
expect(result).toHaveProperty('status');
expect(result).toHaveProperty('version');
expect(result).toHaveProperty('uptime_ms');
expect(result).toHaveProperty('db_tables');
expect(result).toHaveProperty('phase');
expect(result).toHaveProperty('mode');
});
it('should return status ok', async () => {
const result = await healthCheck();
expect(result.status).toBe('ok');
});
it('should respond in < 100ms', async () => {
const start = Date.now();
await healthCheck();
const elapsed = Date.now() - start;
expect(elapsed).toBeLessThan(100);
});
it('should report correct phase', async () => {
// Mock startup phase
const result = await healthCheck();
expect(['phase1', 'phase2']).toContain(result.phase);
});
it('should count DB tables', async () => {
// Requires P0.2.2 to be complete
const result = await healthCheck();
expect(result.db_tables).toBeGreaterThan(0);
});
});
Acceptance criteria to verify:
- src/tools/health.ts exists and exports healthCheck()
- healthCheck() returns object with all required fields
- status = “ok”
- version from package.json (or 0.0.1)
- uptime_ms is a number > 0
- db_tables is a number (count from sqlite_master)
- phase is “phase1” or “phase2”
- mode is NODE_ENV value
- Response time < 100ms
- healthCheck() registered as server/health tool
- npm test passes
Writeback: After completion:
- task_update: task_id=”P0.2.4”, status=”done”, progress=100
- thought_record: task_id=”P0.2.4”, branch=”feature/p0-2-4-health-tool”,
commit_sha=
, tests_run="npm test (health.test.ts: fields, timing, phase, db_tables)", summary="Health check tool (server/health) reports status, version, uptime, DB tables, phase, mode.", blockers="none"
Do NOT edit main checkout. Do NOT commit to main. Work only in feature worktree.
Next step: Phase 0 continues with P0.3 (β Task Pipeline). See task-breakdown.md for P0.3 specs.
### Verification checklist
- [ ] `src/tools/health.ts` exists and exports healthCheck()
- [ ] Returns object with all 6 fields: status, version, uptime_ms, db_tables, phase, mode
- [ ] status = "ok"
- [ ] version defined (0.0.1 or from package.json)
- [ ] uptime_ms is number representing ms since server start
- [ ] db_tables is number from `SELECT COUNT(*) FROM sqlite_master WHERE type='table'`
- [ ] phase is "phase1" or "phase2" (from currentPhase)
- [ ] mode is NODE_ENV value
- [ ] Response time < 100ms (no slow operations)
- [ ] Registered as tool "server/health" with empty schema
- [ ] tests/tools/health.test.ts covers all criteria
- [ ] npm test passes
### Writeback template
```yaml
task_update:
task_id: "P0.2.4"
status: "done"
progress: 100
notes: |
Health check tool implemented.
- Tool name: server/health
- Response < 100ms
- Reports: status, version, uptime, DB tables, phase, mode
thought_record:
task_id: "P0.2.4"
branch: "feature/p0-2-4-health-tool"
commit_sha: "..."
tests_run: |
npm test (health.test.ts: fields, timing, phase, DB count)
summary: |
P0.2.4 complete. Health check tool with all required fields.
Reports server status, uptime, DB table count, startup phase, runtime mode.
blockers: "none"
Common gotchas
-
Uptime tracking: Use a module-level
startTime = Date.now()so uptime is relative to server startup, not tool registration. Calling healthCheck multiple times should show increasing uptime. -
DB access: If DB is not initialized, catching the error and returning db_tables=0 is correct. Don’t crash the health check on DB unavailability.
-
Phase typing: currentPhase is an enum. Cast it to
'phase1' | 'phase2'to satisfy TypeScript. Ensure import is from startup.js. -
Response time: The spec says < 100ms. This is almost always achieved for a simple query, but avoid expensive operations in healthCheck() (e.g., don’t query all tables, just count them).
Navigation
Previous group: P0.1 — Project Infrastructure — see p0.1-infrastructure.md
Next phase: P0.3 — β Task Pipeline (when ready, see task-breakdown.md)
Back to index: task-breakdown.md
Master bootstrap: agent-bootstrap.md (required reading for all Phase 0 agents)
Generated for Colibri R69. Last updated: 2026-04-09