kanbango 2.0.0 → 2.4.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 CHANGED
@@ -1,267 +1,92 @@
1
1
  # kanbango
2
2
 
3
- JSON-first local Kanban board with web GUI, CLI, and MCP server (pure JavaScript).
4
-
5
- ## Features
6
-
7
- - 📊 Local Kanban board stored in JSON task files
8
- - 🎨 Modern web GUI with drag-and-drop interface
9
- - 🖥️ Full CLI support for automation and CI/CD
10
- - 🤖 AI-friendly API (JSON output)
11
- - 📁 Four columns: Active, Planned, Icebox, Done
12
- - ✅ Subtasks with progress tracking
13
- - 🏷️ Epic grouping
3
+ JSON-first local Kanban board for developers and AI agents — CLI, web GUI, and MCP server in one lightweight package.
14
4
 
15
5
  ## Installation
16
6
 
17
- ### Global (recommended)
7
+ ### Global (recommended for CLI)
18
8
  ```bash
19
9
  npm install -g kanbango
20
10
  ```
21
11
 
22
- ### Local (per project)
12
+ ### Local per project (recommended for MCP)
23
13
  ```bash
24
14
  npm install -D kanbango
25
15
  ```
26
16
 
27
- ### Using npx (no installation)
17
+ ### No install
28
18
  ```bash
29
19
  npx kanbango --help
30
20
  ```
31
21
 
32
- ## NPM Package
33
-
34
- - Package name: `kanbango`
35
- - Binaries: `kanban`, `kanban-cmd`
36
- - MCP server entrypoint: `mcp-server.js` (run via `npx kanbango mcp`)
37
-
38
- ## Requirements
39
-
40
- - Node.js 16+
41
-
42
22
  ## Quick Start
43
23
 
44
24
  ```bash
45
- # Initialize backlog structure
25
+ # Initialize backlog directories
46
26
  kanban init
47
27
 
48
- # Start web GUI (opens http://localhost:5500)
28
+ # Start web GUI (stable project port; prints the real URL)
49
29
  kanban serve
50
30
 
51
- # List all tasks (JSON)
31
+ # List all tasks
52
32
  kanban list --json
53
33
 
54
- # Add a new task
55
- kanban add "New feature" --col planned --epic "Phase1"
34
+ # Add a task
35
+ kanban add "My task" --col planned --epic "Phase 1"
56
36
 
57
- # Show task details
58
- kanban show PI-001
37
+ # Show details
38
+ kanban show 001
59
39
 
60
- # Move task between columns
61
- kanban move PI-001 active
40
+ # Move between columns (active | planned | icebox | done)
41
+ kanban move 001 active
62
42
 
63
- # Toggle subtask
64
- kanban toggle PI-001 0
43
+ # Update subtasks in one call
44
+ kanban update 001 '{"subtasks":[{"done":true,"text":"Research"},{"done":false,"text":"Implementation"}]}'
65
45
  ```
66
46
 
67
- ## CLI Commands
47
+ ### Columns
68
48
 
69
- | Command | Description |
70
- |---------|-------------|
71
- | `kanban serve [PORT]` | Start web GUI (default: 5500) |
72
- | `kanban init` | Initialize backlog structure |
73
- | `kanban mcp-init` | Generate MCP config files for Claude Code / OpenCode |
74
- | `kanban list` | List all tasks |
75
- | `kanban show <ID>` | Show task details |
76
- | `kanban add <TITLE>` | Add new task |
77
- | `kanban move <ID> <COL>` | Move task to column |
78
- | `kanban toggle <ID> <IDX>` | Toggle subtask |
79
-
80
- ## Columns
81
-
82
- - `active` — In progress (max 1-2 tasks)
83
- - `planned` — Planned for implementation
84
- - `icebox` — Frozen / nice-to-have
85
- - `done` — Completed
49
+ | Column | Purpose |
50
+ |--------|---------|
51
+ | `active` | In progress (keep to 1–2 tasks) |
52
+ | `planned` | Ready to implement |
53
+ | `icebox` | Nice-to-have / frozen |
54
+ | `done` | Completed |
86
55
 
87
56
  ## Data Structure
88
57
 
89
- Tasks are stored as JSON files in `backlog/<column>/`. Existing Markdown task files are still readable during migration:
58
+ Tasks are JSON files in `backlog/<column>/`:
90
59
 
91
60
  ```json
92
61
  {
93
- "id": "PI-001-feature-title",
94
- "title": "Feature Title",
62
+ "id": "001",
63
+ "title": "My Feature",
95
64
  "column": "planned",
96
65
  "epic_group": "Phase 1",
97
- "created": "2026-07-07",
98
- "description": "High-level context and implementation plan.",
99
- "specs": "Technical details and constraints.",
100
- "acceptance_criteria": [
101
- "First condition",
102
- "Second condition"
103
- ],
66
+ "created": "2026-07-08",
67
+ "description": "High-level context.",
68
+ "specs": "Technical details.",
69
+ "in_scope": ["What this task covers"],
70
+ "out_of_scope": ["What is explicitly excluded"],
71
+ "acceptance_criteria": ["Works as expected"],
72
+ "test_cases": ["Verify the happy path"],
104
73
  "subtasks": [
105
- {
106
- "id": "st-1",
107
- "text": "First subtask",
108
- "done": false,
109
- "description": "Execution details for the subtask"
110
- }
111
- ],
112
- "notes": "Optional freeform notes"
113
- }
114
- ```
115
-
116
- ## AI Integration
117
-
118
- The `kanban-cmd` command provides simplified JSON output for AI agents:
119
-
120
- ```bash
121
- # For AI: always use JSON output
122
- kanban-cmd list
123
-
124
- # Filter by column
125
- kanban-cmd list --col active
126
-
127
- # Show task details
128
- kanban-cmd show PI-001
129
-
130
- # Add task (AI-friendly)
131
- kanban-cmd add "New task" --col planned --epic "Phase 1"
132
- ```
133
-
134
- ### JSON Output Format
135
-
136
- ```json
137
- [
138
- {
139
- "id": "PI-001-feature-title",
140
- "title": "Feature Title",
141
- "column": "planned",
142
- "epic_group": "Phase 1",
143
- "created": "2026-07-07",
144
- "description": "High-level context and implementation plan.",
145
- "specs": "Technical details and constraints.",
146
- "acceptance_criteria": [
147
- "First condition"
148
- ],
149
- "tasks": [
150
- {
151
- "id": "st-1",
152
- "done": false,
153
- "text": "First subtask",
154
- "description": "Execution details for the subtask"
155
- }
156
- ]
157
- }
158
- ]
159
- ```
160
-
161
- ## MCP Task Views
162
-
163
- `kanban_read` supports compact response shaping so agents can avoid reading the whole task every time.
164
-
165
- ```json
166
- {
167
- "operation": "show",
168
- "task_id": "PI-001-feature-title",
169
- "view": "planning"
170
- }
171
- ```
172
-
173
- Supported views:
174
-
175
- - `summary` - `id`, `title`, `column`, `epic_group`, `created`, `progress`
176
- - `planning` - summary + `description`, `specs`, `acceptance_criteria`
177
- - `execution` - planning + `subtasks`
178
- - `full` - execution + `notes`
179
-
180
- You can also request explicit fields:
181
-
182
- ```json
183
- {
184
- "operation": "show",
185
- "task_id": "PI-001-feature-title",
186
- "fields": ["title", "description", "acceptance_criteria"]
187
- }
188
- ```
189
-
190
- `kanban_update` supports patch-style updates and compact responses:
191
-
192
- ```json
193
- {
194
- "operation": "update",
195
- "task_id": "PI-001-feature-title",
196
- "patch": {
197
- "description": "Updated plan",
198
- "acceptance_criteria": ["OAuth works"]
199
- },
200
- "return": "summary"
201
- }
202
- ```
203
-
204
- Errors are returned as structured JSON:
205
-
206
- ```json
207
- {
208
- "error": {
209
- "code": "TASK_NOT_FOUND",
210
- "message": "Task PI-999-missing was not found",
211
- "hint": "Call kanban_read with operation=list to discover valid task ids",
212
- "details": {
213
- "task_id": "PI-999-missing"
214
- },
215
- "retryable": false
216
- }
74
+ { "id": "st-1", "text": "First step", "done": false }
75
+ ]
217
76
  }
218
77
  ```
219
78
 
220
- ## Web GUI
221
-
222
- Start the web interface:
223
- ```bash
224
- kanban serve 5500
225
- ```
226
-
227
- Features:
228
- - Swimlanes grouped by epic
229
- - Drag-and-drop between columns
230
- - Inline editing
231
- - Real-time subtask checkboxes
232
- - Progress tracking
233
-
234
- ## Directory Structure
235
-
236
- ```
237
- backlog/
238
- ├── active/ # Tasks in progress
239
- ├── planned/ # Planned tasks
240
- ├── icebox/ # Frozen tasks
241
- └── done/ # Completed tasks
242
- ```
243
-
244
- ## API for AI Agents
245
-
246
- See [API.md](API.md) for detailed API function definitions.
247
-
248
- ## MCP Server
79
+ ## MCP Server (for Claude Code, OpenCode, Cursor)
249
80
 
250
- This package includes a Model Context Protocol (MCP) server for integration with MCP-compatible clients.
251
-
252
- ### Using the MCP server
81
+ Run the MCP server to let AI agents read / create / update your board:
253
82
 
254
83
  ```bash
255
- # Run the MCP server directly
256
- npm run mcp
257
-
258
- # Or using npx
259
84
  npx kanbango mcp
260
85
  ```
261
86
 
262
- ### MCP Configuration
87
+ ### Give it to your agent
263
88
 
264
- For MCP clients, add this to your configuration:
89
+ Add this to your MCP client config (`.mcp.json`, `opencode.json`, or Claude Desktop config):
265
90
 
266
91
  ```json
267
92
  {
@@ -274,87 +99,74 @@ For MCP clients, add this to your configuration:
274
99
  }
275
100
  ```
276
101
 
277
- ### MCP Per Project (Recommended)
278
-
279
- Install locally in the project so each repo controls its own MCP version:
280
-
281
- ```bash
282
- npm install -D kanbango
283
- ```
284
-
285
- Then point MCP to the local package:
286
-
287
- ```json
288
- {
289
- "mcpServers": {
290
- "kanbango": {
291
- "command": "node",
292
- "args": ["./node_modules/kanbango/mcp-server.js"]
293
- }
294
- }
295
- }
296
- ```
297
-
298
- If you prefer `npx`, you can still use it, but versioning is less explicit:
102
+ Auto-start the web GUI with MCP (opt-in):
299
103
 
300
104
  ```json
301
105
  {
302
106
  "mcpServers": {
303
107
  "kanbango": {
304
108
  "command": "npx",
305
- "args": ["kanbango", "mcp"]
109
+ "args": ["kanbango", "mcp"],
110
+ "env": {
111
+ "KANBANGO_AUTO_GUI": "1"
112
+ }
306
113
  }
307
114
  }
308
115
  }
309
116
  ```
310
117
 
311
- ### MCP Project Automation (Claude Code + OpenCode)
118
+ Optional: pin the port with `KANBANGO_GUI_PORT` (e.g. `"5821"`). Without it, each project gets a stable port in `5510–5999` derived from the project path. The real URL is always available via `kanban_gui` → `status` (and written to `backlog/.kanbango-gui.json` while the GUI runs).
312
119
 
313
- Generate project configs automatically (creates `.mcp.json` and `opencode.json` in the current folder):
120
+ Or generate the config files automatically:
314
121
 
315
122
  ```bash
316
- # Local install (recommended)
317
123
  npx kanbango mcp-init
124
+ ```
318
125
 
319
- # Use npx-based command in configs
320
- npx kanbango mcp-init --npx
126
+ ### What the agent can do
321
127
 
322
- # Only Claude Code config
323
- npx kanbango mcp-init --claude
128
+ Once connected, your agent gets access to these tools:
324
129
 
325
- # Only OpenCode config
326
- npx kanbango mcp-init --opencode
130
+ | Tool | What it does |
131
+ |------|-------------|
132
+ | `kanban_read` | List tasks, filter by column/epic, show details |
133
+ | `kanban_manage` | Create, move, patch-update tasks |
134
+ | `kanban_gui` | Start, stop, or check web GUI status (returns the real URL/port) |
327
135
 
328
- # Overwrite existing files
329
- npx kanbango mcp-init --force
330
- ```
136
+ Your agent stays in sync with your real board — every change is persisted as JSON files.
331
137
 
332
- ### Available MCP Tools
138
+ ## CLI Reference
333
139
 
334
- - `kanban_read` - Read tasks (list all, filter, or get specific task details)
335
- - `kanban_create` - Create a new task
336
- - `kanban_update` - Update tasks (move, toggle subtask, or edit details)
337
- - `kanban_gui_start` - Start the web GUI server (returns URL)
338
- - `kanban_gui_stop` - Stop the web GUI server
339
- - `kanban_gui_status` - Check GUI server status
140
+ | Command | Description |
141
+ |---------|-------------|
142
+ | `kanban init` | Create backlog directory structure |
143
+ | `kanban serve [PORT]` | Start web GUI (stable project port, or PORT / KANBANGO_GUI_PORT) |
144
+ | `kanban list [--col <col>] [--json]` | List tasks |
145
+ | `kanban show <ID>` | Show task details |
146
+ | `kanban add <TITLE>` | Add a new task |
147
+ | `kanban move <ID> <COL>` | Move task |
148
+ | `kanban mcp-init` | Generate MCP config files |
340
149
 
341
- ## Development
150
+ ## Web GUI
342
151
 
343
152
  ```bash
344
- # Install dependencies
345
- npm install
153
+ kanban serve
154
+ ```
155
+ - Swimlanes grouped by epic
156
+ - Drag-and-drop between columns
157
+ - Inline editing and subtask checkboxes
346
158
 
347
- # Run tests
348
- npm test
159
+ ## Using as a Node.js module
349
160
 
350
- # Build (if needed)
351
- npm run build
161
+ ```js
162
+ const kanban = require('kanbango');
163
+ const tasks = await kanban.allEpics();
352
164
  ```
353
165
 
354
- ## License
166
+ ## Requirements
355
167
 
356
- MIT
168
+ Node.js 16+
357
169
 
358
- ## Contributing
170
+ ## License
359
171
 
360
- Contributions are welcome! Please feel free to submit a Pull Request.
172
+ MIT
package/bin/kanban.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const kanban = require('../kanban.js');
4
+ const plan = require('../plan.js');
4
5
  const http = require('http');
5
6
  const fs = require('fs');
6
7
  const path = require('path');
@@ -9,7 +10,7 @@ const BACKLOG = path.join(process.cwd(), 'backlog');
9
10
  const COLS = kanban.COLS;
10
11
 
11
12
  function shortId(taskId) {
12
- const match = taskId.match(/^(PI-\d+[\w.-]*|BUG-\d+|CHORE-\d+)/);
13
+ const match = taskId.match(/^(?:[A-Z]+-)?(\d+)/);
13
14
  return match ? match[1] : taskId;
14
15
  }
15
16
 
@@ -222,21 +223,6 @@ async function cliAdd(title, column, epicGroup) {
222
223
  }
223
224
  }
224
225
 
225
- async function cliToggle(taskId, idx) {
226
- const success = await kanban.doToggle(taskId, idx);
227
- if (!success) {
228
- console.error(`✗ Nie znaleziono: ${taskId} subtask ${idx}`);
229
- process.exit(1);
230
- }
231
-
232
- const task = await kanban.getTask(taskId);
233
- if (idx < task.subtasks.length) {
234
- const subtask = task.subtasks[idx];
235
- const mark = subtask.done ? '✓' : '○';
236
- console.log(` [${idx}] ${mark} ${subtask.text}`);
237
- }
238
- }
239
-
240
226
  async function serveWeb(port) {
241
227
  const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf-8');
242
228
 
@@ -262,7 +248,10 @@ async function serveWeb(port) {
262
248
  const task = await kanban.doCreate(body.title || '', body.column || 'planned', body.epic_group || '—', {
263
249
  description: body.description,
264
250
  specs: body.specs,
251
+ in_scope: body.in_scope,
252
+ out_of_scope: body.out_of_scope,
265
253
  acceptance_criteria: body.acceptance_criteria,
254
+ test_cases: body.test_cases,
266
255
  subtasks: body.subtasks,
267
256
  notes: body.notes
268
257
  });
@@ -312,10 +301,12 @@ async function serveWeb(port) {
312
301
  const patch = body.patch ? { ...body.patch } : {};
313
302
 
314
303
  if (body.title !== undefined) patch.title = body.title;
315
- if (body.tasks !== undefined) patch.subtasks = body.tasks;
316
304
  if (body.description !== undefined) patch.description = body.description;
317
305
  if (body.specs !== undefined) patch.specs = body.specs;
306
+ if (body.in_scope !== undefined) patch.in_scope = body.in_scope;
307
+ if (body.out_of_scope !== undefined) patch.out_of_scope = body.out_of_scope;
318
308
  if (body.acceptance_criteria !== undefined) patch.acceptance_criteria = body.acceptance_criteria;
309
+ if (body.test_cases !== undefined) patch.test_cases = body.test_cases;
319
310
  if (body.subtasks !== undefined) patch.subtasks = body.subtasks;
320
311
  if (body.notes !== undefined) patch.notes = body.notes;
321
312
  if (body.epic_group !== undefined) patch.epic_group = body.epic_group;
@@ -332,11 +323,66 @@ async function serveWeb(port) {
332
323
  }
333
324
  });
334
325
 
335
- server.listen(port, 'localhost', () => {
336
- console.log(`\x1b[1;32m→ Kanban GUI: http://localhost:${port}\x1b[0m`);
337
- console.log(` Backlog: ${BACKLOG}`);
338
- console.log(' Ctrl+C żeby zamknąć');
326
+ const MAX_ATTEMPTS = 10;
327
+
328
+ function listenOnce(server, port) {
329
+ return new Promise((resolve, reject) => {
330
+ function onError(err) {
331
+ server.removeListener('listening', onListening);
332
+ reject(err);
333
+ }
334
+ function onListening() {
335
+ server.removeListener('error', onError);
336
+ resolve();
337
+ }
338
+ server.once('error', onError);
339
+ server.once('listening', onListening);
340
+ server.listen(port, 'localhost');
341
+ });
342
+ }
343
+
344
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
345
+ try {
346
+ await listenOnce(server, port + attempt);
347
+ break;
348
+ } catch (err) {
349
+ if (err.code !== 'EADDRINUSE') throw err;
350
+ console.log(`Port ${port + attempt} zajęty, próbuję ${port + attempt + 1}…`);
351
+ }
352
+ }
353
+
354
+ if (!server.listening) {
355
+ console.log(`Porty ${port}–${port + MAX_ATTEMPTS - 1} zajęte, próbuję losowy port…`);
356
+ await listenOnce(server, 0);
357
+ }
358
+
359
+ const actualPort = server.address().port;
360
+ const portInfo = await kanban.writeGuiPortFile({ port: actualPort, pid: process.pid });
361
+
362
+ async function cleanupGuiPortFile() {
363
+ try {
364
+ await kanban.clearGuiPortFile({ pid: process.pid });
365
+ } catch {
366
+ // best-effort cleanup
367
+ }
368
+ }
369
+
370
+ server.on('close', () => {
371
+ cleanupGuiPortFile();
339
372
  });
373
+
374
+ process.once('SIGINT', async () => {
375
+ await cleanupGuiPortFile();
376
+ server.close(() => process.exit(0));
377
+ });
378
+ process.once('SIGTERM', async () => {
379
+ await cleanupGuiPortFile();
380
+ server.close(() => process.exit(0));
381
+ });
382
+
383
+ console.log(`\x1b[1;32m→ Kanban GUI: ${portInfo.url}\x1b[0m`);
384
+ console.log(` Backlog: ${BACKLOG}`);
385
+ console.log(' Ctrl+C żeby zamknąć');
340
386
  }
341
387
 
342
388
  function readBody(req) {
@@ -379,13 +425,42 @@ async function cliMigrate(dryRun) {
379
425
  }
380
426
  }
381
427
 
428
+ function parseJsonPayload(value) {
429
+ try {
430
+ return JSON.parse(value || '{}');
431
+ } catch (error) {
432
+ throw kanban.createKanbanError('INVALID_JSON', 'Payload is not valid JSON',
433
+ 'Pass a JSON object after --json', { reason: error.message }, false, 400);
434
+ }
435
+ }
436
+
437
+ async function cliPlan(action, payload) {
438
+ try {
439
+ const handlers = {
440
+ create: plan.create,
441
+ advance: plan.advance,
442
+ evidence: plan.evidence,
443
+ done: plan.done,
444
+ status: (input) => plan.status(input.task_id)
445
+ };
446
+ if (!handlers[action]) throw kanban.createKanbanError('INVALID_PLAN_ACTION', `Unknown plan action: ${action}`,
447
+ 'Use create, advance, evidence, done, or status', { action }, false, 400);
448
+ console.log(JSON.stringify(await handlers[action](payload)));
449
+ } catch (error) {
450
+ console.log(JSON.stringify({ ok: false, task_id: payload.task_id || null, subtasks: [], error: {
451
+ code: error.code || 'INTERNAL_ERROR', message: error.message, hint: error.hint || '', details: error.details || {}
452
+ } }));
453
+ process.exitCode = 1;
454
+ }
455
+ }
456
+
382
457
  async function main() {
383
458
  const args = process.argv.slice(2);
384
459
  const cmd = args[0];
385
460
 
386
- if (!cmd || cmd === 'serve') {
387
- const port = parseInt(args[1] || '5500', 10);
388
- await serveWeb(port);
461
+ if (!cmd || cmd === 'serve') {
462
+ const port = kanban.resolvePreferredGuiPort(args[1]);
463
+ await serveWeb(port);
389
464
  } else if (cmd === 'init') {
390
465
  await cliInit();
391
466
  } else if (cmd === 'mcp-init') {
@@ -448,8 +523,8 @@ async function main() {
448
523
  }
449
524
 
450
525
  await cliAdd(args[1], column, epicGroup);
451
- } else if (cmd === 'toggle' && args[1] && args[2]) {
452
- await cliToggle(args[1], parseInt(args[2], 10));
526
+ } else if (cmd === 'plan' && args[1] && args[2] === '--json') {
527
+ await cliPlan(args[1], parseJsonPayload(args[3]));
453
528
  } else {
454
529
  console.error('Unknown command:', cmd);
455
530
  process.exit(1);