CDR from dapei-skill to Fabriq Desktop: Four Hard-Earned Moves
CDR keeps code knowledge alive by 'AI as Scanner, Engine as Validator.' In dapei-skill we shipped it as a headless engine powering a static portal. In Fabriq Desktop we kept the philosophy but rewired the architecture around symbol handles, an embedded engine, and a strict workspace/feature boundary.
TL;DR. CDR (Cognitive Development Records) turns "knowledge that rots in heads and docs" into structured, evidence-anchored YAML assets. dapei-skill proved the model — AI as Scanner, Engine as Validator. Fabriq Desktop kept the model and rebuilt four load-bearing pieces around it: embedded the engine with subprocess fallback, anchored evidence by symbol handle instead of line number, promoted the knowledge UI into a first-class React view inside the workspace, and made workspace/feature dimensions a runtime and CI-enforced boundary. This post walks through what shipped, what broke, and how each move was earned.
1. How we did it in dapei-skill
Our first CDR loop ran outside any IDE. The model was simple and almost too clean:
- A headless Node engine daemon ran CDR pipelines: index → profile → entries → behavior → validate → persist.
- Tree-sitter owned the deterministic work (CodeGraph, AST, file existence). LLM workers handled the semantic extraction and emitted YAML artifacts with
sources[]evidence chains. - The output was a static VitePress portal — site generation over a
docs/as-is/tree, browsable from any browser.
Three-stage rollout, all confirmed in docs/cdr/tech-choose.md:
- Phase 1 — Headless engine: CLI
dapei scan ./mallproduces YAML + VitePress site. - Phase 2 — VS Code extension: Webview with React, calls back into the local engine.
- Phase 3 — Standalone desktop OS: Electron shell, full multi-worktree orchestration.
It worked. We got audit-ready behavior assets, real Symbol-anchored evidence, and confidence levels the team trusted. But several seams started hurting as soon as a second developer tried to use it daily.
2. Four problems we hit
2.1 The subprocess was a seam with no clean home
Every capability call crossed an OS process boundary. The renderer couldn't safely run analysis; cold-start cost made tests slow; integration with the rest of the desktop (file watching, worktree ops, agent sessions) meant writing two adapters for the same operation — one in-process, one over IPC. Engine in a separate repo, engine shared as a subprocess, engine mocked in tests — three sources of truth.
2.2 Line numbers move, evidence breaks
We initially anchored every sources[].line with an absolute integer. Add one comment above a method, shift one block up in a refactor, and the cell that said "fact" became "unknown" overnight. Engineers lost trust: "I didn't change anything, why is the claim wrong?"
2.3 The portal was a sandbox
A VitePress iframe felt like the right way to render the YAML we produced, but it never felt like it belonged to the workspace. The iframe was reachable from P3, but engineers rarely re-opened it. The documents lived in a tab the user had to remember existed — not inside the daily flow.
2.4 Two surfaces, one filesystem, no rule
Code lives under repos/<repo>/, cognition lives under docs/as-is/. Both surfaces write to the same disk. Without a runtime rule, a feature worktree that wanted to "just append one behavior" could dirty the workspace baseline. We had no enforced boundary — only convention.
3. Four counter-moves in Fabriq Desktop
3.1 Embedded the engine (subprocess kept as fallback)
Fabriq Desktop treats the engine as a library with an IPC adapter, not as a daemon.
engine-client.runCapability(id, input, ctx)is the typed edge every service uses.- The inline engine satisfies the surface in dev and tests; the subprocess engine satisfies the same contract in production against a real dapei-skill binary.
- One capability contract, two implementations, Zod-validated payloads on both sides.
What changed for the renderer: the subprocess boundary no longer leaks into product code. Tests run against the inline client, production hits subprocess. No two-adapter drift.
3.2 Anchor evidence to symbol handles, not line numbers
<file>:<line> was upgraded to <file>:<TypeName>#methodName>:
sources:
- file: src/main/java/.../OrderController.java
symbol_handle: OrderController#createOrder
kind: factTree-sitter resolves the handle on every scan. Code can shift across the file by any number of lines; the symbol's identity is preserved. CI checks at scan-time: every handle must currently resolve. If it stops resolving, the artifact demotes to inference automatically — never silently to unknown.
The line number is still shown in UI for human navigation, never used as a primary key.
3.3 Promoted Knowledge to a first-class React view (P3)
The iframe is gone. P3 Knowledge now ships as a native React surface inside the desktop — Browse, Assets, Spec, Pipeline tabs read directly off the workspace's docs/as-is/ index. The YAML, the call graph, the evidence cards all render in-context.
VitePress has been demoted to an optional export (cdr.doc.generate → .dapei/docs-portal/) for external sharing, not for daily use.
What this changes: a knowledge asset opened in P3 is the same object as a knowledge asset in a CMR preview in P5. One identity, many surfaces.
3.4 Dimension rules, enforced at runtime and in CI
We drew a hard line: anything that writes to the workspace surface (docs/as-is/, .dapei/cognitive/, repos/) is blocklisted in feature dimension:
// packages/engine-client/src/dimension-rules.ts
const WORKSPACE_WRITE_BLOCKLIST = [
/^cdr\./,
/^repos\.(add|remove|sync)$/,
// ...
];In the renderer, attempting one of these calls from a feature surface returns:
{
allow: false,
code: "DIMENSION_BLOCKED",
message: "capability 'cdr.behavior.upsert' is a workspace-dimension write ..."
}And there's a CI script (scripts/check-dimension-rules.ts) that scans packages/core/src/capabilities/ and asserts that every workspace-write capability is in the blocklist. Two checks, one rule, no manual audit.
Result: a feature worktree can only read cognition. Promoting new claims back to the baseline goes through the CMR (Cognitive Merge Request) step in the Close Wizard, where every change is a structured proposal the human explicitly approves.
4. What we kept, what we threw away
Kept. The whole dapei-skill core survived intact:
- "AI as Scanner, Engine as Validator" — the boundary is what made CDR trustworthy, and we did not move it.
- Tree-sitter as the deterministic layer.
- Zod schema + Evidence Validator as a dual gate (LLM output → Zod parse → Tree-sitter evidence re-check before commit).
- The three-level evidence scale (
fact/inference/unknown) and the policy thatunknownis never a terminal state — there's always a "confirm / ask the agent" path.
Threw away. Two architectural accidents:
- The VitePress portal as primary delivery. It shipped; it worked; nobody opened it twice.
- Daemon-only engine wiring. We paid in adapters and we paid again in cold-start latency.
Reworked. The dual-dimension rule is now a first-class concept, not a documented convention. Every ADR that touches it points to the blocklist as a single source of truth.
5. What's next
Three things on the runway, all spelled out in docs/cdr/technical-spec.md but worth flagging:
- Cognitive Merge Request (CMR) — when a Feature closes, the engine drafts structured proposals (
update,add,archive) for behavior / state-machine / domain / rule deltas. The user approves each one. No silent baseline writes from a feature. - HYBRID baseline refresh — manual "Sync Baseline" is the primary trigger; auto-detect "main moved forward" is a prompt; advisor "stale artifacts" is a nudge; remote webhook is opt-in. None of these run inside a feature worktree.
- Cross-repo capability maps — once
domains/andbusiness-rules/aggregate stably across repos, the sameevidence[]chain lets P3 render product-level flow diagrams that cross repos.
CDR's bet was that code can be re-read as knowledge if the seam is drawn at the right place. In dapei-skill we proved the bet. In Fabriq Desktop we moved the seam from "between processes" to "between dimensions" — and from "absolute lines" to "symbol handles." The rest is engineering.
If you're using CDR patterns from dapei-skill: start by porting your evidence chains to symbol handles. Everything else flows from that. PRs welcome in ygwa/fabriq-desktop.
Related articles
AI Scanner, Engine Validator: Why We Don't Ask LLMs to Count Lines
Pure static analysis is brittle. Pure LLM analysis hallucinates. Fabriq's third path draws a clean line: Tree-sitter and ASTs answer 'where is X called?' deterministically; LLMs answer 'why is X called?' with fuzzy reasoning. The orchestrator ties them together with evidence chains that are auditable by construction.
Why We Chose Electron (and We're Not Sorry)
Electron gets a bad rap. For a v0.1 desktop shell that must integrate deeply with local processes, Node.js native modules, and external LLM backends — the alternatives were worse. Here's the trade-off analysis that led to our decision.
AI Scanner, Engine Validator: Stop Asking LLMs to Count Lines
Tree-sitter and ASTs answer 'where is X called?' deterministically. LLMs answer 'why is X called?' with fuzzy reasoning. Fabriq draws the line in the right place — and the result is audit-ready cognitive artifacts.
