@pi-unipi/kanboard 3.0.0-alpha.2 → 3.0.0-alpha.21
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 +51 -47
- package/index.ts +293 -55
- package/package.json +8 -8
- package/skills/kanboard/SKILL.md +61 -27
- package/src/badges.ts +93 -0
- package/src/bin.ts +4 -2
- package/src/commands.ts +218 -84
- package/src/debug.ts +20 -0
- package/src/guard.ts +267 -109
- package/src/monitor.ts +330 -0
- package/src/notice-buffer.ts +74 -0
- package/src/progress.ts +27 -0
- package/src/reminders.ts +234 -0
- package/src/settings.ts +69 -15
- package/src/shapes.ts +5 -0
- package/src/runner.ts +0 -817
package/skills/kanboard/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kanboard
|
|
3
|
-
description: "Kanboard — the project's deferred-work board. Use when the user asks to note a task for later, to see what is on the board, or while working a board task: read it with `unipi-kanboard show`, add notes, block with a question, or file follow-up work."
|
|
3
|
+
description: "Kanboard — the project's deferred-work board. Use when the user asks to note a task for later, to see what is on the board, or to work board tasks (e.g. do UNI-5): `start` a task before working on it and `finish` it with a summary when done; while working a board task: read it with `unipi-kanboard show`, add notes, block with a question, or file follow-up work."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Kanboard
|
|
@@ -16,27 +16,35 @@ Always pass the actor and the project, and call it by absolute path (it may not
|
|
|
16
16
|
be on `PATH`):
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
<binary> --actor agent --project <slug> list [--ready] [--json]
|
|
19
|
+
<binary> --actor agent --project <slug> list [--ready] [--all] [--json] # bare list = todo/in_progress/blocked/in_review/done only; --all adds backlog/cancelled/archived
|
|
20
20
|
<binary> --actor agent --project <slug> show <ID>
|
|
21
|
-
<binary> --actor agent --project <slug> next # what
|
|
21
|
+
<binary> --actor agent --project <slug> next # what would be picked next + why (read-only)
|
|
22
22
|
<binary> --actor agent --project <slug> chain <ID> # upstream deps + downstream dependents
|
|
23
23
|
<binary> --actor agent --project <slug> search "<text>" [--all] # id/title/body, archived excluded unless --all
|
|
24
24
|
<binary> --actor agent --project <slug> add "<title>" [--status todo] [--after <ID>] [--body-file <f>] [--attach <file>]…
|
|
25
|
-
<binary> --actor agent --project <slug> note <ID> "<text>"
|
|
25
|
+
<binary> --actor agent --project <slug> note <ID> "<text>" [--attach <file>]…
|
|
26
26
|
<binary> --actor agent --project <slug> attach <ID> <file> --note "<what it shows>"
|
|
27
27
|
<binary> --actor agent --project <slug> attachments <ID>
|
|
28
28
|
<binary> --actor agent --project <slug> edit <ID> --title|--body|--labels … # only tasks you created, while in backlog/todo
|
|
29
|
-
<binary> --actor agent --project <slug>
|
|
29
|
+
<binary> --actor agent --project <slug> start <ID> # todo → in progress, claimed for your session (costs a -do slot)
|
|
30
|
+
<binary> --actor agent --project <slug> finish <ID> --comment "<summary>" [--attach <file>]… # in progress → in review, only a task you started (free)
|
|
31
|
+
<binary> --actor agent --project <slug> move <ID> blocked --comment "<what you need>" [--attach <file>]…
|
|
30
32
|
<binary> --actor agent --project <slug> link <ID> --after <DEP>
|
|
31
33
|
<binary> --actor agent --project <slug> unlink <ID> --after <DEP>
|
|
32
34
|
<binary> --actor agent --project <slug> order <ID> --top|--bottom|--before <ID>
|
|
33
|
-
<binary> --actor agent --project <slug> queue <ID>… | queue --list # your session's work queue (limit: setting queueMax, default 10)
|
|
34
|
-
<binary> --actor agent --project <slug> unqueue [<ID>…] # no ids clears it
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
There is **no queue and no runner** — the session works tasks itself
|
|
38
|
+
(`/unipi:kanboard-autowork` just keeps offering the next ready task).
|
|
39
|
+
`queue`, `unqueue`, `claim-next`, `set-run` and `edit --strategy/--plan` are
|
|
40
|
+
removed and refused.
|
|
41
|
+
|
|
37
42
|
`--json` gives machine-readable output for every subcommand (task objects carry
|
|
38
43
|
`ready`, `waitingFor`, `depsStatus` and `staleness`).
|
|
39
44
|
|
|
45
|
+
`list`/`search` show a one-line body excerpt; read a task's full description with
|
|
46
|
+
`show <ID>` before editing or starting it.
|
|
47
|
+
|
|
40
48
|
`settings show` reads the pi runtime and effective limits; `settings set`
|
|
41
49
|
and `rotate-token` are user-only — the agent is refused.
|
|
42
50
|
|
|
@@ -45,10 +53,11 @@ and `rotate-token` are user-only — the agent is refused.
|
|
|
45
53
|
| Move | Who |
|
|
46
54
|
|---|---|
|
|
47
55
|
| backlog ↔ todo | user, agent |
|
|
48
|
-
| todo → in progress |
|
|
56
|
+
| todo → in progress | the agent with `start <ID>` (claims it for its session; costs a `-do` slot) |
|
|
49
57
|
| edit a task | agent — only tasks it created, and only in backlog/todo |
|
|
50
|
-
| in progress → in review |
|
|
58
|
+
| in progress → in review | the agent with `finish <ID> --comment` — only a task its own session `start`ed |
|
|
51
59
|
| in progress → blocked | agent, system — **comment required** (what you need) |
|
|
60
|
+
| in progress → todo | user or system `release` — comment required (the session died or gave the task up) |
|
|
52
61
|
| blocked → todo | user only — comment required (the answer) |
|
|
53
62
|
| in review → done | user only |
|
|
54
63
|
| in review → todo/backlog | user only — comment required (rework note) |
|
|
@@ -65,26 +74,45 @@ lists them — read the files there directly if you need one.
|
|
|
65
74
|
|
|
66
75
|
1. **Never pass `--actor user`.** That is an honour system: pretending to be the
|
|
67
76
|
user to cancel a task or mark it done breaks the board's whole contract.
|
|
68
|
-
2. **
|
|
69
|
-
|
|
77
|
+
2. **Work the tasks yourself, in this session** (the user says "do UNI-5"):
|
|
78
|
+
**`start` a task before working on it, `finish` it with a summary when done**
|
|
79
|
+
— `start <ID>` before your first edit, `finish <ID> --comment "<what you
|
|
80
|
+
did>"` before your turn ends (or `move <ID> blocked --comment "<what you
|
|
81
|
+
need>"` if you cannot). `finish` and own-claim `blocked`/`note` are always
|
|
82
|
+
free; **`start` costs one of your `-do` task slots** — count the tasks a
|
|
83
|
+
request needs BEFORE starting anything, and if that is more than your
|
|
84
|
+
remaining slots, start none of them: say what you can do now and ask whether
|
|
85
|
+
to raise `kanboard.doTasks` or work in batches. In Review means "the agent
|
|
86
|
+
did the work, a human reviews" — never leave a task you started In Progress
|
|
87
|
+
(the board monitor will nudge you back to it). **Never move a task to
|
|
88
|
+
`done`.**
|
|
70
89
|
3. **Never cancel.** If a task should be dropped, block it with
|
|
71
90
|
`move <ID> blocked --comment "suggest cancel: <why>"` and let the user decide.
|
|
72
|
-
4. **
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
4. **Follow the blocking rule in your task prompt**: by default work
|
|
92
|
+
autonomously and record assumptions with `note <ID> "assumed: <what/why>"`;
|
|
93
|
+
block (with a comment saying exactly what you need) only when you truly
|
|
94
|
+
cannot continue — `move <ID> blocked --comment "<what you need>"`.
|
|
75
95
|
5. **Work only on the task you were given.** Follow-up work goes to the board as
|
|
76
96
|
a new task in Backlog (`add "<title>"`), optionally `link <new> --after <ID>`.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
why. You may
|
|
97
|
+
At most `maxSessions` (default 2) sessions may hold tasks in a project at
|
|
98
|
+
once — if the board refuses a `start`, that is why. You may
|
|
80
99
|
block only the task your own session is running (`move <ID> blocked` checks
|
|
81
100
|
`--session`/`UNIPI_KANBOARD_SESSION` against the claim).
|
|
82
|
-
6. **
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
6. **Sidekicks and subagents can read the board but never write it** — every
|
|
102
|
+
write is refused with "board writes are the lead's job". Brief them with the
|
|
103
|
+
task, then update the board yourself from their reports. (The lead session
|
|
104
|
+
works tasks itself; `/unipi:kanboard-autowork` just keeps offering the next
|
|
105
|
+
ready task in the same session — there is no separate worker.)
|
|
85
106
|
7. Use `note <ID> "<text>"` for progress worth remembering (decisions, what you
|
|
86
107
|
verified, what you left undone) — it is the activity log the next reader sees.
|
|
87
|
-
8.
|
|
108
|
+
8. **Write the `finish` / `blocked` comment as the report the user reads.** It
|
|
109
|
+
is shown as the task's status banner and opens in a reader, rendered as
|
|
110
|
+
markdown. Lead with one plain sentence (what you did, or what stops you),
|
|
111
|
+
then short paragraphs or `-` bullets separated by blank lines; put questions
|
|
112
|
+
for the user as a numbered list under `**Need from you:**`. Name files and
|
|
113
|
+
commands in backticks. Do not repeat a note you already wrote — the report
|
|
114
|
+
replaces it. Attach evidence with `--attach <file>`.
|
|
115
|
+
9. Dependencies form a DAG: a task is ready only when every dep reached the chain
|
|
88
116
|
gate (`in_review` by default, `done` when configured). Cancelled deps block
|
|
89
117
|
forever, so unlink or re-plan instead of waiting. `chain <ID>` shows the
|
|
90
118
|
whole line, `next` shows what would be picked and why others wait.
|
|
@@ -92,15 +120,21 @@ lists them — read the files there directly if you need one.
|
|
|
92
120
|
## Terminal-only execution
|
|
93
121
|
|
|
94
122
|
The web UI can create, edit, reorder, link and move tasks, but it **never runs a
|
|
95
|
-
task** — there is no run button. Work starts only from a terminal
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
task** — there is no run button. Work starts only from a terminal: the session
|
|
124
|
+
itself, driven by `/unipi:kanboard-do` (a task-slot + write budget for one
|
|
125
|
+
request) or `/unipi:kanboard-autowork` (the monitor keeps offering the next
|
|
126
|
+
ready task in this session — no separate worker exists). (The Done column's
|
|
127
|
+
"Summarize & archive" does call the agent command set in the board's Settings,
|
|
128
|
+
but only to write a summary.)
|
|
98
129
|
|
|
99
130
|
## Attachments
|
|
100
131
|
|
|
101
132
|
Users attach screenshots, logs and documents in the board UI; they appear in the
|
|
102
133
|
text as markdown with `att:<ID>/<name>` references, and `show <ID> --json` lists
|
|
103
134
|
them under `attachments` with an absolute `path` — read the file from there
|
|
104
|
-
(use your image-reading tool for images). To hand back evidence
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
(use your image-reading tool for images). To hand back evidence (a screenshot,
|
|
136
|
+
a log, a report), pass `--attach <file>` (repeatable) to `finish`, `move … blocked`
|
|
137
|
+
or `note`, or use `attach <ID> <file> --note "…"`: the file is stored beside the
|
|
138
|
+
board and the comment embeds it, so the user sees the image or file inline. A
|
|
139
|
+
file's path written in the comment is replaced by the embed; otherwise the
|
|
140
|
+
embed is appended. Attaching to a task you started is free.
|
package/src/badges.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/kanboard — transcript badges (user-only, never LLM context).
|
|
3
|
+
*
|
|
4
|
+
* A successful lead `start` / `finish` / `move <ID> blocked` appends a custom
|
|
5
|
+
* transcript entry rendered as `▣ UNI-30 started`, `✓ UNI-30 → In Review`,
|
|
6
|
+
* `⊘ UNI-30 blocked: <comment>`. Custom entries persist for the user but are
|
|
7
|
+
* outside the LLM context (same mechanism as core's progress bars).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { Text, truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
12
|
+
|
|
13
|
+
import { kanboardInvocations } from "./guard.js";
|
|
14
|
+
|
|
15
|
+
export const BADGE_ENTRY = "unipi:kanboard-badge";
|
|
16
|
+
|
|
17
|
+
export type BadgeKind = "started" | "finished" | "blocked";
|
|
18
|
+
|
|
19
|
+
export interface BadgeData {
|
|
20
|
+
kind: BadgeKind;
|
|
21
|
+
id: string;
|
|
22
|
+
/** Short human text (a blocked task carries what is needed). */
|
|
23
|
+
text?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The badge kind a kanboard CLI call produces, or null. */
|
|
27
|
+
export function badgeKindFor(sub: string, args: readonly string[]): BadgeKind | null {
|
|
28
|
+
if (sub === "start") return "started";
|
|
29
|
+
if (sub === "finish") return "finished";
|
|
30
|
+
if (sub === "move" && args.includes("blocked")) return "blocked";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Extract a badge from a successful kanboard bash call, or null. */
|
|
35
|
+
export function badgeFromToolCall(toolName: string, input: Record<string, unknown> | undefined, isError: boolean): BadgeData | null {
|
|
36
|
+
if (isError || (toolName !== "bash" && toolName !== "powershell")) return null;
|
|
37
|
+
for (const invocation of kanboardInvocations(String(input?.command ?? ""))) {
|
|
38
|
+
const kind = badgeKindFor(invocation.sub, invocation.args);
|
|
39
|
+
if (!kind) continue;
|
|
40
|
+
const id = invocation.args.find((arg) => !arg.startsWith("-"));
|
|
41
|
+
if (!id) continue;
|
|
42
|
+
let text: string | undefined;
|
|
43
|
+
if (kind === "blocked") {
|
|
44
|
+
const flagIndex = invocation.args.indexOf("--comment");
|
|
45
|
+
text = flagIndex >= 0 ? (invocation.args[flagIndex + 1] ?? "").replace(/^["']|["']$/g, "") : undefined;
|
|
46
|
+
}
|
|
47
|
+
return { kind, id, ...(text !== undefined && text.length > 0 ? { text } : {}) };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function badgeText(data: BadgeData): string {
|
|
53
|
+
if (data.kind === "started") return `▣ ${data.id} started`;
|
|
54
|
+
if (data.kind === "finished") return `✓ ${data.id} → In Review`;
|
|
55
|
+
const what = data.text ? `: ${data.text.slice(0, 80)}` : "";
|
|
56
|
+
return `⊘ ${data.id} blocked${what}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function badgeColor(kind: BadgeKind): ThemeColor {
|
|
60
|
+
if (kind === "started") return "accent";
|
|
61
|
+
if (kind === "finished") return "success";
|
|
62
|
+
return "warning";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Register the entry renderer (once per extension load; lead only). */
|
|
66
|
+
export function registerBadgeRenderer(pi: ExtensionAPI): void {
|
|
67
|
+
try {
|
|
68
|
+
pi.registerEntryRenderer<BadgeData>(BADGE_ENTRY, (entry, _options, theme) => {
|
|
69
|
+
const data = entry.data;
|
|
70
|
+
if (!data || typeof data.id !== "string") return undefined;
|
|
71
|
+
const t = theme as unknown as {
|
|
72
|
+
fg?: (color: ThemeColor, text: string) => string;
|
|
73
|
+
bold?: (text: string) => string;
|
|
74
|
+
};
|
|
75
|
+
const line = t.fg?.(badgeColor(data.kind), badgeText(data)) ?? badgeText(data);
|
|
76
|
+
return new Text(t.bold ? t.bold(line) : line, 0, 0);
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
// Renderer registration is UI-dependent; skip where unavailable.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Emit a badge for a successful tool result (lead tool_result hook body). */
|
|
84
|
+
export function maybeBadgeToolResult(pi: ExtensionAPI, toolName: string, input: Record<string, unknown> | undefined, isError: boolean, width = 80): Component | null {
|
|
85
|
+
const badge = badgeFromToolCall(toolName, input, isError);
|
|
86
|
+
if (!badge) return null;
|
|
87
|
+
try {
|
|
88
|
+
pi.appendEntry<BadgeData>(BADGE_ENTRY, badge);
|
|
89
|
+
return new Text(truncateToWidth(badgeText(badge), width), 0, 0);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/bin.ts
CHANGED
|
@@ -80,10 +80,12 @@ export function resolveBinary(env: NodeJS.ProcessEnv = process.env, from?: strin
|
|
|
80
80
|
if (explicit) {
|
|
81
81
|
return isRunnable(explicit) ? { path: explicit, source: "env" } : null;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// In a source checkout the dev build wins — the platform package can ship a
|
|
84
|
+
// stale prebuilt binary (seen on coffee: alpha.0 shadowed the debug build).
|
|
85
85
|
const dev = devBuildPath();
|
|
86
86
|
if (dev) return { path: dev, source: "dev-build" };
|
|
87
|
+
const packaged = platformPackagePath(from);
|
|
88
|
+
if (packaged && isRunnable(packaged)) return { path: packaged, source: "platform-package" };
|
|
87
89
|
return null;
|
|
88
90
|
}
|
|
89
91
|
|