umtri-mcp 1.0.2 → 1.2.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.
- package/README.md +1 -1
- package/core/api-client.js +6 -1
- package/core/instructions.js +20 -0
- package/core/policy.js +87 -0
- package/core/resources/commit-sync.md +55 -0
- package/core/resources/plan.md +1 -1
- package/core/resources/seasons.md +47 -0
- package/core/resources/transplant.md +16 -12
- package/core/resources/vision.md +1 -1
- package/core/resources/vocabulary.md +1 -1
- package/core/resources/wiki.md +210 -0
- package/core/resources.js +19 -5
- package/core/tools.js +390 -52
- package/package.json +1 -1
- package/server.js +2 -1
- package/core/resources/seasons-human-only.md +0 -33
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ nodes are meant to be realized, and how a ground behaves while transplanting.
|
|
|
73
73
|
```
|
|
74
74
|
umtri://rules/vocabulary umtri://rules/transplant
|
|
75
75
|
umtri://rules/vocabulary-detailed umtri://rules/plan
|
|
76
|
-
umtri://rules/seasons
|
|
76
|
+
umtri://rules/seasons umtri://about/vision
|
|
77
77
|
umtri://rules/system-structure
|
|
78
78
|
```
|
|
79
79
|
|
package/core/api-client.js
CHANGED
|
@@ -36,7 +36,12 @@ export function createApiClient({ baseUrl, getToken }) {
|
|
|
36
36
|
if (!res.ok) {
|
|
37
37
|
const text = await res.text().catch(() => '');
|
|
38
38
|
if (res.status === 403) {
|
|
39
|
-
|
|
39
|
+
// 403은 한 가지가 아니다 — write scope 없음과 admin 전용 경로(feedback 관리 등)가 같이 온다.
|
|
40
|
+
// 서버가 준 이유를 버리고 토큰 안내로 덮으면, 관리자 경로에서 막힌 사람이 멀쩡한 토큰을
|
|
41
|
+
// 다시 발급하러 간다. 이유를 그대로 싣고, 스코프 문제일 때만 발급 안내를 덧붙인다.
|
|
42
|
+
const reason = text.slice(0, 200) || 'forbidden';
|
|
43
|
+
const hint = /scope/i.test(text) ? " Generate a 'write' token in Settings → API Tokens." : '';
|
|
44
|
+
throw new Error(`${method} ${path} → 403: ${reason}${hint}`);
|
|
40
45
|
}
|
|
41
46
|
throw new Error(`${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
42
47
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// MCP initialize 응답의 instructions — 클라이언트가 세션 시작에 모델에게 그대로
|
|
2
|
+
// 전달한다. 서버가 먼저 말을 걸 수 있는 유일한 자리다(리소스·도구는 불러야 열린다).
|
|
3
|
+
//
|
|
4
|
+
// 그래서 여기엔 "언젠가 읽으면 좋은 것"이 아니라 **읽지 않으면 조용히 어긋나는 것**만
|
|
5
|
+
// 둔다. 매 세션 컨텍스트에 실리므로 길어지면 그 자체가 비용이고, 길수록 안 읽힌다.
|
|
6
|
+
// 상세는 전부 umtri://rules/* 리소스로 넘긴다.
|
|
7
|
+
//
|
|
8
|
+
// send_feedback 문단이 여기 있는 건 그 기준의 예외처럼 보이지만 아니다. 도구 목록에
|
|
9
|
+
// 이름이 있어도 모델은 요청받은 일만 하므로, 이 한 줄이 없으면 도구는 있고 아무도
|
|
10
|
+
// 부르지 않는 상태로 남는다. 마찰을 가장 먼저 겪는 건 에이전트인데 그 관찰은 세션이
|
|
11
|
+
// 끝나면 사라진다. "사람에게 권한다"까지가 문단의 핵심이다 — 자율 발송을 허용하면
|
|
12
|
+
// 접수함이 에이전트의 불평으로 먼저 죽는다.
|
|
13
|
+
|
|
14
|
+
export const SERVER_INSTRUCTIONS = `Umtri maps a project's structure as a tree (trunk → limb → twig → leaf → vein) and tracks how it changes over seasons. Start with list_projects, then get_graph.
|
|
15
|
+
|
|
16
|
+
Umtri never reads git. A commit reaches the ground only because something called create_node / update_node / record_commit — so the tree drifts behind the code unless the repo has a habit that says otherwise. If this repo's own rules file (CLAUDE.md, AGENTS.md, …) has no commit-sync policy, read umtri://rules/commit-sync and offer the human a short one to paste in.
|
|
17
|
+
|
|
18
|
+
Before creating or reclassifying nodes, read umtri://rules/vocabulary. Opening a season seals the current one, so create_season only acts after the user confirms (umtri://rules/seasons).
|
|
19
|
+
|
|
20
|
+
If Umtri itself gets in your way — a call rejected for a reason that looks wrong, a capability that is missing, a tool description that misled you — tell the human what happened and offer to file it with send_feedback. Never file it on your own initiative.`;
|
package/core/policy.js
CHANGED
|
@@ -231,6 +231,93 @@ export function reparentMetadataHint(patch) {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
// ── wiki 본문 ────────────────────────────────────────────────────────
|
|
235
|
+
// 위키를 로그로 쓰는 경향이 실제로 관찰됐다 — 작업을 끝낸 에이전트가 페이지를 열어
|
|
236
|
+
// "이번에 뭘 했는지"를 덧붙이고, 몇 번 반복되면 페이지가 세션 요약 더미가 된다.
|
|
237
|
+
// 규칙 문서(umtri://rules/wiki)에 적어뒀지만 리소스는 pull-only라 안 읽으면 그만이라,
|
|
238
|
+
// 쓰는 순간에 신호를 준다.
|
|
239
|
+
//
|
|
240
|
+
// **거부하지 않고 경고만 한다.** 판정이 휴리스틱이고, 날짜가 정당하게 필요한 문서도
|
|
241
|
+
// 있기 때문이다(마이그레이션 위험 고지 등). 오탐이 잦으면 경고 자체가 무시되므로
|
|
242
|
+
// 정밀도가 높은 신호만 고른다 — 셋 다 산문형 레퍼런스에는 거의 나오지 않는 형태다.
|
|
243
|
+
// 날짜가 제목 **앞머리**에 올 때만 잡는다. "## 2026-08-27 — 인증 분리"는 일지 항목이지만
|
|
244
|
+
// "## 확정된 결정 (2026-07-28)"은 결정에 날짜를 단 것이라 정당하다. 괄호 안 날짜까지
|
|
245
|
+
// 잡으면 오탐이 늘고, 오탐이 늘면 경고를 아무도 안 본다.
|
|
246
|
+
const WIKI_DATE_HEADING_RE = /^#{1,6}\s*[([]?\s*20\d{2}\s*[-./년]\s*\d{1,2}\s*[-./월]\s*\d{1,2}/m;
|
|
247
|
+
const WIKI_CHANGELOG_HEADING_RE = /^#{1,6}\s*(?:.*(?:change\s*(?:log|history)|revision history|release notes|변경\s*(?:이력|내역)|작업\s*이력|업데이트\s*내역|진행\s*(?:표시|상황)).*)$/im;
|
|
248
|
+
const WIKI_CHECKBOX_RE = /^\s*[-*+]\s*\[[ xX]\]/gm;
|
|
249
|
+
|
|
250
|
+
export function wikiLogShapeCheck(body) {
|
|
251
|
+
if (!body || typeof body !== 'string') return [];
|
|
252
|
+
const out = [];
|
|
253
|
+
|
|
254
|
+
if (WIKI_DATE_HEADING_RE.test(body)) {
|
|
255
|
+
out.push({
|
|
256
|
+
rule: 'wiki-dated-entry',
|
|
257
|
+
severity: 'warn',
|
|
258
|
+
message: 'This body has a date heading. A wiki page is a description of how something works now, not a dated log — the page\'s own past is kept in its revisions (list_wiki_revisions) and who-changed-what in list_events. Fold the fact into the prose and drop the date, unless the date is itself a live hazard a reader must know.',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (WIKI_CHANGELOG_HEADING_RE.test(body)) {
|
|
263
|
+
out.push({
|
|
264
|
+
rule: 'wiki-changelog-section',
|
|
265
|
+
severity: 'warn',
|
|
266
|
+
message: 'This body has a change-history / progress section. Revisions already record what the page said before, so that section duplicates them and goes stale. Delete it and let the prose describe the current state.',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const boxes = body.match(WIKI_CHECKBOX_RE);
|
|
271
|
+
if (boxes && boxes.length >= 3) {
|
|
272
|
+
out.push({
|
|
273
|
+
rule: 'wiki-progress-checklist',
|
|
274
|
+
severity: 'info',
|
|
275
|
+
message: `This body has ${boxes.length} checklist items. Outstanding work belongs in bugs (create_bug) or plan nodes, where it can be tracked and closed — a checklist in a wiki page is read by nobody and rots. Keep the page to what is true now.`,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── 도구 호출 마크업 유출 ────────────────────────────────────────────
|
|
283
|
+
// 인자가 필드로 쪼개지지 못하고 첫 필드에 통째로 들어오는 사고가 실제로 있었다
|
|
284
|
+
// (feedback #1: create_bug을 세 번 부르는 동안 세 번 다 solution이 description 꼬리에
|
|
285
|
+
// 문자열로 붙고 solution은 null로 저장됐다). 긴 산문 필드 둘이 나란히 오면 특히 잦다.
|
|
286
|
+
//
|
|
287
|
+
// 저장 자체는 성공하므로 아무도 모른 채 지나가고, 사람이 앱에서 볼 때에야 드러난다.
|
|
288
|
+
// 그래서 쓰기 응답에 그 자리에서 신호를 준다.
|
|
289
|
+
//
|
|
290
|
+
// **거부하지 않고 경고만 한다.** 도구 포맷을 *설명하는* 산문이 이 조각들을 정상적으로
|
|
291
|
+
// 담기 때문이다 — 하필 이 프로젝트의 위키가 그런 글을 쓴다. 대신 코드펜스와 인라인
|
|
292
|
+
// 코드를 걷어내고 검사한다: 설명하는 글은 따옴표 안에 넣고, 진짜 유출은 맨몸으로 온다.
|
|
293
|
+
// 이 구분이 오탐을 거의 없앤다.
|
|
294
|
+
const TOOL_MARKUP_RES = [
|
|
295
|
+
/<\/?(?:antml:)?parameter\b/i,
|
|
296
|
+
/<\/?(?:antml:)?invoke\b/i,
|
|
297
|
+
/<\/?(?:antml:)?function_calls\b/i,
|
|
298
|
+
/<\/(?:description|solution|body|title|label|note|metadata)>/i,
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
function withoutCode(text) {
|
|
302
|
+
return text.replace(/```[\s\S]*?```/g, ' ').replace(/`[^`]*`/g, ' ');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// fields: { 필드이름: 값 } — 문자열이 아닌 값은 건너뛴다.
|
|
306
|
+
export function toolMarkupCheck(fields) {
|
|
307
|
+
const out = [];
|
|
308
|
+
for (const [name, value] of Object.entries(fields || {})) {
|
|
309
|
+
if (!value || typeof value !== 'string') continue;
|
|
310
|
+
const bare = withoutCode(value);
|
|
311
|
+
if (!TOOL_MARKUP_RES.some((re) => re.test(bare))) continue;
|
|
312
|
+
out.push({
|
|
313
|
+
rule: 'tool-markup-leaked',
|
|
314
|
+
severity: 'warn',
|
|
315
|
+
message: `The "${name}" value contains raw tool-call markup (e.g. "</${name}>" or "<parameter name=...>"). That means this call's arguments were not split into fields: the text meant for the next parameter was appended to this one, and that parameter was almost certainly saved empty. Read the record back and re-send the values as separate arguments — nothing else will report this. (Writing *about* tool-call syntax is fine: put it in a code fence or inline code, which this check ignores.)`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
|
|
234
321
|
// ── bug lifecycle ────────────────────────────────────────────────────
|
|
235
322
|
// Bug Codex reads status as three states: open=wild, in_progress=chasing,
|
|
236
323
|
// resolved|closed=catched. The recommended path is wild → chasing → catched:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Recommendation: Keep a Commit-Sync Policy in the Repo
|
|
2
|
+
|
|
3
|
+
**Umtri never reads your git history by itself.** It has no job runner and no
|
|
4
|
+
repo access — a node learns about a commit only because something called
|
|
5
|
+
`record_commit`, and it learns about a *new* file only because something called
|
|
6
|
+
`create_node`. Nothing in this server can interrupt you at commit time.
|
|
7
|
+
|
|
8
|
+
That is why the tree drifts: code moves forward, the ground stays where it was,
|
|
9
|
+
and no one finds out until a person compares the two by hand.
|
|
10
|
+
|
|
11
|
+
## What to do about it
|
|
12
|
+
|
|
13
|
+
Write a short commit-sync policy into **the repo's own agent rules file** — the
|
|
14
|
+
one every session already loads (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`,
|
|
15
|
+
whatever that project uses). That file is read before the work starts, by the
|
|
16
|
+
same agent that will do the committing. This resource cannot be, because MCP
|
|
17
|
+
resources are pull-only: they are read when someone asks for them, which is
|
|
18
|
+
exactly the moment that never arrives on its own.
|
|
19
|
+
|
|
20
|
+
This is a recommendation, not a gate. Nothing here blocks a commit, and a
|
|
21
|
+
project that ignores it still works — the ground simply ages faster than the
|
|
22
|
+
code.
|
|
23
|
+
|
|
24
|
+
## Snippet to adapt
|
|
25
|
+
|
|
26
|
+
Paste into the repo's rules file and edit to fit the project (translate it if
|
|
27
|
+
the file is in another language — the wording matters less than the trigger):
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
### Umtri sync (before committing)
|
|
31
|
+
|
|
32
|
+
Umtri does not read git. When a commit is about to be made, check whether the
|
|
33
|
+
change also belongs in the ground:
|
|
34
|
+
|
|
35
|
+
1. Changed paths vs `metadata.implements` — does every changed file map to a node?
|
|
36
|
+
- New unit of the system (route, screen, table, integration) → `create_node`,
|
|
37
|
+
with `metadata.implements` set. Then wire its connections
|
|
38
|
+
(`create_edge` / `create_api`) — an unconnected node is invisible to impact.
|
|
39
|
+
- File moved or renamed → `update_node` on the affected nodes' `implements`.
|
|
40
|
+
Directory moves are the expensive case: one `git mv` can strand dozens.
|
|
41
|
+
- Behavior changed but structure did not → nothing to do.
|
|
42
|
+
2. Does the commit close a tracked bug? → `update_bug` (status, and `solution`
|
|
43
|
+
describing what was actually done).
|
|
44
|
+
3. Then `record_commit` with the SHA and the changed paths.
|
|
45
|
+
|
|
46
|
+
Ask the human before adding nodes for anything ambiguous — a wrong node is
|
|
47
|
+
harder to notice later than a missing one.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Where automation fits
|
|
51
|
+
|
|
52
|
+
CI (a GitHub Action on push) is a good place for step 3 — it never forgets a
|
|
53
|
+
SHA. It is a poor place for steps 1 and 2: by then the commit exists, the log
|
|
54
|
+
is unread, and the run is green either way. Detection belongs in CI; the
|
|
55
|
+
decision belongs where a human or agent is still in the loop.
|
package/core/resources/plan.md
CHANGED
|
@@ -41,7 +41,7 @@ back onto the nodes. The human commits when satisfied.
|
|
|
41
41
|
## Boundaries
|
|
42
42
|
|
|
43
43
|
- Plan is independent of seasons; you still cannot create or modify seasons
|
|
44
|
-
(see umtri://rules/seasons
|
|
44
|
+
(see umtri://rules/seasons).
|
|
45
45
|
- The brief is still an information-structure diagram, not a work log
|
|
46
46
|
(see umtri://rules/vocabulary) — realize the structure the human intends,
|
|
47
47
|
and name nodes as what they are.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Rule: Seasons Need the Human's Word
|
|
2
|
+
|
|
3
|
+
A `season` is a deliberate growth epoch — "Q3 2026", "v2 redesign", "PoC".
|
|
4
|
+
It is the unit by which a project owner *consciously breaks time* in their
|
|
5
|
+
ground. You may create one, but never on your own judgment.
|
|
6
|
+
|
|
7
|
+
## Why this needs confirming
|
|
8
|
+
|
|
9
|
+
Creating a season is not additive. The ground's current `now` season is sealed
|
|
10
|
+
as `past` and every node in it is marked grown, which locks that node's
|
|
11
|
+
`parent`, `season`, `type` and `sproutedAt`. A season opened because you could
|
|
12
|
+
not decide where a node belonged turns the ground's timeline into noise, and
|
|
13
|
+
the lock it leaves behind is not yours to undo.
|
|
14
|
+
|
|
15
|
+
## The gate
|
|
16
|
+
|
|
17
|
+
`create_season` refuses to create anything until the human has agreed.
|
|
18
|
+
|
|
19
|
+
1. Call it with `slug` and `label` and **no** `confirm`. Nothing is created;
|
|
20
|
+
you get back the label you proposed and the season that would be sealed.
|
|
21
|
+
2. Put that to the user in your own words — the new season and what closes.
|
|
22
|
+
3. Only once they say yes, call again with `confirm: true`.
|
|
23
|
+
|
|
24
|
+
Passing `confirm: true` on the first call is a protocol violation, even when
|
|
25
|
+
the user's request seems to imply it. "Move the pre-April work into a PoC
|
|
26
|
+
season" names a season; it does not confirm sealing the current one.
|
|
27
|
+
|
|
28
|
+
## When not to reach for it at all
|
|
29
|
+
|
|
30
|
+
Unsure which season a new node belongs to? Do **not** open one.
|
|
31
|
+
|
|
32
|
+
- Attach it to the existing season whose state is `now`.
|
|
33
|
+
- If no `now` season exists, ask the user which season to attach to.
|
|
34
|
+
|
|
35
|
+
A missing season is a question for the human, not a gap for you to fill.
|
|
36
|
+
|
|
37
|
+
## What needs no confirmation
|
|
38
|
+
|
|
39
|
+
- Reading seasons (`list_seasons`).
|
|
40
|
+
- Attaching new nodes to existing seasons via the `season` field.
|
|
41
|
+
- Suggesting that a new season *might* be useful, and waiting.
|
|
42
|
+
|
|
43
|
+
## Still human-only
|
|
44
|
+
|
|
45
|
+
Rooting a ground (ending transplant) stays human-only through the UI, and once
|
|
46
|
+
rooted it cannot be re-opened by anyone but an administrator.
|
|
47
|
+
See umtri://rules/transplant.
|
|
@@ -23,22 +23,26 @@ You MAY:
|
|
|
23
23
|
`hard` only to clean up genuine mistakes — it is irreversible.
|
|
24
24
|
|
|
25
25
|
You MAY NOT:
|
|
26
|
-
- **
|
|
27
|
-
(see umtri://rules/seasons
|
|
28
|
-
seasons
|
|
26
|
+
- **Open a season without asking.** `create_season` exists, but it only acts
|
|
27
|
+
after the user confirms (see umtri://rules/seasons). Laying down historical
|
|
28
|
+
seasons is exactly the case where you propose and they decide.
|
|
29
29
|
- **Root (settle) a ground.** Ending transplant is human-only — done from the
|
|
30
30
|
UI ("Root this ground"). You cannot root via MCP.
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Rooting is final
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
Once a ground is rooted, **nothing re-opens transplant** — not you, not the
|
|
35
|
+
person who owns the ground. There is no `reopen_transplant` tool, and
|
|
36
|
+
`POST /api/projects/:slug/root` refuses `rooted: false`. Only an administrator
|
|
37
|
+
can re-open it, out of band.
|
|
38
|
+
|
|
39
|
+
This is deliberate. Re-opening unlocks every past season at once, so a window
|
|
40
|
+
meant for the first import would otherwise become the standard way around a
|
|
41
|
+
guard. If a rooted ground genuinely needs restructuring, say so plainly and
|
|
42
|
+
let the human decide whether to take it to an administrator.
|
|
43
|
+
|
|
44
|
+
Most of what people reach for transplanting to do does not need it — a stale
|
|
45
|
+
`metadata.implements` on a past node is editable as it stands (see below).
|
|
42
46
|
|
|
43
47
|
## Audit stamp
|
|
44
48
|
|
package/core/resources/vision.md
CHANGED
|
@@ -43,4 +43,4 @@ When you (an AI tool) work on an Umtri ground:
|
|
|
43
43
|
touched (`metadata.implements`, `metadata.commits`) via the API — e.g. a
|
|
44
44
|
GitHub Action calling `record_commit` on merge. Umtri stores the form; it
|
|
45
45
|
does not execute the pipeline.
|
|
46
|
-
- It does not
|
|
46
|
+
- It does not open a season on its own judgment (see `umtri://rules/seasons`).
|
|
@@ -98,7 +98,7 @@ nodes (in `type` or `label`) is the most common mistake.
|
|
|
98
98
|
|
|
99
99
|
- Bug / error / issue → `bugs` table — use `create_bug`
|
|
100
100
|
- API / endpoint / integration → `apis` table — use `create_api`
|
|
101
|
-
- Season / sprint / epoch →
|
|
101
|
+
- Season / sprint / epoch → needs the user's confirmation, see `umtri://rules/seasons`
|
|
102
102
|
- Dependency / data flow → `edges` table — use `create_edge`
|
|
103
103
|
|
|
104
104
|
## Bug lifecycle — one step at a time
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Writing a Ground's Wiki
|
|
2
|
+
|
|
3
|
+
The tree holds structure. The wiki holds what the tree cannot: the conventions a
|
|
4
|
+
project settled on, the decisions it made and why, the vocabulary it uses, what
|
|
5
|
+
an investigation concluded.
|
|
6
|
+
|
|
7
|
+
Two things to remember. The first is the shape of a page:
|
|
8
|
+
|
|
9
|
+
> **A page is named after a thing, and its revisions are that thing's life.**
|
|
10
|
+
> Revision 1 is the plan. Later revisions are what it became. The newest
|
|
11
|
+
> revision is what is true now.
|
|
12
|
+
|
|
13
|
+
The second is what a page *is*:
|
|
14
|
+
|
|
15
|
+
> **A page is a description, not a log.**
|
|
16
|
+
> It tells a reader how something works right now. It does not narrate what
|
|
17
|
+
> happened to it.
|
|
18
|
+
|
|
19
|
+
Everything below follows from those two.
|
|
20
|
+
|
|
21
|
+
## Write reference, not a diary
|
|
22
|
+
|
|
23
|
+
This is the mistake that actually happens. An agent finishes some work, opens
|
|
24
|
+
the page, and appends a paragraph about what it just did. Do that a few times
|
|
25
|
+
and the page is a pile of session summaries that nobody can read as an
|
|
26
|
+
explanation of anything.
|
|
27
|
+
|
|
28
|
+
**The page should read like an encyclopedia entry for the thing, in the present
|
|
29
|
+
tense.** Someone who arrives knowing nothing should finish it understanding how
|
|
30
|
+
the thing works. They should not have to reconstruct the current state by
|
|
31
|
+
replaying a timeline.
|
|
32
|
+
|
|
33
|
+
| Log — do not write this | Description — write this |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| "Added retry logic to the webhook handler today." | "The webhook handler retries three times with backoff. Toss redelivers on any non-2xx, so a slow database would otherwise double-charge." |
|
|
36
|
+
| "## 2026-08-27 — split the auth middleware" | "Auth runs in two stages: session or bearer resolution, then scope check. They are separate because MCP loopback needs the second without the first." |
|
|
37
|
+
| "Phase 1 done, Phase 2 in progress" | (belongs in bugs or plan nodes, not here) |
|
|
38
|
+
| "## Change history" / "## 변경 이력" | (delete it — that is what revisions are for) |
|
|
39
|
+
|
|
40
|
+
Concretely, a page should not contain:
|
|
41
|
+
|
|
42
|
+
- **Date headings** or dated entries of any kind
|
|
43
|
+
- **Progress checklists** — "Phase 2 완료", "- [x] W3", "TODO: …"
|
|
44
|
+
- **Sentences about the act of working** — "we added", "이번에 …했다", "next I will"
|
|
45
|
+
- **A changelog section**
|
|
46
|
+
|
|
47
|
+
### Why this is not a loss
|
|
48
|
+
|
|
49
|
+
The temporal record already exists, in better places:
|
|
50
|
+
|
|
51
|
+
| What you want to record | Where it belongs |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| What this page said before | revisions (`list_wiki_revisions`) |
|
|
54
|
+
| Who changed what, when | the ground's history (`list_events`) |
|
|
55
|
+
| Which commit touched which code | `record_commit` → the node's `metadata.commits` |
|
|
56
|
+
| What still needs doing | bugs (`create_bug`) or plan nodes |
|
|
57
|
+
| What you plan to do | revision 1 of the page, written *before* you build |
|
|
58
|
+
|
|
59
|
+
**Because the history lives in the revisions, the body is free to be purely
|
|
60
|
+
present-tense.** That is the whole point of versioning the page: you never have
|
|
61
|
+
to keep an old paragraph around just to preserve the record of it. Rewrite the
|
|
62
|
+
sentence to say what is true now, and the previous wording stays readable in the
|
|
63
|
+
revision underneath.
|
|
64
|
+
|
|
65
|
+
A page that logs is doing, badly, a job four other things already do well — and
|
|
66
|
+
failing at the one job only it can do.
|
|
67
|
+
|
|
68
|
+
### The exception: what "why" survives
|
|
69
|
+
|
|
70
|
+
Present tense does not mean stripping the reasoning. "The webhook handler
|
|
71
|
+
retries because Toss redelivers on any non-2xx" is present tense and is exactly
|
|
72
|
+
the sentence worth keeping. The test is not *is this about the past* but **would
|
|
73
|
+
someone changing this code get it wrong without knowing it** — see "What stays
|
|
74
|
+
on the page" below.
|
|
75
|
+
|
|
76
|
+
Past events belong in a page only when the event is *itself* a live constraint:
|
|
77
|
+
"the `apex` type was renamed to `leaf` in 2026-05; old exports may still carry
|
|
78
|
+
it." That is not a diary entry, it is a hazard a reader needs.
|
|
79
|
+
|
|
80
|
+
## Name the thing, not the document
|
|
81
|
+
|
|
82
|
+
A page is called `mcp`, `billing`, `impact-analysis` — the subject it is about.
|
|
83
|
+
|
|
84
|
+
Never name a page after what it currently *is*: `prd-mcp`, `mcp-spec`,
|
|
85
|
+
`mcp-notes-v2`, `mcp-final`. Those names are accurate for a week. The page that
|
|
86
|
+
begins life as a plan for MCP and ends as the description of the MCP you built
|
|
87
|
+
is the same page the whole time, and `prd-mcp` becomes a lie the moment the
|
|
88
|
+
first line of it ships.
|
|
89
|
+
|
|
90
|
+
Use lowercase letters, digits and hyphens. Say what a maintainer would say out
|
|
91
|
+
loud: `deploy`, not `deployment-process-documentation`.
|
|
92
|
+
|
|
93
|
+
If a name turns out wrong, use `write_wiki`'s **`rename`**. Do not create a new
|
|
94
|
+
page and delete the old one — that throws away every revision, which is the part
|
|
95
|
+
of the page that was worth keeping. A rename on its own adds no revision; a
|
|
96
|
+
revision records content, and the name is not content.
|
|
97
|
+
|
|
98
|
+
## Revision 1 is the plan
|
|
99
|
+
|
|
100
|
+
Before building something, write the page. What you are about to do, why this
|
|
101
|
+
way, what you decided against. That is revision 1, and it is a real deliverable:
|
|
102
|
+
another agent picking up the work reads it instead of guessing.
|
|
103
|
+
|
|
104
|
+
This replaces keeping design docs in the repo. A plan in a repo file goes stale
|
|
105
|
+
silently — nothing makes anyone revisit it, and six months later no one can tell
|
|
106
|
+
whether it describes the code or someone's old intention. A plan that is
|
|
107
|
+
revision 1 of a living page cannot rot the same way, because the page keeps
|
|
108
|
+
being updated and the plan stays readable underneath as history.
|
|
109
|
+
|
|
110
|
+
Plan nodes and this page do different jobs and work together: the plan nodes say
|
|
111
|
+
*where* in the structure something goes, the page says *what and why*. See
|
|
112
|
+
`umtri://rules/plan`.
|
|
113
|
+
|
|
114
|
+
## Write back what you actually built — with `newRevision: true`
|
|
115
|
+
|
|
116
|
+
When the thing exists, come back and make the page describe it. Not "the plan
|
|
117
|
+
plus a note that it's done" — the page should now read as a description of what
|
|
118
|
+
is there.
|
|
119
|
+
|
|
120
|
+
**Pass `newRevision: true` on that write.** This matters more than it looks.
|
|
121
|
+
Writes fold together when they come from the same author within about half an
|
|
122
|
+
hour, so that eight passes of polishing leave one revision instead of eight.
|
|
123
|
+
That folding cannot tell "still drafting the plan" from "the plan has become the
|
|
124
|
+
thing" — clock and author look identical in both cases. Without the flag, an
|
|
125
|
+
agent that plans and then builds in one sitting overwrites its own plan, and
|
|
126
|
+
revision 1 is gone.
|
|
127
|
+
|
|
128
|
+
Use it whenever the page crosses a boundary: plan → built, v1 → v2, one approach
|
|
129
|
+
abandoned for another. Skip it while you are still working on the same stage.
|
|
130
|
+
|
|
131
|
+
## What stays on the page, what becomes history
|
|
132
|
+
|
|
133
|
+
The page is the current state. But "current state" is not just the mechanics —
|
|
134
|
+
**a live reason is part of the current spec.**
|
|
135
|
+
|
|
136
|
+
| Stays on the page | Becomes history |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| How it works now | How it used to work |
|
|
139
|
+
| The constraint that makes it work this way | A constraint that no longer applies |
|
|
140
|
+
| A decision you would repeat, and why | The full argument that led to a reversed decision |
|
|
141
|
+
| The conclusion of a reversal: "we tried A, moved to B, because —" | The prose written while A was still true |
|
|
142
|
+
|
|
143
|
+
The test: **would someone about to change this code get it wrong without
|
|
144
|
+
knowing this?** If yes, it belongs on the page, however old it is.
|
|
145
|
+
|
|
146
|
+
A rule like "team org keeps `plan='free'` because plan describes that org's own
|
|
147
|
+
subscription and a team org has none" is not history. It is a live trap, and a
|
|
148
|
+
page that files it away as an old decision will get rewritten wrong.
|
|
149
|
+
|
|
150
|
+
A page that strips out every reason becomes a manual, and a manual is exactly
|
|
151
|
+
the thing the tree could have held. Do not optimize the wiki into uselessness.
|
|
152
|
+
|
|
153
|
+
## One page per thing
|
|
154
|
+
|
|
155
|
+
Split when a page starts answering two questions a reader would arrive with
|
|
156
|
+
separately. Merge — or never split — when the answer to one requires the other.
|
|
157
|
+
|
|
158
|
+
Do not split by document type. `mcp` and `mcp-architecture` are not two pages;
|
|
159
|
+
they are one page at two points in its life. Do not create a page per file, per
|
|
160
|
+
sprint, or per meeting. A ground with forty one-paragraph pages is harder to
|
|
161
|
+
read than the code.
|
|
162
|
+
|
|
163
|
+
## Choosing how to write
|
|
164
|
+
|
|
165
|
+
| | |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| The thing changed | `mode="replace"` — **read the page first** (`get_wiki`) |
|
|
168
|
+
| The page is missing a whole topic you can add as its own section | `mode="append"` |
|
|
169
|
+
| Crossing into a new stage | add `newRevision: true` |
|
|
170
|
+
| A write went wrong | `list_wiki_revisions`, then `mode="restore"` with the rev |
|
|
171
|
+
| You read, thought, then wrote, and someone else may have written meanwhile | add `baseRev` — the write is refused instead of clobbering |
|
|
172
|
+
|
|
173
|
+
**`append` is the narrow case, not the default.** It is for adding a section
|
|
174
|
+
that was genuinely absent — a new subsystem the page never covered. It is not
|
|
175
|
+
for tacking on what you just did. If your addition would only make sense to
|
|
176
|
+
someone who knows what happened this week, you want `replace`: fold the new fact
|
|
177
|
+
into the sentence that was already there.
|
|
178
|
+
|
|
179
|
+
After any write, look at what came back. `previousBodyChars` is how long the
|
|
180
|
+
body was before you wrote. If it is much larger than what you just wrote, you
|
|
181
|
+
replaced someone's work — check `list_wiki_revisions` before moving on. Nothing
|
|
182
|
+
is lost, but nobody will tell you later.
|
|
183
|
+
|
|
184
|
+
## Attaching to nodes
|
|
185
|
+
|
|
186
|
+
A page can hang off a node or float free. Floating is normal and expected early:
|
|
187
|
+
knowledge arrives before structure, and a page that has to wait for a node is a
|
|
188
|
+
page that never gets written.
|
|
189
|
+
|
|
190
|
+
Once the tree has grown, list the unattached pages (`list_wiki` with
|
|
191
|
+
`node="none"`) and attach the ones that now have a home. Deleting a node
|
|
192
|
+
detaches its pages rather than deleting them.
|
|
193
|
+
|
|
194
|
+
## Deleting
|
|
195
|
+
|
|
196
|
+
Almost never. Deleting removes the page **and its entire revision history** —
|
|
197
|
+
unlike an overwrite, it cannot be undone.
|
|
198
|
+
|
|
199
|
+
A stale page is not a reason to delete. Rewrite it, and let the old state live
|
|
200
|
+
in its revisions. Delete only a page that should never have existed: a
|
|
201
|
+
duplicate, a test, something filed under the wrong ground.
|
|
202
|
+
|
|
203
|
+
## What does not belong here
|
|
204
|
+
|
|
205
|
+
- **Structure** — that is what nodes are for. Do not maintain a prose copy of
|
|
206
|
+
the tree; it will drift within a week.
|
|
207
|
+
- **Defects** — use bugs. A wiki page is not a to-do list.
|
|
208
|
+
- **Requirements dumped in a ground's description** — that field is a one-line
|
|
209
|
+
summary, not a spec. Move it here.
|
|
210
|
+
- **Secrets** — tokens, keys, passwords. The wiki is prose, not a vault.
|
package/core/resources.js
CHANGED
|
@@ -23,11 +23,11 @@ const RESOURCES = [
|
|
|
23
23
|
description: 'Reclassification policy, twig vs leaf decision matrix, twig promotion signals, redundant-child antipattern, Korean label heuristics. Read only when restructuring an existing tree or wrestling with a borderline classification.',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
name: 'seasons
|
|
27
|
-
uri: 'umtri://rules/seasons
|
|
28
|
-
file: 'seasons
|
|
29
|
-
title: 'Seasons
|
|
30
|
-
description: '
|
|
26
|
+
name: 'seasons',
|
|
27
|
+
uri: 'umtri://rules/seasons',
|
|
28
|
+
file: 'seasons.md',
|
|
29
|
+
title: 'Seasons need the human\'s word',
|
|
30
|
+
description: 'create_season exists but is gated: the first call only reports what would be sealed, and creation needs confirm:true after the user agrees. Read before opening a season, or when unsure which season a node belongs to.',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
name: 'transplant',
|
|
@@ -43,6 +43,20 @@ const RESOURCES = [
|
|
|
43
43
|
title: 'Plan nodes are a node-based brief',
|
|
44
44
|
description: 'Nodes marked plan:true are the human\'s structural brief (intent expressed as nodes, not a prompt). Read them as instructions, realize the code, attach metadata.implements (required), keep new detail as plan, and leave committing to the human. Read when get_graph shows any plan:true node.',
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'commit-sync',
|
|
48
|
+
uri: 'umtri://rules/commit-sync',
|
|
49
|
+
file: 'commit-sync.md',
|
|
50
|
+
title: 'Keep the ground in step with commits',
|
|
51
|
+
description: 'Umtri never reads git — nodes drift unless something records the change. Recommends writing a short commit-sync policy into the repo\'s own agent rules file (CLAUDE.md/AGENTS.md), with a snippet to adapt. Read when setting Umtri up in a repo, or when a commit turns out to have left the tree behind.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'wiki',
|
|
55
|
+
uri: 'umtri://rules/wiki',
|
|
56
|
+
file: 'wiki.md',
|
|
57
|
+
title: 'How to write a ground\'s wiki',
|
|
58
|
+
description: 'A wiki page is named after a thing, and its revisions are that thing\'s life: rev 1 is the plan, later revisions are what it became, the newest is what is true now. Covers naming (the subject, never "prd-x"), writing the plan first, marking the plan→built boundary with newRevision, what stays on the page versus what becomes history (a live reason is current spec, not history), when to split pages, replace/append/restore/baseRev, attaching to nodes, and why deleting is almost never right. Read before writing or reorganizing wiki pages.',
|
|
59
|
+
},
|
|
46
60
|
{
|
|
47
61
|
name: 'system-structure',
|
|
48
62
|
uri: 'umtri://rules/system-structure',
|