All articles

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.

ygwa6 min read
cdrarchitecturedecisionsdapei-skillevolution

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:

  1. Phase 1 — Headless engine: CLI dapei scan ./mall produces YAML + VitePress site.
  2. Phase 2 — VS Code extension: Webview with React, calls back into the local engine.
  3. 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: fact

Tree-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 that unknown is 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:

  1. 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.
  2. 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.
  3. Cross-repo capability maps — once domains/ and business-rules/ aggregate stably across repos, the same evidence[] 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