kanbango 2.4.0 → 3.0.2
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/.ai/lessons.jsonl +4 -0
- package/.ai/retro/close.json +1 -0
- package/.ai/retro/last-run.json +1 -0
- package/AGENTS.md +11 -0
- package/CHANGELOG.md +43 -0
- package/LLM_AGENTS.md +110 -28
- package/README.md +6 -3
- package/agent-playbook.js +71 -0
- package/bin/kanban-cmd.js +1 -1
- package/bin/kanban.js +6 -6
- package/gui-registry.js +148 -0
- package/index.js +4 -0
- package/kanban.js +44 -133
- package/mcp-server.js +124 -85
- package/package.json +1 -1
- package/plan.js +1 -1
- package/tests/run.js +2 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"id":"260729-3h4","ts":"2026-07-29","scope":"proj","tags":["tests","mcp","cli","review"],"rule":"DO Add automated tests for every new public CLI/MCP workflow before shipping","when":"api-review","sev":2,"hits":1}
|
|
2
|
+
{"id":"260729-1ur","ts":"2026-07-29","scope":"proj","tags":["javascript","objects"],"rule":"DON'T Use empty object fallbacks in spreads like `...(obj || {})` since spreading falsy is safe","when":"object spread","sev":2,"hits":1}
|
|
3
|
+
{"id":"260729-5eo","ts":"2026-07-29","scope":"proj","tags":["processes","mcp","ownership"],"rule":"DO Track process ownership before stopping or cleaning up discovered services","when":"gui-process management","sev":2,"hits":1}
|
|
4
|
+
{"id":"260729-1ch","ts":"2026-07-29","scope":"proj","tags":["gui","process","mcp"],"rule":"DO GUI/MCP stop must only SIGTERM processes spawned by the current process; treat port-file PIDs as external_running","when":"gui-process management","sev":2,"hits":1}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"goal":"push do npm","done":["session had mutations"],"next":[],"block":[],"lessons":[],"files":["/Users/mkorbas/projects/personal/markdown-kanban/mcp-server.js","/Users/mkorbas/projects/personal/markdown-kanban/kanban.js","/Users/mkorbas/projects/personal/markdown-kanban/tests/mcp-server.test.js","/Users/mkorbas/projects/personal/markdown-kanban/plan.js","/Users/mkorbas/projects/personal/markdown-kanban/CHANGELOG.md","/Users/mkorbas/projects/personal/markdown-kanban/bin/kanban.js","/Users/mkorbas/projects/personal/markdown-kanban/tests/update-tasks.test.js"],"verify":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"ts":"2026-07-29T10:38:42.184Z","sessionID":"ses_052c1be3fffeNPoV7VO6z40X2I","added":["260729-1ch"],"bumped":[]}
|
package/AGENTS.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This document provides build commands, testing procedures, and code style guidelines for working with the kanbango codebase.
|
|
4
4
|
|
|
5
|
+
## Kanbango MCP — token rules (when using this board)
|
|
6
|
+
|
|
7
|
+
Source of truth: `agent-playbook.js` (also MCP tool descriptions + `kanban_read` `operation=help`).
|
|
8
|
+
|
|
9
|
+
- list: `col` filter, `view=summary`; keep `task_id`s; no full-board re-list after every write
|
|
10
|
+
- show: `view=execution` while coding; `full` only if needed
|
|
11
|
+
- create once with `description`, `specs`, `in_scope`, `out_of_scope`, `acceptance_criteria`
|
|
12
|
+
- move/update: `return=none`; subtasks = full array replace (no toggle)
|
|
13
|
+
- non-trivial work: `plan_create` → `plan_advance` → `plan_evidence` (real tests, truncated logs) → `plan_done`
|
|
14
|
+
- gui: `status` before `start`; `stop` only owned; `external_running` = do not kill
|
|
15
|
+
|
|
5
16
|
## Build & Development Commands
|
|
6
17
|
|
|
7
18
|
### Available Scripts
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,49 @@ All notable changes to kanbango will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.0.2] - 2026-07-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `agent-playbook.js` — single source of truth for agent token rules
|
|
12
|
+
- `kanban_read` `operation=help` returns the playbook (no board I/O)
|
|
13
|
+
- Test that `tools/list` descriptions match the playbook module
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- MCP tool descriptions are loaded from `agent-playbook.js` (not duplicated prose)
|
|
17
|
+
- `LLM_AGENTS.md` clarified as human setup docs; agents rely on tool descriptions
|
|
18
|
+
|
|
19
|
+
## [3.0.1] - 2026-07-29
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- MCP tool descriptions encode a token-efficient agent playbook (cheap list/show, fat create once, `return=none`, plan_* only when needed, GUI ownership)
|
|
23
|
+
- `LLM_AGENTS.md`, `AGENTS.md`, and `README.md` document the same rules for project-scoped agents
|
|
24
|
+
|
|
25
|
+
## [3.0.0] - 2026-07-29
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
- New task IDs are zero-padded numeric strings (`001`), not `PI-001-slug`
|
|
29
|
+
- MCP/CLI `toggle` removed; update the full `subtasks` list via `update`
|
|
30
|
+
- Legacy task field alias `tasks` removed; use `subtasks` only
|
|
31
|
+
- `kanban_gui` `stop` only stops a GUI **spawned by the current MCP process**. An external GUI returns `status: "external_running"` and is **not** sent SIGTERM (PID-reuse safety)
|
|
32
|
+
- `kanban_gui` `status` may return `running` (owned), `external_running` (discovered), or `not_running`
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Accepted-plan workflow: `plan.js` + CLI `kanban plan <action> --json` + MCP `plan_*` actions on `kanban_manage`
|
|
36
|
+
- `gui-registry.js` — GUI port file, preferred port, discover helpers (extracted from `kanban.js`)
|
|
37
|
+
- Regression tests for plan workflow and GUI process ownership
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- Create field policy: only `title` is hard-required; `description`, `specs`, `in_scope`, `out_of_scope`, and `acceptance_criteria` are strongly recommended
|
|
41
|
+
- MCP `kanban_manage` create/plan_create returns `warnings` + `missing_recommended` when recommended fields are omitted (still succeeds)
|
|
42
|
+
- MCP tool descriptions and LLM agent docs push agents to always fill planning boundaries
|
|
43
|
+
|
|
44
|
+
## [2.5.0] - 2026-07-27
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- Create field policy: only `title` is hard-required; `description`, `specs`, `in_scope`, `out_of_scope`, and `acceptance_criteria` are strongly recommended
|
|
48
|
+
- MCP `kanban_manage` create/plan_create returns `warnings` + `missing_recommended` when recommended fields are omitted (still succeeds)
|
|
49
|
+
- MCP tool descriptions and LLM agent docs push agents to always fill planning boundaries
|
|
50
|
+
|
|
8
51
|
## [2.4.0] - 2026-07-27
|
|
9
52
|
|
|
10
53
|
### Added
|
package/LLM_AGENTS.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# MCP Server Guide for LLM Agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Human-oriented setup guide. **Agents do not load this file automatically.**
|
|
4
|
+
|
|
5
|
+
Canonical usage rules live in code (`agent-playbook.js`) and are injected into MCP
|
|
6
|
+
`tools/list` descriptions. After install, the agent only needs the MCP server —
|
|
7
|
+
not this markdown.
|
|
8
|
+
|
|
9
|
+
Optional refresh in-session: `kanban_read` with `operation: "help"`.
|
|
4
10
|
|
|
5
11
|
## Quick Integration
|
|
6
12
|
|
|
@@ -63,11 +69,12 @@ Read tasks from kanban board. Can list all tasks, filter by column/epic, or get
|
|
|
63
69
|
**Operations:**
|
|
64
70
|
- `list` - Get all tasks (with optional filters)
|
|
65
71
|
- `show` - Get specific task details
|
|
72
|
+
- `help` - Return token playbook from `agent-playbook.js` (no board I/O)
|
|
66
73
|
|
|
67
74
|
**Parameters:**
|
|
68
75
|
```json
|
|
69
76
|
{
|
|
70
|
-
"operation": "list", // "list"
|
|
77
|
+
"operation": "list", // "list" | "show" | "help"
|
|
71
78
|
"task_id": "014", // Required for "show"
|
|
72
79
|
"col": "planned", // Optional: "active" | "planned" | "icebox" | "done"
|
|
73
80
|
"epic": "Phase 1" // Optional: filter by epic group
|
|
@@ -146,20 +153,22 @@ Create, move, or patch-update kanban tasks. Single tool for all mutations.
|
|
|
146
153
|
```json
|
|
147
154
|
{
|
|
148
155
|
"action": "create",
|
|
149
|
-
"title": "New feature", // Required
|
|
156
|
+
"title": "New feature", // Required (only hard requirement)
|
|
150
157
|
"col": "planned", // Optional, default "planned"
|
|
151
158
|
"epic": "Phase 1", // Optional, default "—"
|
|
152
|
-
"description": "Context and plan",
|
|
153
|
-
"specs": "Technical constraints",
|
|
154
|
-
"in_scope": ["What is included"],
|
|
155
|
-
"out_of_scope": ["What is excluded"],
|
|
156
|
-
"acceptance_criteria": ["Must work"],
|
|
157
|
-
"test_cases": ["Verify X"],
|
|
159
|
+
"description": "Context and plan", // Strongly recommended
|
|
160
|
+
"specs": "Technical constraints", // Strongly recommended
|
|
161
|
+
"in_scope": ["What is included"], // Strongly recommended
|
|
162
|
+
"out_of_scope": ["What is excluded"], // Strongly recommended
|
|
163
|
+
"acceptance_criteria": ["Must work"], // Strongly recommended
|
|
164
|
+
"test_cases": ["Verify X"], // Recommended
|
|
158
165
|
"subtasks": [{"text": "Do it", "done": false}],
|
|
159
166
|
"notes": "Freeform notes"
|
|
160
167
|
}
|
|
161
168
|
```
|
|
162
169
|
|
|
170
|
+
**Create field policy:** only `title` is hard-required (GUI/CLI quick-add stays usable). For agent work, always send the strongly recommended fields. If any are missing, create still succeeds and the response includes `warnings` + `missing_recommended`.
|
|
171
|
+
|
|
163
172
|
**Parameters (move):**
|
|
164
173
|
```json
|
|
165
174
|
{
|
|
@@ -189,7 +198,12 @@ Create a task:
|
|
|
189
198
|
"action": "create",
|
|
190
199
|
"title": "Database optimization",
|
|
191
200
|
"col": "planned",
|
|
192
|
-
"epic": "Performance"
|
|
201
|
+
"epic": "Performance",
|
|
202
|
+
"description": "Reduce N+1 queries on board list",
|
|
203
|
+
"specs": "Keep JSON storage; no new deps",
|
|
204
|
+
"in_scope": ["list query path"],
|
|
205
|
+
"out_of_scope": ["GUI redesign"],
|
|
206
|
+
"acceptance_criteria": ["list stays correct under load"]
|
|
193
207
|
}
|
|
194
208
|
```
|
|
195
209
|
|
|
@@ -225,6 +239,11 @@ Patch-update task fields:
|
|
|
225
239
|
|
|
226
240
|
Control the web GUI server: start, stop, or check status.
|
|
227
241
|
|
|
242
|
+
**Ownership (important):**
|
|
243
|
+
- `stop` only sends SIGTERM to a GUI **spawned by this MCP process** (`owned: true`).
|
|
244
|
+
- If the GUI was started elsewhere (CLI `kanban serve`, another MCP), `stop` returns `external_running` and does **not** kill that PID.
|
|
245
|
+
- `status` distinguishes `running` (owned), `external_running` (discovered via port file), and `not_running`.
|
|
246
|
+
|
|
228
247
|
**Port resolution (start):**
|
|
229
248
|
1. Explicit `port` argument, if provided
|
|
230
249
|
2. Else `KANBANGO_GUI_PORT` env
|
|
@@ -235,9 +254,9 @@ If the preferred port is busy, the server picks the next free port. Always trust
|
|
|
235
254
|
**Auto-start with MCP:** set `KANBANGO_AUTO_GUI=1` in the MCP server env. GUI starts when MCP starts; use `status` to read the URL.
|
|
236
255
|
|
|
237
256
|
**Actions:**
|
|
238
|
-
- `start` - Launch the GUI server
|
|
239
|
-
- `stop` -
|
|
240
|
-
- `status` - Check
|
|
257
|
+
- `start` - Launch the GUI server (or report already_running owned/external)
|
|
258
|
+
- `stop` - Stop only MCP-owned GUI
|
|
259
|
+
- `status` - Check GUI state
|
|
241
260
|
|
|
242
261
|
**Parameters:**
|
|
243
262
|
```json
|
|
@@ -282,31 +301,57 @@ Check status:
|
|
|
282
301
|
```json
|
|
283
302
|
{
|
|
284
303
|
"status": "started",
|
|
304
|
+
"owned": true,
|
|
285
305
|
"port": 5623,
|
|
286
306
|
"pid": 12345,
|
|
287
307
|
"url": "http://localhost:5623"
|
|
288
308
|
}
|
|
289
309
|
```
|
|
290
310
|
|
|
291
|
-
**Response (stop):**
|
|
311
|
+
**Response (stop - owned):**
|
|
292
312
|
```json
|
|
293
313
|
{
|
|
294
314
|
"status": "stopping",
|
|
315
|
+
"owned": true,
|
|
295
316
|
"port": 5623,
|
|
296
317
|
"pid": 12345
|
|
297
318
|
}
|
|
298
319
|
```
|
|
299
320
|
|
|
300
|
-
**Response (
|
|
321
|
+
**Response (stop - external refused):**
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"status": "external_running",
|
|
325
|
+
"owned": false,
|
|
326
|
+
"port": 5623,
|
|
327
|
+
"pid": 12345,
|
|
328
|
+
"url": "http://localhost:5623",
|
|
329
|
+
"hint": "GUI was not started by this MCP process; stop refused."
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Response (status - running owned):**
|
|
301
334
|
```json
|
|
302
335
|
{
|
|
303
336
|
"status": "running",
|
|
337
|
+
"owned": true,
|
|
304
338
|
"port": 5623,
|
|
305
339
|
"pid": 12345,
|
|
306
340
|
"url": "http://localhost:5623"
|
|
307
341
|
}
|
|
308
342
|
```
|
|
309
343
|
|
|
344
|
+
**Response (status - external):**
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"status": "external_running",
|
|
348
|
+
"owned": false,
|
|
349
|
+
"port": 5623,
|
|
350
|
+
"pid": 99999,
|
|
351
|
+
"url": "http://localhost:5623"
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
310
355
|
**Response (status - not running):**
|
|
311
356
|
```json
|
|
312
357
|
{
|
|
@@ -365,8 +410,18 @@ Check status:
|
|
|
365
410
|
### Pattern 2: Task Creation Workflow
|
|
366
411
|
|
|
367
412
|
```json
|
|
368
|
-
// 1. Create task
|
|
369
|
-
{ "tool": "kanban_manage", "arguments": {
|
|
413
|
+
// 1. Create task with strongly recommended planning fields
|
|
414
|
+
{ "tool": "kanban_manage", "arguments": {
|
|
415
|
+
"action": "create",
|
|
416
|
+
"title": "New feature",
|
|
417
|
+
"col": "planned",
|
|
418
|
+
"epic": "Phase 1",
|
|
419
|
+
"description": "Why this exists",
|
|
420
|
+
"specs": "APIs and constraints",
|
|
421
|
+
"in_scope": ["Core path"],
|
|
422
|
+
"out_of_scope": ["Mobile"],
|
|
423
|
+
"acceptance_criteria": ["npm test passes"]
|
|
424
|
+
} }
|
|
370
425
|
|
|
371
426
|
// 2. Get task details to see generated ID
|
|
372
427
|
{ "tool": "kanban_read", "arguments": { "operation": "show", "task_id": "015" } }
|
|
@@ -394,22 +449,49 @@ Check status:
|
|
|
394
449
|
// List all tasks in an epic
|
|
395
450
|
{ "tool": "kanban_read", "arguments": { "operation": "list", "epic": "Performance" } }
|
|
396
451
|
|
|
397
|
-
// Create task in specific epic
|
|
398
|
-
{ "tool": "kanban_manage", "arguments": {
|
|
452
|
+
// Create task in specific epic (still include recommended fields)
|
|
453
|
+
{ "tool": "kanban_manage", "arguments": {
|
|
454
|
+
"action": "create",
|
|
455
|
+
"title": "Cache optimization",
|
|
456
|
+
"epic": "Performance",
|
|
457
|
+
"description": "Reduce repeated board reads",
|
|
458
|
+
"specs": "In-memory cache with TTL",
|
|
459
|
+
"in_scope": ["list endpoint"],
|
|
460
|
+
"out_of_scope": ["distributed cache"],
|
|
461
|
+
"acceptance_criteria": ["p95 list latency down"]
|
|
462
|
+
} }
|
|
399
463
|
```
|
|
400
464
|
|
|
401
465
|
---
|
|
402
466
|
|
|
403
|
-
##
|
|
467
|
+
## Token-efficient playbook (source of truth)
|
|
468
|
+
|
|
469
|
+
**Edit `agent-playbook.js` only.** MCP tool descriptions and `operation=help` are built from it.
|
|
470
|
+
|
|
471
|
+
At runtime agents already receive the rules via tool descriptions. Humans can also call:
|
|
472
|
+
|
|
473
|
+
```json
|
|
474
|
+
{ "operation": "help" }
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
on `kanban_read` (no board I/O).
|
|
478
|
+
|
|
479
|
+
### Drop-in for project `AGENTS.md` (optional)
|
|
404
480
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
481
|
+
Same bullets as `DROP_IN_RULE` in `agent-playbook.js` — copy if you want them outside MCP:
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
Kanbango MCP — token rules:
|
|
485
|
+
- list: col filter, view=summary; keep task_ids; no full-board re-list after writes
|
|
486
|
+
- show: view=execution while coding; full only if needed
|
|
487
|
+
- create once with description,specs,in_scope,out_of_scope,acceptance_criteria
|
|
488
|
+
- move/update: return=none; subtasks=full array replace
|
|
489
|
+
- non-trivial: plan_create → plan_advance → plan_evidence (real tests, truncated logs) → plan_done
|
|
490
|
+
- gui: status before start; stop only owned; external_running = do not kill
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Columns
|
|
494
|
+
`planned` → `active` (few at a time) → `done`. Use `icebox` for parked work.
|
|
413
495
|
|
|
414
496
|
---
|
|
415
497
|
|
package/README.md
CHANGED
|
@@ -129,9 +129,11 @@ Once connected, your agent gets access to these tools:
|
|
|
129
129
|
|
|
130
130
|
| Tool | What it does |
|
|
131
131
|
|------|-------------|
|
|
132
|
-
| `kanban_read` | List tasks
|
|
133
|
-
| `kanban_manage` | Create, move,
|
|
134
|
-
| `kanban_gui` | Start
|
|
132
|
+
| `kanban_read` | List/show tasks (`view=summary` by default — cheap) |
|
|
133
|
+
| `kanban_manage` | Create, move, update, plan_* workflow |
|
|
134
|
+
| `kanban_gui` | Start / status / stop (stop only kills GUI this MCP started) |
|
|
135
|
+
|
|
136
|
+
**Token tip for agents:** rules ship inside MCP tool descriptions (`agent-playbook.js`). Optional: `kanban_read` → `operation: "help"`. Human setup notes: [LLM_AGENTS.md](./LLM_AGENTS.md).
|
|
135
137
|
|
|
136
138
|
Your agent stays in sync with your real board — every change is persisted as JSON files.
|
|
137
139
|
|
|
@@ -146,6 +148,7 @@ Your agent stays in sync with your real board — every change is persisted as J
|
|
|
146
148
|
| `kanban add <TITLE>` | Add a new task |
|
|
147
149
|
| `kanban move <ID> <COL>` | Move task |
|
|
148
150
|
| `kanban mcp-init` | Generate MCP config files |
|
|
151
|
+
| `kanban plan <action> --json '{...}'` | Accepted-plan workflow (create/advance/evidence/done/status) |
|
|
149
152
|
|
|
150
153
|
## Web GUI
|
|
151
154
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for agent-facing kanbango usage rules.
|
|
3
|
+
* MCP tool descriptions are built from this module — not from LLM_AGENTS.md.
|
|
4
|
+
* Docs may quote DROP_IN_RULE; keep them in sync by editing here only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const DROP_IN_RULE = [
|
|
8
|
+
'Kanbango MCP — token rules:',
|
|
9
|
+
'- list: col filter, view=summary; keep task_ids; no full-board re-list after writes',
|
|
10
|
+
'- show: view=execution while coding; full only if needed',
|
|
11
|
+
'- create once with description,specs,in_scope,out_of_scope,acceptance_criteria',
|
|
12
|
+
'- move/update: return=none; subtasks=full array replace',
|
|
13
|
+
'- non-trivial: plan_create → plan_advance → plan_evidence (real tests, truncated logs) → plan_done',
|
|
14
|
+
'- gui: status before start; stop only owned; external_running = do not kill'
|
|
15
|
+
].join('\n');
|
|
16
|
+
|
|
17
|
+
const TOOL_DESCRIPTIONS = {
|
|
18
|
+
kanban_read: [
|
|
19
|
+
'Read board. TOKEN RULES: list defaults to view=summary (id/title/col/progress only).',
|
|
20
|
+
'Always pass col when possible. Prefer show+view=execution over full.',
|
|
21
|
+
'Do not re-list the whole board after every write — keep task_id from create/move.',
|
|
22
|
+
'IDs are numeric ("014" or "14"). views: summary|planning|execution|full; fields[] overrides view.',
|
|
23
|
+
'operation=help returns this playbook as short text (no board I/O).'
|
|
24
|
+
].join(' '),
|
|
25
|
+
|
|
26
|
+
kanban_manage: [
|
|
27
|
+
'Write board / plan. TOKEN RULES: one create with all planning fields beats many updates;',
|
|
28
|
+
'after write use return=none (or summary). Do not dump full task unless needed.',
|
|
29
|
+
'Actions: create|move|update (daily); plan_create→plan_advance→plan_evidence→plan_done (non-trivial only).',
|
|
30
|
+
'create/plan_create: title required; also send description,specs,in_scope,out_of_scope,acceptance_criteria',
|
|
31
|
+
'(missing → warnings, not failure). move: task_id+column. update: task_id + fields or subtasks[] full list',
|
|
32
|
+
'(no toggle). plan_evidence needs real test run: diff,test_command,stdout,stderr,exit_code — truncate logs.',
|
|
33
|
+
'Example create: {"action":"create","title":"Ship image","description":"...","specs":"...","in_scope":["CLI"],',
|
|
34
|
+
'"out_of_scope":["GUI"],"acceptance_criteria":["npm test passes"],"col":"planned"}'
|
|
35
|
+
].join(' '),
|
|
36
|
+
|
|
37
|
+
kanban_gui: [
|
|
38
|
+
'Web GUI control. Prefer status before start. stop only kills GUI this MCP spawned;',
|
|
39
|
+
'external_running = do not retry kill — use the returned url or leave it.',
|
|
40
|
+
'status: running|external_running|not_running. Rarely needed mid-task — open once if user wants UI.'
|
|
41
|
+
].join(' ')
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const MUST_CONTAIN = [
|
|
45
|
+
'TOKEN RULES',
|
|
46
|
+
'view=summary',
|
|
47
|
+
'return=none',
|
|
48
|
+
'plan_create',
|
|
49
|
+
'external_running',
|
|
50
|
+
'subtasks'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function playbookHelpPayload() {
|
|
54
|
+
return {
|
|
55
|
+
source: 'agent-playbook.js',
|
|
56
|
+
note: 'Canonical rules for agents. Same text drives MCP tool descriptions. LLM_AGENTS.md is human docs only.',
|
|
57
|
+
drop_in_rule: DROP_IN_RULE,
|
|
58
|
+
tools: {
|
|
59
|
+
kanban_read: TOOL_DESCRIPTIONS.kanban_read,
|
|
60
|
+
kanban_manage: TOOL_DESCRIPTIONS.kanban_manage,
|
|
61
|
+
kanban_gui: TOOL_DESCRIPTIONS.kanban_gui
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
DROP_IN_RULE,
|
|
68
|
+
TOOL_DESCRIPTIONS,
|
|
69
|
+
MUST_CONTAIN,
|
|
70
|
+
playbookHelpPayload
|
|
71
|
+
};
|
package/bin/kanban-cmd.js
CHANGED
package/bin/kanban.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const kanban = require('../kanban.js');
|
|
4
4
|
const plan = require('../plan.js');
|
|
5
|
+
const guiRegistry = require('../gui-registry.js');
|
|
5
6
|
const http = require('http');
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const path = require('path');
|
|
8
9
|
|
|
9
10
|
const BACKLOG = path.join(process.cwd(), 'backlog');
|
|
10
|
-
const COLS = kanban.COLS;
|
|
11
11
|
|
|
12
12
|
function shortId(taskId) {
|
|
13
13
|
const match = taskId.match(/^(?:[A-Z]+-)?(\d+)/);
|
|
@@ -357,11 +357,11 @@ async function serveWeb(port) {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
const actualPort = server.address().port;
|
|
360
|
-
const portInfo = await
|
|
360
|
+
const portInfo = await guiRegistry.writeGuiPortFile({ port: actualPort, pid: process.pid });
|
|
361
361
|
|
|
362
362
|
async function cleanupGuiPortFile() {
|
|
363
363
|
try {
|
|
364
|
-
await
|
|
364
|
+
await guiRegistry.clearGuiPortFile({ pid: process.pid });
|
|
365
365
|
} catch {
|
|
366
366
|
// best-effort cleanup
|
|
367
367
|
}
|
|
@@ -458,9 +458,9 @@ async function main() {
|
|
|
458
458
|
const args = process.argv.slice(2);
|
|
459
459
|
const cmd = args[0];
|
|
460
460
|
|
|
461
|
-
if (!cmd || cmd === 'serve') {
|
|
462
|
-
|
|
463
|
-
|
|
461
|
+
if (!cmd || cmd === 'serve') {
|
|
462
|
+
const port = guiRegistry.resolvePreferredGuiPort(args[1]);
|
|
463
|
+
await serveWeb(port);
|
|
464
464
|
} else if (cmd === 'init') {
|
|
465
465
|
await cliInit();
|
|
466
466
|
} else if (cmd === 'mcp-init') {
|
package/gui-registry.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const BACKLOG = path.join(process.cwd(), 'backlog');
|
|
5
|
+
const GUI_PORT_FILE = '.kanbango-gui.json';
|
|
6
|
+
const GUI_PORT_MIN = 5510;
|
|
7
|
+
const GUI_PORT_MAX = 5999;
|
|
8
|
+
const GUI_PORT_SPAN = GUI_PORT_MAX - GUI_PORT_MIN + 1;
|
|
9
|
+
|
|
10
|
+
function createGuiError(code, message, hint, details = {}, retryable = false, status = 400) {
|
|
11
|
+
const error = new Error(message);
|
|
12
|
+
error.code = code;
|
|
13
|
+
error.hint = hint;
|
|
14
|
+
error.details = details;
|
|
15
|
+
error.retryable = retryable;
|
|
16
|
+
error.status = status;
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function guiPortFilePath() {
|
|
21
|
+
return path.join(BACKLOG, GUI_PORT_FILE);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hashCwdToPort(cwd = process.cwd()) {
|
|
25
|
+
let hash = 0;
|
|
26
|
+
const input = String(cwd);
|
|
27
|
+
for (let i = 0; i < input.length; i++) {
|
|
28
|
+
hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;
|
|
29
|
+
}
|
|
30
|
+
return GUI_PORT_MIN + (Math.abs(hash) % GUI_PORT_SPAN);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeGuiPort(value) {
|
|
34
|
+
const parsed = Number.parseInt(value, 10);
|
|
35
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolvePreferredGuiPort(explicitPort) {
|
|
42
|
+
if (explicitPort !== undefined && explicitPort !== null && explicitPort !== '') {
|
|
43
|
+
const fromArg = normalizeGuiPort(explicitPort);
|
|
44
|
+
if (fromArg) return fromArg;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fromEnv = normalizeGuiPort(process.env.KANBANGO_GUI_PORT);
|
|
48
|
+
if (fromEnv) return fromEnv;
|
|
49
|
+
|
|
50
|
+
return hashCwdToPort(process.cwd());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPidAlive(pid) {
|
|
54
|
+
const n = Number.parseInt(pid, 10);
|
|
55
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
56
|
+
try {
|
|
57
|
+
process.kill(n, 0);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function ensureBacklogDir() {
|
|
65
|
+
await fs.mkdir(BACKLOG, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function writeGuiPortFile({ port, pid = process.pid } = {}) {
|
|
69
|
+
const normalizedPort = normalizeGuiPort(port);
|
|
70
|
+
if (!normalizedPort) {
|
|
71
|
+
throw createGuiError(
|
|
72
|
+
'VALIDATION_ERROR',
|
|
73
|
+
'Invalid GUI port',
|
|
74
|
+
'Use an integer between 1 and 65535',
|
|
75
|
+
{ port },
|
|
76
|
+
false,
|
|
77
|
+
400
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await ensureBacklogDir();
|
|
82
|
+
const data = {
|
|
83
|
+
port: normalizedPort,
|
|
84
|
+
pid,
|
|
85
|
+
url: `http://localhost:${normalizedPort}`,
|
|
86
|
+
cwd: process.cwd(),
|
|
87
|
+
started_at: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
await fs.writeFile(guiPortFilePath(), JSON.stringify(data, null, 2), 'utf-8');
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function readGuiPortFile() {
|
|
94
|
+
try {
|
|
95
|
+
const raw = await fs.readFile(guiPortFilePath(), 'utf-8');
|
|
96
|
+
const data = JSON.parse(raw);
|
|
97
|
+
if (!data || !normalizeGuiPort(data.port)) return null;
|
|
98
|
+
return data;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error.code === 'ENOENT') return null;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function clearGuiPortFile({ pid, force = false } = {}) {
|
|
106
|
+
const info = await readGuiPortFile();
|
|
107
|
+
if (!info) return false;
|
|
108
|
+
if (!force && pid !== undefined && info.pid !== pid) return false;
|
|
109
|
+
if (!force && pid === undefined && info.pid !== process.pid) return false;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await fs.unlink(guiPortFilePath());
|
|
113
|
+
return true;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.code === 'ENOENT') return false;
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function discoverRunningGui() {
|
|
121
|
+
const info = await readGuiPortFile();
|
|
122
|
+
if (!info || !isPidAlive(info.pid)) {
|
|
123
|
+
if (info) await clearGuiPortFile({ force: true });
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
status: 'running',
|
|
128
|
+
port: info.port,
|
|
129
|
+
pid: info.pid,
|
|
130
|
+
url: info.url || `http://localhost:${info.port}`,
|
|
131
|
+
cwd: info.cwd,
|
|
132
|
+
started_at: info.started_at
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
hashCwdToPort,
|
|
138
|
+
normalizeGuiPort,
|
|
139
|
+
resolvePreferredGuiPort,
|
|
140
|
+
isPidAlive,
|
|
141
|
+
writeGuiPortFile,
|
|
142
|
+
readGuiPortFile,
|
|
143
|
+
clearGuiPortFile,
|
|
144
|
+
discoverRunningGui,
|
|
145
|
+
guiPortFilePath,
|
|
146
|
+
GUI_PORT_MIN,
|
|
147
|
+
GUI_PORT_MAX
|
|
148
|
+
};
|
package/index.js
CHANGED
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
const kanban = require('./kanban.js');
|
|
12
12
|
const plan = require('./plan.js');
|
|
13
|
+
const guiRegistry = require('./gui-registry.js');
|
|
14
|
+
const playbook = require('./agent-playbook.js');
|
|
13
15
|
|
|
14
16
|
module.exports = {
|
|
15
17
|
kanban,
|
|
16
18
|
plan,
|
|
19
|
+
guiRegistry,
|
|
20
|
+
playbook,
|
|
17
21
|
};
|