@rse/ase 2.0.1 → 2.0.3
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/dst/ase-agent-definition-core.js +111 -87
- package/dst/ase-agent-fleet.js +1 -1
- package/dst/ase-agent-runtime-claim.js +5 -3
- package/dst/ase-agent-runtime-engine.js +1 -1
- package/dst/ase-agent-runtime-phase.js +1 -1
- package/dst/ase-agent-runtime-request.js +1 -1
- package/dst/ase-agent-runtime.js +3 -5
- package/dst/ase-artifact.js +3 -3
- package/dst/ase-config-core.js +2 -16
- package/dst/ase-config-schema.js +1 -3
- package/dst/ase-config.js +4 -1
- package/dst/ase-hook.js +14 -19
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +10 -10
- package/dst/ase-mcp.js +4 -4
- package/dst/ase-service-getopt.js +2 -2
- package/dst/ase-service-otel.js +4 -4
- package/dst/ase-service-skills.js +5 -15
- package/dst/ase-service.js +87 -210
- package/dst/ase-setup-common.js +1 -2
- package/dst/ase-setup-settings.js +6 -9
- package/dst/ase-setup.js +2 -3
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-core.js +1 -1
- package/dst/ase-task-board-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +51 -45
- package/dst/ase-task-board-tui-model.js +195 -181
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +88 -79
- package/dst/ase-task-board-web-client.css +1 -1
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +4 -27
- package/dst/ase-task-board.js +1 -1
- package/dst/ase-task-format.js +1 -1
- package/dst/ase-task-store-client.js +17 -5
- package/dst/ase-task-store-core.js +5 -7
- package/dst/ase-task-store-issues.js +91 -0
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +11 -97
- package/dst/ase-task-store-plugin-github.js +5 -77
- package/dst/ase-task-store-plugin-gitlab.js +4 -83
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +16 -42
- package/dst/ase.js +5 -5
- package/dst/man/ase-agent-adhoc.1 +1 -1
- package/dst/man/ase-agent-condenser.1 +1 -1
- package/dst/man/ase-agent-editor.1 +1 -1
- package/dst/man/ase-agent-estimator.1 +1 -1
- package/dst/man/ase-agent-fleet.1 +1 -1
- package/dst/man/ase-agent-implementer.1 +1 -1
- package/dst/man/ase-agent-integrator.1 +1 -1
- package/dst/man/ase-agent-qualifier.1 +1 -1
- package/dst/man/ase-agent-rebooter.1 +1 -1
- package/dst/man/ase-agent-relay.1 +1 -1
- package/dst/man/ase-agent-researcher.1 +1 -1
- package/dst/man/ase-agent.1 +1 -1
- package/dst/man/ase-artifact-list.1 +1 -1
- package/dst/man/ase-artifact-name.1 +1 -1
- package/dst/man/ase-artifact.1 +1 -1
- package/dst/man/ase-config-delete.1 +1 -1
- package/dst/man/ase-config-edit.1 +1 -1
- package/dst/man/ase-config-get.1 +1 -1
- package/dst/man/ase-config-init.1 +1 -1
- package/dst/man/ase-config-list.1 +1 -1
- package/dst/man/ase-config-set.1 +1 -1
- package/dst/man/ase-config.1 +1 -1
- package/dst/man/ase-hook-permission-request.1 +1 -1
- package/dst/man/ase-hook-pre-tool-use.1 +1 -1
- package/dst/man/ase-hook-session-end.1 +1 -1
- package/dst/man/ase-hook-session-persona.1 +1 -1
- package/dst/man/ase-hook-session-start.1 +1 -1
- package/dst/man/ase-hook-stop.1 +1 -1
- package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
- package/dst/man/ase-hook.1 +1 -1
- package/dst/man/ase-man.1 +1 -1
- package/dst/man/ase-mcp.1 +1 -1
- package/dst/man/ase-service-log.1 +1 -1
- package/dst/man/ase-service-send.1 +1 -1
- package/dst/man/ase-service-start.1 +1 -1
- package/dst/man/ase-service-status.1 +1 -1
- package/dst/man/ase-service-stop.1 +1 -1
- package/dst/man/ase-service.1 +1 -1
- package/dst/man/ase-setup-disable.1 +1 -1
- package/dst/man/ase-setup-enable.1 +1 -1
- package/dst/man/ase-setup-install.1 +1 -1
- package/dst/man/ase-setup-mcp.1 +1 -1
- package/dst/man/ase-setup-status.1 +1 -1
- package/dst/man/ase-setup-statusline.1 +1 -1
- package/dst/man/ase-setup-telemetry.1 +1 -1
- package/dst/man/ase-setup-uninstall.1 +1 -1
- package/dst/man/ase-setup-update.1 +1 -1
- package/dst/man/ase-setup.1 +1 -1
- package/dst/man/ase-spec-export.1 +1 -1
- package/dst/man/ase-spec-lint.1 +1 -1
- package/dst/man/ase-spec-preview.1 +1 -1
- package/dst/man/ase-spec.1 +1 -1
- package/dst/man/ase-statusline.1 +1 -1
- package/dst/man/ase-task-assign.1 +1 -1
- package/dst/man/ase-task-board.1 +3 -3
- package/dst/man/ase-task-delete.1 +1 -1
- package/dst/man/ase-task-edit.1 +1 -1
- package/dst/man/ase-task-idscheme.1 +1 -1
- package/dst/man/ase-task-lifecycle.1 +1 -1
- package/dst/man/ase-task-list.1 +1 -1
- package/dst/man/ase-task-load.1 +1 -1
- package/dst/man/ase-task-newid.1 +1 -1
- package/dst/man/ase-task-purge.1 +1 -1
- package/dst/man/ase-task-rename.1 +1 -1
- package/dst/man/ase-task-request.1 +1 -1
- package/dst/man/ase-task-save.1 +1 -1
- package/dst/man/ase-task-status.1 +1 -1
- package/dst/man/ase-task-store.1 +1 -1
- package/dst/man/ase-task-view.1 +1 -1
- package/dst/man/ase-task.1 +1 -1
- package/dst/man/ase-util-compat.1 +1 -1
- package/dst/man/ase-util-diagram.1 +1 -1
- package/dst/man/ase-util-meta.1 +1 -1
- package/dst/man/ase-util-metric.1 +1 -1
- package/dst/man/ase-util-mint.1 +1 -1
- package/dst/man/ase-util-worktree.1 +1 -1
- package/dst/man/ase-util.1 +1 -1
- package/dst/man/ase.1 +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-common-resolve.md +2 -2
- package/plugin/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +16 -12
- package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +26 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-edit/SKILL.md +5 -4
- package/plugin/skills/ase-code-edit/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +52 -27
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +60 -30
- package/plugin/skills/ase-help-skill/help.md +3 -4
- package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
- package/plugin/skills/ase-meta-quorum/help.md +5 -3
- package/plugin/skills/ase-meta-workflow/sample.md +5 -3
- package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
- package/plugin/skills/ase-repo-changelog/help.md +2 -2
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
- package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
- package/plugin/skills/ase-spec-edit/help.md +2 -1
- package/plugin/skills/ase-sync-reconcile/help.md +4 -4
- package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
- package/plugin/skills/ase-task-grill/help.md +1 -1
- package/plugin/skills/ase-task-implement/SKILL.md +58 -1
- package/plugin/skills/ase-task-implement/help.md +16 -0
- package/plugin/skills/ase-task-list/help.md +2 -2
|
@@ -19,8 +19,12 @@ const GROUP_GAP = 1;
|
|
|
19
19
|
/* layout constants of a grown lane: minimum card width, gap between cards */
|
|
20
20
|
const GRID_MIN = 30;
|
|
21
21
|
const GRID_GAP = 1;
|
|
22
|
-
/* make a text of the agent relay safe for the terminal (strip control characters) */
|
|
23
|
-
const clean = (text) => text.replace(
|
|
22
|
+
/* make a text of the agent relay safe for the terminal (strip control and bidirectional formatting characters) */
|
|
23
|
+
const clean = (text) => text.replace(/[\p{Cc}\u{061C}\u{200E}\u{200F}\u{202A}-\u{202E}\u{2066}-\u{2069}]/gu, "");
|
|
24
|
+
/* append to the content of an info line (" " if empty) the hint how to show the
|
|
25
|
+
hidden key hints again, so it need not be remembered while the key hints are hidden */
|
|
26
|
+
const keysHint = (surface, info) => surface.keys ? info : info === " " ? "?: show key hints" :
|
|
27
|
+
[...(Array.isArray(info) ? info : [info]), " · ?: show key hints"];
|
|
24
28
|
/* compute the visible group range starting at a first group, with the
|
|
25
29
|
width of each visible group (collapsed groups stay narrow, expanded
|
|
26
30
|
groups share the remaining width but never drop below the minimum) */
|
|
@@ -35,8 +39,7 @@ export const fitGroups = (groups, collapsed, first, width) => {
|
|
|
35
39
|
widths.push(w);
|
|
36
40
|
used += gap + w;
|
|
37
41
|
}
|
|
38
|
-
const expanded = widths.
|
|
39
|
-
.filter((i) => i >= 0);
|
|
42
|
+
const expanded = [...widths.keys()].filter((i) => !collapsed.includes(groups[first + i].title));
|
|
40
43
|
if (expanded.length > 0) {
|
|
41
44
|
const extra = Math.max(0, width - used);
|
|
42
45
|
const each = Math.floor(extra / expanded.length);
|
|
@@ -331,7 +334,8 @@ export const renderLanes = (ctx) => {
|
|
|
331
334
|
const arrows = shown < total && !grown;
|
|
332
335
|
const group = board.groups[sel.g];
|
|
333
336
|
const info = grown ? `lane ${group.lanes[sel.l].status} of group ${group.title} grown · g shrinks` :
|
|
334
|
-
`groups ${fit.first + 1}–${fit.last + 1} of ${total} · ${arrows ? "←/→ scrolls" : "all visible"}
|
|
337
|
+
`groups ${fit.first + 1}–${fit.last + 1} of ${total} · ${arrows ? "←/→ scrolls" : "all visible"}` +
|
|
338
|
+
(surface.keys ? "" : " · ?: show key hints");
|
|
335
339
|
const view = { board, surface, sel, dim, carry, titles: surface.titles, scroll: { left: -1, right: -1 }, pulse, cardRef, laneRef, groupRef, agents, working };
|
|
336
340
|
/* the scroll bar consumes the entire width remaining besides the info */
|
|
337
341
|
const track = Math.max(1, columns - 2 - info.length - 3);
|
|
@@ -390,8 +394,8 @@ export const renderGraph = (ctx) => {
|
|
|
390
394
|
})))))),
|
|
391
395
|
/* overlay the graph statistics centered onto the top border */
|
|
392
396
|
h(Box, { position: "absolute", top: 0, left: 0, width: "100%", justifyContent: "center" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, `[ ${board.cards.size} tasks, ${deps} dependencies ]`))),
|
|
393
|
-
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), card === undefined ? " " :
|
|
394
|
-
["task: ", h(Text, { key: "id", ...cx("bold") }, card.id), " · status: ", h(Text, { key: "status", ...cx("bold") }, card.status)])),
|
|
397
|
+
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), keysHint(surface, card === undefined ? " " :
|
|
398
|
+
["task: ", h(Text, { key: "id", ...cx("bold") }, card.id), " · status: ", h(Text, { key: "status", ...cx("bold") }, card.status)]))),
|
|
395
399
|
h(Box, { key: "keys1", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), "↑/↓/←/→/Left-Click: select task · ⏎/Double-Click: view task · e: edit task · T: transition task")),
|
|
396
400
|
h(Box, { key: "keys2", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), `D: delete task · A: assign task · E: edit by prompt · R: request by prompt · N: new task · t: ${graphTitles ? "collapse" : "expand"} titles · ` +
|
|
397
401
|
`s: ${surface.standalone ? "hide" : "show"} standalone tasks`)),
|
|
@@ -434,7 +438,7 @@ export const renderFlow = (ctx) => {
|
|
|
434
438
|
})))))),
|
|
435
439
|
/* overlay the graph statistics centered onto the top border */
|
|
436
440
|
h(Box, { position: "absolute", top: 0, left: 0, width: "100%", justifyContent: "center" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, `[ ${agents?.agents.length ?? 0} agents, ${flow.states.length} states ]`))),
|
|
437
|
-
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), rel !== null ? [
|
|
441
|
+
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), keysHint(ctx.surface, rel !== null ? [
|
|
438
442
|
"state: ", h(Text, { key: "state", ...cx("bold") }, rel.state),
|
|
439
443
|
" · observed by: ", h(Text, { key: "in", ...cx("bold") }, names(rel.input)),
|
|
440
444
|
" · written by: ", h(Text, { key: "out", ...cx("bold") }, names(rel.output)),
|
|
@@ -448,7 +452,7 @@ export const renderFlow = (ctx) => {
|
|
|
448
452
|
" · output: ", h(Text, { key: "out", ...cx("bold") }, states.output.join(", ") || "none"),
|
|
449
453
|
...(states.gate !== undefined ? [" · gate: ", h(Text, { key: "gate", ...cx("bold") }, states.gate)] : [])
|
|
450
454
|
])
|
|
451
|
-
])),
|
|
455
|
+
]))),
|
|
452
456
|
h(Box, { key: "keys1", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), "↑/↓/←/→/Left-Click: select agent/state · ⏎/Double-Click: view agent · " +
|
|
453
457
|
"⇈/⇊/⇤/⇥/Wheel: scroll canvas")),
|
|
454
458
|
h(Box, { key: "keys2", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), `v/V: ${viewCycleHint("flow", agents !== null)} · M: ${mouse ? "disable" : "enable"} mouse · ?: hide key hints · q: quit`))
|
|
@@ -530,22 +534,22 @@ export const renderAgents = (ctx) => {
|
|
|
530
534
|
const thumbH = Math.max(1, Math.round(logH * Math.min(1, logH / Math.max(1, logLines.length))));
|
|
531
535
|
const thumbY = logMax === 0 ? 0 : Math.round((logH - thumbH) * logFirst / logMax);
|
|
532
536
|
const field = (name, value, width, wrap = "truncate") => h(Box, { key: name }, h(Box, { flexShrink: 0 }, h(Text, cx("accent", dim && "dimmed"), name.padEnd(width))), h(Box, { flexGrow: 1, flexShrink: 1 }, h(Text, { ...cx("accent", "bold", dim && "dimmed"), wrap }, value)));
|
|
533
|
-
const pane = (key, text, fields, first = false, accent = false) => h(Box, { key, flexGrow: 1, flexBasis: 0, marginLeft: first ? 0 : 1 }, h(Box, { paddingX: 1, ...cx("grow", "column", "frame", accent ? "border-accent" : "border-dim", dim && "border-dimmed") }, ...(agent === undefined ? [h(Text, { key: "none", ...cx("dim", "truncate", dim && "dimmed") }, empty)] : fields())), title(text, accent));
|
|
537
|
+
const pane = (key, text, fields, first = false, accent = false) => h(Box, { key, flexGrow: 1, flexBasis: 0, marginLeft: first ? 0 : 1 }, h(Box, { paddingX: 1, ...cx("grow", "column", "frame", accent ? "border-accent" : "border-dim", dim && "border-dimmed") }, ...(agent === undefined ? [h(Text, { key: "none", ...cx("dim", "truncate", dim && "dimmed") }, empty)] : fields(agent))), title(text, accent));
|
|
534
538
|
const op = h(Box, { key: "op", height: agentOpHeight(agentOp), flexShrink: 0 }, pane("operation", "Operation", () => [
|
|
535
539
|
h(Text, { key: "title", ...cx("normal", "truncate", dim && "dimmed") }, h(Text, cx("normal", "badge", dim && "dimmed"), ` ⚉ ${agentOp.name} `), " ", agentOp.title !== "" ? h(Text, cx("bold"), agentOp.title) :
|
|
536
540
|
agentOp.desc.length === 0 ? h(Text, cx("dim"), "(no operation title and description)") : ""),
|
|
537
541
|
...agentOp.desc.map((line, i) => h(Text, { key: `desc-${i}`, ...cx("normal", "truncate", dim && "dimmed") }, line))
|
|
538
542
|
], true));
|
|
539
|
-
const info = h(Box, { key: "info", height: AGENT_INFO_H, flexShrink: 0 }, pane("process", "Process", () => [
|
|
540
|
-
field("host:", clean(
|
|
541
|
-
field("pid:", String(
|
|
542
|
-
field("uid:", clean(agentUser(
|
|
543
|
-
field("cwd:", clean(
|
|
544
|
-
], true, true), pane("accounting", "Accounting", () => [
|
|
545
|
-
field("uptime:", agentUptime(
|
|
546
|
-
field("heartbeat:", `${agentDuration(now -
|
|
547
|
-
field("usage (total):", agentUsage(
|
|
548
|
-
field("usage (last job):", agentUsage(
|
|
543
|
+
const info = h(Box, { key: "info", height: AGENT_INFO_H, flexShrink: 0 }, pane("process", "Process", (a) => [
|
|
544
|
+
field("host:", clean(a.host), 6),
|
|
545
|
+
field("pid:", String(a.pid), 6),
|
|
546
|
+
field("uid:", clean(agentUser(a)), 6),
|
|
547
|
+
field("cwd:", clean(a.cwd ?? "n/a"), 6, "truncate-start")
|
|
548
|
+
], true, true), pane("accounting", "Accounting", (a) => [
|
|
549
|
+
field("uptime:", agentUptime(a), 18),
|
|
550
|
+
field("heartbeat:", `${agentDuration(now - a.beat)} ago`, 18),
|
|
551
|
+
field("usage (total):", agentUsage(a.usage?.total), 18),
|
|
552
|
+
field("usage (last job):", agentUsage(a.usage?.last), 18)
|
|
549
553
|
], false, true));
|
|
550
554
|
const log = h(Box, { key: "log", flexGrow: 1 }, h(Box, { paddingLeft: 1, ...cx("grow", "column", "frame", "border-dim", dim && "border-dimmed") }, ...(agent === undefined ? [h(Text, { key: "none", ...cx("dim", dim && "dimmed") }, empty)] :
|
|
551
555
|
lines.length === 0 ? [h(Text, { key: "none", ...cx("dim", dim && "dimmed") }, "(no processing log lines yet)")] :
|
|
@@ -554,19 +558,19 @@ export const renderAgents = (ctx) => {
|
|
|
554
558
|
const detail = h(Box, { key: "detail", flexGrow: 1, height: boardH, marginLeft: 1, flexDirection: "column" }, op, info, log);
|
|
555
559
|
return [
|
|
556
560
|
h(Box, { key: "agents", height: boardH, paddingX: 1 }, master, detail),
|
|
557
|
-
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", "truncate", dim && "dimmed"), agent === undefined ? " " : [
|
|
561
|
+
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", "truncate", dim && "dimmed"), keysHint(ctx.surface, agent === undefined ? " " : [
|
|
558
562
|
"agent: ", h(Text, { key: "agent", ...cx("bold") }, clean(agentLabel(agent))),
|
|
559
563
|
...(agentOp.title !== "" ? [
|
|
560
564
|
" · operation: ", h(Text, { key: "op", ...cx("bold") }, agentOp.title)
|
|
561
565
|
] : [])
|
|
562
|
-
])),
|
|
566
|
+
]))),
|
|
563
567
|
h(Box, { key: "keys1", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), "↑/↓/Left-Click: select agent · ⇈/⇊/⇤/⇥: page/top/bottom agent log · Mouse-Wheel: scroll agent log")),
|
|
564
568
|
h(Box, { key: "keys2", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), `v/V: ${viewCycleHint("agents", agents !== null)} · M: ${mouse ? "disable" : "enable"} mouse · ?: hide key hints · q: quit`))
|
|
565
569
|
];
|
|
566
570
|
};
|
|
567
571
|
/* the heights of the buttons of the time ranges (with a blank line below them), and of the
|
|
568
|
-
frames "Task Progress" (and "Open Tasks"
|
|
569
|
-
it), and "Top-5 Tasks" (and "Finished Tasks" and "Efficiency"
|
|
572
|
+
frames "Task Progress" (and "Open Tasks" beside it), "Usage" (and "Effort" beside
|
|
573
|
+
it), and "Top-5 Tasks" (and "Finished Tasks" and "Efficiency" beside it), each
|
|
570
574
|
with an inner blank line at its top and bottom (except for the column charts), of the
|
|
571
575
|
statistics view */
|
|
572
576
|
const RANGES_H = 2;
|
|
@@ -614,15 +618,60 @@ const statsBar = (value, max, width) => {
|
|
|
614
618
|
const n = max > 0 ? Math.round(value / max * width) : 0;
|
|
615
619
|
return "█".repeat(n).padEnd(width, "░");
|
|
616
620
|
};
|
|
621
|
+
/* the color of a tone of the statistics view (like the lanes) */
|
|
622
|
+
const statsTint = (tone) => tone === "active" ? palette.accent : tone === "done" ? palette.dim : palette.normal;
|
|
623
|
+
/* a framed section of a height of the statistics view (dimmed, resp. in accent color),
|
|
624
|
+
with its title centered onto its top border */
|
|
625
|
+
const statsFrame = (dim, accent) => (key, height, title, padY, ...content) => h(Box, { key, height, flexShrink: 0 }, h(Box, { paddingX: 1, paddingY: padY, ...cx("grow", "column", "frame", accent ? "border-accent" : "border-dim", dim && "border-dimmed") }, ...content), h(Box, { position: "absolute", top: 0, left: 0, width: "100%", justifyContent: "center" }, h(Text, { ...cx(accent ? "accent" : "dim", dim && "dimmed"), wrap: "truncate" }, "[ ", h(Text, cx("bold"), title), " ]")));
|
|
626
|
+
/* a framed column chart of an inner width of the statistics view, where the highest column leaves the top row
|
|
627
|
+
for its number, each number sits on top of its column (being at least a thin line,
|
|
628
|
+
even without any number, like in the web board), the columns stand on a blank line above
|
|
629
|
+
their labels (each cut to its first five characters, without an ellipsis, for
|
|
630
|
+
readability) and their sub-labels below them (like the effort of a state, cut with
|
|
631
|
+
an ellipsis, and blank if empty, to keep the columns of all charts aligned), and each
|
|
632
|
+
group of columns is labeled below its line spanning its columns
|
|
633
|
+
(like in the web board), followed by a footer line (blank if empty, to keep the columns
|
|
634
|
+
of all charts aligned), where the columns start directly at the left padding of the
|
|
635
|
+
frame, are separated by a gap of one column, and leave the remaining width on the right,
|
|
636
|
+
and where a compact chart of a height omits the blank line, the sub-labels, and the groups */
|
|
637
|
+
const statsColChart = (dim, key, title, width, cols, groups, footer, height, compact = false) => {
|
|
638
|
+
const frame = statsFrame(dim, false);
|
|
639
|
+
const areaH = Math.max(1, height - (compact ? 4 : 8));
|
|
640
|
+
const colW = Math.max(2, Math.floor((width + 1) / cols.length));
|
|
641
|
+
const lineW = colW - 1;
|
|
642
|
+
const fitW = Math.max(1, Math.min(14, lineW));
|
|
643
|
+
const barW = fitW > 1 && (lineW - fitW) % 2 !== 0 ? fitW - 1 : fitW;
|
|
644
|
+
const barL = Math.floor((lineW - barW) / 2);
|
|
645
|
+
const max = Math.max(1, ...cols.map((c) => c.count));
|
|
646
|
+
const gapOf = (i, n) => i < n - 1 ? " " : "";
|
|
647
|
+
const column = (k, tone, text) => h(Text, { key: k, color: statsTint(tone), dimColor: dim }, " ".repeat(barL) + text + " ".repeat(Math.max(0, lineW - barW - barL)) + gapOf(k, cols.length));
|
|
648
|
+
const rows = Array.from({ length: areaH }, (_x, r) => {
|
|
649
|
+
const level = areaH - 1 - r;
|
|
650
|
+
return h(Box, { key: `row${r}` }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => {
|
|
651
|
+
const fill = Math.max(1, Math.round(c.count / max * (areaH - 1) * 8));
|
|
652
|
+
const cell = Math.max(0, Math.min(8, fill - level * 8));
|
|
653
|
+
const text = String(c.count);
|
|
654
|
+
if (cell === 0 && level === Math.ceil(fill / 8) && text.length > barW)
|
|
655
|
+
return h(Text, { key: i, color: statsTint(c.tone), dimColor: dim }, center(text, lineW) + gapOf(i, cols.length));
|
|
656
|
+
return column(i, c.tone, cell > 0 ? lowerEighths[cell].repeat(barW) :
|
|
657
|
+
level === Math.ceil(fill / 8) ? center(text, barW) : " ".repeat(barW));
|
|
658
|
+
})));
|
|
659
|
+
});
|
|
660
|
+
const labels = h(Box, { key: "labels" }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => h(Text, { key: i, color: statsTint(c.tone), dimColor: dim, bold: true }, center(c.label.slice(0, Math.min(5, lineW)), lineW) + gapOf(i, cols.length)))));
|
|
661
|
+
const foot = h(Box, { key: "footer" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, center(footer, Math.max(0, cols.length * colW - 1))));
|
|
662
|
+
if (compact)
|
|
663
|
+
return frame(key, height, title, 0, ...rows, labels, foot);
|
|
664
|
+
return frame(key, height, title, 0, ...rows, h(Box, { key: "base" }, h(Text, { wrap: "truncate" }, " ")), labels, h(Box, { key: "subs" }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => h(Text, { key: i, color: statsTint(c.tone), dimColor: dim }, center(c.sub ?? "", lineW) + gapOf(i, cols.length))))), h(Box, { key: "groups" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, groups.map((g, i) => "─".repeat(Math.max(0, g.n * colW - 1)) + gapOf(i, groups.length)).join(""))), h(Box, { key: "group-labels" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, groups.map((g, i) => center(g.label, g.n * colW - 1) + gapOf(i, groups.length)).join(""))), foot);
|
|
665
|
+
};
|
|
617
666
|
/* render the statistics view: the framed column chart "Tasks per State" of the numbers of tasks per
|
|
618
667
|
lifecycle state (labeled by state, its effort, and phase, in the colors of their lanes, with the framed column
|
|
619
|
-
chart "Throughput" of the finished tasks of the last weeks
|
|
668
|
+
chart "Throughput" of the finished tasks of the last weeks beside it), the framed stacked bar
|
|
620
669
|
"Task Progress" of the cancelled, finished, work-in-progress, and open tasks (with the framed
|
|
621
|
-
stacked bar "Open Tasks" of the ready and blocked open tasks
|
|
670
|
+
stacked bar "Open Tasks" of the ready and blocked open tasks beside it), the framed bars "Usage"
|
|
622
671
|
of the total times, tokens, and costs of the tasks (with the framed column chart "Effort"
|
|
623
|
-
of the numbers of tasks per effort size
|
|
672
|
+
of the numbers of tasks per effort size beside it), and the framed table "Top-5 Tasks" of the
|
|
624
673
|
tasks of the highest usage (with the framed "Finished Tasks" of the usage per finished task and
|
|
625
|
-
the framed "Efficiency" of the AI usage
|
|
674
|
+
the framed "Efficiency" of the AI usage beside it), below the buttons of the time ranges */
|
|
626
675
|
export const renderStats = (ctx) => {
|
|
627
676
|
const { stats, usage, range, rangeRef, columns, boardH, dim, mouse, filter, agents } = ctx;
|
|
628
677
|
const innerW = Math.max(20, columns - 6);
|
|
@@ -635,55 +684,15 @@ export const renderStats = (ctx) => {
|
|
|
635
684
|
/* the number of columns the frames "Finished Tasks" and "Efficiency" are each grown by
|
|
636
685
|
(at the expense of the frame "Top-5 Tasks") */
|
|
637
686
|
const avgGrow = 4;
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
const frame = frameOf(false);
|
|
641
|
-
const accentFrame = frameOf(true);
|
|
687
|
+
const frame = statsFrame(dim, false);
|
|
688
|
+
const accentFrame = statsFrame(dim, true);
|
|
642
689
|
if (stats === null)
|
|
643
690
|
return [h(Box, { key: "stats", height: boardH }), h(Text, { key: "info" }, " "), h(Text, { key: "keys1" }, " "), h(Text, { key: "keys2" }, " ")];
|
|
644
|
-
/*
|
|
645
|
-
for its number, each number sits on top of its column (being at least a thin line,
|
|
646
|
-
even without any number, like in the web board), the columns stand on a blank line above
|
|
647
|
-
their labels (each cut to its first five characters, without an ellipsis, for
|
|
648
|
-
readability) and their sub-labels below them (like the effort of a state, cut with
|
|
649
|
-
an ellipsis, and blank if empty, to keep the columns of all charts aligned), and each
|
|
650
|
-
group of columns is labeled below its line spanning its columns
|
|
651
|
-
(like in the web board), followed by a footer line (blank if empty, to keep the columns
|
|
652
|
-
of all charts aligned), where the columns start directly at the left padding of the
|
|
653
|
-
frame, are separated by a gap of one column, and leave the remaining width on the right,
|
|
654
|
-
and where a compact chart of a height omits the blank line, the sub-labels, and the groups */
|
|
691
|
+
/* the height of the column charts, taking the height left by the other rows */
|
|
655
692
|
const chartH = Math.max(9, boardH - RANGES_H - PROGRESS_H - USAGE_H - TOP_H);
|
|
656
|
-
const colChart = (key, title, width, cols, groups, footer = "", height = chartH, compact = false) => {
|
|
657
|
-
const areaH = Math.max(1, height - (compact ? 4 : 8));
|
|
658
|
-
const colW = Math.max(2, Math.floor((width + 1) / cols.length));
|
|
659
|
-
const lineW = colW - 1;
|
|
660
|
-
const fitW = Math.max(1, Math.min(14, lineW));
|
|
661
|
-
const barW = fitW > 1 && (lineW - fitW) % 2 !== 0 ? fitW - 1 : fitW;
|
|
662
|
-
const barL = Math.floor((lineW - barW) / 2);
|
|
663
|
-
const max = Math.max(1, ...cols.map((c) => c.count));
|
|
664
|
-
const gapOf = (i, n) => i < n - 1 ? " " : "";
|
|
665
|
-
const column = (k, tone, text) => h(Text, { key: k, color: tint(tone), dimColor: dim }, " ".repeat(barL) + text + " ".repeat(Math.max(0, lineW - barW - barL)) + gapOf(k, cols.length));
|
|
666
|
-
const rows = Array.from({ length: areaH }, (_x, r) => {
|
|
667
|
-
const level = areaH - 1 - r;
|
|
668
|
-
return h(Box, { key: `row${r}` }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => {
|
|
669
|
-
const fill = Math.max(1, Math.round(c.count / max * (areaH - 1) * 8));
|
|
670
|
-
const cell = Math.max(0, Math.min(8, fill - level * 8));
|
|
671
|
-
const text = String(c.count);
|
|
672
|
-
if (cell === 0 && level === Math.ceil(fill / 8) && text.length > barW)
|
|
673
|
-
return h(Text, { key: i, color: tint(c.tone), dimColor: dim }, center(text, lineW) + gapOf(i, cols.length));
|
|
674
|
-
return column(i, c.tone, cell > 0 ? lowerEighths[cell].repeat(barW) :
|
|
675
|
-
level === Math.ceil(fill / 8) ? center(text, barW) : " ".repeat(barW));
|
|
676
|
-
})));
|
|
677
|
-
});
|
|
678
|
-
const labels = h(Box, { key: "labels" }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => h(Text, { key: i, color: tint(c.tone), dimColor: dim, bold: true }, center(c.label.slice(0, Math.min(5, lineW)), lineW) + gapOf(i, cols.length)))));
|
|
679
|
-
const foot = h(Box, { key: "footer" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, center(footer, Math.max(0, cols.length * colW - 1))));
|
|
680
|
-
if (compact)
|
|
681
|
-
return frame(key, height, title, 0, ...rows, labels, foot);
|
|
682
|
-
return frame(key, height, title, 0, ...rows, h(Box, { key: "base" }, h(Text, { wrap: "truncate" }, " ")), labels, h(Box, { key: "subs" }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => h(Text, { key: i, color: tint(c.tone), dimColor: dim }, center(c.sub ?? "", lineW) + gapOf(i, cols.length))))), h(Box, { key: "groups" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, groups.map((g, i) => "─".repeat(Math.max(0, g.n * colW - 1)) + gapOf(i, groups.length)).join(""))), h(Box, { key: "group-labels" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, groups.map((g, i) => center(g.label, g.n * colW - 1) + gapOf(i, groups.length)).join(""))), foot);
|
|
683
|
-
};
|
|
684
693
|
/* the column chart of the number of tasks per state (labeled by the states and below by the
|
|
685
694
|
effort of their estimated tasks, grouped by their phases, and with the total number of tasks
|
|
686
|
-
as its footer), with the column chart of the throughput of the last weeks
|
|
695
|
+
as its footer), with the column chart of the throughput of the last weeks beside it (labeled by
|
|
687
696
|
their week numbers, prefixed with "W" if fitting, and grouped by all weeks with the total
|
|
688
697
|
and average number of finished tasks, and with the forecast of the backlog as its footer),
|
|
689
698
|
grown like the frames "Finished Tasks" and "Efficiency" together, to align with them */
|
|
@@ -699,10 +708,10 @@ export const renderStats = (ctx) => {
|
|
|
699
708
|
const weekW = Math.max(2, Math.floor(weeksW / Math.max(1, stats.throughput.length))) - 1;
|
|
700
709
|
const weekSum = stats.throughput.reduce((n, w) => n + w.count, 0);
|
|
701
710
|
const weekAvg = stats.throughput.length > 0 ? weekSum / stats.throughput.length : 0;
|
|
702
|
-
const chart = pair("charts", chartH,
|
|
711
|
+
const chart = pair("charts", chartH, statsColChart(dim, "chart", "Tasks per State", leftW - 2 * avgGrow - 4, stats.states.map((s) => ({
|
|
703
712
|
label: s.status, count: s.count, tone: s.tone,
|
|
704
713
|
sub: s.effort.estimated > 0 ? statsHoursN(s.effort.minutes) : ""
|
|
705
|
-
})), phases, `total tasks: ${stats.states.reduce((n, s) => n + s.count, 0)}
|
|
714
|
+
})), phases, `total tasks: ${stats.states.reduce((n, s) => n + s.count, 0)}`, chartH), statsColChart(dim, "throughput", "Throughput", weeksW, stats.throughput.map((w) => ({ label: weekW >= 3 ? `W${w.week}` : String(w.week), count: w.count, tone: "idle" })), [{ label: `${weekSum} finished · ⌀ ${weekAvg.toFixed(1)}/week`, n: stats.throughput.length }], statsForecast(stats.forecast.backlog, stats.forecast.weeks), chartH), 2 * avgGrow);
|
|
706
715
|
/* a framed stacked bar of the numbers of tasks of parts, with its legend */
|
|
707
716
|
const stacked = (key, title, width, parts) => {
|
|
708
717
|
const total = parts.reduce((n, p) => n + p.count, 0);
|
|
@@ -780,9 +789,9 @@ export const renderStats = (ctx) => {
|
|
|
780
789
|
[h(Text, { key: "loading", ...cx("dim", dim && "dimmed") }, "loading …")] : [head, ...lines]));
|
|
781
790
|
/* the compact column chart of the distribution of the effort sizes (the numbers of the
|
|
782
791
|
estimated tasks per size, from the smallest to the largest size), with the numbers of
|
|
783
|
-
the estimated and of all tasks as its footer,
|
|
792
|
+
the estimated and of all tasks as its footer, beside the bars of the total usage */
|
|
784
793
|
const effort = stats.states.reduce((sum, s) => ({ estimated: sum.estimated + s.effort.estimated, tasks: sum.tasks + s.effort.tasks }), { estimated: 0, tasks: 0 });
|
|
785
|
-
const usages = pair("usage", USAGE_H, matrix,
|
|
794
|
+
const usages = pair("usage", USAGE_H, matrix, statsColChart(dim, "sizes", "Effort", rightW + 2 * avgGrow - 4, stats.sizes.map((s) => ({ label: s.size, count: s.count, tone: "idle" })), [], `estimated: ${effort.estimated} of ${effort.tasks} tasks`, USAGE_H, true), 2 * avgGrow);
|
|
786
795
|
/* the table of the tasks of the highest usage: the id (inverse behind "≡", in the color of
|
|
787
796
|
its lane), the title (cut with an ellipsis), a bar of its share of the total costs (resp.
|
|
788
797
|
tokens), the costs, and the input and output tokens (as "<input> ▷ <output>", with
|
|
@@ -803,7 +812,7 @@ export const renderStats = (ctx) => {
|
|
|
803
812
|
stats.top.length === 0 ? [h(Text, { key: "none", ...cx("dim", dim && "dimmed") }, "no usage")] :
|
|
804
813
|
[topHead, ...stats.top.map((t, i) => {
|
|
805
814
|
const id = fit(t.id, idW - 4).trimEnd();
|
|
806
|
-
return h(Text, { key: `top${i}`, ...cx("normal", "truncate", dim && "dimmed") }, h(Text, { key: "id", color:
|
|
815
|
+
return h(Text, { key: `top${i}`, ...cx("normal", "truncate", dim && "dimmed") }, h(Text, { key: "id", color: statsTint(t.tone), dimColor: dim, ...cx("badge") }, ` ≡ ${id} `), " ".repeat(idW - id.length - 4) + gap + fit(clean(t.title), titleW) + gap, h(Text, { key: "bar", color: palette.accent, dimColor: dim }, statsBar(t.share, 1, pctW)), gap + statsCost(t.cost).padStart(costW) + gap +
|
|
807
816
|
`${statsTokens(t.input).padStart(inW)} ▷ ${statsTokens(t.output).padStart(outW)}`.padStart(tokW));
|
|
808
817
|
})];
|
|
809
818
|
/* the averages and lead times of the finished tasks and the efficiency of the AI usage (the
|
|
@@ -839,8 +848,8 @@ export const renderStats = (ctx) => {
|
|
|
839
848
|
const shown = stats.states.reduce((n, s) => n + s.count, 0);
|
|
840
849
|
return [
|
|
841
850
|
h(Box, { key: "stats", height: boardH, paddingX: 1, flexDirection: "column", overflow: "hidden" }, ranges, chart, progress, usages, tops),
|
|
842
|
-
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), ["statistics of ", h(Text, { key: "n", ...cx("bold") }, String(shown)),
|
|
843
|
-
` ${filter !== "" ? "filtered " : ""}tasks`, ...(range !== "total" ? [" ", h(Text, { key: "r", ...cx("bold") }, statsRangeLabels[range])] : [])])),
|
|
851
|
+
h(Box, { key: "info", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), keysHint(ctx.surface, ["statistics of ", h(Text, { key: "n", ...cx("bold") }, String(shown)),
|
|
852
|
+
` ${filter !== "" ? "filtered " : ""}tasks`, ...(range !== "total" ? [" ", h(Text, { key: "r", ...cx("bold") }, statsRangeLabels[range])] : [])]))),
|
|
844
853
|
h(Box, { key: "keys1", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), `←/→: select time range · /: filter tasks · v/V: ${viewCycleHint("stats", agents !== null)}`)),
|
|
845
854
|
h(Box, { key: "keys2", ...cx("bar") }, h(Text, cx("hint", dim && "dimmed"), `M: ${mouse ? "disable" : "enable"} mouse · ?: hide key hints · q: quit`))
|
|
846
855
|
];
|