umtri-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ # Rule: Nodes Represent Information Structures, Not Code Files
2
+
3
+ When modelling a project in Umtri, nodes should be **information units** —
4
+ the parts a *user or external tool* can perceive — not implementation files.
5
+
6
+ ## The distinction
7
+
8
+ - ❌ **Wrong (developer view)**: nodes named after code files
9
+ — `index.jsx`, `NodeLayer.jsx`, `routes/projects.js`.
10
+ - ✅ **Right (user view)**: nodes named after the information unit
11
+ — "tree diagram", "header", "node table", "projects API".
12
+
13
+ A single information unit may be implemented by several files. The mapping
14
+ from concept to code lives in `metadata.implements` (array of file paths),
15
+ not in the node label.
16
+
17
+ ## Completeness — map the whole project, don't summarise
18
+
19
+ The "information unit, not file" rule is about **labelling grain, not
20
+ coverage.** It tells you *how to name and group* nodes — it does **not** tell
21
+ you to leave parts of the project out. The goal is a **faithful, navigable map
22
+ of the entire codebase**: every meaningful module, screen, endpoint, table,
23
+ external integration, and piece of non-obvious logic should be locatable in the
24
+ tree (a node with `metadata.implements`).
25
+
26
+ - **Cover everything, at the right grain.** Group files into information units
27
+ so it's not one node per file — but do not drop whole capabilities. If a
28
+ maintainer would expect to find feature X in the repo, X must be findable in
29
+ the tree.
30
+ - **Don't abstract a project down to a sketch.** A handful of trunks with a few
31
+ leaves each is almost always *under*-captured. Real projects have many leaves;
32
+ a faithful tree is large. Breadth is expected — `get_graph` slicing (rootId /
33
+ maxType / descriptions) exists precisely so a big, complete tree stays cheap
34
+ to read.
35
+ - **The restraint rules are not licence to omit.** "Reserve veins" (vocabulary)
36
+ means don't turn *every function* into a vein — not skip the logic worth
37
+ finding. "Bushy is fine, don't over-nest" means don't add empty grouping
38
+ layers — not prune real branches. Group and pick good grain; keep the coverage.
39
+ - **Completeness pass.** After modelling, walk the repo and check: is there a
40
+ directory, module, route file, table, or integration with **no** node? If so,
41
+ it's a gap — add it. Aim for "every real part of the system is represented,"
42
+ not "the gist is represented."
43
+ - **Completeness has a boundary.** It covers what *runs*, not everything in the
44
+ repo — documents, build output and inert config stay out. See "What is NOT a
45
+ node" below before treating an uncovered path as a gap.
46
+
47
+ ## What is NOT a node — the boundary of completeness
48
+
49
+ Completeness pushes you to cover everything that *runs*. It is not a licence to
50
+ map everything that *exists in the repo*. The tree answers "what is this system
51
+ made of," so anything that only **describes** the system stays out.
52
+
53
+ **The test: if you deleted it, would the system behave differently?** If no, it
54
+ is not a node.
55
+
56
+ Keep out:
57
+
58
+ - **Internal documents** — PRDs, RFCs/ADRs, READMEs, CHANGELOGs, design notes,
59
+ roadmaps. These are *about* the system. Mixing them in makes the map answer
60
+ two questions at once ("what runs" and "what we wrote down") and it stops
61
+ being a reliable map of either.
62
+ - **Build output and vendored code** — `dist/`, `build/`, lockfiles,
63
+ `node_modules/`.
64
+ - **Tooling config with no runtime behaviour** — linter/formatter configs,
65
+ editor settings, `.gitignore`.
66
+
67
+ Point at documents instead of turning them into nodes: put the reference in the
68
+ `description` of the node it explains (e.g. "propagation rules:
69
+ `PRD-impact-analysis.md`"). The document stays findable exactly where someone
70
+ would look for it, without becoming a branch.
71
+
72
+ **The exception that matters: documentation you ship is a system.** A docs site
73
+ served to users (its pages, its i18n, its shell) is a product surface and
74
+ belongs in the tree. The line is not "markdown vs code" — it is *served to
75
+ users* vs *read by us*.
76
+
77
+ ## Trunk and limb rules
78
+
79
+ - `trunk` = a big domain (e.g. `www`, `server`, `db`). Pick stable,
80
+ long-lived names — usually matching a top-level code area.
81
+ - `limb` = a category inside a trunk (e.g. `graph`, `table`, `dashboard`).
82
+ - Do **not** create one limb per time period. Time is expressed through
83
+ `season` and `created_at`, not by carving the tree into temporal slices.
84
+ - Infrastructure (servers, DNS) is *not* a trunk — it is the environment the
85
+ system runs in, not a part of the system. For documents, see "What is NOT a
86
+ node" above.
87
+
88
+ ## Useful metadata keys
89
+
90
+ These conventions let other tools (including this MCP) answer richer
91
+ questions about a ground.
92
+
93
+ | Key | Applies to | Shape |
94
+ | -------------------- | ----------------------------------- | -------------------------------------------------- |
95
+ | `implements` | all nodes | `string[]` — file paths, optionally `path#identifier` |
96
+ | `created_by_commit` | all nodes | `string` — git short hash |
97
+ | `replaces` | follow-up node | `string[]` — node ids that were absorbed |
98
+ | `replaced_by` | deprecated node | `string[]` — node ids that took over |
99
+ | `endpoints` | API-domain twigs | `{method, path, summary}[]` |
100
+ | `columns` | DB-domain twigs | `{name, type, pk?, fk?}[]` |
101
+ | `represents` | UI twigs reflecting data | `string` — corresponding DB twig id |
102
+ | `reparented_at` | reparented nodes | `string` — ISO date when the parent changed |
103
+ | `reparented_from` | reparented nodes | `string` — prior parent id |
104
+ | `reparented_to` | reparented nodes | `string` — new parent id |
105
+ | `extracted_from` | nodes decomposed from a parent file | `string` — original node id |
106
+ | `role_change_at` | type-changed nodes | `string` — ISO date |
107
+ | `role_change_reason` | type-changed nodes | `string` — short prose explaining the shift |
108
+ | `placeholder` | intentionally-empty structure | `true` — opts out of dormant cleanup suggestions |
109
+
110
+ ## One file, multiple nodes
111
+
112
+ A single source file may legitimately decompose into multiple nodes when it
113
+ contains several distinct information units. Example: `AppShell.jsx` exports
114
+ `Sidebar`, `NavItem`, `UserMenu`, `Intro`, `MobileDrawer` — each is its own
115
+ component and earns its own `leaf` node. The split uses two metadata keys:
116
+
117
+ - `metadata.implements`: array of `file#identifier` references — e.g.
118
+ `"www/src/components/layout/AppShell.jsx#Sidebar"`. The `#identifier`
119
+ suffix points at the exported function/class within the file.
120
+ - `metadata.extracted_from`: id of the node that originally represented the
121
+ whole file. Preserves the decomposition history so the prior shape stays
122
+ inspectable.
123
+
124
+ Decompose when the units are independently reusable, separately addressable,
125
+ or named distinctly by users. Do **not** decompose just because a file has
126
+ many internal helpers — pure implementation details stay inside the leaf.
127
+
128
+ ## Restructuring history
129
+
130
+ Trees evolve. When a node moves to a different parent, record the move in
131
+ metadata so the restructure stays inspectable later:
132
+
133
+ - `metadata.reparented_at` — ISO date (e.g. `"2026-05-21"`)
134
+ - `metadata.reparented_from` — prior parent id
135
+ - `metadata.reparented_to` — new parent id
136
+
137
+ The MCP `update_node` tool emits a `reparent-metadata-hint` info warning
138
+ when you change `parent` without including these keys. Recording the move
139
+ matters because the ltree path silently rewrites on parent change — without
140
+ these keys, you can't tell from the current state that the node ever moved.
141
+
142
+ When the type also changes (twig → leaf, limb → twig, …), add
143
+ `role_change_at` and a short `role_change_reason` so the reclassification
144
+ stays explainable. Pair with `replaces` / `replaced_by` when one node
145
+ absorbs another (e.g. an ad-slot twig absorbing its sole leaf child).
146
+
147
+ ## When adding a node
148
+
149
+ Ask first: *"What part of the system does this represent for someone using
150
+ or maintaining it?"* If the answer is a file, you're probably one level too
151
+ deep — go up to the information unit and add the file under
152
+ `metadata.implements`. If the answer is a single exported component within
153
+ a larger file, the node is still legitimate — use `path#identifier` in
154
+ `implements` and add `extracted_from` pointing at the file-level node.
155
+
156
+ ## Connections — consider an edge or api each time you add a node
157
+
158
+ The tree shows *containment* (what lives under what). It does **not** show how
159
+ things relate across branches — that's what edges and apis are for. After you
160
+ add a leaf or vein, pause and ask: **does this node call, feed, or depend on
161
+ another node?** If so, record the connection — the graph is only half-true
162
+ without it.
163
+
164
+ - **api** (`create_api`) — a **request / call / data flow** between two nodes:
165
+ a screen calling an endpoint, an endpoint hitting a table, an external
166
+ integration (NicePay, SMS, a webhook). First-class because flows are the
167
+ story of how the system runs. Direction is caller → callee (`start` → `end`).
168
+ - **edge** (`create_edge`) — a **structural relation**: `dependency` ("A is
169
+ built on / needs B") or `data_flow` ("data moves A → B" outside a request).
170
+ Use for module/library dependencies, a route depending on the data store, a
171
+ job writing to a table. Direction is `source` → `target`.
172
+
173
+ Rule of thumb: if it's a *runtime request*, it's an api; if it's a *build-time
174
+ or structural reliance*, it's a dependency edge. Don't connect everything —
175
+ add the connections that a maintainer would actually want to trace (cross-module
176
+ calls, external integrations, the few key dependencies). A standalone leaf with
177
+ no real relation needs no edge; skipping is fine. But the common failure is the
178
+ opposite — adding nodes and never their connections, leaving a tree of isolated
179
+ islands.
180
+
181
+ ### How problems propagate along a connection
182
+
183
+ The arrow records *who calls / depends on whom*. **Impact — which node is hurt
184
+ when another breaks — does not always flow with the arrow.** Read each
185
+ connection type as follows when tracing a blast radius:
186
+
187
+ | Connection | Arrow means | A problem propagates |
188
+ | ----------------- | ------------------------------- | ------------------------------------------------------- |
189
+ | edge `dependency` | `source` is built on / needs `target` | **against the arrow** — if `target` breaks, `source` is affected (target → source) |
190
+ | edge `data_flow` | data moves `source` → `target` | **with the arrow** — bad data at `source` corrupts `target` (source → target) |
191
+ | api (`start`→`end`) | caller → callee | **against the arrow** — if the callee (`end`) is down, the caller (`start`) fails (end → start) |
192
+
193
+ So "what is affected if X breaks?" = walk **dependency edges backward** (to their
194
+ sources) ∪ **data_flow edges forward** (to their targets) ∪ **apis backward**
195
+ (to their callers), transitively. This is the rule the `get_impact` tool
196
+ implements.
197
+
198
+ Two cautions:
199
+ - The result is a **list of nodes to check, not a proven failure set** — it is
200
+ only as complete as the connections you have recorded. A sparse graph yields
201
+ a falsely small blast radius; treat an empty result as "nothing recorded,"
202
+ not "nothing affected."
203
+ - A connection can carry impact **both ways** in reality (a dependency that also
204
+ passes data back). Record the dominant direction; when in doubt, trace impact
205
+ in both directions rather than trusting a single arrow.
206
+
207
+ ## The file-as-leaf antipattern
208
+
209
+ A common drift: a twig groups a concept (e.g. `Bug log API`), and its only
210
+ child is a leaf named after the implementation file (`bugs.js`). This is
211
+ **one information unit split into two nodes** — the twig and the leaf
212
+ describe the same thing at different vocabularies.
213
+
214
+ Symptoms:
215
+ - Twig with exactly one leaf child whose label is a filename.
216
+ - Leaf label echoes the twig label plus a file extension.
217
+ - The leaf carries no additional information the twig doesn't already have.
218
+
219
+ Fix: absorb the file into the twig's `metadata.implements`, then soft-delete
220
+ the leaf. The twig itself becomes the information unit; the file is its
221
+ implementation detail, not a separate node. Decompose into multiple leaves
222
+ *only* when the file contains several independently-meaningful units (see
223
+ "One file, multiple nodes" above).
224
+
225
+ The MCP `create_node` / `update_node` tools emit a `file-as-leaf` info
226
+ warning when a leaf is created with a filename- or path-shaped label, and
227
+ an `implementation-jargon` warning when any label uses code-architecture
228
+ words (router/handler/middleware/layer/store/...) — both push toward
229
+ naming the information unit rather than the implementation.
@@ -0,0 +1,63 @@
1
+ # Rule: Transplanting a ground (옮겨심기)
2
+
3
+ A ground that was **not** born in Umtri — an existing project imported for the
4
+ first time — needs heavy restructuring before its shape settles. To allow
5
+ that, a freshly created ground starts in a **transplanting** state, where the
6
+ normal guardrails are relaxed. When the human is satisfied with the shape they
7
+ **root** the ground (뿌리내리기), and the guardrails snap back on.
8
+
9
+ `get_graph` returns `project.transplanting` (boolean). Check it before doing
10
+ anything that the normal rules would block.
11
+
12
+ ## While `transplanting = true`
13
+
14
+ You MAY:
15
+ - **Add or edit nodes in any season, including past seasons.** The usual
16
+ "cannot create/move into a past season" and "grown (past-season) nodes are
17
+ immutable" rules are lifted, so you can reconstruct the project's history
18
+ season by season.
19
+ - **Hard-delete import mistakes.** `delete_node` accepts `hard: true` and the
20
+ active-descendant guard is lifted (subtree cascades). Use this to remove
21
+ wrongly-imported nodes for good. Soft delete is still the default; reach for
22
+ `hard` only to clean up genuine mistakes — it is irreversible.
23
+
24
+ You MAY NOT:
25
+ - **Create or modify seasons.** Seasons are always human-only
26
+ (see umtri://rules/seasons-human-only). The human lays down the historical
27
+ seasons; you fill nodes into them.
28
+ - **Root (settle) a ground.** Ending transplant is human-only — done from the
29
+ UI ("Root this ground"). You cannot root via MCP.
30
+
31
+ ## Re-opening transplant (`reopen_transplant`)
32
+
33
+ A rooted ground can be put back into transplant with the `reopen_transplant`
34
+ tool — but **only when the user explicitly asks for it** (e.g. "switch this
35
+ ground to transplanting", "옮겨심기로 바꿔줘"). This removes safety guardrails,
36
+ so you must **never** decide to re-open on your own initiative, however
37
+ convenient it would be for a restructuring task. No explicit request → do not
38
+ call it; instead tell the user the ground is rooted and ask whether they want
39
+ to re-open transplant. After re-opening, the user roots it again from the UI
40
+ when the work is done.
41
+
42
+ ## Audit stamp
43
+
44
+ Every node you create while the ground is transplanting is automatically
45
+ stamped `metadata.transplanted = true`. The stamp persists after rooting so a
46
+ human can later see which nodes entered during the unsettled period. Do not
47
+ remove it.
48
+
49
+ ## After rooting (`transplanting = false`)
50
+
51
+ All normal guards return: past seasons reject new/moved nodes, grown nodes are
52
+ immutable, `delete_node` is soft-only and refuses nodes with active children.
53
+ Treat the tree as settled — small, observable changes only
54
+ (see umtri://about/vision).
55
+
56
+ ## How to behave
57
+
58
+ - Use the relaxed window to get the **structure** right — it is still an
59
+ information-structure diagram, not a work log (see umtri://rules/vocabulary).
60
+ Reconstruct what the project *is*, not the history of commits.
61
+ - Prefer soft delete; reserve `hard` for clear mistakes.
62
+ - When the shape looks settled, tell the human it's ready to root — don't keep
63
+ operating in transplant mode longer than needed.
@@ -0,0 +1,46 @@
1
+ # About Umtri
2
+
3
+ Umtri is a platform for visualising **how a project grows over time**.
4
+ A project is a *ground*; from it rise *trunks* (apps, services); seasons
5
+ pass; branches and leaves accumulate; bugs erode; APIs connect.
6
+
7
+ ## Why this exists
8
+
9
+ Complex systems forget their own past. As code and decisions pile up, the
10
+ question *"why is it like this?"* becomes harder to answer than *"how does
11
+ it work?"*. Umtri is a project archaeology tool: a place to trace and
12
+ back-trace structure, dependencies, and decisions across the lifetime of a
13
+ project.
14
+
15
+ ## Primary purposes
16
+
17
+ - **Track growth** — see how a project's structure expanded season by season.
18
+ - **Trace dependencies** — follow API flows and references across the tree.
19
+ - **Locate origins** — when something is broken or surprising, find the
20
+ point in time and space where it entered the system.
21
+ - **Open to tools** — Umtri exposes its data through a REST API and this
22
+ MCP server, so AI assistants, CI pipelines, issue trackers, and IDEs can
23
+ read and contribute alongside humans.
24
+
25
+ ## Tone for tools
26
+
27
+ When you (an AI tool) work on an Umtri ground:
28
+
29
+ - Prefer the language of the metaphor over generic terms — *grounds, trunks,
30
+ limbs, twigs, leaves, veins, seasons, bugs*. It is intentional.
31
+ - Treat the project as alive. Names, season transitions, and bugs carry
32
+ meaning the user assigned; do not "tidy up" without asking.
33
+ - Default to small, observable changes. The point of Umtri is to make the
34
+ history of a project legible — silent bulk edits defeat that.
35
+
36
+ ## What this MCP is not
37
+
38
+ - It is not a code-editing tool. It does not read or write source files —
39
+ an external agent does that, using the plan tree as its brief
40
+ (see `umtri://rules/plan`).
41
+ - It does not run CI/CD jobs. Instead it is the **configuration-record hub**
42
+ that CI integrates with: commits are recorded back onto the nodes they
43
+ touched (`metadata.implements`, `metadata.commits`) via the API — e.g. a
44
+ GitHub Action calling `record_commit` on merge. Umtri stores the form; it
45
+ does not execute the pipeline.
46
+ - It does not create seasons (see `umtri://rules/seasons-human-only`).
@@ -0,0 +1,278 @@
1
+ # Umtri Vocabulary — Detailed Guidance
2
+
3
+ Read this only when you are restructuring an existing tree, auditing legacy
4
+ data, or wrestling with a borderline classification. The core rules live in
5
+ `umtri://rules/vocabulary`; this resource expands them.
6
+
7
+ ## Hierarchy rules (convention, not schema-enforced)
8
+
9
+ | Parent role | Allowed child role(s) | Note |
10
+ | ----------- | ----------------------------- | ---------------------------------------------------------- |
11
+ | structure (trunk/limb/twig) | structure / object | can hold each other or jump straight to a `leaf` |
12
+ | object (`leaf`) | action (`vein`) only | a leaf cannot branch back into structure |
13
+ | action (`vein`) | — | **vein is terminal — no children** |
14
+
15
+ The schema accepts any combination. The MCP warns when violated but lets
16
+ the data through (legacy migration, experimental use). For new authoring,
17
+ follow the table strictly.
18
+
19
+ **Skip-level traversal is allowed within structure**: `trunk → leaf`,
20
+ `limb → leaf`, even `trunk → twig` are valid. The full descent
21
+ `trunk → limb → twig → leaf → vein` is canonical, not mandatory.
22
+
23
+ ## Trunk = system-unit, not domain
24
+
25
+ A `trunk` must be a **deployable system or independent service** — something
26
+ you could point at, deploy, or replace as a whole. Apps, databases, servers,
27
+ external services. Examples that fit: `user-app`, `api-server`, `iot-server`,
28
+ `db`, `www`, `kiosk-app`.
29
+
30
+ Domains or feature areas (auth, payments, reservations, dashboard, …) are
31
+ **`limb`**, not trunk. They live inside a trunk. "Authentication is a domain
32
+ that spans the user-app and admin trunks" — fine. "Authentication is a
33
+ trunk" — no.
34
+
35
+ Naming convention (recommended, not enforced):
36
+ - Apps: `<name>-app` (e.g. `user-app`, `kiosk-app`)
37
+ - Servers: `<name>-server` (e.g. `api-server`, `iot-server`)
38
+ - Infra: short single words (`db`, `www`, `cdn`)
39
+
40
+ ## Leaf vs vein — extended heuristics
41
+
42
+ `leaf` (object) and `vein` (action) look adjacent but carry opposite intents:
43
+
44
+ - **leaf** — the thing that exists. A component, class, domain entity,
45
+ screen, card. Noun phrase.
46
+ - **vein** — what the object *does*: a method, function, or operation that
47
+ exists on it. Verb phrase naming a standing **capability** — not a task,
48
+ patch, migration step, or change that was performed. A vein is `환불 처리`
49
+ ("processes refunds"), never `환불 버그 수정` (a completed task).
50
+
51
+ Korean label patterns:
52
+
53
+ | Pattern | Type | Example |
54
+ | -------------------------------------------------------------------- | ---- | -------------------------------------------------------- |
55
+ | Noun-final / domain model | leaf | `예약 카드`, `결제 정책`, `사용자 메뉴` |
56
+ | UI components (모달·카드·페이지·대시보드·패널·화면·표·뷰) | leaf | `랜딩 페이지`, `매출 대시보드`, `공지 이미지 리사이저` |
57
+ | Verb-final capability (`…충전`·`…차감`·`…환불`·`…조회`·`…발송`·`…등록`·`…검증`·`…동기화`·`…집계`) | vein | `포인트 충전`, `세션 검증`, `방문 집계` |
58
+ | Handlers / methods / crons / jobs (standing capabilities) | vein | `레슨 자동 완료 cron`, `방문 집계 핑` |
59
+
60
+ Note the boundary with the work-log rule: a vein names an operation the
61
+ object *performs* (a capability that stays true tomorrow), not the act of
62
+ fixing/adding/migrating it. `정적 파일 캐시 버스팅` (a capability the build
63
+ has) is a vein; `캐시 버스터 추가` (work done) is not a node at all.
64
+
65
+ **Vein litmus — one vein ≈ one locatable code unit.** A vein earns its place
66
+ when you can point to where it lives (a function, guard, calculation, cron) —
67
+ it should carry `metadata.implements`. Reserve veins for the *non-obvious,
68
+ worth-finding-again* logic: business invariants, security, money math
69
+ (`코트·시간대 중복 예약 방지`, `보너스 포인트 FIFO 차감`, `휴대폰 AES-256-GCM
70
+ 암호화`). Do **not** explode every endpoint into CRUD veins, and do **not**
71
+ turn a requirement or rule you can't point to in code into a vein — that
72
+ belongs in the leaf's `description`, or as a `bug`/plan node. A good tree uses
73
+ veins sparingly (often <10% of nodes); a forest of veins under every leaf is
74
+ the over-veining smell.
75
+
76
+ ## Reclassification policy (for AI auditors)
77
+
78
+ When migrating or auditing legacy data, AI tools **may reclassify** a `leaf`
79
+ node to `vein` (or vice-versa) **only if both** hold:
80
+
81
+ 1. The label clearly fits the opposite category by the heuristics above.
82
+ 2. The node has **no children** (vein cannot have children).
83
+
84
+ Always make reclassification edits **one node at a time**, with the label
85
+ visible in the diff. Bulk regex sweeps over labels are explicitly disallowed
86
+ — the Korean heuristics are advisory, not deterministic. When unsure, leave
87
+ the node alone and surface it as a question.
88
+
89
+ ## Build order — leaf-driven, depth-first
90
+
91
+ The canonical descent `trunk → limb → twig → leaf → vein` describes the
92
+ *shape* of a finished tree. It is **not** an instruction to build the tree
93
+ breadth-first (all trunks, then all limbs, then all leaves). Building that
94
+ way produces a skeleton of empty structure and forces you to guess the IA
95
+ top-down before you know what actually lives in it.
96
+
97
+ **Author one leaf at a time, depth-first.** The leaf (or vein) is the only
98
+ node that names a thing that genuinely exists; trunk/limb/twig exist solely
99
+ to hold leaves. So let the leaf pull its ancestors into being:
100
+
101
+ ```
102
+ for each information unit you want to record (a leaf or vein):
103
+ path = [trunk, limb, twig?] it belongs under
104
+ create_node for each ancestor in path that does NOT exist yet (reuse the rest)
105
+ create_node for the leaf, with metadata.implements
106
+ create_node for each vein the leaf owns
107
+ ```
108
+
109
+ Worked example — recording two screens in a `user-app`:
110
+
111
+ 1. First leaf `예약 카드` lives under `user-app › 예약 › 예약 화면`.
112
+ - `user-app` (trunk) missing → create it.
113
+ - `예약` (limb) missing → create it.
114
+ - `예약 화면` (twig) missing → create it.
115
+ - create leaf `예약 카드`.
116
+ 2. Next leaf `결제 버튼` lives under `user-app › 결제 › 결제 화면`.
117
+ - `user-app` already exists → reuse.
118
+ - `결제` (limb) missing → create it.
119
+ - `결제 화면` (twig) missing → create it.
120
+ - create leaf `결제 버튼`.
121
+
122
+ Each new leaf reuses the ancestors earlier leaves already built and only
123
+ creates structure where the path diverges.
124
+
125
+ **Why depth-first by leaf beats breadth-first by layer:**
126
+
127
+ - **No dormant scaffolding.** Every trunk/limb/twig gets its first leaf in
128
+ the same pass, so the `structure-without-children` warning fires only for
129
+ genuinely-intended placeholders — not for half-built layers.
130
+ - **Structure is earned, not guessed.** You decide a `limb` exists *because*
131
+ a concrete leaf needs a home, not by sketching an org chart first. This is
132
+ also when the twig-vs-skip-to-leaf decision (next section) is actually
133
+ answerable — you can see how many siblings the concept really has.
134
+ - **Recoverable if interrupted.** Stop after any leaf and the tree is a set
135
+ of complete branches, not a headless skeleton.
136
+
137
+ A trunk or limb with no leaf descendant *during authoring* is the signal
138
+ you've slipped back into layer-by-layer mode. Add its first leaf before
139
+ moving on, or don't create the structure node yet.
140
+
141
+ ## Twig vs jump straight to leaf
142
+
143
+ `twig` is for **information groups** — a cluster of related leaves under a
144
+ shared concept. Use it only when you have (or expect to soon have) multiple
145
+ sibling `leaf` nodes that belong together.
146
+
147
+ If the concept boils down to *one* component, file, or object, **skip twig
148
+ and put the leaf directly under the limb**.
149
+
150
+ | Case | Use |
151
+ | ------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
152
+ | One React component, one file, one service | `limb → leaf` directly. No twig. |
153
+ | Several leaves that share a clear theme (e.g. design system = Button, Modal, Field, …) | `limb → twig → leaf+` |
154
+ | Several components composing one flow (e.g. auth = SignIn, Login, SignUp) | `limb → twig → leaf+` |
155
+ | A single file that decomposes into multiple meaningful units | Multiple leaves with `metadata.implements` carrying `path#identifier` (see system-structure) |
156
+
157
+ A twig with no leaves is dormant. Skipping twig when only one object lives
158
+ underneath keeps the tree readable.
159
+
160
+ The MCP `create_node` tool emits a `structure-without-children` info
161
+ warning when you create a `limb` or `twig`, reminding you that the node
162
+ renders dormant until a leaf/vein appears.
163
+
164
+ ## Tree shape — bushy is fine, don't over-nest
165
+
166
+ A healthy Umtri tree is **bushy, not deep**: most nodes sit at the middle
167
+ levels (limbs and their leaves), and the full `trunk → limb → twig → leaf →
168
+ vein` descent is reached only where the detail genuinely warrants it. Depth
169
+ is *earned by content*, not added for tidiness.
170
+
171
+ The biggest pressure toward unnecessary depth is the urge to group siblings
172
+ that merely share a name. **Sharing a label prefix is not, by itself, a
173
+ reason to create a twig.** Korean domain labels naturally share prefixes
174
+ (`예약 생성` / `예약 취소`, `포인트 충전` / `포인트 환불`) — two or three such
175
+ siblings under a limb are perfectly legible as-is. Insert a twig only when a
176
+ parent is genuinely crowded (≈4+ leaves) *and* a real sub-theme groups
177
+ several of them. Wrapping every pair in a twig makes the tree taller and
178
+ harder to read, not easier.
179
+
180
+ `get_graph` (summary) returns a `shape` block — `maxDepth`, nodes per level,
181
+ and `wideBranches` — plus a `childCount` on each branch node. Use it to judge
182
+ the tree before restructuring: mass concentrated at mid-levels with a shallow
183
+ `maxDepth` is the goal, not a smell. Over-wide trunks are usually fine (a
184
+ trunk's children are top-level domains); an over-wide **limb** (many direct
185
+ leaves) is the case the `junk-drawer-limb` / `sibling-cluster` hints flag.
186
+
187
+ ## Promotion signals — when a twig should be a limb
188
+
189
+ Some concepts start as twigs nested inside a domain limb but actually
190
+ deserve sibling-limb status. Treat these as **promotion signals** when
191
+ authoring or restructuring:
192
+
193
+ | Signal | Example |
194
+ | ------------------------------------------------------- | ---------------------------------------------------------------- |
195
+ | Route-independent global shell | A sidebar shown on every screen, a global header |
196
+ | Distinct entry flow (splash → continuation) | Login + signup pages reached before the main app |
197
+ | Globally reusable assets | Design tokens + shared components + global toasts |
198
+ | Domain limb growing into a "junk drawer" (>5 unrelated twigs) | A `dashboard` limb absorbing sidebar + auth + UI + pages |
199
+
200
+ When any apply, create the unit as a **limb sibling** of the original
201
+ domain limb rather than burying it as a twig.
202
+
203
+ The MCP `create_node` and `update_node` tools emit a
204
+ `twig-promotion-candidate` info warning when a twig's `description`
205
+ mentions promotion signals (전역, 라우트 비종속, 스플래시, 재사용 자산,
206
+ global, route-independent, splash, …).
207
+
208
+ ## Labels as information units (anti-patterns)
209
+
210
+ A label should name the thing a user or external observer perceives, not
211
+ the code construct implementing it.
212
+
213
+ | Anti-pattern label | Problem | Better |
214
+ | --------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------- |
215
+ | `Bugs router`, `Auth middleware`, `Webhook handler` | names the Express artifact | `Bug log API`, `Request authorization`, `Provider webhooks` |
216
+ | `API layer`, `Service tier`, `Data store` | layer/tier/store = architectural plumbing | split by domain, name the surface |
217
+ | `store.js`, `middleware/auth.js`, `index.jsx` | filenames belong in `metadata.implements` | `Persistence`, `Auth guard`, `Graph root` |
218
+ | `Helper`, `Manager`, `Bootstrap`, `Wrapper` | generic dev nouns, no info content | the actual responsibility it carries |
219
+
220
+ The `file-as-leaf` warning also catches the **redundant-child antipattern**:
221
+ a twig whose only child is a leaf named after the same concept's file is
222
+ just one node split into two. Absorb the file into the twig's
223
+ `metadata.implements` and remove the leaf. The twig itself *is* the
224
+ information unit; the file is its implementation.
225
+
226
+ Apply these on existing data only when a node's label clearly violates the
227
+ rule. Coordinated bulk renames go one node at a time with the diff visible.
228
+
229
+ ## Dormant cleanup — group by branch
230
+
231
+ When proposing dormant cleanup, group by parent so the user can decide on
232
+ whole branches at once rather than one node at a time. Common reasons a
233
+ node is dormant:
234
+
235
+ - Placeholder for upcoming work (intentional outline)
236
+ - A plan sketched before any code landed
237
+ - A stale outline that was never filled in (cleanup candidate)
238
+
239
+ If a node's season is `past` and the label is vague, the user may choose
240
+ to soft-delete. Nodes with `metadata.placeholder = true` are excluded —
241
+ respect that marker and do not flag them.
242
+
243
+ ## Back-filling historical projects (`sproutedAt`)
244
+
245
+ When importing a project that existed **before Umtri** — past commits,
246
+ migrated trees, archaeological work on legacy code — the node's "creation
247
+ moment" is the original event, not the moment the record landed in Umtri.
248
+
249
+ `create_node` (and `update_node`) accept an optional `sproutedAt` field
250
+ (ISO 8601 with offset, e.g. `"2024-03-15T09:00:00Z"`). When set, **the
251
+ visualization timeline follows this value** — sibling order, season
252
+ visibility masking, and the events feed all use `sproutedAt` as the
253
+ effective creation moment. The system audit timestamp (`created_at`,
254
+ which records when the row landed in the database) stays untouched and is
255
+ not surfaced in graph responses.
256
+
257
+ Guidelines:
258
+
259
+ - **Default behavior is correct for live work.** Omit `sproutedAt` for any
260
+ node created in the present — the system timestamp is the truth.
261
+ - **Use only for back-fill.** Importing past commit history, recovering a
262
+ deleted project, migrating from another tool, dogfooding a project's
263
+ history into Umtri. Do not back-date "for clarity" on live work.
264
+ - **Order matters.** When importing many nodes, set `sproutedAt` to their
265
+ real historical timestamps so sibling order and season visibility line
266
+ up. Random or batch-identical timestamps produce a deformed timeline.
267
+ - **Pair with `season`.** Place each back-filled node in the season that
268
+ was active at its `sproutedAt`. Otherwise the slider will hide nodes
269
+ whose season has not yet started at T = `sproutedAt`.
270
+ - **Clearing.** `update_node` with `patch.sproutedAt = null` clears the
271
+ override; the node falls back to its system timestamp.
272
+ - **Cannot back-fill `removed_at` or `grown_at`.** These follow the system
273
+ clock; only the *birth* of a node can be antedated.
274
+
275
+ Past-season nodes are still rejected from new `create_node` calls (the
276
+ "cannot create node in past season" rule). For full back-fill of an
277
+ archived season, set the target season to `now` first, import, then
278
+ human-promote it back to `past` via the seasons UI.