English · 简体中文
Normify · Normalized Architecture Map Builder
Describe an entire project as a fractal module tree that both humans and AI can read: the AI analyses and authors, a deterministic engine validates, compiles and renders — open any module and you get a finer sub-graph.
---
## 0. TL;DR
Normify is a **DeepSeek Harness (DSH) plugin** and, at the same time, **a development workflow written for AI agents**:
- **For the AI**: a `normify-gen` skill plus **7 primitive tools** (`normify_project` / `read` / `write` / `check` /
`build` / `render` / `change`, dispatched by `op`; the 31 legacy tool names remain available via `legacyTools` for
the transition) — the model turns a repository into a module-tree structure database and keeps it alive through
development (design-first planned trees → implement → close the change).
- **For the engine**: zero-tolerance validation (L1 write-time / L2 project-wide / L3 frozen artifacts),
deterministic compilation (five artifacts with a self-verifiable `receipt.sha256` chain) and
render datasets (`renders/`) that decide *how each level is drawn*.
- **For humans**: a **single-file interactive architecture map** (`normify.html`) — drill-down levels, hover
descriptions, one-click zh/en switch, deep links, multi-tree, **API-anchored arrows**, cross-level aggregation,
zoom and search. Zero external dependencies: open it by double-clicking.
> It is not a "render a picture and you're done" tool: the structure data and the code are **contracts for each other**.
> `normify_check{op:"drift"}` detects drift after every change, and `normify_change{op:"close"}` (zero-error enforced)
> closes the loop, so the map never drifts away from the code.
Engine level: 14 modules, API-anchored arrows (each arrow lands on a concrete API row), labelled subsystem dependencies, dashed cross-level aggregation
## 1. What problem does it solve?
| Pain | How Normify answers it |
| --- | --- |
| Diagrams rot the moment they are drawn | The structure data is **validatable source data**: `normify_check{op:"drift"}` reports fingerprint drift, `normify_change{op:"close"}` enforces zero errors |
| Diagrams are too coarse to show contracts | Granularity goes down to a **single functional unit**; APIs live on leaves and arrows can anchor to a concrete API (`from_api` / `to_api`) |
| AI cannot see the whole picture while editing | `normify_read{op:"brief"}` returns target contracts, impact surface (who depends on me), policy constraints and an acceptance checklist |
| Design-after-code always drifts | **Planned-first**: create the tree in `state: planned`, then `normify_change{op:"close"}` refreshes fingerprints and activates planned modules (legacy: `normify_module_refresh(activate)`) |
| Conventions rely on discipline | `policy.yml` rules (dependency direction / forbid / acyclic / depth / cross-tree / naming) are enforced by `validate` |
| Full regeneration is too heavy for big repos | Incremental regeneration: only the affected subtree is rebuilt, `layouts_to_review` names the levels to re-check |
## 2. Features
### 2.1 Data model — fractal and redundancy-free
- **One element**: the whole database is made of identical **modules** (each module = one Markdown file).
- **Only `parent` is stored**: one-way reference; `children` is derived, so parent and children can never disagree.
- **APIs only on leaves**, stored once: aggregation, statistics and indexes are compile-time derivations.
- **Two kinds of edges**: containment (tree edges, navigation skeleton) + dependency (arrows, cross-subtree and cross-tree, coloured by `kind`).
- **Path-style ids + immutable uid**: the AI navigates level by level like a binary search; `uid` survives renames and moves,
so diffs stay stable.
- **Unlimited depth** (since 0.5.0): split as fine as you need; depth is no longer a reason to merge modules.
### 2.2 Three validation layers, fail-closed
| Layer | When | What |
| --- | --- | --- |
| **L1** | every write | required fields, id grammar, uid, parent consistency, bilingual lengths, source/apis/deps shapes, state/replacement |
| **L2** | `normify_check{op:"validate"}` | uniqueness, file↔id mapping, leaf/non-leaf rules, globally unique API keys, dependency targets, cycles, render-data cross checks, policy, change log, optional repository evidence (source existence + fingerprint match) |
| **L3** | `normify_build{op:"build"}` | nothing is emitted when any error exists; five artifacts (tree/outline/api-index/structure-manifest/receipt) plus a `receipt.sha256` receipt chain |
Every diagnostic carries `severity / code / message / subject / evidence / supportedFixes` so the **AI can fix it itself**.
### 2.3 Renderer — single file, drill-down, API-direct
- One HTML file (inline CSS/JS, **no external deps**, no telemetry); easy to archive or send to a colleague.
- Drill-down levels + breadcrumbs + search (module/API) + outline view + API browser.
- **API-direct arrows**: leaf boxes show API detail rows and arrows anchor to a concrete API row;
multiple edges sharing one API row are fanned apart automatically.
- Cross-level dependencies aggregate into dashed `×N` edges (hidden by default; toggle with the toolbar or `?agg=1`).
- Deep links: `#module=`, `#api=`, `#view=outline`, `?lang=zh|en`, `?agg=1`; zoom, hover highlight, light/dark theme.
- **Geometry self-check**: the renderer is built around per-level geometry assertions (lines stay inside the viewBox,
do not hug, cross or overlap boxes); the early standalone checker script is not shipped in this repository.
### 2.4 Companion development — design first
```
normify_change{op:"open"} → normify_read{op:"brief"} → normify_check{op:"check"}
→ normify_write{op:"batch"}(state=planned) → 【write the code】 → normify_change{op:"close"} (zero-error enforced)
→ verified + revision.after
```
- **Planned state**: a module may exist before its source does (`fingerprint: pending`); `normify_check{op:"validate"}` allows it.
- **No fake activation**: activating or closing before the source lands is refused.
- **Closing is a loop, not a flag**: refresh fingerprints → validate (zero-error) → build (optionally render) → mark
`verified`; any failing step leaves the change untouched.
- **The map follows the code**: `normify_check{op:"drift"}` uses `git diff` plus untracked files to find affected modules and drift;
`normify_write{op:"patch"}` updates them.
### 2.5 Policy first (`policy.yml`)
| Rule | Purpose |
| --- | --- |
| `dependency-direction` | layer order defines the allowed dependency direction (e.g. plugin → tools → engine) |
| `forbid-dependency` | forbid given from → to dependencies (filterable by kind / state) |
| `acyclic` | the dependency graph must be acyclic (optionally including cross-tree edges) |
| `max-depth` | id segment limit — **optional**; omit it and depth is unlimited (default since 0.5.0) |
| `cross-tree` | cross-tree strategy: `forbid` / `allow` / `require-to-api` |
| `naming` | regex over id segments inside a scope |
Once installed, `normify_check` (op=validate/check/drift) and `normify_build` (op=build) enforce it: **fix the design, don't bypass the rule**.
## 3. Release highlights
### v0.7.0 — default tool surface switched to 7 primitives (current)
- **7 primitives by default**: `normify_project / read / write / check / build / render / change` (dispatched by `op`,
see §7); resident schema size and tool-selection ambiguity drop significantly.
- **Smooth transition**: `config.legacyTools: true` additionally registers the 31 legacy names (one minor);
`config.toolset: "full"` restores the old 31-tool surface completely.
- **Unchanged**: structure-data format v1, five artifacts + SBOM + `verify` receipt chain, CLI commands/exit codes,
installation and DSH compatibility range.
- **Evaluation**: new retrieval-layer evaluation (n≥3, reproducible — see [docs/EVAL-RESULTS.md](docs/EVAL-RESULTS.md));
old A/B wording changed to "n=1 feasibility experiment" (methodology in [docs/EVAL.md](docs/EVAL.md)).
- **Distribution & decision gates**: new [docs/MARKET.md](docs/MARKET.md) (official market / dshbase / dshhub materials);
ADR-0011 (no in-Harness UI panel for 0.7), ADR-0012 (no MCP adapter for 0.7; CLI-first).
- Verification: `pack-smoke --all --strict` **PASS 43 / FAIL 0 / WARN 0**; platform-contract 25/25 (7-primitive / legacy / full modes).
### v0.6.0 — Phase 1 internal refactor (core/dsh/cli + CLI + SBOM)
- `src/core` (pure-Node engine) / `src/dsh` (DSH adapter) / `src/cli` (cross-agent CLI); `lib/` no longer committed.
- Added CLI, structure SBOM (`structure-manifest.json`), self-verifiable `receipt.sha256` chain, `normify verify`,
semantic structure diff.
- DSH adapter: real `output.schema` / presenters / destroy approval; test & CI matrix; much faster write path.
- **Public surface unchanged**: 31 legacy tool names, data format v1, installation contract (7 primitives are opt-in).
### v0.5.5 — P0 stop-the-bleeding: support the current DSH 0.2.x
- **Current DSH only**: `engines.dsh` is now `>=0.2.1-rc.1 <0.3.0` (target runtime: DSH `0.2.1-rc.5`).
Only the current DSH line is supported; stay on 0.5.4 if you still run 0.1.x.
- **Security fixes**: `normify_sync` rejects `-`-prefixed git diff ranges (option injection); `normify_render`
accepts a plain file name for `out` only (out-of-root names fail with `render/invalid-out`); and
`normify_fingerprint` accepts repo-relative POSIX source paths only — out-of-root paths and symlinks are treated
as missing rather than read.
- **Self-verifiable L3 receipts**: `receipt.json` no longer lists itself in `artifacts`; a sibling `receipt.sha256`
sidecar covers it, so all five artifacts can be recomputed independently.
- **Data correctness**: reserved `index` last segment rejected before writing (`structure/id-reserved`), explicit
errors for empty batches / orphan upserts / unreadable files (`args/empty-batch` / `structure/parent-not-exist`),
cycle detection back in L2 (`structure/dependency-cycle`), and `delete`-only changes no longer deadlock
`change_close`.
- **Renderer safety**: HTML `` escaping and the `/s+/g` fix; atomic writes for artifact output.
- **Docs**: installation rewritten for the DSH 0.2.x plugin manager / plugin market; compatibility table, tool count
(31), source size (17 `.ts` files / ~7.6k lines) and test suites (5) aligned.
### v0.5.4 — the four tool-side defects found by the second A/B run
Round 2 switched the project (a **spreadsheet formula engine + CLI**; same spec discipline, 88 hidden black-box
checks plus **differential fuzzing**). Final scores: B 88/88, A 87/88 (the single gap being one §5.2 ordering rule).
This release fixes the four *tool-side* issues that run exposed:
- **`mode:"patch"` silent no-op is now rejected**: `items:[{patch:{id, tags:[...]}}]` (one nesting level short) used to
return `ok:true, count:1` while changing **nothing** — the worst kind of false success. It now fails with
`args/invalid-patch`, echoing the received keys and the correct shape `{patch:{id, patch:{...}}}`. A single-module
`normify_module_patch` with an empty patch now fails with `args/empty-patch` too (passing only `expect_updated_at`
no longer slips through).
- **`normify_module_refresh` no longer hard-requires git**: with a non-git `repoRoot` it used to fail
(the agent had to `git init` just to activate modules); it now degrades with a `refresh/git-unavailable` warning.
- **Clearer `change_open` acceptance errors**: putting a `{zh,en}` object into `acceptance` used to produce one vague
error; it now says which entry is wrong ("entry #N is not a non-empty string … acceptance only accepts plain
strings") and suggests putting localized text in `title`/`intent`.
- **`normify_help` gained `topic:"tool:"`**: the `tools` topic now lists required/optional params per tool, and
the new topic prints a full parameter tree (type / description / required), generated live from the registry and
therefore in sync with runtime validation.
### v0.5.3 — the four friction points found by a real companion-development A/B
These come from an actual A/B run: two AIs implemented the same backend spec, one with the plugin driving the
companion flow, one writing plain code. Both scored **42/42** on a hidden black-box suite. The plugin side shipped an
extra 41-module / 110-API / 10-layer architecture dataset — and hit the four rough edges below:
- **`normify_help` now takes a `topic`**: it used to ignore its arguments entirely and always return the same field
cheat-sheet, so the agent read the plugin source just to get exact parameter names (~4 minutes lost). Topics:
`fields` (default) / `deps` (arrows + API-direct) / `renders` / `flow` / `tools` / `policy` / `errors` / `all`.
An unknown topic now **fails loudly** and lists the valid ones instead of being silently ignored.
- **Project bootstrap**: new 31st tool `normify_project_init` creates `normify-/` plus the default policy,
optionally with a planned root module in one call (idempotent). `normify_change_open` now also **creates the project
directory on demand** (it used to fail with a "project directory missing" error), and `normify_brief` on a missing module returns an
actionable hint instead of a bare error.
- **Causal batch diagnostics**: one `label-too-long` used to cascade into three `dep/target-missing` errors (L1-failed
modules are removed from the batch working set). Follow-on errors are now reported as `dep/target-dropped` /
`structure/parent-dropped` naming the **root-cause diagnostic**, and the failed response carries `root_causes` + a `hint`.
- **API-direct guidance**: arrows whose endpoints both declare APIs but that have no `from_api` / `to_api` now produce an
aggregated `dep/unanchored` warning (count + first three examples). This was the wasted capability in the experiment:
110 declared APIs, 54 arrows, zero anchored — unanchored arrows can only land on the box edge, never on an API row.
Guidance, not relaxation: a wrong anchor key is still an error.
### v0.5.2 — three real data-corrupting defects fixed
- **`normify_module_upsert` keeps its required list**: `parameters.required` is `["frontmatter"]` again, and the 9
mandatory frontmatter fields (uid / id / parent / name / description / source / revision / updated_at / fingerprint)
are back in the schema. A nested schema was compiled twice, which silently dropped the whole required list, so the
contract the model saw no longer matched what the runtime enforced.
- **`normify_module_move` rewrites migrated render data**: `id` / `order` / `groups.children` / `edge_hints` are all
remapped to the new ids, plus the **old parent** (drops references to moved-out children) and the **new parent**
(appends the new id to `order`) are maintained. Before the fix a move left the project failing L2 with
`layout/id-mismatch` + `layout/order-child` (8–9 errors measured; 0 after 0.5.2).
- **Promotion hands APIs off**: when a leaf becomes a container (explicit `normify_module_promote`, auto-promotion when
writing a child, or moving a subtree under a leaf) the stale `apis` are stripped from the container and reported as a
`structure/api-dropped-on-promote` warning listing the dropped keys — previously the project hard-failed with `api/non-leaf`.
- **All API rows by default**: layout field `max_api_rows` defaults to **0 = expand all**; pass 1..48 to truncate.
- Each defect is locked in by `tests/regression-0.5.2.mjs` (34 assertions, all green).
### v0.5.1 — renderer: no more overlapping lines (12 → 0)
- **Fixed collinear line overlap**: from **12 overlaps across 28 levels → 0**. Four root causes:
1. the primary router accepted the first candidate that merely did not cross *other* boxes — it never checked
whether it lay on top of an already-drawn line → now only candidates with `violations === 0` are accepted;
2. API-anchored ports were not fanned (several edges share one API row) → ±5.5px in-row fan-out;
3. API ports on top/bottom sides were placed inside the box → they now fall back to even distribution along the edge;
4. `segClear` skipped the source/target boxes entirely → new "enters its own box" check (inset 2px).
- Node/group spacing 170 → 220 so dense levels have more free channels.
### v0.5.0 — no module-count ceiling
- **Removed the hard `MAX_DEPTH = 12` limit**: drill down to single functional units; if a project really wants a depth
limit, declare it in `policy.yml` (`maxDepth` widened to 1..64, scoped).
- Skill guidance: unlimited depth, batch limit 40 → 200, "keep 3–5 APIs per leaf".
Earlier versions (v0.4.x / v0.3 / v0.2 / v0.1)
- **v0.4.1**: renderer v3 (free-channel routing, dynamic viewBox, API-direct arrows, cross-level aggregation, zoom/hover);
`normify_sync` now sees **untracked new files** (`git ls-files --others`).
- **v0.4.0**: companion development (`planned`/`deprecated`, `replacement`, `tags`), `policy.yml`, change log with
`normify_change_close` (zero-error enforced), editing operators (`module_patch/batch/move/refresh`),
`normify_brief` / `normify_check`, sync v2, reminder hook; tools 15 → 30.
- **v0.3.0**: render datasets (`order`/`groups`/`mode`/`reading`/`edge_hints`), id depth 8 → 12, finer granularity.
- **v0.2.0**: DSH 0.1.5-rc.2 adaptation; tool names `normify.x.y` → `normify_x_y`; bundle layer `cordis.patch.yml`;
parameters compiled to standard JSON Schema; read-only tools marked `isConcurrencySafe`.
- **v0.1.0**: initial release (14 tools, data model v1, renderer v1).
## 4. Screenshots
| Overview (129 modules) | Tools (0.6 view: 31 tools; 7 primitives by default since 0.7) |
| --- | --- |
|  |  |
| Engine level (API-direct arrows) | Model level (clean routing after the 0.5.1 fix) |
| --- | --- |
|  |  |
> All four are **Normify mapping its own source** (129 modules / 214 APIs / 257 arrows / 28 render levels, `validate` 0 error).
## 5. Installation
Normify ships as a **DSH bundle** and supports **DSH 0.2.x only** (`engines.dsh: ">=0.2.1-rc.1 <0.3.0"`).
Open a **new DSH session** after installing: the tool list is snapshotted when a session starts.
### Option A — plugin manager / plugin market (recommended)
In a DSH session, ask the agent to install it through the plugin manager (`plugin_manager` → `install_bundle`).
The target can be:
- **this repository directory** (run `npm install && npm run build` first when installing from source;
`lib/` is no longer committed to git, so it must be built);
- **the packed tarball** (`dsh-external-dsh-normify-.tgz` produced by `npm pack`, or the matching
Release asset) — the exact install spec is the one suggested by the plugin manager;
- **a plugin-market entry**, if the market index you configured already lists this plugin.
The DSH plugin manager validates `engines`, writes the profile and registers the bundle.
**You never edit the profile's `package.json`, `node_modules` or `cordis.patch.yml` by hand** — the plugin row is
declared by the bundle itself (`package.json > dsh.bundle.patch: ./cordis.patch.yml`).
### Option B — `dsh plugin` CLI
```bash
# is the current DSH profile name (see ~/.dsh/profiles/)
dsh plugin --profile add
```
CLI arguments follow the `dsh plugin --help` shipped with your current DSH build; syntax may differ slightly
between builds.
### Verify
```text
In a new DSH session, ask the agent: list the normify tools you have
# default: 7 primitives; legacyTools:true adds the 31 legacy names; toolset:"full" gives the 31 legacy tools only
```
> ⚠️ On a version mismatch the plugin manager reports `incompatible-version` and rolls back: use **0.7.0+**
> (supports DSH 0.2.x); if you still need DSH 0.1.x, stay on 0.5.4.
## 6. Quick start
Once installed, just talk to the AI. The three most common prompts:
```text
# 1) map a repository
Use the normify-gen skill to build a structure tree and render an architecture map for F:\my-project,
with granularity down to single functional units.
# 2) design first, then code
I want to add a "rate limiting" module to my-project: give me a brief first (normify_read op=brief),
create it in planned state, then I implement it and you change_close (it refreshes and activates planned modules).
# 3) sync after code changes
Sync the structure map of my-project (normify_check op=drift): update affected modules and render data,
then build + render once validate reports 0 errors.
```
Under the hood the agent runs (7 primitives by default since 0.7; ops in parentheses):
```text
normify_project{op:"init"} → normify_write{op:"batch"} (root + first level, atomic)
→ normify_read{op:"list"} (drill down)
→ normify_write{op:"layout_upsert"} (one render dataset per container)
→ normify_check{op:"validate"} (zero-error gate)
→ normify_build{op:"build"} → normify_render{op:"render"}
→ normify_build{op:"verify"} (or CLI `verify`) confirms the receipt chain
```
Artifacts (inside the structure directory `normify-/`):
| Artifact | Content |
| --- | --- |
| `modules/**/*.md` | the structure data itself (frontmatter + body) |
| `renders/**/*.json` | one render dataset per container level |
| `policy.yml` | architecture rules (defaults installed at project creation) |
| `changes/.json` | development change log, archived together with the structure |
| `tree.json` | compiled: module dictionary + API index + edges + layouts + policy + change stats |
| `outline.md` / `api-index.json` | human-readable outline / API index |
| `structure-manifest.json` | structure SBOM: deterministic module/API/dependency inventory + artifact hashes (since 0.6.0) |
| `receipt.json` | SHA-256 frozen receipt of all artifacts (does not include itself) |
| `receipt.sha256` | checksum of `receipt.json` itself (sidecar, for external recomputation) |
| `normify.html` | the single-file interactive map |
## 7. Tool surface: 7 primitives (legacyTools transition)
Since 0.7.0 DSH registers **7 primitives by default** (dispatched by `op`). The CLI still uses the full tool set internally
(commands unchanged).
| Primitive | ops | Purpose |
| --- | --- | --- |
| `normify_project` | `init` / `list` / `status` | initialise a project (optional planned root) / list projects / project overview |
| `normify_read` | `get` / `list` / `search` / `deps` / `outline` / `brief` / `layout` / `policy` | read-only queries: module / list / search / reverse deps / outline / brief / layout / policy |
| `normify_write` | `upsert` / `patch` / `batch` / `move` / `delete` / `promote` / `layout_upsert` / `layout_delete` | transactional writes (L1 validation, atomic batch, `dry_run`) |
| `normify_check` | `validate` / `check` / `drift` / `policy` | L2 validation / design pre-flight / git drift & incremental plan / read policy |
| `normify_build` | `build` / `verify` / `sbom` | compile five artifacts + SBOM + receipt chain / verify-only / SBOM only |
| `normify_render` | `render` | render the single-file interactive HTML (`out` accepts a plain file name only) |
| `normify_change` | `open` / `update` / `list` / `close` | change lifecycle; `close` enforces zero errors and activates planned modules |
**Tool-surface config** (plugin config):
| Config | Default | Effect |
| --- | --- | --- |
| `toolset: "core"` | ✅ | register the 7 primitives only |
| `toolset: "full"` | — | register the 31 legacy tools only (the 0.6 surface) |
| `legacyTools: true` | `false` | additionally register the 31 legacy names (one minor, removed in 0.8) |
- `legacyTools:true` registers 7 + 28 = 35 entries; `normify_check` / `normify_build` / `normify_render` are also legacy names,
so use the primitive `op` during the transition.
- The CLI (`normify ...` / `call `) is not affected by the plugin `toolset` and can always use all 31 legacy tools.
**31 legacy names → primitive op (migration map)**
| Legacy tool (0.6) | 0.7 usage |
| --- | --- |
| `normify_project_init` / `normify_tree_list` | `normify_project{op:"init" / "list"}` |
| `normify_module_get` / `_list` / `_search` / `_deps_find` / `_outline` / `_brief` | `normify_read{op:"get"/"list"/"search"/"deps"/"outline"/"brief"}` |
| `normify_layout_get` / `normify_policy_get` | `normify_read{op:"layout"/"policy"}` |
| `normify_module_upsert` / `_patch` / `_batch` / `_move` / `_delete` / `_promote` | `normify_write{op:"upsert"/"patch"/"batch"/"move"/"delete"/"promote"}` |
| `normify_layout_upsert` / `normify_layout_delete` | `normify_write{op:"layout_upsert"/"layout_delete"}` |
| `normify_validate` / `normify_sync` | `normify_check{op:"validate"/"drift"}` |
| `normify_build` / `normify_render` | `normify_build{op:"build"/"verify"/"sbom"}` / `normify_render{op:"render"}` |
| `normify_change_open` / `_update` / `_list` / `_close` | `normify_change{op:"open"/"update"/"list"/"close"}` |
| `normify_fingerprint` / `normify_module_refresh` / `normify_help` / `normify_policy_upsert` | **no primitive equivalent**: enable `legacyTools:true` or use the CLI (`normify call ...`) |
> Parameter trees: `normify_help{topic:"tool:"}` (legacy mode) or
> `node lib/cli/index.js call --args-json ...`.
## 8. Module frontmatter
```yaml
---
uid: 8c69b5a8 # 8 lowercase hex, unique per project, never changes
id: dsh-normify.engine.ids # dotted path id; first segment = tree name; unlimited depth
parent: dsh-normify.engine # must equal the id minus its last segment; null for a root
name: {zh: "标识与路径", en: "Identifiers & Paths"}
description: # bilingual, ≤500 chars each, read by humans and AI alike
zh: >
模块 id 的文法、派生与 id ↔ 文件路径的双向映射。
en: >
Module id grammar, derivations and the id ↔ file-path mapping.
source: # code evidence (repo-relative path + optional line range)
- {path: src/engine/ids.ts, line: 6, end_line: 34}
revision: 90df4a10… # 40-hex git SHA at generation time
updated_at: "2026-09-12T12:00:00Z"
fingerprint: 630ac9020dba… # deterministic fingerprint of source (use normify_fingerprint)
state: active # active | planned | deprecated
tags: [engine, ids] # optional, ≤12
apis: # leaves only; 3–5 per leaf recommended
- protocol: rpc # http|ws|rpc|amqp|kafka|mysql|redis|file|grpc|graphql
path: splitId
description: {zh: "解析 id 为段数组。", en: "Parses an id into segments."}
deps: # outgoing arrows (stored on the source side only)
- kind: call # call|event|dataflow|reference
to: dsh-normify.engine.model.module
from_api: rpc:splitId # optional: anchor to one of this module's APIs (API-direct)
to_api: rpc:Module
label: {zh: "id 契约", en: "Id contract"}
---
(body: optional long-form introduction for humans)
```
File layout: `modules///index.md` for containers, `.md` for leaves — maintained by the tools.
## 9. Render datasets (`renders/`)
One per **container** module, mirroring the module tree:
```json
{
"schema_version": 1,
"id": "dsh-normify.engine.model",
"updated_at": "2026-09-12T12:00:00Z",
"mode": "grid", // auto | layers | groups | grid
"max_columns": 3, // 1..6
"max_api_rows": 0, // 0 = expand all (default); 1..48 = truncate
"reading": {"zh": "本层 8 个子模块…", "en": "…"},
"order": ["dsh-normify.engine.model.text", "…"],
"groups": [{"id": "model", "title": {"zh": "模型与契约", "en": "Model"}, "children": ["…"]}],
"edge_hints": [{"from": "a", "to": "b", "lane": 2, "style": "curve"}]
}
```
Readability recipes: **order = data flow**, **groups = domain boundaries**, **reading = the path**, long back-edges
get explicit lanes via `edge_hints`.
## 10. Companion development
### 10.1 Design first
```text
(1) normify_change{op:"open"} open a change (title / intent / modules / acceptance)
(2) normify_read{op:"brief"} contracts, impact surface, policy constraints, suggested modules, checklist
(3) normify_check{op:"check"} pre-flight the proposed modules and dependencies
(4) normify_write{op:"batch"} create planned modules (state=planned, fingerprint=pending)
plus normify_write{op:"layout_upsert"} for that level in the same round
(5) 【write the code】
(6) normify_change{op:"close"} refreshes fingerprints and activates planned modules
(7) zero-error close: refresh → validate → build → verified + revision.after
```
Planned modules keep `normify_check{op:"validate"}` at 0 errors (a missing source is only a warning), and activating or
closing before the code lands is refused.
### 10.2 Companion update
```text
(1) change the code (committed or not)
(2) normify_check{op:"drift"} changed_files / affected / drift_fingerprints / layouts_to_review
and "suggested modules" for new files (with ids and target paths)
(3) normify_write{op:"patch"} add APIs, refresh descriptions, tags, etc.
(4) normify_check{op:"validate"} 0 errors
(5) normify_change{op:"close"} close and rebuild artifacts (tree.json reflects the change stats)
```
## 11. Repository layout & engineering
```bash
npm install # devDependencies (typescript / @types/node / cordis / schemastery / cosmokit)
npm run build # src/ → lib/ via tsc (lib/ is not committed; build before use)
npm run typecheck # tsc --noEmit
npm test # 6 e2e/regression suites: engine / companion / regression-0.5.2 / 0.5.3 / 0.5.4 / 0.5.5
npm run test:unit # core-unit (engine unit tests)
npm run test:contract # platform-contract (7-primitive / legacy / full modes) + pack-smoke --contract
npm run test:perf # performance smoke (300 modules, generous threshold)
node ci-contract-check.cjs # bundle declaration + full 31 tools + provider-safe names
node scripts/check-core-boundary.mjs # core must not import @deepseek-ai; cli must not import ../dsh
node scripts/gen-diagnostics.mjs # regenerate docs/DIAGNOSTICS.md from source
node scripts/pack-smoke.mjs --all --strict # release-package smoke (requires local DSH desktop app)
```
| Path | Content |
| --- | --- |
| `src/core/` | pure-Node engine (DSH-independent): `engine/*` + `toolset.ts` (31 legacy specs) + `primitives.ts` (7 primitive specs) |
| `src/dsh/` | DSH adapter: real `ToolDefinition`s (output schema + presenters + approval); decides 7-primitive / legacy / full surface |
| `src/cli/` | CLI (built to `lib/cli/index.js`, `bin: normify`); depends on core only |
| `lib/` | compiled output (`tsc`; **not in git**, shipped in the npm package) |
| `skills/normify-gen/` | thin skill `SKILL.md` (≤150 lines) + `references/` (format/validation/recipes/migration/examples/toolset) |
| `tests/` | 6 e2e/regression suites + core-unit + platform-contract + performance smoke |
| `docs/` | `SPEC.zh-CN.md` (single source of truth) + COMPATIBILITY / MIGRATION / RELEASE-CHECKLIST / DIAGNOSTICS / MARKET / EVAL / adr |
| `vendor/` | vendored schemastery + cosmokit (loaded by relative path) |
## 12. Compatibility & troubleshooting
| Item | Requirement |
| --- | --- |
| DSH | `0.2.1-rc.5` (engines `>=0.2.1-rc.1 <0.3.0`; peer: `@deepseek-ai/cordis ^4`) |
| Support policy | **only the current DSH line (0.2.x)**; stay on 0.5.4 if you still run 0.1.x |
| Tool surface | 7 primitives by default; `legacyTools:true` adds 31 legacy names; `toolset:"full"` = 31 legacy tools |
| Node.js | ≥ 18 |
> Full compatibility matrix and evidence: [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md); upgrade guide: [docs/MIGRATION.md](docs/MIGRATION.md).
- **Installed but no tools** → the tool list is snapshotted at session start: open a **new session** (or restart DSH).
- **Legacy tool name reports unknown tool** → 0.7 registers only the 7 primitives by default: set `legacyTools: true`
(transition) or use the primitive op (map in §7).
- **Install fails with `incompatible-version`** → the plugin version is outside the DSH runtime's engines range:
upgrade the plugin to 0.7.0+; on DSH 0.1.x stay on 0.5.4.
- **`evidence/fingerprint-drift`** → the code moved ahead of the structure data: run `normify_check{op:"drift"}`,
then `change{op:"close"}` refreshes (legacy: `normify_module_refresh`).
- **`structure/leaf-too-coarse`** → not an error: that leaf can be split further (towards single functional units).
## 13. Security & privacy
- **No telemetry, no network calls**: structure data and artifacts are generated locally; `normify.html` loads nothing external.
- **No credential handling**: the plugin never reads or writes tokens, keys or passwords; `.gitignore` excludes
`.env*`, `*.pem`, `*.credentials.yaml`. **There are no credentials in this repository — please keep it that way.**
- **Read-only on your code**: the generator never modifies the analysed repository; it only writes into `normify-/`.
- `source` records only repo-relative paths and line numbers, never source text.
## 14. CLI (cross-agent / CI)
The CLI uses the **same engine** as the DSH tools and does not depend on the DSH runtime: use it in terminals, CI and other agents.
`lib/` is not committed, so build first when running from source (`npm run build`); the npm package already ships `lib/`.
```bash
node lib/cli/index.js --help # commands, options and exit codes
node lib/cli/index.js tools # JSON: the full 31-tool set (the CLI is not affected by the plugin toolset)
node lib/cli/index.js verify --project demo # recompute receipt.sha256 → receipt → artifacts
# Common: locate a project with --project or --dir
node lib/cli/index.js init --project demo --root-id demo
node lib/cli/index.js validate --project demo --repo-root F:/repo
node lib/cli/index.js build --project demo # tree/outline/api-index/structure-manifest/receipt
node lib/cli/index.js render --project demo --out normify.html
node lib/cli/index.js sync --project demo --repo-root F:/repo --diff HEAD~1
node lib/cli/index.js change-open --project demo --args-json '{"title":{"zh":"…","en":"…"},"acceptance":["…"]}'
node lib/cli/index.js call --args-json '' # invoke any tool directly
```
| Command | Purpose |
| --- | --- |
| `init` / `status` / `list` / `get` / `search` / `brief` | init / overview / list modules / get module / search / development brief |
| `validate` / `check` | project-wide L2 validation / design pre-flight |
| `build` / `verify` / `render` | compile the five artifacts / receipt-chain verification / single-file HTML |
| `sync` | incremental regeneration planner (read-only) |
| `change-open` / `change-list` / `change-close` | change log and zero-error close |
| `project-list` / `policy` / `help` / `tools` / `call` | list projects / read policy / help / tool list / direct tool call |
- After installation use `normify ` (the `bin` maps to `lib/cli/index.js`); otherwise `node lib/cli/index.js`.
- JSON output by default; `--quiet` prints one-line JSON. **Exit codes are a stable contract**:
`0` success; `2` validation/L2 error (including a failed `verify`); `4` argument/usage error; `5` other failure; `6` security refusal.
- `build --verify-only` (CLI) / `normify_build{op:"verify"}` (primitive) only verifies the receipt chain, without recompiling.
## 15. Documentation
| Document | Content |
| --- | --- |
| [`docs/SPEC.zh-CN.md`](docs/SPEC.zh-CN.md) | formal specification v1.0 (data model / source format / artifacts / validation / generator / renderer, single source of truth) |
| [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md) | DSH × plugin version × verification status (pack-smoke evidence) |
| [`docs/MIGRATION.md`](docs/MIGRATION.md) | 0.5.4→0.5.5, 0.5.x→0.6.0, 0.6.x→0.7.0 (default surface switch + legacyTools) |
| [`docs/RELEASE-CHECKLIST.md`](docs/RELEASE-CHECKLIST.md) | per-release checklist (build/test/smoke/checksum/tag) |
| [`docs/DIAGNOSTICS.md`](docs/DIAGNOSTICS.md) | diagnostic-code index (generated from source by `scripts/gen-diagnostics.mjs`) |
| [`docs/MARKET.md`](docs/MARKET.md) | marketplace materials (official market / dshbase / dshhub + user action checklist) |
| [`docs/EVAL.md`](docs/EVAL.md) / [`docs/EVAL-RESULTS.md`](docs/EVAL-RESULTS.md) | evaluation protocol (n≥3 / isolated profiles / blind review) and retrieval-layer results |
| [`docs/adr/0011-ui-panel-decision.md`](docs/adr/0011-ui-panel-decision.md) / [`0012-mcp-adapter-decision.md`](docs/adr/0012-mcp-adapter-decision.md) | decision gates: no UI panel / no MCP for 0.7 (CLI-first) |
| [`skills/normify-gen/SKILL.md`](skills/normify-gen/SKILL.md) | thin skill entry (details in `references/`: format/validation/recipes/migration/examples/toolset) |
| [`docs/RELEASE-0.7.0.md`](docs/RELEASE-0.7.0.md) | current release notes (7-primitive default surface) |
| [`CHANGELOG.md`](CHANGELOG.md) | version history (0.1.0 → 0.7.0) |
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | contributing guide |
| [`SECURITY.md`](SECURITY.md) | security policy |
## License
[MIT](LICENSE) © yan-mc
---
Normify — let the architecture map grow together with the code.