AGENTS.md in the repository root.
Prerequisites
- Node.js >= 22 — runtime and build toolchain
- pnpm — package manager (monorepo workspace support)
- Git — version control
- Linux for deployment (macOS works for development only)
Setup
Install dependencies
better-sqlite3, sharp).Build all packages
Coding Standards
Principles
The codebase follows five mandatory engineering principles:- KISS — Prefer straightforward control flow over meta-programming
- YAGNI — Do not add config keys, port methods, or feature flags without a concrete caller
- DRY (Rule of Three) — Extract shared helpers only after three repetitions
- Fail Fast — Return
err()for unsupported or unsafe states via the Result pattern - Secure by Default — Security rules enforced at lint time
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Functions | camelCase | createCircuitBreaker() |
| Types/Interfaces | PascalCase | ChannelPort, NormalizedMessage |
| Factory functions | createXxx() | createHookRunner() |
| Port interfaces | *Port suffix | MemoryPort, SkillPort |
| Adapter classes | *Adapter suffix | SqliteMemoryAdapter |
| Constants | SCREAMING_SNAKE | MAX_RETRIES |
| Files | kebab-case.ts | hook-runner.ts |
Key Rules
- Return
Result<T, E>— never throw exceptions. Useok(),err(),tryCatch(), andfromPromise()from@comis/shared. - Use
safePath()from@comis/core/security— never usepath.join()(prevents directory traversal attacks). - Use
SecretManager— never readprocess.envdirectly for secrets. - No
eval()orFunction()constructor — banned entirely. - No empty
.catch(() => {})— usesuppressError()from@comis/shared. - ES modules only (
"type": "module") with.jsextensions in import paths.
Testing
- Unit tests are co-located with source:
component.tsalongsidecomponent.test.ts - Each package has its own
vitest.config.tswithinclude: ["src/**/*.test.ts"] - Run a single package:
cd packages/core && pnpm test - Run a single test file:
pnpm vitest run src/path/to/file.test.ts - Integration tests live in
test/integration/and requirepnpm buildfirst (they import fromdist/) - Integration tests run sequentially (
maxConcurrency: 1,pool: "forks") - Run integration tests:
pnpm test:integration - Run full E2E orchestration with log validation and JSON report:
pnpm test:orchestrate - Run the live-fire real-provider test tier:
pnpm test:live --dry(dry run, no cost) orpnpm test:live(requiresCOMIS_LIVE=1) - Run the connectivity sweep (all provider probes, one per integration):
pnpm test:live sweep(requiresCOMIS_LIVE=1); filter withCOMIS_LIVE_PROBES=llm-anthropic,llm-openai pnpm test:live sweep - Run Phase 136 core-loop scenarios (LOOP-01..04):
pnpm test:live loop(requiresCOMIS_LIVE=1for real-LLM Stage-C assertions; Stage-A structural tests run without it); covers multi-turn.test.ts, tool-call.test.ts, restart.test.ts, streaming.test.ts - Run Phase 137 LLM-cache scenarios (CACHE-01..03):
pnpm test:live cache(requiresCOMIS_LIVE=1for real-LLM Stage-C assertions; Stage-A structural tests run without it); covers anthropic-cache.test.ts, gemini-cache.test.ts, cache-matrix.test.ts - Run Phase 138 context engine scenarios (CTX-01..05):
pnpm test:live ctx(requiresCOMIS_LIVE=1for real-LLM Stage-C assertions; Stage-A structural tests run without it); covers dag-invariants.test.ts, summarization.test.ts, expansion.test.ts, pipeline.test.ts - Run Phase 139 long-term memory scenarios (MEM-01..08):
pnpm test:live memory(requiresCOMIS_LIVE=1for real-LLM Stage-C assertions; Stage-A structural and Stage-B local-embedding tests run without it); covers recall-golden.test.ts, embedding-matrix.test.ts, recall-lanes.test.ts, trust-safety.test.ts, consolidation.test.ts, cost-features.test.ts, budget-interaction.test.ts - Run Phase 140 MCP transport/policy/trust scenarios (MCP-01..03):
pnpm test:live mcp(requiresCOMIS_LIVE=1for real-OAuth Stage-C; Stage-A structural and Stage-B mock-server tests run without it); covers transport-auth.test.ts, policy-ratelimit.test.ts, trust-sandbox.test.ts - Run Phase 140 built-in tool invocation and mode scenarios (TOOL-01..02):
pnpm test:live tools(requiresCOMIS_LIVE=1for real-LLM Stage-C; Stage-A structural and Stage-B config-driven tests run without it); covers builtin-invoke.test.ts, modes.test.ts - Run Phase 141 subagent orchestration scenarios (ORCH-01..04):
pnpm test:live orch(requiresCOMIS_LIVE=1for real-LLM Stage-C assertions; Stage-A structural and Stage-B config-driven tests run without it); covers dag-pipeline.test.ts, background-reentry.test.ts, routing.test.ts, isolation.test.ts - Run Phase 142 media scenarios (MEDIA-01..04):
pnpm test:live media(Stage-A/B run keyless — ffmpeg-absent text-fallback, STT fallback-chain routing, vision capability routing, and autoMode delivery decisions are deterministic and assert against the real product functions; Stage-C real STT/TTS/vision/image-gen round-trips requireCOMIS_LIVE=1plus the matching provider keys, and ffmpeg for voice format conversion); covers voice-roundtrip.test.ts, fallback-chain.test.ts, vision.test.ts, image-gen.test.ts - Run Phase 143 web/search/docs scenarios (WEB-01..03):
pnpm test:live web(Stage-B runs keyless — document extraction of CSV/text withinmaxCharswith the truncation marker, DOCX returnsunsupported_mimevia the default composite,wrapWebContenttaint markers + marker-sanitization, and 8-provider search config-shape + per-provider key-gating + the freshness filter on the public schema are all deterministic and assert against the real product functions; Stage-C real per-provider judged answers requireCOMIS_LIVE=1plus the matching search key, link fetching requires network egress, DuckDuckGo runs keyless with network, and PDF OCR fallback requires a vision key); covers search-providers.test.ts, link-understanding.test.ts, document-extraction.test.ts - Run Phase 144 channel scenarios (CHAN-01..03):
pnpm test:live channels(Stage-B runs keyless — the echo golden round-trip through the real channel registry, the empty-input credential-validation breadth across all 9 real channel adapters, the crash-safe SQLite delivery-queue crash-mid-delivery resume with a persistence-oracle integrity check, and the streaming/queue/overflow/dmScope config-shape against the real schemas are all deterministic and assert against the real product code; Stage-C real-account send-to-agent-to-reply round-trips, positive token validation, live Slack Socket Mode, and live IMAP/OAuth email requireCOMIS_LIVE=1plus the channel’s credentials and are recorded manually intest/live/RUNBOOK.md); covers echo-golden.test.ts, delivery-modes.test.ts - Run Phase 145 security and failure-injection scenarios (SEC-01..06):
pnpm test:live sec(Stage-A/B run keyless — fault-injection degradation classified via the realclassifyError, per-source prompt-injection neutralization throughwrapExternalContent(taint markers + the suspicious-pattern callback) across the fullExternalContentSourceunion,validateMemoryWritememory-poisoning classification (dangerous-command content blocked CRITICAL), the secret-residency scan (a planted canary positive control + the product redaction-to-residency chain + report/ledger),GatewayTokenSchemascope-disjointness (mcp-client co-issuance rejected) + the approval-gate pause/resolve state machine, and the macOSsandbox-execdeny-default exec-confinement are all deterministic and assert against the real product code; Stage-C real-provider failover under a real 429/5xx, the AgentDojo/ASB injection benchmark, the real-LLM redaction-on residency sweep, and the live-gateway admin-RPC-denial + rate-limit-429 requireCOMIS_LIVE=1plus a real provider, andbwrap+net{open,broker-only}is SKIPPED(no-bwrap/linux-only) on macOS); covers failure-injection.test.ts, prompt-injection.test.ts, memory-poisoning.test.ts, secret-residency.test.ts, gateway-scopes.test.ts, sandbox-net.test.ts - Run Phase 146 platform scenarios (PLAT-01..04):
pnpm test:live plat(Stage-B runs keyless — the config system fail-fast (the closedConfigErrorcodes), layering precedence (defaults < envLayer < YAML),${VAR}resolution, theisImmutableConfigPathtruth-table (security.storageis immutable, restart-required), and theconfig-auditrecord +config:patchedevent shape; the 3security.storagesecrets backends (encrypted/file/env) each resolving a canary credential viaselectSecretStore+ the encrypted-no-SECRETS_MASTER_KEYfail-fast + the no-leak residency scan + the persistence oracle onsecrets.db; the scheduler cron-fire (scheduler:job_started/scheduler:job_completed) + auto-suspend + concurrency cap + theexecution.jsonlread-back + heartbeat ok/alert via injectable stubs; and the terminal-driver auto-answer/escalate-always/loop-guard/cap arithmetic + the terminal config-shape fail-fast are all deterministic and assert against the real product code; Stage-C the real-LLM-turn-from-cron, the liveconfig.patch+restart+rollback over the gateway, and the real-boot credential auth requireCOMIS_LIVE=1plus a real provider, and driving a real interactive CLI is SKIPPED(no-bwrap/linux-only) on macOS); covers config-system.test.ts, secrets-backends.test.ts, scheduler.test.ts, terminal-driver.test.ts - Run Phase 147 end-to-end journey scenarios (E2E-01..05):
pnpm test:live journeys(Stage-A/B run keyless — the extensible user-story library: a zod-validatedUserStoryschema (a malformed story rejects at parse — the schema is the executable acceptance spec), a self-registeringSTORY_LIBRARY(mirrorsplatform-tools/registry.ts), one genericjourney-runnerthat interprets ANY story by its data, the open/closed zero-harness-change extensibility test (a synthetic story registered viaregisterStoryalone auto-joins the library + the coverage view + the run grid with no runner/steps/schema edit), the step-vocabulary interpreter driving a story on the echo channel with dummy keys, the coverage auto-wiring view, therequires→skip-with-reason gating (an unmet provider/capability/platform/component-cert skips, never fails), and the 8 seed-story shapes US-01..08 are all deterministic and assert against the real product code; Stage-C/D the real-LLM multi-turn journey execution (goal-achieved + judged task-success + one stitchedtraceId+ journey-levelobs.billing, the N-run pass-rate ×(scenario×model)grid) requiresCOMIS_LIVE=1plus a real provider and the component Stage-C certs from phases 136–146, and J7 terminal-driven is SKIPPED(no-bwrap/linux-only) on macOS); covers types.test.ts, registry.test.ts, steps.test.ts, journey-runner.test.ts, coverage-wiring.test.ts, readiness-wiring.test.ts. Adding a journey = drop one declarativeUserStoryspec file undertest/live/journeys/stories/that callsregisterStory(...)(imported from./registry-core.js) + add one import line inregistry.ts— zero change to the journey-runner, steps, or schema (open/closed). The new story automatically joins the next live run, the(scenario×model)grid, the coverage view, and READINESS. - Run Phase 148 PROVE scenarios (PROVE-01..05):
pnpm test:live prove(Stage-A/B run keyless — the observability meta-validation (PROVE-01: billed-tokens-equal-response-tokens cross-stream agreement + a turn reconstructed from theobs.tracesession-index bytraceId/messageIdover a seeded index + no ERROR/WARN withouthint+errorKindviarunLogOracle— the §5 observability oracle validating ITSELF), the cold-start tarball-bundle-mechanics contract (PROVE-02: thebundledDependencies@comis/*set the published tarball ships, every workspace packageprivate:true) + thedoctor/healthfinding-shape contract, and the short deterministic soak smoke (PROVE-03: therunSoakharness reuses theSTORY_LIBRARYas traffic + parses the daemon “Daemon health” line for RSS/heap trend + zerostuckSubAgentRuns/deadLetterQueueSize/promptTimeoutsLast5m+ emptydegradedProviders) are all deterministic and assert against the real product code; the real multi-hour Linux-VPS soak is SKIPPED(operator), the full cold-start install→comis configure→boot→doctor/healthgreen is SKIPPED(linux/validate:full), the real-provider full-run meta is SKIPPED(no-live), and judged real-key claims requireCOMIS_LIVE_JUDGE_*(skip otherwise); covers obs-meta.test.ts, cold-start.test.ts, soak-smoke.test.ts. The live tier now runs under its owntest/live/vitest.config.ts(fileParallelism:false) — daemon-booting scenarios run sequentially, sopnpm test:liveis reliable WITHOUT the manual--no-file-parallelismflag. Generate the readiness summary withpnpm test:live --readiness(writesREADINESS.md— honest PARTIAL on a keyless run, every category A–V + per-story US-01..08). A scheduled, budget-capped release gate (.github/workflows/live-fire.yml, weekly + manual dispatch) runspnpm test:live alland publishesREADINESS.md; it is operator-armed via repo secrets, never runs per-PR, and the pre-push hook stays mock-only — live never blocks a push. - Clean up integration test artifacts:
pnpm test:cleanup
Tests-First Protocol
Every fix and every feature inpackages/*/src/** starts with a failing test that fails on the pre-patch code, then a production patch that flips it to GREEN. Commit the test first when practical so the failing state is reproducible from that commit alone.
Exempt from the tests-first rule: pure docs, comments, formatting, and build-tooling/CI/config edits — when in doubt, write the test.
RED → GREEN → REFACTOR
-
RED — Write the failing test first. Run it and confirm it fails on the current codebase (
pnpm vitest run src/your-file.test.tsexits non-zero). Commit the test-only change when it would compile against the current code. - GREEN — Write the minimum production code to make the test pass. Run the test again and confirm it passes.
- REFACTOR (optional) — After GREEN, simplify if the patch leaves duplication or awkward seams. All tests must stay green.
main) and the GREEN commit second. Combining is acceptable when the test would not compile against the pre-patch code, when the bug is too narrow to surface from a separate commit, or when shipping a security patch.
Test types:
- Bug fix → regression test that fails on the current codebase
- New feature → contract test that pins the new behavior
- Co-locate unit tests:
src/component.ts+src/component.test.ts - Integration tests (daemon-level flows only): live in
test/integration/
Validation Gates
Runpnpm validate before every push. This is the full gate chain:
| Step | Command | What It Checks |
|---|---|---|
| Docs compile | pnpm docs:check | Compiles every docs/**/*.mdx through the MDX compiler — catches syntax errors the Mintlify deploy would reject |
| Clean build | pnpm build:clean | pnpm clean && pnpm build — clean-room TypeScript build using project references (not incremental) |
| Circular deps (dist) | pnpm cycles | madge dist-mode check — finds circular dependencies between compiled packages |
| Circular deps (refs) | pnpm cycles:refs | tsc -b --dry packages/comis — catches project-reference cycles (TS6202) |
| Security lint | pnpm lint:security | ESLint security rules |
| Coverage | pnpm test:coverage | vitest run --coverage with per-package coverage floors |
Pre-Push Hook
A pre-push git hook (.githooks/pre-push) is auto-installed by the prepare script (git config core.hooksPath .githooks). It runs pnpm validate and blocks the push on failure.
To bypass for a single push (e.g., docs-only changes): git push --no-verify
The hook does not run integration, E2E, tarball, or audit tiers — those require Linux and additional system dependencies. Run pnpm validate:full on Linux for those.
Commits and Branches
Commit format: Conventional Commits with package scope:main using the pattern:
feature/<description>— new featuresfix/<description>— bug fixesdocs/<description>— documentation changes
Anti-Patterns
Common mistakes to avoid — these are enforced by ESLint and will fail CI:| Instead of | Use | Why |
|---|---|---|
path.join() | safePath() | SSRF and directory traversal protection |
process.env.X | SecretManager | Centralized secret management |
throw new Error() | return err(new Error()) | Result pattern for explicit error handling |
Empty .catch(() => {}) | suppressError() | Explicit error suppression |
| Cross-package internal imports | Public package exports | Maintain package boundaries |
Risk Tiers
Changes are classified by risk level, which determines the review and testing requirements:- Low risk: Documentation, tests, type-only changes
- Medium risk: Behavior changes in existing features
- High risk: Security, ports, gateway, daemon, config, domain, and bootstrap changes
Related
Architecture
Understand the codebase structure
Packages
Package roles and dependency rules
Custom Adapters
Build your first adapter
Developer Guide
Back to developer guide overview
