Skip to content

Module Reference

keryx is a Bun/TypeScript CLI that scaffolds and maintains a per-project .metaproject/ workspace of agent-facing knowledge, quality signals, and managed work. It is organized as a thin dispatcher (cli.ts) routing to a set of loosely coupled feature modules, each with a commands/<name>.ts handler over a <feature>/*.ts domain layer, all sitting on a shared lib/ toolkit. Modules never call each other's code paths directly; they integrate by reading and writing files under the shared .metaproject/ workspace.

This reference documents one module per section, covering the real CLI surface, key source files, mechanics, the .metaproject/ paths each module reads and writes, and cross-module integrations. Behavior described here is what the source actually implements.

Cross-cutting opt-in substrate (roadmap-2026). The optional, model/asset-backed features described below (gdgraph's symbol layer, memory's embedding index, the security model backends, etc.) all instantiate three shared, deterministic-safe mechanisms rather than each rolling their own: the Capability System (src/capability/resolveCapability(cwd, spec) returns an adapter only when the capability is enabled in the manifest, its optional dependency imports lazily, and its asset resolves; otherwise it returns null and the caller runs its deterministic fallback), the Asset Resolver (src/assets/ — resolves + sha256- verifies an opt-in asset from a config path / cache, never networking implicitly), and the fixture harness (src/eval/ — runs a block's detector over a committed labeled corpus and computes a reproducible precision/recall report). The invariant across every module is the same: the deterministic path is the default and the fallback; each model/asset feature is opt-in and degrades gracefully to that path.


cli-core

Purpose. The CLI core is the entrypoint and lifecycle layer. It owns the argv dispatcher (src/cli.ts) and the cross-cutting lifecycle commands — init, update, dashboard/dash, status, modules, and standard — plus MODULE_COMMANDS, the single source of truth for each module's canonical subcommand list. Its job is to parse the top-level subcommand, scaffold the workspace and its nine default-configurable modules (plus the opt-in mcp module, for a possible tenth), keep managed "service" files in sync without ever touching user "data" artifacts, and emit both an agent-facing index.md and a human-facing HTML dashboard.

CLI surface (top-level commands). main() is a flat if-chain mapping args[0] to a handler:

Command Action
(none) / --help / -h printHelp()
--version / -v prints package version
init scaffold .metaproject/, enable modules + hooks, write manifest
status print manifest module enabled/disabled state
update refresh service files, runtime, tasks backfill, dashboard
dashboard / dash build/open the HTML dashboard (bare dash = open)
modules view and toggle modules (status / enable <name> / disable <name> / interactive)
standard validate the workspace against the Metaproject Standard: validate / doctor / capabilities / emit llms [--stdout]
security agent I/O + artifact security layer (routed to securityCommand)
mcp serve module facades over the Model Context Protocol (routed to mcpCommand, opt-in module)
agents manage global agent bootstrap routing (routed to agentsCommand)
gdgraph, ctx, wiki, skills, skill-verify-skill, health, test, memory, flow, rules routed to the feature module handler
unknown error + help + exit 1

Key lifecycle flags: init accepts --yes/-y, --no-<module> for each of the 9 modules, --gdskills-profile <v>, and --no-*-hook variants; update accepts --hooks, --skip-runtime, --no-tasks.

Key files. - src/cli.ts — entrypoint main() argv dispatcher, printHelp(), version. - src/commands/init.ts — scaffolds .metaproject/, resolves module + hook enablement, writes the manifest. - src/commands/update.ts — refreshes service files, self-updates the runtime, backfills tasks, builds the dashboard (buildDashboard is exported from here). - src/commands/dashboard.tsdashboard build|open (delegates to buildDashboard). - src/commands/status.ts — reads the manifest, prints module state (read-only). - src/commands/module-commands.tsMODULE_COMMANDS, the canonical per-module subcommand map. - scripts/install.sh — project/global installer.

How it works. The defining pattern is idempotent write helpers: writeTextIfMissing (seed once, never overwrite user edits), writeTextIfChanged/writeJsonIfChanged (managed files reconciled to their rendered template), and copyFileIfChanged. The core invariant is data vs service separation: init/update regenerate templates, manifests, skills, hooks, and dashboards but never write under .metaproject/data/. init resolves enablement for 9 modules (gdgraph, gdctx, gdwiki, gdskills, health, testing, memory, tasks, security), scaffolds base + per-module dirs, installs managed git hooks (injecting # keryx:<id>:begin…:end blocks), and writes a typed MetaprojectManifest. update optionally git-fetches the runtime repo it was launched from (.metaproject/runtime/keryx/.git or $HOME/.keryx/...), re-renders all managed files, recovers/migrates a missing or legacy manifest (wikigdwiki), and backfills the tasks module. The dashboard is a single self-contained HTML file with size-capped Markdown/JSON embedded inline so it works over file:// without fetch.

Data & artifacts. Reads/writes .metaproject/metaproject.json (authoritative config). Writes managed docs (index.md, READMEs, per-module manifests and skills/<m>/SKILL.md), seed-once config files (gdctx.config.json, health.config.json, testing.config.json, memory.config.json), git hooks under .git/hooks/, and .metaproject/keryx-dashboard.html. collectDashboardData reads (read-only) data/health/artifacts/latest.json, data/gdgraph/storage/{nodes,edges}.jsonl, data/testing/artifacts/latest.json, and wiki/memory Markdown trees.

Dependencies / integrations. Node/Bun builtins (fs, path, url, child_process) + git. Imports render/config/type helpers from every feature module for scaffolding. MODULE_COMMANDS is the one place the manifest commands arrays come from; module-commands.test.ts enforces it stays in sync with the actual dispatchers. Note the naming skew: manifest module id tasks vs CLI verb flow, and module id gdwiki vs CLI verb wiki.


gdgraph

Purpose. gdgraph builds a deterministic intra-project file graph and can optionally enrich it with tree-sitter symbols and resolved call edges. Persisted JSONL and Markdown artifacts support concept search, exact symbol inspection, call-aware impact, shortest paths, affected sets, cycles, orphans, and bounded repository maps.

CLI surface. Entry point gdgraphCommand(args): - build — builds the graph for process.cwd(), writes artifacts, prints node/edge counts. - query cycles — prints each cycle as a -> b -> a, or "No cycles found." - query orphans — prints orphan module paths, or "No orphan modules found." - find "<terms>" — ranks matching files and available symbols. - symbol "<name>" [--impact] [--depth N] — definitions, callers, callees, documented-in wiki pages, and optional transitive caller impact. - symbols <enable|disable|status> — explicitly control or inspect the optional tree-sitter capability. - path "<A>" "<B>" — shortest path between file or symbol endpoints. - affected <file-or-symbol> [--depth N] [--ranked] [--json] — direct or transitive dependencies/dependents with optional ranking or structured output. - repomap [--budget N] [--seed <path>...] [--changed] — writes a PageRank-ranked, token-budgeted artifacts/repomap.md. - context — emits the graph portion of the bounded orientation block. - assets list | verify [<id>] | pull <id> — manage the optional tree-sitter grammar assets. - --help/-h/no args — usage.

Only cycles and orphans are valid query targets. Natural-language queries receive actionable guidance to use gdgraph find, ctx rg, and affected. Loose symbol queries that match multiple distinct names stop for disambiguation instead of unioning unrelated callers and impact.

Key files. - src/commands/gdgraph.ts — dispatcher, local-runner delegation, help, output. - src/gdgraph/build.ts — filesystem walk, import-specifier extraction, language-aware resolution (TS/JS tsconfig, Java Maven/Gradle, Python), artifact writing. - src/gdgraph/query.ts — graph loading (incl. the symbol layer) + the three query algorithms. - src/gdgraph/types.tsGraphNode, GraphEdge, GraphData, plus the additive SymbolNode/CallEdge/SymbolLayer. - src/gdgraph/service.tscreateGdgraphService() facade (build/loadGraph/affected/repomap/query): the transport-independent service contract for in-process callers. - src/gdgraph/affected.ts — pure N-hop transitive affected (BFS over reverse-dependents). - src/gdgraph/pagerank.ts + repomap.ts — deterministic personalized PageRank + the token-budgeted repo map. - src/gdgraph/config.ts — optional .metaproject/gdgraph.config.json loader (deep-merged over defaults; missing/malformed ⇒ defaults). - src/gdgraph/treesitter/ — the opt-in web-tree-sitter symbol-layer adapter (adapter.ts, extract.ts, grammars.ts) + enrich.ts.

How it works. collectSourceFiles walks the tree from the project root, skipping IGNORE_DIRS (.git, .metaproject, node_modules, build/output dirs, etc.) and collecting .ts/.tsx/.js/.jsx, .java, and .py files as file nodes. extractImportSpecifiers is language-aware: TS/JS are scanned with the Bun transpiler (falling back to regex); Java (import a.b.C;) and Python (import a.b, from a.b import c, and relative from . import x / from ..pkg import y) use regex patterns — regex-based, not AST, so unusual syntax can be missed. Resolution is dispatched by the importing file's language through an ImportResolver interface (all resolvers built once per build, source roots cached):

  • TypeScript/JavaScript — tsconfig paths/baseUrl aliases plus relative probing over candidate extensions and index.* (unchanged; the TS/JS graph is byte-identical to prior versions).
  • Java — a fully-qualified name a.b.C maps to <sourceRoot>/a/b/C.java, with source roots discovered from pom.xml (sourceDirectory, multi-module <modules>) and build.gradle(.kts) sourceSets, defaulting to src/main/java / src/test/java. Wildcard import a.b.*; never fabricates a file edge.
  • Python — dotted modules resolve to pkg/mod.py or pkg/__init__.py against the project root and src/; relative imports resolve against the importing file's package by leading-dot level.

A resolved specifier becomes an imports edge; a specifier matching a known asset extension on disk becomes an asset edge. A specifier that fails to resolve becomes an unresolved edge — for TS/JS relative/aliased specifiers, and for all non-relative Java/Python imports (e.g. external java.util.*, org.springframework.*), which are recorded rather than silently dropped. Bare TS/JS package specifiers are still skipped; in-repo source-to-source edges are the target. The summary.md import-resolution metric reports resolved / extracted, or n/a when nothing was extracted (never a false 100% from a 0/0 denominator). Queries: getOrphans returns nodes with no inbound/outbound resolved edges; getAffected matches the target by exact path or suffix; getCycles runs a recursive DFS over imports edges only, deduping cycles by canonical rotation (reports one representative per rotation, not all elementary circuits).

Block B additions (symbol layer + ranked map). An optional symbol layersrc/gdgraph/treesitter/, an adapter over web-tree-sitter, opt-in via init --treesitter — parses sources into function/class/method SymbolNodes and calls/defines CallEdges, written additively to storage/symbols.jsonl + storage/calls.jsonl. It is the only code in src/ that loads web-tree-sitter (lazily via await import() behind the shared Capability Seam) and it never throws out: when the dependency or a grammar asset is unavailable it degrades to null and the regex/file-level graph stays the byte-identical default. affected is now transitive (affected.ts, a BFS over reverse-dependents to --depth N, with a --ranked blast radius and --json); at depth 1 the rendered Dependencies/ Dependents output is byte-for-byte identical to the pre-block renderer. repomap (pagerank.ts + repomap.ts) ranks files (and symbols, when the layer is present) by deterministic personalized PageRank and renders a hard-token-budgeted artifacts/repomap.md. createGdgraphService() (service.ts) is the new transport-independent facade over these surfaces; config.ts supplies the optional, deep-merged config.

Data & artifacts. Storage .metaproject/data/gdgraph/storage/: nodes.jsonl, edges.jsonl (and, only when the tree-sitter capability is active, symbols.jsonl + calls.jsonl). Artifacts .metaproject/data/gdgraph/artifacts/: module-map.json (module → file paths), summary.md (stats, top modules, unresolved imports, skipped dirs, next-command hints), and — from repomaprepomap.md. Optional config .metaproject/gdgraph.config.json.

Dependencies / integrations. Node builtins only (fs, path, child_process); self-contained, no shared-lib or cross-module imports. Its persisted JSONL/artifacts are consumed read-only by gdwiki collect, gdskills evidence, and the cli-core dashboard.


gdctx

Purpose. gdctx is a token-aware wrapper that runs common developer commands (git diff, ripgrep, arbitrary shell commands) and reads files, then emits compact, summarized Markdown instead of raw verbose output. Full raw output is persisted to disk while only a distilled summary is printed, minimizing tokens consumed by an agent. Each run records bytes-in vs bytes-out and whether output was truncated.

CLI surface. Dispatched by ctxCommand:

Subcommand Invocation
status keryx ctx status — metaproject/manifest/config/data presence + gdctx-enabled flag
diff keryx ctx diff [args...] — runs git diff <args>, summarizes
rg keryx ctx rg "<pattern>" [path] — runs rg --line-number --column --no-heading, summarizes
read keryx ctx read <file> [--mode outline\|compact\|full] (default compact)
run keryx ctx run -- <command...> — runs an arbitrary command after --
show keryx ctx show [latest\|<name>] [--raw] — prints a saved artifact
install-hook keryx ctx install-hook [--runtime <id\|all>] — install the opt-in routing guard
uninstall-hook remove only the managed routing guard for selected runtimes
--help/-h/(none) help

Key files. Single-file module src/commands/ctx.ts (~637 lines): ctxCommand router, loadConfig, per-mode orchestrators (diffAndSummarize, rgAndSummarize, readAndSummarize, runAndSummarize), runCommand (Bun.spawn), writeArtifact, showArtifact, and the summarizer/parser helpers.

How it works. Summarization is per-kind: diff parses per-file add/remove churn, flags risky files (lockfiles, tsconfig, .github/, scripts/, src/cli.ts, src/commands/), and lists hunk headers + error/warning lines; rg groups file:line:col:text matches by file with example snippets; run emits command/exit-code/byte-counts plus compacted output (head + omitted marker + tail with important lines injected); read dispatches on mode — full (whole file), compact (head + omitted + tail), or outline (a regex/line-scan structural extract of imports, exports, declarations, TODOs — no AST). The optional routing guard classifies shell commands before execution, blocks broad raw rg/grep/cat/git-diff reads, and points the agent to a bounded ctx equivalent; runtime-specific installers are merge-safe and reversible. Config (CtxConfig) tunes line/group caps and head/tail sizes. truncated is set whenever the summary is smaller than the raw input (the normal case). Despite older spec claims, there is no gdgraph integration; the only cross-module awareness is reading manifest.modules.gdctx.enabled for status.

Data & artifacts. Root .metaproject/data/gdctx/. Writes timestamped raw/<id>.log + artifacts/<id>.md (with an appended ## Metadata JSON block), plus overwritten raw/latest.log and artifacts/latest.md. show latest reads artifacts/latest.md (or raw/latest.log with --raw). status also references .metaproject/metaproject.json and .metaproject/gdctx.config.json.

Dependencies / integrations. Node/Bun builtins + Bun.spawn; internal lib/args (optionValue), lib/fs (pathExists), lib/json (readJsonFileOr). Spawns external tools at runtime: git, rg/ripgrep, and any binary passed to run. No dependency on gdgraph or other keryx modules.


gdwiki

Purpose. gdwiki is the local project knowledge base — a curated, file-based wiki of knowledge meant to outlive a single task (architecture, domain models, business rules, user scenarios, component/service responsibilities, integrations, decisions). It is Markdown-on-disk under .metaproject/wiki/, human-editable and indexed/validated by CLI commands. Beyond hand-authored pages it can auto-collect deterministic draft pages from sibling modules' generated data.

CLI surface. Dispatched by wikiCommand:

Subcommand Behavior Exit
wiki status enabled?, root, page counts per type, last index/link-check state 0
wiki new <type> <slug> --title "<t>" [--force] scaffold a page from template; validates type+slug 1 on missing args
wiki collect [--force] [--changed [--since <ref>]] [--limit <n>] generate hierarchical full-coverage drafts from gdgraph/health/testing data, reindex, and report the prose-enrichment work front 0
wiki index rebuild the managed index block in index.md 0
wiki check-links resolve internal Markdown links, write report 1 if broken
wiki validate metadata + link + index-staleness checks (superset of check-links) 1 if issues
wiki ask "<q>" [--k <n>] [--rerank] deterministic Q&A over wiki pages + current memory, returns cited answer 0
wiki context emit the wiki-index portion of the bounded orientation block 0
wiki backlinks <wiki-page-or-code-file> show knowledge backlinks and, for code files, graph dependents 0

Eight fixed page types (WIKI_PAGE_TYPES), each mapping to a folder: architecture, domain-model, business-rule, user-scenario, component, service, integration, decision. --limit defaults to 12.

Key files. - src/commands/wiki.ts — thin CLI handler. - src/wiki/service.ts — all domain logic (status/create/index/collect/check-links/validate + collectors) + the createGdWikiService() facade. - src/wiki/ask.tswikiAsk: deterministic lexical Q&A over collected wiki pages + current memory, returning citations + an assembled Markdown answer. - src/wiki/templates.ts — Markdown renderers + managed-block sentinels. - src/wiki/types.tsWikiPageType, WIKI_PAGE_TYPES, DTOs, GdWikiService interface (incl. ask), WikiAsk* types.

How it works. Pages carry plain-text metadata lines (Version:, Type:, Status: — regex-parsed, not YAML frontmatter). wikiCollect reads three read-only sources — gdgraph nodes.jsonl/edges.jsonl (emits a project-map page plus per-module component pages), health latest.json (a quality-map page), and testing context.md (a testing-map page) — and rebuilds the index; missing sources are skipped silently. A draft-ownership guard (writeCollectedPage) means collect only overwrites a page still marked Status: draft with the literal "Generated by keryx wiki collect" marker; once a human edits it, it is left untouched. wikiGenerateIndex maintains a managed block between WIKI_INDEX_BEGIN/END sentinels, preserving surrounding human prose. wikiCheckLinks walks all .md (except templates/), resolves internal links, and reports broken ones. wikiValidate folds link checks + metadata checks + index-staleness into one pass.

Block C addition (ask). GdWikiService.ask (src/wiki/ask.ts) answers a question deterministically by lexical (Jaccard) retrieval scoped strictly to the project's own collected wiki pages + current (non-superseded, in-validity) memory entries — never an arbitrary corpus — returning the top-k citations and an assembled Markdown answer. An optional embedding rerank reorders the citation set when the memory embedding capability resolves; it never changes the set's provenance and degrades back to the deterministic lexical order. It is exposed as an MCP Tool (wiki.ask), alongside the wiki/memory trees served as read-only MCP Resources.

Data & artifacts. Wiki root .metaproject/wiki/ (existence = "enabled"), pages under wiki/<folder>/<slug>.md, wiki/index.md, wiki/templates/page.md, and the link-check report .metaproject/data/gdwiki/link-check/latest.md. Reads (for collect) data/gdgraph/storage/{nodes,edges}.jsonl, data/health/artifacts/latest.json, data/testing/context.md. init/update scaffold modules/gdwiki.md and skills/gdwiki/SKILL.md.

Dependencies / integrations. Node builtins + lib/fs, lib/args. Consumed by commands/init.ts/update.ts (scaffolding) and gdskills/verify.ts (calls wikiValidate). collect is deterministic and offline — it only reads other modules' materialized artifacts, so it must run after those modules produce data.


gdskills

Purpose. gdskills is the working-skills subsystem. It manages bundled skills (a fixed code-defined catalog of ~90 skills installed into .metaproject/skills/gdskills/**) and project skills (per-entity/per-module skill packages generated under .metaproject/project-skills/). Around these it provides routing, verification against repo evidence, auditable learning proposals, export/sync to runtimes, and five JSON contract schemas with a built-in validator. Everything is local-first: no network or global-runtime writes happen implicitly.

CLI surface. Dispatched by skillsCommand:

Subcommand Notes
status [--json] local install status summary
list [--json] registered project skills as a table
inspect <project-skill> [--json] metadata + file presence
route <query-or-target> [--json] ranked matches with reasons/follow-ups
catalog [--profile minimal\|recommended\|full\|custom] bundled catalog Markdown
install [--profile ...] installs bundled skills/catalog/manifest/contracts (needs .metaproject/)
create <target> / generate <target> create a project-skill package (--module, --name, --format, --dry-run)
verify <skill-or-target> / verify --all verify against evidence, write report (--dry-run, --json)
learn <--from-*> --skill <m>/<s> create an auditable learning proposal (does NOT mutate SKILL.md)
learn apply <proposal.json> apply a proposal to SKILL.md + changelog, bump patch version
export <project-skill> --runtime codex\|claude\|plugin export to a runtime artifact
sync --runtime ... --target <dir> sync exported skills to an explicit dir only
contracts list / contracts validate <file> --schema <name> list/validate JSON contracts

skill-verify-skill <skill-or-target> is a top-level alias for verify. Profiles: minimal | recommended | full | custom (unknown → recommended; custom resolves to recommended).

Key files. - src/commands/skills.ts — CLI router, arg parsing, route scoring, status aggregation, help. - src/gdskills/catalog.tsBUNDLED_GDSKILLS, profile filtering, SKILL.md/catalog/manifest renderers. - src/gdskills/install.ts — installs bundled skills/rules/contracts. - src/gdskills/project-skills.ts — creates project-skill packages, owns ProjectSkillRegistryEntry. - src/gdskills/resolve.ts — resolves an input to a package root + registry entry. - src/gdskills/verify.ts — collects evidence signals, classifies freshness, writes reports. - src/gdskills/learn.ts — extracts lessons into proposals; applies reviewed proposals. - src/gdskills/export.ts / sync.ts — runtime artifact export + safety-checked sync. - src/gdskills/contracts.ts + contracts/*.schema.json — five schemas + hand-rolled validator.

How it works. Bundled skills are hardcoded via a skill(...) helper (name, category, purpose, workflow, triggers, profiles) and installed by rendering a SKILL.md (or copying a bundled source dir). Project skills are generated by createProjectSkill, which slugifies module/name, collects Evidence (target existence + gdgraph/gdctx/gdwiki artifacts), writes the package (package vs single format), and registers it in the manifest. route is a keyword/heuristic scorer (exact key/name +100, target substring +80, path +70, etc.). verify collects signals (required files, metadata, registry membership, target existence, gdgraph/gdctx/gdwiki/health/memory evidence) and classifies fresh / needs-review / stale / blocked. learn splits into an auditable proposal (never mutates SKILL.md) and apply (path-guarded, bumps patch version, appends lessons only into allowed sections, writes an idempotency guard). export copies safe dirs into .metaproject/runtime/skills/<runtime>/...; sync copies to an explicit target validated to be inside the project or $HOME (never a root). contracts validate runs a hand-written subset validator ($ref, type, enum, minimum, minLength, pattern, required, properties, additionalProperties, array items).

Data & artifacts. Under .metaproject/: metaproject.json (modules.gdskills.projectSkillRegistry), skills/gdskills/<category>/<name>/SKILL.md, skills/catalog.md, modules/gdskills.md, core/gdskills/contracts/*.schema.json, project-skills/<module>/<name>/, data/gdskills/reports/*-verification.json, data/gdskills/proposals/<id>.{json,md} + <id>.applied.json, and runtime/skills/<runtime>/<module>-<name>/. Reads (evidence, read-only) gdgraph/gdctx/gdwiki/health/memory artifacts.

Dependencies / integrations. Node/Bun builtins + lib/args, lib/fs, lib/json, lib/ui. Cross-module (verify only): memory/relevant (relevantAcceptedMemory) and wiki/service (wikiValidate). Bundled asset resolution supports both dev and packaged layouts.


health

Purpose. The health module is keryx's code-quality signal aggregator and quality gate. It runs (or imports the output of) a set of external and built-in quality tools, normalizes their output into a uniform Finding model, aggregates findings into per-scope health scores (project/module/component/ file/skill), compares each against a stored baseline to compute regressions and trends, and evaluates a pass/warn/fail gate that can block a merge (non-zero exit). Everything is file-based; per-skill attribution lets gdskills "learn from health."

CLI surface. keryx health <subcommand>:

Subcommand Behavior
run [--strict] [--scope ...] [--changed [--since <ref>]] [--source ...] full pipeline; writes latest.json/md + history; exit 1 if gate = fail
status reads latest.json: enabled?, last run, gate, score, regressions, per-source status, trend
gate [--strict-warn] re-reads last gate (does not re-run); exit 1 if fail (or warn + --strict-warn)
sources detect + list each source's mode/required/status without running tools
explain <file-or-module> scope metrics + its findings (first 20)
baseline update [--scope ...] write current scores into the baseline
trend [--scope <key>] [--limit <n>] history-based trend line

Key files. commands/health.ts (CLI), health/service.ts (facade), health/run.ts (the run pipeline), health/scoring.ts (pure math), health/scopes.ts, health/gate.ts, health/config.ts, health/baseline.ts, health/history.ts, health/report.ts, health/skills.ts (file→skill ownership), plus metrics/* (complexity/coverage/churn/hotspot) and sources/* (adapters).

How it works. Architecture is an adapter (plugin) pattern + linear pipeline. Each quality tool is a SourceAdapter (detect/run/import/parse) registered in FINDING_ADAPTERS: ESLint (error→P1/warning→P2), TypeScript (error→P0), Tests (each failure→P0, reusing the testing module's report loader), dependency audit (critical/high→P0), SonarQube (import-only, disabled by default). Metric sources are handled separately: coverage (Istanbul summary, import-only, feeds a penalty), complexity (built-in token-based cyclomatic, emits P2 findings over threshold), churn (git numstat, informational). Per-scope scoring: risk_score = Σ priority-weighted counts (P0=100, P1=20, P2=5, P3=1); health_score = clamp(100 - normalized) with per-LOC normalization; trend vs baseline; regression_score = baseline − current. The gate escalates pass<warn<fail: any P0 → fail, regression ≥10 → fail (≥3 → warn), missing required source → fail only under --strict, coverage below soft floor → warn. strict mode makes auto-mode importers return missing instead of spawning tools.

Block D addition (hotspots). A churn×complexity hotspot signal (src/health/metrics/hotspot.ts, rankHotspots — score = git churn × Σ per-function cyclomatic complexity, ranked score-desc) is folded additively into healthScore via scoring.hotspotWeight, which defaults to 0 — so default scores, the baseline, and the gate are all unchanged out of the box — and is surfaced as report.hotspots. computeGate is unchanged.

Data & artifacts. Under .metaproject/data/health/: artifacts/latest.json (the full HealthReport, source for status/gate/explain), artifacts/latest.md (human report), history/<stamp>.json (per-run snapshot), raw/<source>/<stamp>.log. Baseline at .metaproject/health/baselines/scores.json (auto-seeded on first run). Config .metaproject/health.config.json.

Dependencies / integrations. Bun runtime + git; external tools eslint, tsc, bun/npm, bun test (resolved via local node_modules/.bin then Bun.which), Istanbul coverage JSON, SonarQube issues export. Internal lib/fs, lib/json, lib/args. Cross-module: the tests adapter reuses testing/service + testing/types; skills.ts reads modules.gdskills.projectSkillRegistry to tag scope.skill, feeding gdskills learn --from-health.


testing

Purpose. The testing module gives keryx a project-agnostic, agent-facing view of a codebase's test setup and results. It (1) discovers and persists reusable "testing context" (frameworks, scripts, test/config/CI files, human-written conventions) and (2) runs the project's existing test runner (optionally scoped to changed files), parsing raw output into a normalized JSON + Markdown report. It is deliberately non-destructive — it never creates test files, installs dependencies, or invents a stack.

CLI surface. testCommand:

Subcommand Flags Behavior
test init / test analyze scan tree, write context.{json,md} + recommendations (init is an alias of analyze)
test run --changed, --strict/--gate, --since <ref>, --scope <path>, --kind unit\|integration\|e2e\|smoke select → run runner → parse → write report; exit 1 on fail/error
test status one-line summary (enabled, frameworks, test count, last run)
test context print saved context + recommendations
test report latest --json print latest normalized report
test related <file> tests related to a source file (naming/directory heuristics)
test explain <file-or-scope> frameworks + related tests + failures filtered by target

--gate is an alias for --strict.

Key files. src/commands/test.ts (thin dispatcher), src/testing/service.ts (all domain logic — analyze, run, select, parse, persist, render), coverage-map.ts (coverage-map Test Impact Analysis: parse/normalize/select), types.ts (data contracts), templates.ts (config/manifest/hook generators).

How it works. analyzeTestingProject walks the tree, reads package.json scripts/deps, classifies files by four regexes (test/config/CI/instruction), detects frameworks (bun/vitest/jest/playwright/cypress/testing-library), and harvests testing conventions. runTesting selects tests (changed-file selection via git diff + related-by-naming, with a config-driven fallback when nothing matches), resolves a command (prefer a package script, else direct bun test), runs it via Bun.spawn, and parses failures/counts. Parsing is bun-test-shaped ((fail) <name>, N pass, N fail); for other runners the exit code still drives pass/fail but per-test extraction is approximate. The strict gate is the key rule: --strict --changed with no matched tests is forced to fail (synthetic P0) when the fallback isn't "none" — this makes the pre-push hook block. Note some declared config (changedSelection.strategies incl. gdgraph, runner mode, historyLimit, keepRawLogs) is latent/aspirational — not honored by current logic.

Block D additions (coverage-map TIA + smoke tier). An opt-in Test Impact Analysis (src/testing/coverage-map.ts, the coverageMap capability) selects tests map-first when a normalized coverage-map.json is present — intersecting changed files/lines against a testFile → coveredFiles map parsed from existing lcov / V8-bun coverage output (no bespoke instrumentation on the default path, no new dependency) — and otherwise falls back to the byte-identical static changed-file selection (loadCoverageMap returns null on an absent/malformed map, never throws). An always-on smoke tier (smoke.selectors, empty by default ⇒ no-op) is unioned into every selection mode, composing with rather than suppressing the scoped/changed set. Any persisted raw coverage log is routed through the security write seam.

Data & artifacts. Under .metaproject/data/testing/: context.json/context.md/ recommendations.md (from analyze), artifacts/latest.json/latest.md (newest report), history/<timestamp>.json, logs/latest.raw.log. Config .metaproject/testing.config.json. loadCompatibleTestingReport reuses a report only if its gitRef + scope match the request (cache-validity check).

Dependencies / integrations. Bun runtime + optional git + the project's own test runner; internal lib/fs, lib/args. Consumed by Code Health (the tests adapter reads normalized reports via loadCompatibleTestingReport) and gdskills (learn --from-test ingests artifacts/latest.json). Init-time file materialization (config, hooks, SKILL.md, wiki page) is driven by templates.ts from the top-level keryx init, not test init.


memory

Purpose. The memory module implements long-term, typed project memory: a durable, agent-facing knowledge base of lessons, decisions, constraints, known mistakes, patterns, and more. Markdown files under .metaproject/memory/ are the source of truth; the module reads, ranks, deduplicates, and consolidates them deterministically by default — pure token/trigram similarity over canonical Markdown (never a generated/inverted index), with an optional embedding rerank (Block C) that is opt-in and always degrades back to lexical. It is a Mem0-style memory layer reimplemented deterministically, and it feeds a "learning signal" (only accepted entries) into gdskills.

CLI surface. memoryCommand:

Command Usage
memory new new <type> [slug] --title "<t>" [--force] — scaffold a draft entry; prints possible duplicates
memory index optionally build the reproducible disposable catalog data/memory/index/index.json; search does not consume it
memory search search "<q>" [--module <m>] [--entity <e>] [--status <s>] [--limit <n>] [--as-of <YYYY-MM-DD>] [--class <semantic\|episodic\|procedural>] [--semantic] [--save-report] — filesystem-pure ranked retrieval by default; --save-report explicitly publishes an ignored bounded runtime report
memory transition transition <path> --to <draft\|accepted\|conflict\|deprecated> [--reason <text>] — explicit validated lifecycle mutation through the guarded atomic seam
memory supersede supersede <old-path> --by <new-path> [--date <YYYY-MM-DD>] — non-destructively replace an entry (sets Supersedes/Superseded-By, closes the old validity interval)
memory ingest ingest --from-<review\|health\|job\|skill-verifier> <path> — ADD/UPDATE entries
memory check integrity lint; non-zero exit on issues
memory reflect cluster entries by tag; create pattern drafts for clusters ≥ minClusterSize

There are 11 entry types (MEMORY_TYPES), all accepted by the type registry and memory new.

Key files. commands/memory.ts (dispatcher), memory/service.ts (createMemoryService facade: create/index/search/ingest/supersede/check), store.ts (Markdown→MemoryEntry parser, incl. the bitemporal header), search.ts (weighted ranking + optional semantic rerank), dedup.ts, text.ts (similarity primitives), ingest.ts (ADD-or-UPDATE), reflect.ts (consolidation), relevant.ts (cross-module lookup), supersede.ts (non-destructive replacement), inject.ts (procedural-memory prompt injection), embedding/ (opt-in embedding index over a configured transformer-compatible adapter), check.ts, config.ts, and templates.ts.

How it works. Markdown-as-database: parseEntry extracts a structured record from # Title, Key: value header fields, and ## sections. Deterministic similarity (text.ts): tokenize + jaccard + trigram titleSimilarity. Weighted linear ranking (search.ts): score = Σ weight × {relevance, recency (exponential decay), confidence, status, scope}. Mem0-style lifecycle: ingest = ADD new or reconcile (append provenance to) near-duplicates, keyed on (source, link, date); reflect = CONSOLIDATE tag-clusters into pattern drafts (no LLM synthesis). Dedup marks a duplicate at title-similarity ≥ 0.8 or summary-jaccard ≥ 0.6 with a shared scope/tag; conflict detection flags candidate decisions/constraints against accepted ones (never auto-resolves). check lints metadata, links, dedup, and conflicts; the generated catalog is optional and its absence is not an integrity failure. Note reflect and relevant bypass the MemoryService interface (5 of the 6 subcommands).

Block C additions (bitemporal + typing + embeddings). Entries gained an optional bitemporal header (Class, Valid-From, Valid-To, Recorded-At, Supersedes, Superseded-By) parsed by store.ts; queries return current entries by default and honor --as-of <date> for a point-in-time view, and supersede.ts records a non-destructive replacement (sets both sides + closes the old validity interval, idempotent — it never deletes). Entries authored before Block C omit these fields and parse exactly as before. Memory typing maps every type to a knowledge class via MEMORY_CLASS_MAP (semantic/episodic/procedural, total and defaulting to semantic); inject.ts splices accepted, current, procedural-class memory into task-implementer / flow prompts (typing.injectClasses defaults to ["procedural"]). An optional embedding index (src/memory/embedding/, resolved through the Capability Seam + Asset Resolver) is a derived, disposable content-hash-keyed vector cache under data/memory/embeddings/ that reranks the lexical candidate pool; it never mutates the Markdown store, and lexical search stays the default and the fallback on any unavailability or error.

Data & artifacts. Canonical memoryRoot is .metaproject/memory/ (Markdown, one subfolder per type). Generated catalogs and optional embeddings live under .metaproject/data/memory/ and are ignored/disposable. Explicit bounded reports live under .metaproject/runtime/memory/search/<run-id>/; no service or default search writes artifacts/latest.*, and no generated catalog is a runtime source of truth. Config is .metaproject/memory.config.json.

Dependencies / integrations. Node builtins + lib/fs, lib/json, lib/args. No embedding runtime is shipped; the default search path is deterministic and an explicitly configured compatible adapter is loaded only on the opt-in semantic path. Consumed by gdskills via relevantAcceptedMemory (the memory→skills learning signal, skills learn --from-memory); init/update scaffold config/index/ skill files via templates.ts. allowAutoAccept is deprecated, ignored, and warns when configured true; ingest/reflection remain draft-only.


flow (tasks)

Purpose. The flow module implements an agent-first, managed work lifecycle: each unit of work ("flow" / "story") is scaffolded into a self-contained package on disk, driven through a strict status state machine, and finished only after passing hard completion gates. The CLI is the sole writer of flow state — agents and humans never edit flow.json or frozen acceptance criteria by hand. The CLI does deterministic mechanics; cognitive work is layered on by gdskills subagents (flow-init, flow-manager, flow-complete).

Naming note. The manifest module key is tasks ("Task Manager") but the command is keryx flow. Both names refer to the same module.

CLI surface. flowCommand:

Subcommand Behavior
init (--issue <url> \| --title "<t>") [--slug <s>] scaffold a flow package
list list all flows with status + task counts
status <id> one flow: status, source, AC state, PR, tasks, last 5 history events
freeze <id> record AC checksum, initializing → ready
start <id> ready → in-progress
task add <id> --title "<t>" [--kind context\|implement\|test\|review\|docs] append a task
task done <id> <taskId> mark a task done
ac confirm <id> <ACn> [--note "<evidence>"] confirm one acceptance criterion
ac update <id> --reason "<why>" re-freeze AC checksum, void prior confirmations
implemented <id> --pr <url> in-progress → implemented, record draft PR
complete <id> [--comment] run completion gates; pass → done, fail → in-progress
block <id> --reason "<why>" * → blocked (saves previous status)
unblock <id> restore previousStatus
check consistency audit across all flows

task/ac are command groups; the atomic verbs are task add, task done, ac confirm, ac update.

Key files. commands/flow.ts (dispatcher), flow/service.ts (lifecycle logic), flow/machine.ts (transition table), flow/store.ts (persistence, id resolution, AC checksums), flow/context.ts (deterministic context collection), flow/types.ts, flow/templates.ts (package + manifest + subagent skill renderers), flow/tracker/github.ts (GitHub adapter).

How it works. The state machine (machine.ts) runs initializing → ready → in-progress → implemented → completing → done, with blocked reachable from any non-terminal state and unblock restoring the saved previousStatus. AC freeze: acChecksum normalizes the criteria file and sha256s it; freeze refuses if there are zero real criteria; assertAcIntact recomputes the checksum before most mutations and throws (directing to ac update) if the file was edited outside the CLI. Completion gates (complete): (1) acceptance-criteria (intact + every criterion confirmed), (2) pull-request (PR exists + checks green, or skipped if tracker unavailable), (3) health (deps.healthGate(cwd)). passed = gates.every(g => g.status !== "fail")skipped gates do not block. Every mutation goes through save() which bumps updatedAt, pushes history, writes flow.json, and appends to journal.md. The service is constructed with FlowServiceDeps (tracker, healthGate, now) for testability.

Data & artifacts. Root .metaproject/flows/. Each flow is a directory <NNN>-<YYYY-MM-DD>-<slug>/ containing flow.json (CLI-owned state), description.md, context.md, plan.md, tasks.md, acceptance-criteria.md, journal.md. Context collection opportunistically reads (all optional) data/gdgraph/artifacts/*, data/health/artifacts/latest.json, and metaproject.json.

Dependencies / integrations. Node builtins (incl. node:crypto) + Bun runtime + optional gh CLI. Internal lib/fs, lib/args, lib/ui. Cross-module: memory/{config,search,store} enriches context.md; health/service (createCodeHealthService().gate) is wired as the completion health gate. The GitHub TrackerAdapter shells out to gh and degrades gracefully (gates become skipped) when it is absent.


review

Purpose. The review module turns review output into a durable, validated package. It supports standalone review flows, reviews attached to an existing Task Manager flow, and ingestion of a pre-existing report. Each package records target identity, reviewer coverage, findings, decisions, learning candidates, and lifecycle status.

CLI surface. reviewCommand provides:

Subcommand Behavior
attach --flow <id> --target <kind> --ref <ref> create a package linked to an existing flow
start --target <kind> --ref <ref> create a standalone managed review package
ingest --report <path> [--flow <id>] --ref <ref> seed a package from a report
status <review-id-or-path> print lifecycle, target, flow, and coverage state
complete <review-id-or-path> validate required artifacts and mark complete
lightweight report-only mode; persist no managed review artifacts

Key files. src/commands/review.ts handles parsing and output; src/review/managed.ts owns package creation, validation, lookup, and completion; src/review/types.ts defines modes, targets, manifest shape, coverage entries, and findings.

How it works. attach-review, review-flow, and ingest modes converge on a single manifest-driven package format. Completion is structural: the service refuses to complete a package when mandatory coverage, decisions, findings, learning, report, or scope artifacts are absent. Flow attachment preserves the implementation lifecycle while keeping review state independently auditable.

Data & artifacts. Attached packages live under the owning flow's reviews/ tree. Standalone packages live under .metaproject/reviews/<review-id>/. The public contract is documented under docs/requirements/managed-review-feedback-loop/.


security

Purpose. security is the agent input/output and artifact security layer. It scans content for secrets, PII, prompt-injection, and data-exfiltration (egress) signals, resolves those into a policy decision (allow → warn → redact → require-approval → block), and evaluates a pass/needs-approval/fail gate that can block a controlled write or CI run. It is deliberately separate from health (which imports dependency findings as quality signals) and from security-audit (dependency/committed-secret scanning): this module protects prompts, external content, generated outputs, and .metaproject/ artifacts. It is deterministic by default (rule + entropy detectors; the model backends are opt-in and default off) and leak-safe — findings never carry raw sensitive values, only fixed-width masks and local-only HMAC hashes. The shipped scope is spec §16 Phase 1+2+3 (engine + CLI + write-seam integrations), extended by roadmap-2026 Block E with modern exfil/PII detectors, a multi-runtime hook registry, a red-team eval harness, and opt-in model backends (below); the always-on gateway mode (Phase 4) remains not implemented.

CLI surface. Dispatched by securityCommand:

Subcommand Behavior Exit
security status print effective config: mode, raw retention, gate, config-checksum, per-policy action 0
security scan <path> [--json] [--source <kind>] scan a file, resolve a decision, write artifacts/latest.{md,json} mode-gated (see below)
security scan-mcp <manifest\|dir> [--json] [--pin] scan MCP tool manifest(s) for injection/exfil signals against a pinned baseline (--pin records the baseline) mode-gated
security check-input [--source <kind>] [--file <path>] [--json] evaluate incoming content (default source untrusted-external; stdin if no --file) mode-gated
security check-output [--target <kind>] [--file <path>] [--json] evaluate generated content (default source generated, target unknown) mode-gated
security redact <path> [--out <path>] mask detected spans; write --out or print to stdout 0
security report [--since <ref>] [--json] aggregate the last scan artifact (no re-scan) into a category summary 1 in ci mode when gate = fail
security policy validate schema-validate the config + verify config checksum 1 on schema/checksum failure
security incidents [--limit <n>] list the append-only incident trail (newest first) 0
security eval [--corpus <injection\|exfil\|structured-pii\|secret\|all>] [--with-model] [--json] run the labeled red-team corpora through the detectors, print a per-detector FN-rate report 1 when a detector breaches its committed threshold
security hooks <install\|uninstall> --runtime <claude\|cursor\|windsurf\|generic-mcp\|all> install/remove the multi-runtime agent-hook registry entries mode-gated

The mode-gated commands honor config.mode: advisory (default) always exits 0; ci exits 1 on a gate fail; enforced exits 1 on fail or needs-approval.

Key files. - src/commands/security.ts — CLI dispatcher, arg/flag parsing, rendering, exit-code mapping, help. - src/security/service.tsanalyze/runScan/runReport/runGate + the createSecurityService in-process contract (advisory-safe check, redact, report, gate). - src/security/detect/ — the deterministic detectors: secrets.ts, entropy.ts, pii.ts (checksum-validated structured PII), injection.ts, egress.ts, exfil.ts (markdown-image/EchoLeak), mcp.ts (MCP-manifest scan), plus index.ts (runDetectors + overlap dedup); opt-in model adapters live under detect/injection/adapter.ts (Prompt Guard 2) and detect/pii/ner-adapter.ts (NER). - src/security/agent-hooks/runtimes.ts — multi-runtime agent-hook registry; src/security/eval/harness.ts — labeled-corpus red-team eval harness. - src/security/resolve.ts — finding construction, action precedence, injection→egress escalation, confidence gate, computeGate. - src/security/redact.ts — fixed-width masks, safe redactedPreview, local-only HMAC key management + hmacHash. - src/security/self-protect.ts — config-checksum / mode-downgrade / disabled-policy self-protection. - src/security/report.ts — report building + committable artifact writing. - src/security/config.tsDEFAULT_SECURITY_CONFIG, load/merge, computeConfigChecksum/verifyConfigChecksum, validateSecurityConfig. - src/security/incidents.ts — append-only JSONL incident trail. - src/security/{schemas,templates,types}.ts — JSON-schema subset, scaffold renderers, and the type surface.

How it works. The pipeline is detectors → resolution/gate → report. runDetectors runs each enabled detector category (entropy additionally gated by the entropy backend flag) and de-duplicates overlapping spans, keeping the strongest signal per region. resolveDecision turns each match into a SecurityFinding, applying a confidence gate (a match below the policy's minConfidence is downgraded to warn) and the injection→egress escalation (a lone prompt-injection signal stays warn; when an egress signal co-occurs it is escalated to the prompt-injection policy action). The decision's action is the strongest across findings by the precedence block > require-approval > redact > warn > allow; computeGate maps findings to pass / needs-approval / fail (any block action or any finding at/above gate.failOn severity fails). Leak-safety is structural: buildFinding never stores the raw value — only an HMAC-SHA256 hash (per-project key, local-only) and a fixed-width [REDACTED:…] mask, with the redactedPreview masking every sensitive span in the surrounding window so a preview can't reveal a neighbouring secret. Self-protection (evaluateSelfProtection) folds extra findings into the decision when the config checksum mismatches (policies edited outside the tool), the mode is downgraded, or a policy is disabled — each also appends an incident. Reports are written to data/security/artifacts/latest.{md,json}; report/gate read that artifact and never re-scan.

Block E additions (roadmap-2026 hardening). Modern data-exfiltration detectors (src/security/detect/exfil.ts) flag markdown-image / auto-render EchoLeak-style leaks (CVE-2025-32711) against a policies.egress.allowlist (deny-by-default), and egress.ts adds always-on SSRF / cloud-metadata host detection (RFC-1918 / loopback / link-local / metadata.google.internal). Structured-PII validators are now checksum-verified (detect/pii.ts: IBAN mod-97, credit-card Luhn, US-SSN range, IP) so invalid-checksum candidates are not flagged. Agent hooks became multi-runtime (agent-hooks/runtimes.ts: claude / cursor / windsurf / generic-mcp), and a labeled-corpus red-team eval harness (eval/harness.ts, security eval [--corpus <injection\|exfil\|structured-pii\|secret\|all>] [--with-model]) proves detection quality against committed fixtures. Two opt-in model backends sit on the backends config seam — both default off, resolved through the Capability Seam + Asset Resolver: a Prompt Guard 2 injection adapter (detect/injection/adapter.ts, backends.injectionModel) and an NER PII adapter (detect/pii/ner-adapter.ts, backends.piiModel). All remain leak-safe and degrade to the deterministic rule detectors when a dependency/asset is unavailable.

Data & artifacts. Config .metaproject/security.config.json (seed-once). Data root .metaproject/data/security/ with subtrees artifacts/ (committable latest.{md,json}), incidents/ (append-only JSONL trail), redactions/, policies/, and raw/. raw/ is gitignored and local-only — it holds the per-project HMAC key (hmac.key, generated on first use) and a report.local.json, so hashes stay unlinkable across machines and no raw sensitive material is committed. Default mode is advisory and default rawRetention is off.

Dependencies / integrations. Node builtins only (node:crypto for HMAC + config checksum, fs/promises, path) + internal lib/fs, lib/args, lib/json, lib/ui. init/update scaffold the config, modules/security.md, and core/security/README.md via security/templates.ts.

Write-seam integrations (Phase 3). A shared in-process guard (src/security/guard.ts, exporting guardOutput / redactRaw / securityFlowGate / formatGuardWarning) wraps the frozen Phase 1+2 engine and is now called at five write seams — the first real inbound calls into this module from other modules:

  • memory ingest (src/memory/ingest.ts, target: memory) — before writing each accepted entry;
  • wiki collect (src/wiki/service.ts, target: wiki) — before writing a collected draft;
  • testing (src/testing/service.ts, target: report) — before persisting the captured raw log;
  • gdctx (src/commands/ctx.ts, redactRaw) — redacts secrets from raw output before persist/summarize;
  • flow complete (src/commands/flow.tsflow/service.ts gate 4, securityFlowGate) — a security completion gate.

Semantics are uniform: advisory (default) reports and continues — it never blocks or mutates (the gdctx seam still redacts detected secrets, a pure safety step); enforced/ci blocks or suppresses the write with a masked category+count reason; disabled is a zero-cost no-op. The guard degrades to allow on any engine error, imports only from the engine + shared libs (so the seam stays acyclic), and never leaks raw content into reasons or logs.

Hooks (Phase 4). Two optional hooks extend enforcement to surfaces outside keryx's own workflows. Both are offered by init only when security is enabled, default on (confirm prompt; accepted under --yes), and no-op when the module is disabled. Both honor config.modeadvisory (the default) warns but never blocks; enforced/ci block.

  • git pre-push gateinstallSecurityPrePushHook (src/commands/init.tsrenderSecurityPrePushHook in src/lib/templates.ts) writes a managed # keryx:security-pre-push:begin…:end block into .git/hooks/pre-push. It runs keryx security scan <file> --source trusted-project over each changed file in the push range and delegates blocking to the CLI exit code (advisory exits 0/warns; enforced/ci exit non-zero and block the push). It coexists with the testing pre-push block and user content, degrades to a skip if keryx is not on PATH, and is recorded in the manifest at security.hooks.prePush. Opt out with --no-security-hook.
  • agent .claude/settings.json hookinstallSecurityAgentHooks (src/security/agent-hooks.ts) merges, merge-safely (a _keryxManaged: "security-agent-hooks" sentinel keeps re-install idempotent and preserves all existing settings/hooks), two Claude Code hooks: UserPromptSubmitkeryx security check-input --source untrusted-external and PreToolUse(Write|Edit)keryx security check-output. Claude Code-specific, project-local, advisory by default; recorded at security.hooks.agent. Opt out with --no-security-agent-hook.

update refreshes each hook only when the manifest already records it.


mcp

Purpose. mcp (Block A, roadmap-2026) is a new, opt-in, cross-cutting module that exposes keryx's existing module service facades over the Model Context Protocol so an external agent can call them as MCP Tools and read generated .metaproject/ artifacts as read-only MCP Resources. It adds no new domain logic: every Tool is a thin wrapper that shapes input, calls one facade method, and returns the typed result. It is stdio-first (an isolated localhost HTTP/SSE transport is a further opt-in), and modules.mcp defaults off — the module entry + config are scaffolded only via init --mcp.

CLI surface. Dispatched by mcpCommand:

Subcommand Behavior
mcp / mcp serve run the stdio JSON-RPC MCP server (default)
mcp serve --http use the isolated localhost HTTP/SSE transport (requires http.enabled)
mcp serve --cwd <project-root> expose a specific project regardless of the MCP client's launch directory
mcp install --runtime <cursor\|claude\|opencode\|generic\|all> write project-scoped client config with --cwd <project-root>
mcp uninstall --runtime <cursor\|claude\|opencode\|generic\|all> remove only the managed keryx client entry
mcp --help / -h usage

Key files. - src/commands/mcp.ts — thin handler; parses serve/--http, never imports the SDK. - src/mcp/server.ts — stdio-first server loop; lazy-loads @modelcontextprotocol/sdk via await import() and hard-fails with an actionable message when it is absent (the sanctioned opt-in exception). - src/mcp/tools.ts — the Tool registry over the gdgraph/gdctx/security/memory/health/testing/wiki/flow/standard/sac facades. - src/mcp/resources.ts — the read-only metaproject://<class>/<relpath> Resource registry (artifacts/wiki/memory), path-confined to each class root. - src/mcp/dispatch.ts, discovery.ts, config.ts, redact-seam.ts, transport/{stdio,http-sse}.ts.

How it works. Tools wrap the module facades (createSecurityService, createMemoryService, createCodeHealthService, createGdWikiService, createFlowService, runValidate) plus gdgraph's pure query module (getAffected/getCycles/getOrphans/loadGraph); there is no logic in mcp/ beyond input shaping. Resources enumerate and read on-disk generated artifacts only — no computation, no mutation — and every URI is resolved and confined to its class root (any path escaping the root is rejected). The module is boundary-guarded: an import test enforces that src/mcp/ imports only service facades + src/lib/* + the security guard seam (never a module's internals), and the golden-rule static guard forbids any top-level import of the MCP SDK anywhere in src/.

Data & artifacts. Reads (read-only) the generated .metaproject/ trees it serves as Resources (data/**/artifacts, wiki/, memory/). Config .metaproject/core/mcp/mcp.config.json (deep-merged over defaults: transport, HTTP host/port default off, Tool include/exclude, Resource roots). init --mcp scaffolds the core/mcp/ structure + config and the modules.mcp manifest entry.

Dependencies / integrations. Node/Bun builtins + lib/* + each module's service facade + the security guard seam; the MCP SDK (@modelcontextprotocol/sdk) is an optional dependency loaded lazily only in server.ts. It is the outbound consumer of every other module's facade — the first cross-module integration that reaches in through the public service contracts rather than the shared file workspace. SAC tools (sac.overview, sac.read, sac.collaboration, sac.propose, sac.review) live in this same registry and deny HTTP with sac_transport_denied before workspace discovery.


sac

Purpose. Shared Agent Context is the local-first collaboration entry point: a file-backed workspace registry, a budgeted Facts / Work / Know-how read path, and a propose/review lifecycle that promotes a completed session into wiki, memory, or skills through guarded owner writers. It stores workspace-relative references, not copies. It is not one of the nine default init modules and has no modules.sac toggle — the CLI verb is always on CLI_ROUTES.

CLI surface. workspaceCommand (src/commands/workspace.ts):

Subcommand Behavior
workspace create --title <t> [--component <ref>] create workspace.json
workspace list / show <id> list or print the manifest
workspace add-resource <id> --kind --uri [--revision] attach a typed ./… ref
workspace overview / read FWK read path (--max-items, --max-tokens, --explain)
workspace propose / review session wrap-up → terminal review + owner write
workspace collaboration <id> read-only collaboration overview
workspace policy-readiness diagnose the opt-in policy-experiment chain

keryx commands omits this verb; use keryx workspace --help.

Key files. - src/sac/index.ts — schemas, validateSacContract, trusted ActorContext, rank/guard. - src/sac/workspace-service.tsworkspace.json, authorize-at-use, POSIX openat/O_NOFOLLOW reads. - src/sac/fwk-service.ts — overview/read, access-receipt ledger, resolvePolicySelection. - src/sac/proposal-lifecycle.ts — create/review; harness vs fail-closed local factories. - src/sac/wiki-owner-writer.ts, memory-owner-writer.ts, skill-owner-writer.ts. - src/sac/receipt-integrity.ts — hash-chained access receipts. - src/commands/workspace.ts — CLI adapter. - src/mcp/tools.tssac.* tools (stdio only). - src/harness/tool/builtin/workspace-context-tool.tsworkspace_overview / workspace_read.

How it works. Three thin adapters (CLI, MCP stdio, harness reads) call the same in-process factories and emit JSON.parse(JSON.stringify(...)) results. Identity is the local OS user. Accept is possible only through createHarnessProposalLifecycleService. Know-how kinds are only wiki | memory | skill. Policy experiment default is enabled: false / killSwitch: true.

Data & artifacts. - .metaproject/workspaces/<id>/workspace.json — primary record. - .metaproject/workspaces/<id>/proposals/ + activity.jsonl. - .metaproject/context-operations/access-receipts.jsonl (+ checkpoint). - Accepted knowledge lands in .metaproject/wiki/decisions/, .metaproject/memory/, .metaproject/project-skills/sac/.

Dependencies / integrations. Flow (read-only Work), wiki/memory/gdskills (Know-how + owner writes), session store (propose --session), security guarded writes, MCP/harness adapters. POSIX required for source-content reads. Operator guide: Shared Agent Context.


rules

Purpose. The rules module keeps a project's root agent entrypoints (AGENTS.md, CLAUDE.md, and manifest-declared variants) in sync with the generated .metaproject/ workspace so agents reliably route through keryx tooling. It (1) syncs — imports each root entrypoint verbatim into .metaproject/rules/ and injects/upgrades a managed "Metaproject" routing block into the root file — and (2) distills — splits large monolithic entrypoints into typed artifacts (project rules, procedural skills, or root-only instructions), rewriting the root to keep only global/always-on instructions plus the managed block.

CLI surface. rulesCommand:

Command Description
rules / rules --help / rules -h usage
rules sync import root entrypoints into .metaproject/rules/, inject managed block, refresh index
rules distill split large entrypoints into high-priority rules + project skills
any other "Unknown rules command" + help + exit 1

Only sync and distill are accepted; the only recognized flag is --help/-h.

Key files. - src/rules/agent-entrypoints.ts — sync engine: discover entrypoints, write import mirrors, inject/upgrade the managed <!-- keryx:index --> block (syncAgentRules, ensureMetaprojectReference). - src/rules/distill.ts — distillation engine: section split + classify + emit rules/skills/root, rewrite root, write index (distillAgentEntrypoints). - src/commands/rules.tsrules sync/distill handler + help.

How it works. Patterns: idempotent writes (writeTextIfChanged/IfMissing), a managed marker block (<!-- keryx:index --> sentinel — everything after is regenerated, everything before preserved), policy migration/self-healing (ensureMetaprojectReference migrates old policy strings, de-duplicates, removes the flow policy when tasks are disabled, appends missing policies), feature-flag gating (enableTasks from modules.tasks.enabled), and symlink de-duplication (realpath-based, so an AGENTS.mdCLAUDE.md symlink isn't imported twice). Distill is a superset of sync: it runs syncAgentRules first, then splits each source into sections via splitMarkdownSections, classifies each heuristically (classifySection — root vs skill vs rule by keyword signal sets), and emits typed artifacts with frontmatter. The classifier is intentionally biased toward rule/skill capture.

Data & artifacts. Reads .metaproject/metaproject.json (modules.<name>.enabled, agentEntrypoints.root) and writes it back with the synced sources. Outputs: .metaproject/rules/<slug>.md (import mirrors), .metaproject/rules/README.md, .metaproject/skills/project-rules/README.md, .metaproject/rules/entrypoints/<slug>.md (distilled rules), .metaproject/project-skills/entrypoints/<slug>/SKILL.md (distilled skills), .metaproject/rules/entrypoints/index.md, refreshed .metaproject/index.md, and the root AGENTS.md/CLAUDE.md (created if missing, managed block injected; fully rewritten on distill). Requires .metaproject/ to exist, else throws.

Dependencies / integrations. Node builtins only + lib/fs (pathExists) and lib/templates (entrypoint/rules/index renderers). Cross-cutting: syncAgentRules/ensureMetaprojectReference are also invoked by init/update during workspace bootstrap/refresh, so this module's sync engine runs beyond its own command. No co-located tests exist — the heuristic classifier and policy-migration logic are the highest-risk untested areas.


agents

Purpose. The agents module manages the global agent bootstrap — an optional managed routing block injected into a runtime's global (home-directory) agent entrypoint so that any project the agent opens routes through keryx tooling. Unlike rules (which manages a project's root AGENTS.md/CLAUDE.md), this operates on per-runtime global config files under $HOME and is idempotent, merge-safe, and fully reversible.

CLI surface. Dispatched by agentsCommand; the only subcommand group is bootstrap:

Subcommand Behavior
agents bootstrap status [--runtime <r>] report per-runtime install state (installed / current / outdated / missing)
agents bootstrap install [--runtime <r>] [--dry-run] inject/refresh the managed bootstrap block in each runtime's global entrypoint
agents bootstrap uninstall [--runtime <r>] [--dry-run] remove the managed block only, preserving surrounding content
agents bootstrap print print the rendered bootstrap block to stdout
agents / --help / -h usage

--runtime accepts claude, opencode, zcode, codex, antigravity, or all (comma-separated; default all). Unknown runtimes error with exit 1. status is the default action when no action is given.

Key files. - src/commands/agents.ts — CLI dispatcher, arg/flag parsing, help. - src/agents/bootstrap.ts — the runtime registry (AGENT_BOOTSTRAP_RUNTIMES), block renderer, managed-block sentinels, and agentBootstrapStatus/installAgentBootstrap/uninstallAgentBootstrap/resolveAgentBootstrapRuntimes.

How it works. Each runtime maps to a global entrypoint path under $HOME (.claude/CLAUDE.md, .config/opencode/AGENTS.md, .zcode/AGENTS.md, .codex/AGENTS.md, .config/antigravity/AGENTS.md). install renders the routing block between the <!-- keryx:global-bootstrap --> / <!-- /keryx:global-bootstrap --> sentinels and injects or upgrades it in place, preserving all surrounding user content; re-installing an up-to-date block is a no-op. status compares the on-disk block against the rendered one (installed/current/outdated/missing). --dry-run prints planned writes/removals without touching files.

Data & artifacts. Reads/writes the per-runtime global agent entrypoint files under $HOME only. It does not read or write the project .metaproject/ workspace.

Dependencies / integrations. Node builtins + lib/args, lib/fs, lib/ui. Standalone: no cross-module imports.


orientation

Purpose. Orientation provides bounded startup context so an agent sees the project-root Metaproject entrypoint, current graph map, wiki index, and freshness state before broad navigation. It is an opt-in presentation/integration layer, not a new source of project knowledge.

CLI surface. keryx orient [<runtime>] emits formatted context; orient install-hook --runtime <id|all> and uninstall-hook manage compatible turn-start hooks. Hook installation is supported for Claude, Codex, and Cursor; Windsurf and Zed are reported as unsupported because they lack a compatible context-injection hook.

Key files. src/ctx/orient.ts builds the bounded project-root .metaproject/index.md excerpt and the graph/wiki portions; src/ctx/orient-runtimes.ts owns runtime-specific locations, merge/strip logic, formatting, and validation; src/commands/orient.ts is the CLI adapter.

How it works. Orientation reads .metaproject/index.md only from the cwd treated as the project root (never from ancestors), retains at most 60 useful lines while skipping low-value Data/Refresh sections, and appends an explicit truncation/read-the-full-file marker when needed. It also reads and bounds the existing graph/wiki artifacts, then formats the combined output for the selected runtime. Installers modify only their managed sentinel/config entry and preserve surrounding user configuration.


harness

Purpose. The harness (src/harness/) is keryx's own agent runtime: the loop that lets a model operate on a project through controlled tools, with a policy engine above it, an OS sandbox below it, and an append-only evidence record beside it. It is not a .metaproject/ module in the manifest sense — nothing enables or disables it — but it is the largest subsystem in the repository and the one every other runtime surface (shell, serve, sessions) sits on.

CLI surface. Dispatched by harnessCommand:

Subcommand Behavior
harness run --provider <p> --model <m> [--base-url <url>] [--record <path>] "<prompt>" one prompt through the run loop; prints one structured JSON blob
harness exec [containment flags] -- <path> [args...] one contained subprocess under the OS sandbox
harness extension --spec <path> dispatch one declared extension (the only path that reaches checkApproval)
harness wave --spec <path> plan and run a declared multi-agent wave
harness replay --record <path> [--fixture <p>] [--write-fixture <p>] [--json] validate a replay fixture against a recorded run
agents external list [--json] [--no-probe] / probe <id> [--json] inspect the external agent registry and its three-state availability; read-only, spends no quota (src/commands/agents-external.ts)

Key files. - src/harness/run/run.tsrunOffline, the assembled loop: startup → context manifest → provider stream → policy decision → budget/loop guards → tool executor → redaction + append-only session → completion gate. - src/harness/policy/engine.tsdecide() over seven risk classes. - src/harness/mutation/guard.ts, approval.ts — structural safety before policy; fingerprint-bound single-use approvals. - src/harness/process/sandbox/ — Seatbelt and bubblewrap launchers, the loopback allowlist proxy, credential masking, the fail-closed adapter. - src/harness/completion/gate.ts — the completion verdict; flow/managed-flow-port.ts is its only route into Task Manager. - src/harness/replay/replay.ts — fixture build and validate-log comparison. - src/harness/child/, extension/ — child dispatch with budgets, extensions and bounded waves. - src/harness/external/ — the external agent runtime: a two-entry registry described as data, one pure codec per CLI (argv, event parsing, failure classification), the fail-closed runtime block validator, the copy-then-strip child environment, prompt assembly, supervision, and runtime.ts composing them. - src/capability/external-agents.ts — the opt-in gate: the user-global externalAgents config, the project manifest opt-in, and the remote/CI hard disable. src/harness/run-external-factory.ts builds the runExternal hook spawn_subagent accepts, or returns undefined when the capability is off.

External children. A dispatch may carry an optional runtime block ({"kind":"external","agent":"claude-cli","sandbox":"read-only"}) asking for a vendor coding CLI — codex exec or claude -p — instead of the in-process loop. The hook runs after admission, so the budget ledger and the depth/child caps have already applied and no second spawn path exists. Execution is read-only in a disposable git worktree with a stripped environment and a restricted tool roster; worktree-write is a valid contract value the runtime refuses with its own named reason. The capability is off by default, opt-in through the user-global externalAgents config (plus keryx init --external-agents inside a workspace), and hard disabled on a remote transport or under CI. keryx never reads a vendor credential store, so availability is available / binary-missing / not-probed and never "ready". Every codec is pure, and the whole layer is tested offline against the recorded transcripts in fixtures/external/ — nothing has been run against a real vendor process.

How it works. Every decision core is pure with clock and idSeq injected; the only effect surfaces are injected adapters (MutationAdapter, ProcessAdapter, ProviderPort, ToolExecutorPort), which is what makes a run deterministic and offline-testable. Tool results are scanned and redacted before persistence — a failed scan blocks the write entirely. The completion gate reports and never advances flow state itself; the harness cannot write flow.json, and three independent mechanisms enforce that.

Data & artifacts. Sessions live under the per-project session store (see below), not under .metaproject/data/. --record writes a run record wherever the caller points it.

Dependencies / integrations. Reads .metaproject/ through an optional metaproject port for blast-radius escalation; calls src/security for the redaction scanner; reaches Task Manager only through ManagedFlowPort.

See the harness page for the feature-level tour, including what the harness deliberately does not do.


sessions & shell

Purpose. src/session/ is the durable per-project conversation store, and src/commands/shell.ts + src/tui/ are the interactive front end over it. This is where tools actually run: the non-interactive harness paths register none.

CLI surface.

Command Behavior
shell [-c\|--continue] [-r\|--resume [id]] [--provider <p>] [--model <m>] [--agent\|--chat] [--tui\|--no-tui] start the interactive agent shell
sessions list [--json] sessions for this project, newest first; forks marked
sessions fork <id> [--title "<t>"] [--json] branch a session, ancestry recorded
sessions export <id> Markdown transcript
sessions path the on-disk sessions directory
shell /sessions interactive session picker + live switch in TUI
shell /status session inspector (TUI modal; readline text dump). /session-info / /info are not aliases
shell /flows [id] browse project flows, newest first; List selects with ↑/↓, Detail scrolls with ↑/↓ and switches flows with [/]; optional one-package detail
shell /theme [name] no arg: TUI picker (live preview, applies on Enter/Apply); [name]: applies immediately on any surface
shell /interrupt hard-stop main turn in TUI
shell /delegate <agent> <task> hand a bounded read-only task to an external agent CLI; refused with a named reason when the capability is off (the default)

session is a singular alias for sessions.

In the TUI, submitting a normal message while the main turn is busy opens a selector: the main queue (default — displays in a dedicated panel above the input, drains FIFO after the current turn, each item has clickable Force/Edit/Delete buttons; Ctrl+Q for keyboard-only mode; /queue command still available) or side-1, a read-only worker outside main history, processed sequentially and surfaced in the sidebar and transcript. /status and /flows are always allowed while busy.

Key files. - src/session/store.ts — create, list, find, load, persist, compact, fork; atomic multi-file writes. - src/session/paths.ts — project-keyed directory layout under the data root. - src/session/compact.ts — model-window compaction that preserves the archive. - src/commands/sessions.ts, src/commands/shell.ts, src/tui/ — CLI and TUI adapters. - src/tui/external-operator.ts, external-inspector.ts, external-transcript.ts, addressee-queue.ts, external-delivery.ts — the operator surface for external children: the live run store, the Work/Meta/Command modal, per-addressee message queues, and the delivery planner. Every formatter is pure, so the whole surface is exercisable with no renderer.

How it works. Sessions never cross project roots: the key is the git toplevel, or the absolute cwd outside a repository. Each session directory holds context.jsonl (the model window a resume loads) and archive.jsonl (the full audit log, which survives /compact); transcript.jsonl is a legacy mirror. All writes are temp-plus-rename. Directories are forced owner-only below the data root. fork copies both files into a new session and sets parentSessionId, so a branch starts from the same history without sharing files with its source.

Data & artifacts. <data root>/sessions/<project-key>/<session-id>/. Nothing under .metaproject/.

Dependencies / integrations. lib/config-dir for the shared root and its permissions; the harness for the turn loop.


sync

Purpose. Keep the derived layers — graph, wiki, memory — in step with the code after a git pull, a fetch, or a branch switch, so an agent never reads a map of a tree that no longer exists.

CLI surface.

Subcommand Behavior
sync advisory report: per artifact, either "up to date" or the added/changed/deleted counts since it was built
sync --apply rebuild each stale artifact incrementally and advance its provenance
sync install-hooks / uninstall-hooks post-merge + post-checkout git hooks that run the report

Key files. src/commands/sync.ts (dispatcher), src/sync/provenance.ts (per-artifact commit record and SYNCED_MODULES), src/sync/diff.ts (the code-only diff since a commit), src/sync/hooks.ts (hook install/removal).

How it works. Each synced artifact records the commit it was built from. sync diffs that commit against HEAD, filters to code paths, and reports. With --apply it calls the module's own incremental builder — gdgraph build, wikiCollect --changed plus a reindex, memory index — then advances provenance. An artifact with no provenance is built as a baseline. When the wiki updates and files were deleted, orphaned pages are pruned; a human-owned page whose module disappeared is reported rather than deleted. Outside a git repository the command says there is nothing to sync. The bare report always exits 0 — it is advisory, and the hook decides what to do with it.

Data & artifacts. Reads and advances the provenance records; delegates every artifact write to the owning module.

Dependencies / integrations. gdgraph, gdwiki, memory.


serve

Purpose. A loopback-bound HTTP entry over the harness, so a bot or another product can drive turns. Opt-in and off until started; not a manifest module.

CLI surface. serve with the flags in the CLI reference.

How it works. Authentication happens before routing, so an unauthenticated caller cannot distinguish a known path from an unknown one. The remote policy profile is compared against the local one on every turn and a weaker remote profile is refused. There is no approval transport yet: a turn whose decision is ask ends in a recorded denial rather than being auto-approved.

Key files. src/commands/serve.ts, src/lib/serve-*.ts.

Dependencies / integrations. The harness (runOffline), the project registry that remote entry addresses projects by (projects).


shared-lib

Purpose. src/lib/ is the cross-cutting utility toolkit every feature module and command handler builds on. It provides six small, dependency-light modules: CLI argument extraction, filesystem predicates, JSON file I/O, interactive terminal prompts, terminal styling/UI output, and — by far the largest — a template library that renders the entire .metaproject/ workspace scaffold (Markdown manifests, an HTML dashboard, shell git-hooks, config files, skill/rule docs) as strings. There is no orchestration or domain logic here.

CLI surface. None — lib/ is a library layer, not a command. It is consumed by every other module (import counts: fs 32, json 10, args 8, templates 8, ui 6, prompt 1).

Key files / API. - args.tsoptionValue(args, name): the token following a named flag (the project's only flag reader). - fs.tspathExists (most-used helper), toPosix, isPathInside (workspace-boundary guard). - json.tsreadJsonFile<T> (rethrows Invalid JSON in <path>), readJsonFileOr<T> (returns fallback on failure). - prompt.tsconfirm, choice<T>: TTY prompts that return defaults when stdin isn't a TTY (CI-safe). Depends on ui.ts. - ui.tscolorEnabled, style, symbols, banner, heading, statusLine, nextSteps, note, and help* renderers. - templates.ts — ~23 render*(): string builders (~1836 LOC) that materialize the generated agent workspace: renderIndexMarkdown, renderMetaprojectDashboardHtml (+ MetaprojectDashboardData type), agent-entrypoint/rules renderers, config/gitignore renderers, git-hook script renderers, and gdgraph/gdctx scaffold renderers.

How it works. Flat utility library — named functions, no classes/DI/barrel; callers import files directly. Key patterns: pure string templating (the whole .metaproject/ scaffold is generated from TS template literals, no engine), feature-flag composition (renderIndexMarkdown/dashboard take 8 boolean enable* flags and conditionally include sections), TTY-aware graceful degradation (ui/prompt detect isTTY/NO_COLOR/FORCE_COLOR), fail-soft I/O (readJsonFileOr/pathExists swallow errors), and a self-contained HTML dashboard (all CSS/JS + page Markdown inlined because fetch is blocked under file://).

Data & artifacts. lib/ reads no config itself but produces config content (renderGdctxConfig emits gdctx.config.json defaults). ui.ts/prompt.ts read env vars NO_COLOR/FORCE_COLOR. The strings it renders become nearly every file init/update/dashboard/rules write to .metaproject/.

Dependencies / integrations. Node/Bun builtins only (fs/promises, path, process, readline/promises); no third-party runtime libraries. The only intra-lib edge is prompt.ts → ui.ts; lib/ depends on nothing else (it is the bottom layer). templates.ts is effectively the source of truth for the agent-facing .metaproject/ contract — the workspace format lives there.