@gavin-hu/my-pi-agent 0.21.0 → 0.22.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 +46 -31
- package/README.zh-CN.md +40 -26
- package/assets/screens/board.png +0 -0
- package/assets/screens/chrome.png +0 -0
- package/assets/screens/file-browse.png +0 -0
- package/assets/screens/plan-review.png +0 -0
- package/assets/screens/plan.png +0 -0
- package/docs/extensions/README.md +22 -24
- package/docs/extensions/board.md +361 -147
- package/docs/extensions/bytefly.md +152 -111
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +30 -31
- package/docs/extensions/file-open.md +9 -11
- package/docs/extensions/file-read.md +12 -14
- package/docs/extensions/goal.md +15 -17
- package/docs/extensions/job.md +54 -42
- package/docs/extensions/memory.md +17 -19
- package/docs/extensions/plan.md +166 -126
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +34 -18
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +72 -33
- package/docs/extensions/todo.md +18 -18
- package/docs/extensions/watch.md +31 -31
- package/docs/extensions/web-access.md +2 -4
- package/docs/extensions/wechat.md +34 -35
- package/docs/extensions/worktree.md +56 -50
- package/extensions/board/commands.ts +56 -15
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +5 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +4 -40
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +13 -11
- package/extensions/board/result.ts +30 -16
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +42 -10
- package/extensions/board/snapshot.ts +2 -1
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +131 -44
- package/extensions/board/types.ts +36 -32
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +33 -15
- package/extensions/board/view/dialog.ts +169 -6
- package/extensions/board/view/empty.ts +47 -0
- package/extensions/board/view/keys.ts +13 -2
- package/extensions/board/view/live.ts +89 -21
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +23 -9
- package/extensions/board/view/render.ts +208 -271
- package/extensions/board/view/styles.ts +123 -31
- package/extensions/board/view/team.ts +297 -76
- package/extensions/bytefly/block/fallback.ts +1 -1
- package/extensions/bytefly/doctor/runtime.ts +5 -2
- package/extensions/bytefly/footer/collect/label.ts +60 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
- package/extensions/bytefly/footer/config.ts +4 -2
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/lines.ts +7 -4
- package/extensions/bytefly/footer/render/segments.ts +37 -10
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/config.ts +1 -1
- package/extensions/bytefly/index.ts +32 -6
- package/extensions/bytefly/mode/editor.ts +115 -0
- package/extensions/bytefly/panel/commands.ts +210 -120
- package/extensions/bytefly/panel/format.ts +4 -4
- package/extensions/bytefly/panel/settings.ts +73 -6
- package/extensions/bytefly/panel/tui/keys.ts +2 -6
- package/extensions/bytefly/update/cache.ts +1 -1
- package/extensions/bytefly/update/register.ts +4 -4
- package/extensions/bytefly/working/config.ts +5 -7
- package/extensions/bytefly/working/format.ts +10 -13
- package/extensions/bytefly/working/register.ts +22 -27
- package/extensions/check/commands.ts +3 -2
- package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
- package/extensions/context/commands.ts +2 -2
- package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
- package/extensions/file-browse/index.ts +67 -36
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/job/child.ts +527 -0
- package/extensions/job/clock.ts +142 -0
- package/extensions/job/commands.ts +4 -2
- package/extensions/job/config.ts +8 -1
- package/extensions/job/format.ts +22 -1
- package/extensions/job/log-writer.ts +13 -2
- package/extensions/job/registry.ts +63 -7
- package/extensions/job/render.ts +34 -0
- package/extensions/job/runtime.ts +296 -655
- package/extensions/job/session.ts +132 -19
- package/extensions/job/store.ts +7 -7
- package/extensions/job/tools.ts +3 -3
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
- package/extensions/job/waiters.ts +44 -1
- package/extensions/memory/commands.ts +4 -2
- package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
- package/extensions/plan/commands.ts +12 -9
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +53 -52
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plans.ts +5 -54
- package/extensions/plan/policy.ts +8 -10
- package/extensions/plan/review-lazy.ts +85 -0
- package/extensions/plan/review.ts +2 -2
- package/extensions/plan/runtime.ts +49 -39
- package/extensions/plan/sections.ts +1 -1
- package/extensions/plan/steps.ts +3 -3
- package/extensions/plan/todos.ts +1 -1
- package/extensions/plan/tools/cancel.ts +27 -62
- package/extensions/plan/tools/enter.ts +55 -81
- package/extensions/plan/tools/exit.ts +221 -238
- package/extensions/plan/tools/refusal.ts +18 -0
- package/extensions/plan/tools/steps.ts +75 -92
- package/extensions/plan/tools/write.ts +77 -120
- package/extensions/plan/tools.ts +188 -30
- package/extensions/plan/tui.ts +5 -5
- package/extensions/plan/types.ts +48 -5
- package/extensions/plan/web-client.ts +3 -0
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +9 -67
- package/extensions/plan/web-payload.ts +3 -3
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web.ts +1 -12
- package/extensions/question/tools.ts +6 -2
- package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
- package/extensions/rewind/commands/commands.ts +6 -3
- package/extensions/rewind/state/runtime.ts +1 -1
- package/extensions/rewind/{view/status.ts → status.ts} +4 -4
- package/extensions/rewind/view/review-tui.ts +4 -4
- package/extensions/serve/config.ts +10 -8
- package/extensions/serve/index.ts +17 -3
- package/extensions/serve/registry.ts +26 -17
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +2 -1
- package/extensions/session-bus/mail-watch.ts +26 -17
- package/extensions/session-bus/protocol.ts +15 -3
- package/extensions/session-bus/resolve.ts +5 -7
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/types.ts +19 -0
- package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
- package/extensions/subagent/agents.ts +1 -1
- package/extensions/subagent/commands.ts +4 -2
- package/extensions/subagent/format.ts +15 -15
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +3 -3
- package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
- package/extensions/team/commands.ts +24 -7
- package/extensions/team/dispatch.ts +18 -4
- package/extensions/team/format.ts +17 -2
- package/extensions/team/index.ts +9 -8
- package/extensions/team/schema.ts +6 -5
- package/extensions/team/tools.ts +107 -45
- package/extensions/team/types.ts +3 -2
- package/extensions/todo/commands.ts +4 -2
- package/extensions/todo/format.ts +11 -0
- package/extensions/todo/render.ts +57 -0
- package/extensions/todo/runtime.ts +1 -1
- package/extensions/todo/tools.ts +1 -1
- package/extensions/todo/view/todo-screen.ts +122 -0
- package/extensions/todo/widget.ts +47 -0
- package/extensions/watch/commands.ts +4 -2
- package/extensions/watch/format.ts +36 -7
- package/extensions/watch/render.ts +57 -0
- package/extensions/watch/runtime.ts +27 -22
- package/extensions/watch/tools.ts +4 -4
- package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
- package/extensions/web-access/fetch/tool.ts +12 -4
- package/extensions/wechat/commands/commands.ts +18 -14
- package/extensions/wechat/index.ts +72 -37
- package/extensions/wechat/state/credentials.ts +1 -1
- package/extensions/wechat/state/state.ts +1 -1
- package/extensions/wechat/text/status.ts +112 -0
- package/extensions/wechat/types.ts +2 -2
- package/extensions/wechat/view/status-screen.ts +4 -103
- package/extensions/worktree/commands.ts +11 -9
- package/extensions/worktree/enter.ts +2 -2
- package/extensions/worktree/exit.ts +2 -2
- package/extensions/worktree/index.ts +5 -5
- package/extensions/worktree/screen.ts +3 -3
- package/extensions/worktree/status.ts +2 -2
- package/extensions/worktree/tools.ts +113 -116
- package/lib/README.md +27 -13
- package/lib/board/actions.ts +111 -0
- package/lib/board/artifact.ts +149 -0
- package/lib/board/card.ts +163 -52
- package/lib/board/config.ts +58 -0
- package/lib/board/contract.ts +113 -0
- package/lib/board/join.ts +38 -0
- package/lib/board/lanes.ts +182 -0
- package/lib/board/lead.ts +130 -0
- package/lib/board/limits.ts +78 -0
- package/lib/board/links.ts +31 -10
- package/lib/board/page-pointer.ts +1 -1
- package/lib/board/read.ts +83 -0
- package/lib/board/reason.ts +102 -0
- package/lib/board/round-text.ts +68 -6
- package/lib/board/round.ts +31 -15
- package/lib/board/rules.ts +151 -307
- package/lib/board/seat.ts +35 -0
- package/lib/board/session.ts +136 -54
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +87 -30
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +70 -58
- package/{extensions/watch → lib}/child.ts +40 -21
- package/lib/env.ts +65 -7
- package/lib/format.ts +13 -0
- package/lib/http/index.ts +14 -12
- package/lib/http/port.ts +15 -10
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +39 -10
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +8 -8
- package/lib/session-label.ts +111 -5
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +5 -4
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +23 -8
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +9 -7
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +3 -3
- package/lib/web/README.md +21 -0
- package/lib/web/components.ts +5 -1
- package/lib/web/index.ts +1 -0
- package/lib/web/markdown.ts +83 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +9 -10
- package/package.json +6 -1
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/seat.ts +0 -28
- package/extensions/todo/tui.ts +0 -212
- package/lib/tui/index.ts +0 -34
- package/skills/project-docs/SKILL.md +0 -133
- package/skills/project-docs/references/audit.md +0 -131
- package/skills/project-docs/references/changelog.md +0 -105
- package/skills/project-docs/references/discovery.md +0 -127
- package/skills/project-docs/references/readme.md +0 -80
- package/skills/project-docs/references/release.md +0 -84
- package/skills/project-docs/references/roadmap.md +0 -93
- package/skills/project-docs/scripts/state.ts +0 -316
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The board's page, loaded when `/board` first needs it.
|
|
3
|
+
*
|
|
4
|
+
* The page module owns the HTTP surface, the snapshots, and the view; none of it
|
|
5
|
+
* is needed to register the command or the tool. The factory therefore returns a
|
|
6
|
+
* proxy over `lib/lazy`: every command path loads the real page once, and
|
|
7
|
+
* `closeAll` on shutdown only closes a page that was already opened, so a
|
|
8
|
+
* session that never touched the board imports none of it.
|
|
9
|
+
*
|
|
10
|
+
* The loader is a seam: a test drives the proxy without a socket.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BoardSession } from "../../lib/board/session.ts";
|
|
14
|
+
import { lazy } from "../../lib/lazy.ts";
|
|
15
|
+
import type { BoardPage, BoardPageDeps } from "./page.ts";
|
|
16
|
+
|
|
17
|
+
/** The page module, as the proxy loads it. */
|
|
18
|
+
export type BoardPageModule = typeof import("./page.ts");
|
|
19
|
+
|
|
20
|
+
/** How the proxy reaches the page module; production uses the dynamic import. */
|
|
21
|
+
export type BoardPageLoader = () => Promise<BoardPageModule>;
|
|
22
|
+
|
|
23
|
+
export function createLazyBoardPage(
|
|
24
|
+
session: BoardSession,
|
|
25
|
+
deps: BoardPageDeps,
|
|
26
|
+
load: BoardPageLoader = () => import("./page.ts"),
|
|
27
|
+
): BoardPage {
|
|
28
|
+
const page = lazy(async () => (await load()).createBoardPage(session, deps));
|
|
29
|
+
return {
|
|
30
|
+
start: async (ctx, view) => (await page.get()).start(ctx, view),
|
|
31
|
+
stop: async () => (await page.get()).stop(),
|
|
32
|
+
status: async () => (await page.get()).status(),
|
|
33
|
+
dropStale: async (ctx) => (await page.get()).dropStale(ctx),
|
|
34
|
+
// Shutdown must not start a load: a page that never opened has nothing to close.
|
|
35
|
+
closeAll: async () => {
|
|
36
|
+
const opened = page.peek();
|
|
37
|
+
if (opened !== undefined) await opened.closeAll();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
package/extensions/board/page.ts
CHANGED
|
@@ -33,7 +33,8 @@ import { mintToken, type MountSource } from "../../lib/http/index.ts";
|
|
|
33
33
|
import { messageOf } from "../../lib/errors.ts";
|
|
34
34
|
import type { IdleReason } from "../../lib/http/sink.ts";
|
|
35
35
|
import { join } from "node:path";
|
|
36
|
-
import {
|
|
36
|
+
import type { Actor } from "../../lib/board/rules.ts";
|
|
37
|
+
import { pageActor } from "../../lib/board/seat.ts";
|
|
37
38
|
import { viewOf } from "./snapshot.ts";
|
|
38
39
|
import { type BoardServer, createBoardServer } from "./transport/server.ts";
|
|
39
40
|
import { BOARD_CLIENT_JS } from "./view/client.ts";
|
|
@@ -120,13 +121,9 @@ export interface BoardPage {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): BoardPage {
|
|
123
|
-
let running: { server: BoardServer; store: BoardStore; token: string } | undefined;
|
|
124
|
+
let running: { server: BoardServer; store: BoardStore; token: string; seat: Actor } | undefined;
|
|
124
125
|
/** The page another session serves, when this session opened that one instead. */
|
|
125
126
|
let attachedTo: PagePointer | undefined;
|
|
126
|
-
// The page is a seat, not a session: one name and one id for its whole life, so a card
|
|
127
|
-
// a browser claimed is still the seat's after the session that served it ends.
|
|
128
|
-
const seat = pageActor();
|
|
129
|
-
|
|
130
127
|
const storeOf = async (ctx: ExtensionContext): Promise<BoardStore> => runtime.store(ctx.cwd);
|
|
131
128
|
|
|
132
129
|
/**
|
|
@@ -174,6 +171,11 @@ export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): Boa
|
|
|
174
171
|
} catch (error) {
|
|
175
172
|
return failed(error);
|
|
176
173
|
}
|
|
174
|
+
// The page is a seat, not a session: one name and one id for its whole life, so a card a
|
|
175
|
+
// browser claimed is still the seat's after the session that served it ends. That name is
|
|
176
|
+
// the board's lead, so serving the page is what makes a session the lead — a repository
|
|
177
|
+
// that names one gets a page served under that name, and the gate accepts it.
|
|
178
|
+
const seat = pageActor(store.config().lead);
|
|
177
179
|
const open = (url: string) => (deps.open ?? openInBrowser)(view === "board" ? url : `${url}&view=team`);
|
|
178
180
|
/** Watch the page another session serves: its note is the URL, its seat the name. */
|
|
179
181
|
const attach = (pointer: PagePointer): PageStart => {
|
|
@@ -218,7 +220,7 @@ export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): Boa
|
|
|
218
220
|
idleMs: deps.idleMs ?? IDLE_STOP_MS,
|
|
219
221
|
firstIdleMs: deps.firstIdleMs ?? IDLE_FIRST_MS,
|
|
220
222
|
onIdle: (reason) => void stopIdle(ctx, reason),
|
|
221
|
-
page: async (view) => renderBoardPage({ view, base: BOARD_BASE }),
|
|
223
|
+
page: async (view) => renderBoardPage({ view, base: BOARD_BASE, repo: store.name(), root: store.root() }),
|
|
222
224
|
script: BOARD_CLIENT_JS,
|
|
223
225
|
view: () =>
|
|
224
226
|
viewOf({
|
|
@@ -269,7 +271,7 @@ export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): Boa
|
|
|
269
271
|
ctx.ui.notify(`board: the page did not start (${reason})`, "error");
|
|
270
272
|
return { kind: "failed", reason };
|
|
271
273
|
}
|
|
272
|
-
running = { server, store, token };
|
|
274
|
+
running = { server, store, token, seat };
|
|
273
275
|
open(server.url);
|
|
274
276
|
return describe(running);
|
|
275
277
|
} catch (error) {
|
|
@@ -288,9 +290,9 @@ export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): Boa
|
|
|
288
290
|
);
|
|
289
291
|
};
|
|
290
292
|
|
|
291
|
-
const describe = (state: { server: BoardServer; store: BoardStore }): PageStart => ({
|
|
293
|
+
const describe = (state: { server: BoardServer; store: BoardStore; seat: Actor }): PageStart => ({
|
|
292
294
|
kind: "served",
|
|
293
|
-
seat: seat.label,
|
|
295
|
+
seat: state.seat.label,
|
|
294
296
|
port: state.server.port,
|
|
295
297
|
url: state.server.url,
|
|
296
298
|
repo: state.store.name(),
|
|
@@ -309,7 +311,7 @@ export function createBoardPage(runtime: BoardSession, deps: BoardPageDeps): Boa
|
|
|
309
311
|
if (running !== undefined) {
|
|
310
312
|
return {
|
|
311
313
|
running: true,
|
|
312
|
-
seat: seat.label,
|
|
314
|
+
seat: running.seat.label,
|
|
313
315
|
port: running.server.port,
|
|
314
316
|
url: running.server.url,
|
|
315
317
|
repo: running.store.name(),
|
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { type Card, cardRef } from "../../lib/board/card.ts";
|
|
12
|
+
import { LANE_RULES } from "../../lib/board/lanes.ts";
|
|
12
13
|
import { sanitizeLine } from "../../lib/format.ts";
|
|
13
14
|
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
14
|
-
import type { TailResultInput, TailSection } from "../../lib/tui/tool.ts";
|
|
15
|
+
import type { TailResultInput, TailSection, TailRowLine } from "../../lib/tui/tool.ts";
|
|
15
16
|
import { plural } from "../../lib/board/round-text.ts";
|
|
16
17
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
17
|
-
import {
|
|
18
|
+
import { boardRowLineFor } from "./rows.ts";
|
|
19
|
+
import { TONE_ROLE } from "./tone.ts";
|
|
18
20
|
import type { BoardDetails } from "./types.ts";
|
|
19
21
|
|
|
20
|
-
/** One section of the expanded block. */
|
|
21
|
-
function section(label: string | undefined, cards: readonly Card[]): TailSection<Card> {
|
|
22
|
-
return { label, rows: cards, more: 0, rowLine
|
|
22
|
+
/** One section of the expanded block, drawn as the session that asked for it. */
|
|
23
|
+
function section(label: string | undefined, cards: readonly Card[], rowLine: TailRowLine<Card>): TailSection<Card> {
|
|
24
|
+
return { label, rows: cards, more: 0, rowLine };
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -32,19 +34,25 @@ function section(label: string | undefined, cards: readonly Card[]): TailSection
|
|
|
32
34
|
*/
|
|
33
35
|
export function resultInput(details: BoardDetails): TailResultInput<Card> | undefined {
|
|
34
36
|
if (details.action !== "list") return undefined;
|
|
35
|
-
|
|
37
|
+
// Only a read narrowed to this session's own cards marks them, and it marks them with
|
|
38
|
+
// the rule the filter used: the rows and the filter agree about what "mine" means.
|
|
39
|
+
const rowLine = boardRowLineFor(details.mine === true ? details.me : undefined);
|
|
40
|
+
if (details.card) return { kind: "list", rows: [details.card], more: 0, rowLine };
|
|
36
41
|
const round = details.round;
|
|
37
42
|
if (!round) {
|
|
38
43
|
const cards = details.cards ?? [];
|
|
39
|
-
return cards.length === 0 ? undefined : { kind: "list", rows: cards, more: 0, rowLine
|
|
44
|
+
return cards.length === 0 ? undefined : { kind: "list", rows: cards, more: 0, rowLine };
|
|
40
45
|
}
|
|
41
46
|
const sections: TailSection<Card>[] = [];
|
|
42
|
-
for (const group of round.owners) sections.push(section(group.label, group.cards));
|
|
43
|
-
if (round.unassigned.length > 0) sections.push(section("Unassigned", round.unassigned));
|
|
44
|
-
if (round.unplanned.length > 0) sections.push(section("Unplanned", round.unplanned));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (round.
|
|
47
|
+
for (const group of round.owners) sections.push(section(group.label, group.cards, rowLine));
|
|
48
|
+
if (round.unassigned.length > 0) sections.push(section("Unassigned", round.unassigned, rowLine));
|
|
49
|
+
if (round.unplanned.length > 0) sections.push(section("Unplanned", round.unplanned, rowLine));
|
|
50
|
+
// The lead's queue: a contract nobody has signed is a card the flow stops on, so it is
|
|
51
|
+
// drawn in the order `formatRound` prints it rather than left to the text above.
|
|
52
|
+
if (round.unapproved.length > 0) sections.push(section("Awaiting approval", round.unapproved, rowLine));
|
|
53
|
+
if (round.blocked.length > 0) sections.push(section("Blocked", round.blocked, rowLine));
|
|
54
|
+
if (round.idle.length > 0) sections.push(section("Idle", round.idle, rowLine));
|
|
55
|
+
if (round.done.length > 0) sections.push(section("Done", round.done, rowLine));
|
|
48
56
|
return sections.length === 0 ? undefined : { kind: "sections", sections };
|
|
49
57
|
}
|
|
50
58
|
|
|
@@ -77,9 +85,15 @@ export function collapsedLine(theme: Theme, details: BoardDetails): string {
|
|
|
77
85
|
return `${paint(theme, "label", plural(count, "card"))} ${SEPARATORS.item} ${plural(owners, "owner")} ${SEPARATORS.item} ${me}`;
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* `#4 execute · Billing webhook accepts orders`, clipped by the caller's row helper.
|
|
90
|
+
*
|
|
91
|
+
* The lane wears the colour the page gives it — `LANE_RULES`' role, through the tone
|
|
92
|
+
* bridge — rather than one accent for all five.
|
|
93
|
+
*/
|
|
81
94
|
function cardLine(theme: Theme, card: Card): string {
|
|
82
95
|
const id = paint(theme, "meta", cardRef(card.id));
|
|
83
|
-
const lane = paint(theme,
|
|
84
|
-
|
|
96
|
+
const lane = paint(theme, TONE_ROLE[LANE_RULES[card.lane].role], card.lane);
|
|
97
|
+
const title = paint(theme, "label", sanitizeLine(card.title));
|
|
98
|
+
return `${id} ${lane} ${SEPARATORS.item} ${title}`;
|
|
85
99
|
}
|
package/extensions/board/rows.ts
CHANGED
|
@@ -4,45 +4,82 @@
|
|
|
4
4
|
* Every field comes from a card file, which the model wrote and a person may have
|
|
5
5
|
* edited, so it is sanitized here at the boundary that reaches the terminal, and
|
|
6
6
|
* every row is clipped to the width it was given by the shared row helpers.
|
|
7
|
+
*
|
|
8
|
+
* The colours come from `lib/board/tone.ts` — the table the page reads too — through
|
|
9
|
+
* `TONE_ROLE`, so a lane's word in the transcript is the lane's colour on the page. Each
|
|
10
|
+
* word is painted on its own, which means the separator between two words is painted as
|
|
11
|
+
* well: a bare ` · ` would fall back to the terminal's default foreground between two
|
|
12
|
+
* coloured words.
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
15
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { type Card, cardRef, DEFAULT_PRIORITY, priorityOf } from "../../lib/board/card.ts";
|
|
16
|
+
import { type Card, cardRef, DEFAULT_PRIORITY, holds, priorityOf } from "../../lib/board/card.ts";
|
|
17
|
+
import { LANE_RULES } from "../../lib/board/lanes.ts";
|
|
18
|
+
import type { Actor } from "../../lib/board/rules.ts";
|
|
19
|
+
import { type BoardTone, REASON_TONE } from "../../lib/board/tone.ts";
|
|
11
20
|
import { sanitizeLine } from "../../lib/format.ts";
|
|
21
|
+
import { clip, splitRow } from "../../lib/tui/columns.ts";
|
|
12
22
|
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
13
|
-
import {
|
|
23
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
14
24
|
import type { TailRowLine } from "../../lib/tui/tool.ts";
|
|
25
|
+
import { TONE_ROLE } from "./tone.ts";
|
|
15
26
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
/** One fact the row states about a card, and the tone it wears. */
|
|
28
|
+
interface Fact {
|
|
29
|
+
text: string;
|
|
30
|
+
tone: BoardTone;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
34
|
+
* Who holds a card, the lane it sits in, and — for work in flight — whether it was planned
|
|
35
|
+
* first, with the priority when it is not the ordinary one.
|
|
25
36
|
*
|
|
26
37
|
* A row carries the lane because a flat listing has no section heading to say it, and
|
|
27
|
-
* "planned" because a
|
|
28
|
-
*
|
|
29
|
-
*
|
|
38
|
+
* "planned" because a card nobody planned is the one a lead looks at. The priority rides
|
|
39
|
+
* along for the same reason: inside a lane the order is the priority, and an order nobody
|
|
40
|
+
* can see reads as an accident.
|
|
30
41
|
*/
|
|
31
|
-
function
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
function factsOf(card: Card): Fact[] {
|
|
43
|
+
const facts: Fact[] = [
|
|
44
|
+
{
|
|
45
|
+
text: card.owner === undefined ? "unassigned" : sanitizeLine(card.owner.label),
|
|
46
|
+
tone: "muted",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
const priority = priorityOf(card);
|
|
50
|
+
if (priority !== DEFAULT_PRIORITY) facts.push({ text: priority, tone: "muted" });
|
|
51
|
+
facts.push({ text: card.lane, tone: LANE_RULES[card.lane].role });
|
|
52
|
+
// The pool has no plan to speak of and a finished card is history, so only a lane
|
|
53
|
+
// somebody is working in says whether the work was planned first. An unplanned card
|
|
54
|
+
// wears the tone the page's reason line gives the same fact.
|
|
55
|
+
const rule = LANE_RULES[card.lane];
|
|
56
|
+
if (!rule.ownerless && !rule.terminal) {
|
|
57
|
+
facts.push(
|
|
58
|
+
card.planPath === undefined
|
|
59
|
+
? { text: "unplanned", tone: REASON_TONE.contract }
|
|
60
|
+
: { text: "planned", tone: "muted" },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return facts;
|
|
35
64
|
}
|
|
36
65
|
|
|
37
|
-
/** One card: the id and title, with its
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
/** One card: the id and title, with its facts trailing. */
|
|
67
|
+
function cardRowText(card: Card, width: number, theme: Theme, self: boolean): string {
|
|
68
|
+
const head = paint(theme, self ? "accent" : "label", `${cardRef(card.id)} ${sanitizeLine(card.title)}`);
|
|
69
|
+
const gap = paint(theme, "meta", SEPARATORS.item);
|
|
70
|
+
const trailing = factsOf(card)
|
|
71
|
+
.map((fact) => paint(theme, TONE_ROLE[fact.tone], fact.text))
|
|
72
|
+
.join(gap);
|
|
73
|
+
return splitRow(head, clip(trailing, width), width);
|
|
45
74
|
}
|
|
46
75
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The row renderer every board section uses, for the session reading it.
|
|
78
|
+
*
|
|
79
|
+
* `me` matters: a `list` narrowed to this session's own cards should look like it, so the
|
|
80
|
+
* holder's rows wear the accent the page gives the card the reader is looking at. Passing
|
|
81
|
+
* no actor draws every row the same, which is what a card produced by an action gets.
|
|
82
|
+
*/
|
|
83
|
+
export function boardRowLineFor(me?: Actor): TailRowLine<Card> {
|
|
84
|
+
return (card, width, theme) => cardRowText(card, width, theme, me !== undefined && holds(card, me));
|
|
85
|
+
}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Type } from "typebox";
|
|
11
|
-
import {
|
|
11
|
+
import { MAX_NOTE, PRIORITIES } from "../../lib/board/card.ts";
|
|
12
|
+
import { listOf } from "../../lib/format.ts";
|
|
13
|
+
import { BLOCKERS } from "../../lib/board/card.ts";
|
|
14
|
+
import { LANES, movableLanes } from "../../lib/board/lanes.ts";
|
|
12
15
|
import { BOARD_ACTIONS } from "./types.ts";
|
|
13
16
|
|
|
14
17
|
export const BoardParams = Type.Object({
|
|
@@ -24,15 +27,18 @@ export const BoardParams = Type.Object({
|
|
|
24
27
|
card: Type.Optional(
|
|
25
28
|
Type.String({
|
|
26
29
|
description:
|
|
27
|
-
'The card id for one card, e.g. "4" or "#4". `list` reads that card; every other action but `add` acts on it.',
|
|
30
|
+
'The card id for one card, e.g. "4" or "#4". `list` reads that card, and refuses `lane`, `query`, `mine`, and `round` with it; every other action but `add` acts on it.',
|
|
28
31
|
}),
|
|
29
32
|
),
|
|
30
|
-
|
|
33
|
+
lane: Type.Optional(
|
|
31
34
|
Type.Union(
|
|
32
|
-
|
|
35
|
+
LANES.map((lane) => Type.Literal(lane)),
|
|
33
36
|
{
|
|
37
|
+
// The lanes a move may name come from the transition table, so a route added
|
|
38
|
+
// there is offered here without a second list to forget.
|
|
34
39
|
description:
|
|
35
|
-
"`list`: show only this lane, and not skip the `done` cards.
|
|
40
|
+
"`list`: show only this lane, and not skip the `done` cards. " +
|
|
41
|
+
`\`move\`: the lane to move to — ${listOf(movableLanes())}; \`backlog\` is \`release\`'s, not a move's, and a card may move only where a transition exists from its own lane.`,
|
|
36
42
|
},
|
|
37
43
|
),
|
|
38
44
|
),
|
|
@@ -42,6 +48,15 @@ export const BoardParams = Type.Object({
|
|
|
42
48
|
"`move` to `blocked`: what the card waits on — the decision a person has to make, one line, at most 200 characters.",
|
|
43
49
|
}),
|
|
44
50
|
),
|
|
51
|
+
blocker: Type.Optional(
|
|
52
|
+
Type.Union(
|
|
53
|
+
BLOCKERS.map((blocker) => Type.Literal(blocker)),
|
|
54
|
+
{
|
|
55
|
+
description:
|
|
56
|
+
"`move` to `blocked`, required: why it is blocked — the errand, not the route. A blocked card resumes in `plan`, or in `execute` once its contract is approved.",
|
|
57
|
+
},
|
|
58
|
+
),
|
|
59
|
+
),
|
|
45
60
|
title: Type.Optional(Type.String({ description: "`add` and `edit`: the feature's outcome, one line." })),
|
|
46
61
|
paths: Type.Optional(
|
|
47
62
|
Type.Array(Type.String(), {
|
|
@@ -55,9 +70,25 @@ export const BoardParams = Type.Object({
|
|
|
55
70
|
}),
|
|
56
71
|
),
|
|
57
72
|
text: Type.Optional(
|
|
58
|
-
Type.String({
|
|
73
|
+
Type.String({
|
|
74
|
+
description: `\`note\`: the note to append; \`approve\` and \`review\`: what the signer saw, recorded with the sign; one line, at most ${MAX_NOTE} characters.`,
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
77
|
+
path: Type.Optional(
|
|
78
|
+
Type.String({
|
|
79
|
+
description:
|
|
80
|
+
"`contract`, required: the plan file this card is executed against — the path `plan` action `write` returned, inside this repository's plan store.",
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
goal: Type.Optional(
|
|
84
|
+
Type.String({ description: "`contract`: what the feature is for, one line, at most 400 characters." }),
|
|
85
|
+
),
|
|
86
|
+
acceptance: Type.Optional(
|
|
87
|
+
Type.String({ description: "`contract`: how a reader can tell it is done, one line, at most 400 characters." }),
|
|
88
|
+
),
|
|
89
|
+
scopeOut: Type.Optional(
|
|
90
|
+
Type.String({ description: "`contract`: what this deliberately does not do, one line, at most 400 characters." }),
|
|
59
91
|
),
|
|
60
|
-
path: Type.Optional(Type.String({ description: "`plan`: the plan file this card is executing." })),
|
|
61
92
|
priority: Type.Optional(
|
|
62
93
|
Type.Union(
|
|
63
94
|
PRIORITIES.map((priority) => Type.Literal(priority)),
|
|
@@ -70,7 +101,7 @@ export const BoardParams = Type.Object({
|
|
|
70
101
|
dependsOn: Type.Optional(
|
|
71
102
|
Type.Array(Type.String(), {
|
|
72
103
|
description:
|
|
73
|
-
"`edit`: the cards this one waits for, as ids. It cannot be claimed or moved to
|
|
104
|
+
"`edit`: the cards this one waits for, as ids. It cannot be claimed, or moved to execute or done, while one of them is unfinished; `[]` removes every link.",
|
|
74
105
|
}),
|
|
75
106
|
),
|
|
76
107
|
mine: Type.Optional(
|
|
@@ -84,13 +115,14 @@ export const BoardParams = Type.Object({
|
|
|
84
115
|
),
|
|
85
116
|
round: Type.Optional(
|
|
86
117
|
Type.Boolean({
|
|
87
|
-
description:
|
|
118
|
+
description:
|
|
119
|
+
"`list`: also return the lead's rollup — owners, unassigned, unplanned, awaiting approval, blocked, idle, and done.",
|
|
88
120
|
}),
|
|
89
121
|
),
|
|
90
122
|
archived: Type.Optional(
|
|
91
123
|
Type.Boolean({
|
|
92
124
|
description:
|
|
93
|
-
"`list`: read the archive instead of the board — the finished cards `archive` set aside, which no other read opens. `query` and `mine` narrow it; `
|
|
125
|
+
"`list`: read the archive instead of the board — the finished cards `archive` set aside, which no other read opens. `query` and `mine` narrow it; `lane` and `round` do not apply to a pile that is out of the board.",
|
|
94
126
|
}),
|
|
95
127
|
),
|
|
96
128
|
});
|
|
@@ -34,10 +34,11 @@ export async function viewOf(context: SnapshotContext): Promise<BoardView> {
|
|
|
34
34
|
cards,
|
|
35
35
|
now: context.now(),
|
|
36
36
|
bytes: await context.store.bytes(),
|
|
37
|
-
skipped
|
|
37
|
+
skipped,
|
|
38
38
|
repo: context.store.name(),
|
|
39
39
|
root: context.store.root(),
|
|
40
40
|
where: context.store.where(),
|
|
41
|
+
limits: context.store.config().wip,
|
|
41
42
|
...(roster === undefined ? {} : { roster }),
|
|
42
43
|
});
|
|
43
44
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal's word for a board tone.
|
|
3
|
+
*
|
|
4
|
+
* The tone table (`lib/board/tone.ts`) says what a lane, a reason, and a decision row mean;
|
|
5
|
+
* the page wears those words as CSS properties directly. The terminal has its own vocabulary —
|
|
6
|
+
* `lib/tui/tokens.ts`'s roles are `ok`, `warn`, `bad`, `meta`, `faint`, and deliberately not
|
|
7
|
+
* `success`, `danger`, or `muted` — so this is the one place the two are bridged, in the
|
|
8
|
+
* extension, because only the transcript speaks the terminal's words.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { BoardTone } from "../../lib/board/tone.ts";
|
|
12
|
+
import type { TuiRole } from "../../lib/tui/tokens.ts";
|
|
13
|
+
|
|
14
|
+
/** The role each board tone is painted as, closest first. */
|
|
15
|
+
export const TONE_ROLE: Record<BoardTone, TuiRole> = {
|
|
16
|
+
muted: "meta",
|
|
17
|
+
dim: "faint",
|
|
18
|
+
warn: "warn",
|
|
19
|
+
accent: "accent",
|
|
20
|
+
danger: "bad",
|
|
21
|
+
success: "ok",
|
|
22
|
+
};
|