umtri-mcp 1.2.1 → 1.3.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/core/policy.js +8 -8
- package/core/resources/vocabulary.md +2 -2
- package/core/tools.js +23 -12
- package/package.json +1 -1
package/core/policy.js
CHANGED
|
@@ -320,7 +320,7 @@ export function toolMarkupCheck(fields) {
|
|
|
320
320
|
|
|
321
321
|
// ── bug lifecycle ────────────────────────────────────────────────────
|
|
322
322
|
// Bug Codex reads status as three states: open=wild, in_progress=chasing,
|
|
323
|
-
// resolved|closed=
|
|
323
|
+
// resolved|closed=resolved. The recommended path is wild → chasing → resolved:
|
|
324
324
|
// flip to in_progress the moment work starts, then resolve when it lands.
|
|
325
325
|
//
|
|
326
326
|
// Why this matters beyond tidiness: in_progress is the only signal that someone
|
|
@@ -328,26 +328,26 @@ export function toolMarkupCheck(fields) {
|
|
|
328
328
|
// "chasing", so a second agent (or the human) has no way to see the work in
|
|
329
329
|
// flight and may start the same fix. The jump also collapses the two timestamps
|
|
330
330
|
// the codex uses to tell "found → started" from "started → shipped".
|
|
331
|
-
const
|
|
331
|
+
const RESOLVED_STATUSES = new Set(['resolved', 'closed']);
|
|
332
332
|
|
|
333
333
|
export function bugStatusTransitionCheck(from, to) {
|
|
334
334
|
if (!from || !to || from === to) return null;
|
|
335
335
|
|
|
336
|
-
// wild →
|
|
337
|
-
if (from === 'open' &&
|
|
336
|
+
// wild → resolved, skipping chasing.
|
|
337
|
+
if (from === 'open' && RESOLVED_STATUSES.has(to)) {
|
|
338
338
|
return {
|
|
339
339
|
rule: 'bug-skips-chasing',
|
|
340
340
|
severity: 'info',
|
|
341
|
-
message: `Bug goes straight from "open" (wild) to "${to}"
|
|
341
|
+
message: `Bug goes straight from "open" (wild) to "${to}", skipping "in_progress" (chasing). Recommended flow: set status="in_progress" when you start the fix, then "${to}" once it lands. in_progress is the only marker that someone is already on this bug — without it a parallel agent can't tell the work is in flight, and the codex loses the found → started → shipped timeline. If the fix was genuinely instant, this is fine as-is.`,
|
|
342
342
|
};
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
// Re-opening a
|
|
346
|
-
if (
|
|
345
|
+
// Re-opening a resolved bug — not wrong, but worth naming so it isn't a silent regression.
|
|
346
|
+
if (RESOLVED_STATUSES.has(from) && to === 'open') {
|
|
347
347
|
return {
|
|
348
348
|
rule: 'bug-reopened',
|
|
349
349
|
severity: 'info',
|
|
350
|
-
message: `Bug moves from "${from}"
|
|
350
|
+
message: `Bug moves from "${from}" back to "open" (wild) — it escaped. If you are resuming work rather than reporting a regression, "in_progress" (chasing) says so more precisely. If it is a regression, note what shipped and broke it in the description.`,
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
|
|
@@ -109,9 +109,9 @@ The Bug Codex reads `status` as three states:
|
|
|
109
109
|
|---|---|---|
|
|
110
110
|
| `open` | wild | found, nobody is on it |
|
|
111
111
|
| `in_progress` | chasing | someone is fixing it right now |
|
|
112
|
-
| `resolved` / `closed` |
|
|
112
|
+
| `resolved` / `closed` | resolved | it landed |
|
|
113
113
|
|
|
114
|
-
Move one step at a time: **wild → chasing →
|
|
114
|
+
Move one step at a time: **wild → chasing → resolved**.
|
|
115
115
|
|
|
116
116
|
- Starting a fix? Set `status="in_progress"` **before** you work, not after.
|
|
117
117
|
- Landed it? Then `status="resolved"` (optionally with
|
package/core/tools.js
CHANGED
|
@@ -64,21 +64,32 @@ function computeDormantSet(nodes) {
|
|
|
64
64
|
return dormant;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// 버그 필터 어휘 → DB status 집합. 도구 표면은 제품 어휘(wild/chasing/
|
|
67
|
+
// 버그 필터 어휘 → DB status 집합. 도구 표면은 제품 어휘(wild/chasing/resolved)를 쓴다 —
|
|
68
68
|
// UI·vocabulary.md가 쓰는 말과 같아야 사람이 본 화면과 도구 응답이 같은 것을 가리킨다.
|
|
69
69
|
// DB 상태값(open/in_progress/…)도 계속 받는다: 기존 호출자를 깨지 않기 위해서다.
|
|
70
70
|
//
|
|
71
71
|
// 'open'은 여기서 **wild와 다르다.** REST의 status=open은 store에서 open+in_progress로
|
|
72
72
|
// 번역되고(앱 대시보드의 Bugs Alive가 그 의미에 기대고 있다) 그래서 REST로는 순수 wild를
|
|
73
73
|
// 뽑을 수 없다. 그 갈래는 이 표에서만 갈린다.
|
|
74
|
+
// 버그 상태 필터 입력. 스키마(에이전트가 보는 enum)에는 'catched'를 싣지 않고, 들어오면
|
|
75
|
+
// 'resolved'로 바꿔 받는다 — 옛 말을 쓰는 호출은 살리되 새로 배우는 쪽엔 한 가지 말만 보이게.
|
|
76
|
+
function bugStatusInput() {
|
|
77
|
+
return z.preprocess(
|
|
78
|
+
(v) => (v === 'catched' ? 'resolved' : v),
|
|
79
|
+
z.enum(['active', 'wild', 'chasing', 'resolved', 'all', 'open', 'in_progress', 'closed']),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
const BUG_STATUS_SETS = {
|
|
75
84
|
active: ['open', 'in_progress'],
|
|
76
85
|
wild: ['open'],
|
|
77
86
|
chasing: ['in_progress'],
|
|
78
|
-
|
|
87
|
+
// 'resolved'는 끝난 버그 전부(resolved + closed)다. 예전엔 이 자리가 'catched'였고 'resolved'는
|
|
88
|
+
// DB 값 그대로(closed 제외)였는데, 화면과 도구의 말을 resolved 하나로 합쳤다(2026-09-23).
|
|
89
|
+
// 'catched'는 스키마에서 뺐고, 들어오면 bugStatusInput()이 'resolved'로 바꿔 받는다.
|
|
90
|
+
resolved: ['resolved', 'closed'],
|
|
79
91
|
open: ['open', 'in_progress'], // REST status=open과 같은 의미 (하위호환)
|
|
80
92
|
in_progress: ['in_progress'],
|
|
81
|
-
resolved: ['resolved'],
|
|
82
93
|
closed: ['closed'],
|
|
83
94
|
};
|
|
84
95
|
|
|
@@ -348,7 +359,7 @@ export function registerTools(server, { api }) {
|
|
|
348
359
|
role: z.enum(['structure', 'object', 'action']).optional().describe('Cross-section by role (structure=trunk/limb/twig, object=leaf, action=vein). Parents may fall outside the result.'),
|
|
349
360
|
season: z.string().optional().describe('Born-in delta — return only nodes that first appeared in this season (season id from list_seasons): what grew that season.'),
|
|
350
361
|
descriptions: z.enum(['none', 'excerpt', 'full']).optional().describe('How much of each node/bug description to include in summary view: none (drop them — lightest structural read), excerpt (200-char preview, default), or full (verbatim). Ignored when view="full" (always verbatim).'),
|
|
351
|
-
bugStatus:
|
|
362
|
+
bugStatus: bugStatusInput().optional().describe('Which bugs to include. Default "active" = wild + chasing (open + in_progress) — resolved ones are healed erosion, noise on the tree. "wild" for the untouched only, "resolved" for healed, "all" for every bug. Raw DB statuses still work. Use list_bugs for richer bug queries (limit/order), get_bug for one you can name.'),
|
|
352
363
|
}),
|
|
353
364
|
},
|
|
354
365
|
async ({ slug, view, rootId, depth, maxType, role, season, descriptions, bugStatus }) => {
|
|
@@ -408,11 +419,11 @@ export function registerTools(server, { api }) {
|
|
|
408
419
|
'list_bugs',
|
|
409
420
|
{
|
|
410
421
|
title: 'List bugs of a ground',
|
|
411
|
-
description: 'Scans the bugs (issues eroding the ground) of one ground. Looking at ONE bug you already know the number/id of? Call get_bug instead — no need to list. Bugs default to status="active" (wild + chasing): a healed bug is history, and history is not what a scan is for. Ask for "
|
|
422
|
+
description: 'Scans the bugs (issues eroding the ground) of one ground. Looking at ONE bug you already know the number/id of? Call get_bug instead — no need to list. Bugs default to status="active" (wild + chasing): a healed bug is history, and history is not what a scan is for. Ask for "resolved" or "all" when you actually want it. Status speaks the product\'s own words — wild (found, nobody on it) → chasing (someone is fixing it) → resolved (it landed) — the same words the UI shows. Each bug has a score (0–8 change-risk; 8 = riskiest), and any bug whose solution carries a checklist also carries solutionProgress ("2/5" — ticked/total), so you can see what is left without opening a single body. Targets: a node, an api, or the ground itself. By default returns a summary view — each bug carries id, seq (the number a human says, "#14"), target, title, score, status, createdAt/resolvedAt, plus a 200-char description excerpt. Each node/api bug also carries impactCount — how many other nodes its target reaches by blast radius (affected direction), so you can spot wide-blast bugs at a glance; call get_impact on that bug for the full reached list. Pass view="full" for full descriptions and metadata. Use limit + order to bound a scan (e.g. the 5 newest) instead of pulling every bug.',
|
|
412
423
|
inputSchema: z.object({
|
|
413
424
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
414
|
-
status:
|
|
415
|
-
.describe('Which bugs to include. Default "active" = wild + chasing (open + in_progress). "wild" = found but untouched, "chasing" = someone is on it, "
|
|
425
|
+
status: bugStatusInput().optional()
|
|
426
|
+
.describe('Which bugs to include. Default "active" = wild + chasing (open + in_progress). "wild" = found but untouched, "chasing" = someone is on it, "resolved" = healed (resolved + closed), "all" = every bug. The raw DB statuses are accepted too; note "open" means wild + chasing there (REST semantics), so pass "wild" when you want only the untouched ones.'),
|
|
416
427
|
limit: z.coerce.number().int().positive().optional().describe('Return at most this many bugs (applied after the status filter). Omit for all matching.'),
|
|
417
428
|
order: z.enum(['asc', 'desc']).optional().describe('By creation time: "asc" oldest first (default), "desc" newest first. Pair with limit for "the N newest".'),
|
|
418
429
|
view: z.enum(['summary', 'full']).optional().describe('summary (default) for a compact list; full for verbatim descriptions + metadata.'),
|
|
@@ -424,14 +435,14 @@ export function registerTools(server, { api }) {
|
|
|
424
435
|
const qs = ['impact=true']; // 각 버그에 impactCount(blast radius 크기) 자동 첨부
|
|
425
436
|
if (order) qs.push(`order=${encodeURIComponent(order)}`);
|
|
426
437
|
// status·limit을 서버로 넘기는 건 REST 필터가 원하는 집합과 **정확히** 같을 때만이다.
|
|
427
|
-
// wild(순수 open)·
|
|
438
|
+
// wild(순수 open)·resolved(resolved+closed)는 REST에 대응 값이 없어 여기서 거른다 —
|
|
428
439
|
// 그때 limit까지 넘기면 걸러지기 전 개수로 잘려 결과가 모자란다.
|
|
429
440
|
const REST_EXACT = {
|
|
430
441
|
active: 'open', open: 'open', // REST의 open = open + in_progress
|
|
431
442
|
chasing: 'in_progress', in_progress: 'in_progress',
|
|
432
|
-
|
|
443
|
+
closed: 'closed', // resolved는 closed까지 묶여 REST 값과 다르다 — 여기서 거른다
|
|
433
444
|
};
|
|
434
|
-
const exact = REST_EXACT[want] || null; // wild·
|
|
445
|
+
const exact = REST_EXACT[want] || null; // wild·resolved는 REST에 대응 값이 없다
|
|
435
446
|
if (exact) qs.push(`status=${exact}`);
|
|
436
447
|
if (exact || want === 'all') { if (limit) qs.push(`limit=${limit}`); }
|
|
437
448
|
let bugs = await api.get(`/api/projects/${encodeURIComponent(slug)}/bugs?${qs.join('&')}`);
|
|
@@ -448,7 +459,7 @@ export function registerTools(server, { api }) {
|
|
|
448
459
|
'get_bug',
|
|
449
460
|
{
|
|
450
461
|
title: 'Get one bug of a ground',
|
|
451
|
-
description: 'Returns a single bug by reference — the cheap path when you already know which bug you mean. `ref` accepts either the human-facing number shown in the UI (seq — "14" or #14) or the internal id (bug-<uuid>), so a human saying "#8" needs no listing at all. Unlike list_bugs this ignores status: a
|
|
462
|
+
description: 'Returns a single bug by reference — the cheap path when you already know which bug you mean. `ref` accepts either the human-facing number shown in the UI (seq — "14" or #14) or the internal id (bug-<uuid>), so a human saying "#8" needs no listing at all. Unlike list_bugs this ignores status: a resolved bug is still readable by number, which is how you review what a past fix actually did. Use list_bugs only to scan for bugs you cannot yet name. The response carries the full description and metadata, plus impact — the blast radius reached from the bug\'s target (reachedCount, the reached nodes with hop distance, and other active bugs sitting in that radius). Numbers are per-ground and never reused, so a deleted bug leaves a gap rather than shifting the others. Returns an error if no bug matches.',
|
|
452
463
|
inputSchema: z.object({
|
|
453
464
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
454
465
|
ref: z.union([z.string().min(1), z.coerce.number().int().positive()])
|
|
@@ -967,7 +978,7 @@ export function registerTools(server, { api }) {
|
|
|
967
978
|
description: [
|
|
968
979
|
'Partial update of a bug. Most common use: status transition.',
|
|
969
980
|
'Send only the fields that actually change. A status transition is patch:{status} alone — do not re-send an unchanged solution, description, or metadata. Those fields carry prose, and re-emitting them costs far more time than the update itself and risks a malformed-argument retry.',
|
|
970
|
-
'Follow the lifecycle one step at a time: open (wild) → in_progress (chasing) → resolved
|
|
981
|
+
'Follow the lifecycle one step at a time: open (wild) → in_progress (chasing) → resolved.',
|
|
971
982
|
'Set status="in_progress" the moment you start the fix, not after it lands — it is the only marker that someone is already on this bug, so a parallel agent can see the work in flight instead of duplicating it. Then set "resolved" once it ships.',
|
|
972
983
|
'Skipping straight from open to resolved returns a warning (not a rejection) — acceptable when the fix was genuinely instant.',
|
|
973
984
|
'Other patchable fields: title, description, solution, score (0–8 change-risk), metadata.',
|
package/package.json
CHANGED