kanbango 3.6.2 → 5.1.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/AGENTS.md +25 -18
- package/API.md +58 -170
- package/CHANGELOG.md +76 -0
- package/LLM_AGENTS.md +21 -502
- package/README.md +45 -12
- package/agent-playbook.js +27 -49
- package/bin/kanban.js +395 -300
- package/config-wizard.js +441 -0
- package/index.html +124 -36
- package/kanban.js +892 -263
- package/mcp-server.js +319 -196
- package/package.json +1 -1
- package/plan.js +39 -40
- package/plugins/tui-kanban-controller.js +117 -0
- package/plugins/tui-kanban.tsx +314 -0
- package/workflow.js +368 -21
- package/.ai/lessons.jsonl +0 -9
- package/.ai/retro/close.json +0 -1
- package/.ai/retro/last-run.json +0 -1
- package/.ait-quality.yml +0 -25
- package/bin/kanban-cmd.js +0 -40
- package/kan2.md +0 -76
- package/kanbango.md +0 -48
- package/planv2.md +0 -317
- package/tests/fixtures/fake-opencode.js +0 -69
- package/tests/index.js +0 -19
- package/tests/kanban-cli.js +0 -118
- package/tests/kanban.js +0 -104
- package/tests/run.js +0 -33
package/kanbango.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# kanbango — Rename + Migrate Tool Plan
|
|
2
|
-
|
|
3
|
-
## Phase 1 — Add `migrate` command to `kanban.js`
|
|
4
|
-
|
|
5
|
-
**New exported function `migrateAll(options)`** in `kanban.js`:
|
|
6
|
-
- Walks all 4 columns
|
|
7
|
-
- Finds `.md` files, reads via existing `parseMarkdownTask`, writes `.json` via `writeTask`
|
|
8
|
-
- `writeTask` already deletes the old `.md` file automatically
|
|
9
|
-
- `options.dryRun` — preview only, returns what would migrate
|
|
10
|
-
- Returns `{ migrated: [{id, from, to}], errors: [{file, reason}] }`
|
|
11
|
-
|
|
12
|
-
## Phase 2 — Add `kanban migrate` CLI subcommand
|
|
13
|
-
|
|
14
|
-
In `bin/kanban.js`:
|
|
15
|
-
- `kanban migrate` — runs migration, reports each file converted
|
|
16
|
-
- `kanban migrate --dry-run` — preview only
|
|
17
|
-
|
|
18
|
-
## Phase 3 — Rename everything from `markdown-kanban` → `kanbango`
|
|
19
|
-
|
|
20
|
-
**Files to update:**
|
|
21
|
-
|
|
22
|
-
| File | Changes |
|
|
23
|
-
|------|---------|
|
|
24
|
-
| `package.json` | name → `kanbango`, description → "JSON-first local Kanban board with web GUI, CLI, and MCP server", repo/homepage/bugs URLs → `k0r81/kanbango` |
|
|
25
|
-
| `CHANGELOG.md` | Add `[2.0.0]` entry, rename references |
|
|
26
|
-
| `bin/kanban.js` | `claudeMcpConfig` and `openCodeMcpConfig` — change `npx markdown-kanban` → `npx kanbango` (lines 26, 42) |
|
|
27
|
-
| `README.md` | All `markdown-kanban` → `kanbango`, update description |
|
|
28
|
-
| `LLM_AGENTS.md` | Same renames |
|
|
29
|
-
| `AGENTS.md` | Same renames |
|
|
30
|
-
| `.npmignore` | Check if any path references need updating |
|
|
31
|
-
|
|
32
|
-
**What stays the same:**
|
|
33
|
-
- CLI binary names: `kanban` and `kanban-cmd` — no change
|
|
34
|
-
- `backlog/` directory structure
|
|
35
|
-
- All internal logic in `kanban.js`, `mcp-server.js`, `index.js`
|
|
36
|
-
|
|
37
|
-
## Phase 4 — Tag & Publish
|
|
38
|
-
|
|
39
|
-
1. Bump version to `2.0.0` in `package.json`
|
|
40
|
-
2. `git tag v2.0.0 -m "kanbango: rename + migrate tool"`
|
|
41
|
-
3. Push tag to GitHub
|
|
42
|
-
4. Rename GitHub repo `k0r81/markdown-kanban` → `k0r81/kanbango` (manual, via GitHub UI)
|
|
43
|
-
5. `npm publish` — first publish as `kanbango`
|
|
44
|
-
|
|
45
|
-
## Phase 5 — Verify
|
|
46
|
-
|
|
47
|
-
- `npm search kanbango` to confirm it's live
|
|
48
|
-
- Check download page at `https://www.npmjs.com/package/kanbango`
|
package/planv2.md
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
# Plan V2
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Evolve `markdown-kanban` into an LLM-first task system with:
|
|
6
|
-
|
|
7
|
-
- rich task and subtask descriptions
|
|
8
|
-
- minimal token usage in MCP flows
|
|
9
|
-
- a minimal MCP surface area
|
|
10
|
-
- structured, actionable error messages
|
|
11
|
-
- partial reads so agents only fetch the task slices they need
|
|
12
|
-
|
|
13
|
-
## Core Decision
|
|
14
|
-
|
|
15
|
-
Use JSON as the canonical task storage format.
|
|
16
|
-
|
|
17
|
-
Reasoning:
|
|
18
|
-
|
|
19
|
-
- Markdown is human-friendly, but becomes fragile once tasks need nested rich fields.
|
|
20
|
-
- JSON is easier to validate, partially read, partially update, and return through MCP.
|
|
21
|
-
- Token savings will come primarily from response shaping in MCP, not from the on-disk format itself.
|
|
22
|
-
|
|
23
|
-
## Token Strategy
|
|
24
|
-
|
|
25
|
-
The main token optimization should happen in MCP responses, not by compressing field names or over-optimizing file syntax.
|
|
26
|
-
|
|
27
|
-
Avoid:
|
|
28
|
-
|
|
29
|
-
- always returning the full task
|
|
30
|
-
- adding many narrow MCP methods
|
|
31
|
-
- cryptic short keys like `sp`, `ac`, `d`
|
|
32
|
-
|
|
33
|
-
Prefer:
|
|
34
|
-
|
|
35
|
-
- one read tool with multiple views
|
|
36
|
-
- explicit field selection
|
|
37
|
-
- compact summary responses by default
|
|
38
|
-
- structured update responses with configurable return payloads
|
|
39
|
-
|
|
40
|
-
## Recommended Task Schema
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{
|
|
44
|
-
"id": "014",
|
|
45
|
-
"title": "Google Calendar Integration",
|
|
46
|
-
"column": "active",
|
|
47
|
-
"epic_group": "Phase 1",
|
|
48
|
-
"created": "2026-07-07",
|
|
49
|
-
"description": "High-level context and implementation plan in markdown.",
|
|
50
|
-
"specs": "Technical constraints, APIs, data model, edge cases.",
|
|
51
|
-
"in_scope": [
|
|
52
|
-
"OAuth connect/disconnect",
|
|
53
|
-
"Two-way event sync"
|
|
54
|
-
],
|
|
55
|
-
"out_of_scope": [
|
|
56
|
-
"Microsoft Calendar",
|
|
57
|
-
"Mobile push notifications"
|
|
58
|
-
],
|
|
59
|
-
"acceptance_criteria": [
|
|
60
|
-
"User can connect Google account",
|
|
61
|
-
"Events sync in both directions",
|
|
62
|
-
"Sync conflicts are logged"
|
|
63
|
-
],
|
|
64
|
-
"subtasks": [
|
|
65
|
-
{
|
|
66
|
-
"id": "st-1",
|
|
67
|
-
"text": "Implement OAuth flow",
|
|
68
|
-
"done": false,
|
|
69
|
-
"description": "Use PKCE, store refresh token encrypted."
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
"notes": "Optional freeform notes"
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Field Semantics
|
|
77
|
-
|
|
78
|
-
- `description`: the main context, why the work exists, and the implementation plan
|
|
79
|
-
- `specs`: concrete technical facts, constraints, APIs, data shape, edge cases
|
|
80
|
-
- `in_scope`: what this task includes (boundaries)
|
|
81
|
-
- `out_of_scope`: explicit non-goals / exclusions
|
|
82
|
-
- `acceptance_criteria`: what must be true for the task to count as done
|
|
83
|
-
- `subtasks[].description`: local execution detail for a subtask
|
|
84
|
-
- `notes`: optional freeform leftovers, references, or observations
|
|
85
|
-
|
|
86
|
-
This split is useful for both humans and agents.
|
|
87
|
-
|
|
88
|
-
## MCP Surface
|
|
89
|
-
|
|
90
|
-
Keep the MCP surface at 3 tools:
|
|
91
|
-
|
|
92
|
-
- `kanban_read`
|
|
93
|
-
- `kanban_create`
|
|
94
|
-
- `kanban_update`
|
|
95
|
-
|
|
96
|
-
Do not add separate tools like:
|
|
97
|
-
|
|
98
|
-
- `kanban_read_specs`
|
|
99
|
-
- `kanban_read_ac`
|
|
100
|
-
- `kanban_read_subtasks`
|
|
101
|
-
|
|
102
|
-
That would increase surface area without meaningfully reducing tokens.
|
|
103
|
-
|
|
104
|
-
## kanban_read Design
|
|
105
|
-
|
|
106
|
-
Add support for response shaping.
|
|
107
|
-
|
|
108
|
-
### Option A: view presets
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"operation": "show",
|
|
113
|
-
"task_id": "014",
|
|
114
|
-
"view": "planning"
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Suggested views:
|
|
119
|
-
|
|
120
|
-
- `summary`: id, title, column, epic, created, progress counts
|
|
121
|
-
- `planning`: summary + description + specs + in_scope + out_of_scope + acceptance_criteria
|
|
122
|
-
- `execution`: planning + subtasks
|
|
123
|
-
- `full`: everything including notes and metadata
|
|
124
|
-
|
|
125
|
-
### Option B: explicit fields
|
|
126
|
-
|
|
127
|
-
```json
|
|
128
|
-
{
|
|
129
|
-
"operation": "show",
|
|
130
|
-
"task_id": "014",
|
|
131
|
-
"fields": ["title", "description", "acceptance_criteria"]
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Recommendation: support both.
|
|
136
|
-
|
|
137
|
-
- `view` is simple and ergonomic
|
|
138
|
-
- `fields` is precise for advanced agents
|
|
139
|
-
|
|
140
|
-
If both are supplied, `fields` should win.
|
|
141
|
-
|
|
142
|
-
## kanban_create Design
|
|
143
|
-
|
|
144
|
-
Allow rich fields during creation, but keep minimal creation possible.
|
|
145
|
-
|
|
146
|
-
Minimal create:
|
|
147
|
-
|
|
148
|
-
```json
|
|
149
|
-
{
|
|
150
|
-
"title": "Add user authentication"
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Full create:
|
|
155
|
-
|
|
156
|
-
```json
|
|
157
|
-
{
|
|
158
|
-
"title": "Add user authentication",
|
|
159
|
-
"col": "planned",
|
|
160
|
-
"epic": "Auth",
|
|
161
|
-
"description": "Implement auth flow and session model.",
|
|
162
|
-
"specs": "Use OAuth + session cookies.",
|
|
163
|
-
"acceptance_criteria": [
|
|
164
|
-
"User can sign in",
|
|
165
|
-
"Session persists after refresh"
|
|
166
|
-
],
|
|
167
|
-
"subtasks": [
|
|
168
|
-
{
|
|
169
|
-
"text": "Create auth routes",
|
|
170
|
-
"description": "Add login and callback handlers."
|
|
171
|
-
}
|
|
172
|
-
],
|
|
173
|
-
"notes": "Optional"
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## kanban_update Design
|
|
178
|
-
|
|
179
|
-
Use a patch-like payload and let callers control the returned payload size.
|
|
180
|
-
|
|
181
|
-
```json
|
|
182
|
-
{
|
|
183
|
-
"operation": "update",
|
|
184
|
-
"task_id": "014",
|
|
185
|
-
"patch": {
|
|
186
|
-
"description": "Updated implementation plan...",
|
|
187
|
-
"acceptance_criteria": [
|
|
188
|
-
"OAuth works",
|
|
189
|
-
"Tokens refresh correctly"
|
|
190
|
-
]
|
|
191
|
-
},
|
|
192
|
-
"return": "summary"
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Suggested `return` values:
|
|
197
|
-
|
|
198
|
-
- `none`
|
|
199
|
-
- `summary`
|
|
200
|
-
- `full`
|
|
201
|
-
|
|
202
|
-
Default should be `summary`, not `full`.
|
|
203
|
-
|
|
204
|
-
This avoids sending large descriptions back after every small edit.
|
|
205
|
-
|
|
206
|
-
## Read Strategy For Agents
|
|
207
|
-
|
|
208
|
-
Agents should not always read the whole task.
|
|
209
|
-
|
|
210
|
-
Recommended workflows:
|
|
211
|
-
|
|
212
|
-
### Triage
|
|
213
|
-
|
|
214
|
-
Use `summary` only.
|
|
215
|
-
|
|
216
|
-
### Planning
|
|
217
|
-
|
|
218
|
-
Read:
|
|
219
|
-
|
|
220
|
-
- `description`
|
|
221
|
-
- `specs`
|
|
222
|
-
- `acceptance_criteria`
|
|
223
|
-
|
|
224
|
-
### Implementation
|
|
225
|
-
|
|
226
|
-
Read:
|
|
227
|
-
|
|
228
|
-
- planning fields
|
|
229
|
-
- subtasks
|
|
230
|
-
|
|
231
|
-
### Verification
|
|
232
|
-
|
|
233
|
-
Read:
|
|
234
|
-
|
|
235
|
-
- `acceptance_criteria`
|
|
236
|
-
- progress / status fields
|
|
237
|
-
|
|
238
|
-
### Backlog cleanup
|
|
239
|
-
|
|
240
|
-
Read:
|
|
241
|
-
|
|
242
|
-
- summary only
|
|
243
|
-
|
|
244
|
-
This is not a bad idea. It is the correct way to reduce token use while staying useful.
|
|
245
|
-
|
|
246
|
-
## Error Design
|
|
247
|
-
|
|
248
|
-
Errors should be structured JSON, not a plain string.
|
|
249
|
-
|
|
250
|
-
Recommended shape:
|
|
251
|
-
|
|
252
|
-
```json
|
|
253
|
-
{
|
|
254
|
-
"error": {
|
|
255
|
-
"code": "TASK_NOT_FOUND",
|
|
256
|
-
"message": "Task 014 was not found",
|
|
257
|
-
"hint": "Call kanban_read with operation=list to discover valid task ids",
|
|
258
|
-
"details": {
|
|
259
|
-
"task_id": "014"
|
|
260
|
-
},
|
|
261
|
-
"retryable": false
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Suggested error codes:
|
|
267
|
-
|
|
268
|
-
- `TASK_NOT_FOUND`
|
|
269
|
-
- `INVALID_COLUMN`
|
|
270
|
-
- `INVALID_SUBTASK_INDEX`
|
|
271
|
-
- `VALIDATION_ERROR`
|
|
272
|
-
- `MISSING_REQUIRED_FIELD`
|
|
273
|
-
- `TASK_CONFLICT`
|
|
274
|
-
- `PARSE_ERROR`
|
|
275
|
-
|
|
276
|
-
Good errors should help an agent recover without extra back-and-forth.
|
|
277
|
-
|
|
278
|
-
## Migration Strategy
|
|
279
|
-
|
|
280
|
-
Existing Markdown files are already persisted data, so backward compatibility matters during migration.
|
|
281
|
-
|
|
282
|
-
Recommended migration plan:
|
|
283
|
-
|
|
284
|
-
1. Add JSON schema support.
|
|
285
|
-
2. Support reading both `.md` and `.json` task files during transition.
|
|
286
|
-
3. Write new tasks as JSON.
|
|
287
|
-
4. Add an optional migration command later if needed.
|
|
288
|
-
5. Once the repo is clean and the migration is accepted, decide whether to keep Markdown read support permanently.
|
|
289
|
-
|
|
290
|
-
## Suggested Defaults
|
|
291
|
-
|
|
292
|
-
- Canonical storage: JSON
|
|
293
|
-
- MCP tools: keep exactly 3
|
|
294
|
-
- Default read view: `summary` for lists, `planning` or `summary` for show depending on client choice
|
|
295
|
-
- Default update return payload: `summary`
|
|
296
|
-
- Rich task fields: `description`, `specs`, `acceptance_criteria`, `notes`
|
|
297
|
-
- Rich subtask fields: `text`, `done`, `description`
|
|
298
|
-
|
|
299
|
-
## Non-Goals
|
|
300
|
-
|
|
301
|
-
Avoid for now:
|
|
302
|
-
|
|
303
|
-
- many specialized MCP methods
|
|
304
|
-
- abbreviated schema keys just to save a few tokens
|
|
305
|
-
- always rendering HTML for descriptions
|
|
306
|
-
- over-structuring subtasks with too many fields
|
|
307
|
-
|
|
308
|
-
## Next Implementation Steps
|
|
309
|
-
|
|
310
|
-
1. Define the JSON schema in code and docs.
|
|
311
|
-
2. Update `kanban.js` to read and write JSON tasks.
|
|
312
|
-
3. Add transitional support for reading existing Markdown tasks.
|
|
313
|
-
4. Extend `kanban_read` with `view` and `fields`.
|
|
314
|
-
5. Extend `kanban_create` and `kanban_update` with rich fields and `patch` semantics.
|
|
315
|
-
6. Add structured MCP error responses with codes and hints.
|
|
316
|
-
7. Update the web GUI to edit `description`, `specs`, `acceptance_criteria`, and subtask descriptions.
|
|
317
|
-
8. Add tests for partial reads, structured errors, and Markdown-to-JSON compatibility.
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Fake opencode runner for workflow-gates tests.
|
|
4
|
-
* Env:
|
|
5
|
-
* FAKE_OPENCODE_MODE = pass|fail|blocked|gate_pass|gate_fail|gate_missing|hang|exit1
|
|
6
|
-
* FAKE_OPENCODE_REVIEW_MODE = gate_pass|gate_fail|gate_missing (optional; overrides when --agent is review)
|
|
7
|
-
* FAKE_OPENCODE_ARGV_FILE = path to write JSON argv
|
|
8
|
-
* FAKE_OPENCODE_HANG_MS = hang duration (default 60000)
|
|
9
|
-
*/
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const args = process.argv.slice(2);
|
|
14
|
-
const agentIdx = args.indexOf('--agent');
|
|
15
|
-
const agent = agentIdx >= 0 ? args[agentIdx + 1] : '';
|
|
16
|
-
|
|
17
|
-
let mode = String(process.env.FAKE_OPENCODE_MODE || 'pass').trim();
|
|
18
|
-
const reviewMode = String(process.env.FAKE_OPENCODE_REVIEW_MODE || '').trim();
|
|
19
|
-
// Review agents: prefer review mode, else map testing modes to gate_* defaults.
|
|
20
|
-
if (agent === 'temida' || agent === 'review' || /temida|review/i.test(agent)) {
|
|
21
|
-
if (reviewMode) {
|
|
22
|
-
mode = reviewMode;
|
|
23
|
-
} else if (mode === 'pass') {
|
|
24
|
-
mode = 'gate_pass';
|
|
25
|
-
} else if (mode === 'fail') {
|
|
26
|
-
mode = 'gate_fail';
|
|
27
|
-
} else if (mode === 'blocked' || mode === 'gate_missing') {
|
|
28
|
-
mode = mode === 'blocked' ? 'gate_missing' : mode;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const argvFile = process.env.FAKE_OPENCODE_ARGV_FILE;
|
|
33
|
-
if (argvFile) {
|
|
34
|
-
try {
|
|
35
|
-
fs.mkdirSync(path.dirname(argvFile), { recursive: true });
|
|
36
|
-
fs.writeFileSync(argvFile, JSON.stringify(args, null, 2) + '\n', 'utf-8');
|
|
37
|
-
} catch (err) {
|
|
38
|
-
process.stderr.write(`argv write failed: ${err.message}\n`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (mode === 'hang') {
|
|
43
|
-
const ms = Number(process.env.FAKE_OPENCODE_HANG_MS || 60000);
|
|
44
|
-
setTimeout(() => process.exit(0), ms);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const outputs = {
|
|
49
|
-
pass: { out: 'Tests green\nPASS\n', code: 0 },
|
|
50
|
-
fail: { out: 'Tests red\nFAIL\n', code: 1 },
|
|
51
|
-
blocked: { out: 'Missing env\nBLOCKED\n', code: 0 },
|
|
52
|
-
gate_pass: {
|
|
53
|
-
out: 'Nagroda: solidny diff.\nKara kosmetyczna: naming.\nWedka: ok.\nGATE: PASS\n',
|
|
54
|
-
code: 0
|
|
55
|
-
},
|
|
56
|
-
gate_fail: {
|
|
57
|
-
out: 'Kara: brak testow produkcji.\nWedka: dodaj testy.\nGATE: FAIL\n',
|
|
58
|
-
code: 0
|
|
59
|
-
},
|
|
60
|
-
gate_missing: {
|
|
61
|
-
out: 'Wyrok bez markera bramki.\nTylko narracja.\n',
|
|
62
|
-
code: 0
|
|
63
|
-
},
|
|
64
|
-
exit1: { out: 'crashed without verdict\n', code: 1 }
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const picked = outputs[mode] || outputs.pass;
|
|
68
|
-
process.stdout.write(picked.out);
|
|
69
|
-
process.exit(picked.code);
|
package/tests/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function run() {
|
|
5
|
-
const pkg = require(path.join(__dirname, '..', 'index.js'));
|
|
6
|
-
assert.ok(pkg.kanban, 'index exports kanban');
|
|
7
|
-
assert.ok(pkg.plan, 'index exports plan');
|
|
8
|
-
assert.ok(pkg.workflow, 'index exports workflow');
|
|
9
|
-
assert.ok(pkg.guiRegistry, 'index exports guiRegistry');
|
|
10
|
-
assert.ok(pkg.playbook, 'index exports playbook');
|
|
11
|
-
assert.ok(pkg.kanban.COLS.includes('testing'));
|
|
12
|
-
assert.ok(pkg.kanban.COLS.includes('review'));
|
|
13
|
-
assert.strictEqual(typeof pkg.workflow.loadConfig, 'function');
|
|
14
|
-
assert.strictEqual(typeof pkg.workflow.maybeEnqueueOnColumnEnter, 'function');
|
|
15
|
-
assert.strictEqual(typeof pkg.plan.done, 'function');
|
|
16
|
-
console.log('✓ index.test.js passed');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
run();
|
package/tests/kanban-cli.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const crypto = require('crypto');
|
|
3
|
-
const fs = require('fs').promises;
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { spawnSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const PKG = path.join(__dirname, '..');
|
|
9
|
-
const CLI = path.join(PKG, 'bin', 'kanban.js');
|
|
10
|
-
const SRC_QA = path.join(PKG, 'agents', 'qa-tester.md');
|
|
11
|
-
|
|
12
|
-
function runCli(cwd, args) {
|
|
13
|
-
return spawnSync(process.execPath, [CLI, ...args], {
|
|
14
|
-
cwd,
|
|
15
|
-
encoding: 'utf-8'
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function sha256(content) {
|
|
20
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function run() {
|
|
24
|
-
// init creates testing/review dirs + README
|
|
25
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'kanbango-bin-'));
|
|
26
|
-
const init = runCli(root, ['init']);
|
|
27
|
-
assert.strictEqual(init.status, 0, init.stderr);
|
|
28
|
-
for (const col of ['testing', 'review', 'active', 'planned', 'icebox', 'done']) {
|
|
29
|
-
const st = await fs.stat(path.join(root, 'backlog', col));
|
|
30
|
-
assert.ok(st.isDirectory(), col);
|
|
31
|
-
}
|
|
32
|
-
const readme = await fs.readFile(path.join(root, 'backlog', 'README.md'), 'utf-8');
|
|
33
|
-
assert.ok(readme.includes('testing/'));
|
|
34
|
-
assert.ok(readme.includes('review/'));
|
|
35
|
-
|
|
36
|
-
// mcp-init --opencode copies agents + writes manifest
|
|
37
|
-
const ocRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kanbango-bin-oc-'));
|
|
38
|
-
const oc = runCli(ocRoot, ['mcp-init', '--opencode']);
|
|
39
|
-
assert.strictEqual(oc.status, 0, oc.stderr);
|
|
40
|
-
const agentDir = path.join(ocRoot, '.opencode', 'agent');
|
|
41
|
-
const agents = await fs.readdir(agentDir);
|
|
42
|
-
assert.ok(agents.includes('qa-tester.md'));
|
|
43
|
-
assert.ok(agents.includes('temida.md'));
|
|
44
|
-
assert.ok(agents.includes('.kanbango-agents.json'));
|
|
45
|
-
const temida = await fs.readFile(path.join(agentDir, 'temida.md'), 'utf-8');
|
|
46
|
-
assert.ok(temida.includes('GATE: PASS'));
|
|
47
|
-
|
|
48
|
-
const srcQa = await fs.readFile(SRC_QA, 'utf-8');
|
|
49
|
-
const srcHash = sha256(srcQa);
|
|
50
|
-
const manifest1 = JSON.parse(await fs.readFile(path.join(agentDir, '.kanbango-agents.json'), 'utf-8'));
|
|
51
|
-
assert.strictEqual(manifest1.agents['qa-tester.md'], srcHash);
|
|
52
|
-
|
|
53
|
-
// second run, identical source → unchanged
|
|
54
|
-
const againSame = runCli(ocRoot, ['mcp-init', '--opencode']);
|
|
55
|
-
assert.strictEqual(againSame.status, 0);
|
|
56
|
-
assert.ok(againSame.stdout.includes('Bez zmian') || againSame.stdout.includes('unchanged')
|
|
57
|
-
|| againSame.stdout.includes('Pominięto') === false);
|
|
58
|
-
assert.ok(againSame.stdout.includes('Bez zmian .opencode/agent/qa-tester.md'));
|
|
59
|
-
assert.strictEqual(await fs.readFile(path.join(agentDir, 'qa-tester.md'), 'utf-8'), srcQa);
|
|
60
|
-
|
|
61
|
-
// local edit while manifest matches previous package hash → conflict, keep local
|
|
62
|
-
await fs.writeFile(path.join(agentDir, 'qa-tester.md'), 'KEEP_LOCAL_EDIT', 'utf-8');
|
|
63
|
-
const conflictRun = runCli(ocRoot, ['mcp-init', '--opencode']);
|
|
64
|
-
assert.strictEqual(conflictRun.status, 0);
|
|
65
|
-
assert.ok(
|
|
66
|
-
conflictRun.stdout.includes('Konflikt') || conflictRun.stdout.includes('Pominięto'),
|
|
67
|
-
`expected conflict/skip message, got: ${conflictRun.stdout}`
|
|
68
|
-
);
|
|
69
|
-
assert.strictEqual(
|
|
70
|
-
await fs.readFile(path.join(agentDir, 'qa-tester.md'), 'utf-8'),
|
|
71
|
-
'KEEP_LOCAL_EDIT'
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// clean older package version: dest matches recorded hash, source "new" → auto update
|
|
75
|
-
// Simulate by writing dest = old body, manifest hash = old hash, then force-path via
|
|
76
|
-
// restoring package content is fixed; instead write dest to old body with matching manifest.
|
|
77
|
-
const oldBody = 'OLD_PACKAGE_VERSION\n';
|
|
78
|
-
const oldHash = sha256(oldBody);
|
|
79
|
-
await fs.writeFile(path.join(agentDir, 'qa-tester.md'), oldBody, 'utf-8');
|
|
80
|
-
const man = JSON.parse(await fs.readFile(path.join(agentDir, '.kanbango-agents.json'), 'utf-8'));
|
|
81
|
-
man.agents['qa-tester.md'] = oldHash;
|
|
82
|
-
await fs.writeFile(
|
|
83
|
-
path.join(agentDir, '.kanbango-agents.json'),
|
|
84
|
-
JSON.stringify(man, null, 2) + '\n',
|
|
85
|
-
'utf-8'
|
|
86
|
-
);
|
|
87
|
-
const autoUpdate = runCli(ocRoot, ['mcp-init', '--opencode']);
|
|
88
|
-
assert.strictEqual(autoUpdate.status, 0, autoUpdate.stderr);
|
|
89
|
-
assert.ok(
|
|
90
|
-
autoUpdate.stdout.includes('Zaktualizowano .opencode/agent/qa-tester.md'),
|
|
91
|
-
autoUpdate.stdout
|
|
92
|
-
);
|
|
93
|
-
assert.strictEqual(await fs.readFile(path.join(agentDir, 'qa-tester.md'), 'utf-8'), srcQa);
|
|
94
|
-
const manAfter = JSON.parse(await fs.readFile(path.join(agentDir, '.kanbango-agents.json'), 'utf-8'));
|
|
95
|
-
assert.strictEqual(manAfter.agents['qa-tester.md'], srcHash);
|
|
96
|
-
|
|
97
|
-
// --force overwrites local edit
|
|
98
|
-
await fs.writeFile(path.join(agentDir, 'qa-tester.md'), 'FORCE_ME', 'utf-8');
|
|
99
|
-
const forceRun = runCli(ocRoot, ['mcp-init', '--opencode', '--force']);
|
|
100
|
-
assert.strictEqual(forceRun.status, 0);
|
|
101
|
-
assert.strictEqual(await fs.readFile(path.join(agentDir, 'qa-tester.md'), 'utf-8'), srcQa);
|
|
102
|
-
|
|
103
|
-
// --claude does not create .opencode/agent
|
|
104
|
-
const claudeRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kanbango-bin-cl-'));
|
|
105
|
-
const cl = runCli(claudeRoot, ['mcp-init', '--claude']);
|
|
106
|
-
assert.strictEqual(cl.status, 0, cl.stderr);
|
|
107
|
-
await assert.rejects(
|
|
108
|
-
() => fs.access(path.join(claudeRoot, '.opencode', 'agent')),
|
|
109
|
-
(err) => err.code === 'ENOENT'
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
console.log('✓ bin-kanban.test.js passed');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
run().catch((err) => {
|
|
116
|
-
console.error(err);
|
|
117
|
-
process.exit(1);
|
|
118
|
-
});
|
package/tests/kanban.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const fs = require('fs').promises;
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
async function run() {
|
|
7
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kanbango-kanban-'));
|
|
8
|
-
process.chdir(tempRoot);
|
|
9
|
-
const kanban = require(path.join(__dirname, '..', 'kanban.js'));
|
|
10
|
-
|
|
11
|
-
assert.ok(kanban.COLS.includes('testing'));
|
|
12
|
-
assert.ok(kanban.COLS.includes('review'));
|
|
13
|
-
assert.strictEqual(kanban.STATUS_MAP.testing, 'testing');
|
|
14
|
-
assert.strictEqual(kanban.STATUS_MAP.review, 'review');
|
|
15
|
-
|
|
16
|
-
await kanban.ensureBacklogDir();
|
|
17
|
-
for (const col of ['testing', 'review']) {
|
|
18
|
-
const st = await fs.stat(path.join(tempRoot, 'backlog', col));
|
|
19
|
-
assert.ok(st.isDirectory());
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const ev = kanban.normalizeEvidence([{
|
|
23
|
-
diff: 'old',
|
|
24
|
-
test_command: 't',
|
|
25
|
-
stdout: '',
|
|
26
|
-
stderr: '',
|
|
27
|
-
exit_code: 0,
|
|
28
|
-
stage: 'testing',
|
|
29
|
-
agent: 'qa-tester',
|
|
30
|
-
verdict: 'pass',
|
|
31
|
-
summary: 'ok'
|
|
32
|
-
}]);
|
|
33
|
-
assert.strictEqual(ev[0].stage, 'testing');
|
|
34
|
-
assert.strictEqual(ev[0].verdict, 'pass');
|
|
35
|
-
|
|
36
|
-
const wf = kanban.normalizeWorkflow({
|
|
37
|
-
stage: 'review',
|
|
38
|
-
status: 'running',
|
|
39
|
-
agent: 'temida',
|
|
40
|
-
run_id: 'run-x'
|
|
41
|
-
});
|
|
42
|
-
assert.strictEqual(wf.stage, 'review');
|
|
43
|
-
assert.strictEqual(wf.status, 'running');
|
|
44
|
-
|
|
45
|
-
const task = await kanban.doCreate('Kanban unit', 'active', '—', {});
|
|
46
|
-
const moved = await kanban.updateTask(task.id, { column: 'testing' });
|
|
47
|
-
assert.strictEqual(moved.column, 'testing');
|
|
48
|
-
|
|
49
|
-
await assert.rejects(
|
|
50
|
-
() => kanban.updateTask(task.id, { column: 'done' }),
|
|
51
|
-
(err) => {
|
|
52
|
-
assert.strictEqual(err.code, 'INVALID_TRANSITION');
|
|
53
|
-
assert.deepStrictEqual(err.details.allowed_columns, ['active', 'review']);
|
|
54
|
-
assert.strictEqual(err.details.from, 'testing');
|
|
55
|
-
assert.strictEqual(err.details.to, 'done');
|
|
56
|
-
assert.ok(err.hint.includes('active, review'));
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
assert.deepStrictEqual(kanban.allowedColumnsFrom('active'), ['planned', 'testing', 'icebox']);
|
|
61
|
-
assert.deepStrictEqual(kanban.allowedColumnsFrom('review'), ['active', 'done']);
|
|
62
|
-
kanban.validateTransition('active', 'testing', '001');
|
|
63
|
-
kanban.validateTransition('testing', 'testing', '001');
|
|
64
|
-
|
|
65
|
-
const appended = await kanban.updateTask(task.id, {
|
|
66
|
-
appendEvidence: {
|
|
67
|
-
diff: '',
|
|
68
|
-
test_command: 'fake',
|
|
69
|
-
stdout: 'PASS',
|
|
70
|
-
stderr: '',
|
|
71
|
-
exit_code: 0,
|
|
72
|
-
stage: 'testing',
|
|
73
|
-
agent: 'qa-tester',
|
|
74
|
-
verdict: 'pass',
|
|
75
|
-
summary: 'PASS'
|
|
76
|
-
},
|
|
77
|
-
workflow: {
|
|
78
|
-
stage: 'testing',
|
|
79
|
-
status: 'pass',
|
|
80
|
-
agent: 'qa-tester',
|
|
81
|
-
run_id: 'run-1'
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
assert.ok(appended.evidence.some((e) => e.verdict === 'pass'));
|
|
85
|
-
assert.strictEqual(appended.workflow.status, 'pass');
|
|
86
|
-
|
|
87
|
-
assert.strictEqual(
|
|
88
|
-
kanban.deriveEpicStatus([{ column: 'testing' }], {}),
|
|
89
|
-
'active'
|
|
90
|
-
);
|
|
91
|
-
const progress = kanban.getEpicProgress([
|
|
92
|
-
{ column: 'testing' },
|
|
93
|
-
{ column: 'review' }
|
|
94
|
-
]);
|
|
95
|
-
assert.strictEqual(progress.tasks_testing, 1);
|
|
96
|
-
assert.strictEqual(progress.tasks_review, 1);
|
|
97
|
-
|
|
98
|
-
console.log('✓ kanban.test.js passed');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
run().catch((err) => {
|
|
102
|
-
console.error(err);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
});
|