@rse/ase 2.0.2 → 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 +2 -0
- package/dst/ase-hook.js +12 -12
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +4 -5
- 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-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +41 -35
- package/dst/ase-task-board-tui-model.js +185 -167
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +74 -70
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +2 -2
- 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 +16 -1
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +9 -32
- package/dst/ase-task-store-plugin-github.js +3 -12
- package/dst/ase-task-store-plugin-gitlab.js +2 -18
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +13 -37
- 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 +1 -1
- 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-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +10 -7
- package/plugin/skills/ase-code-analyze/SKILL.md +18 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-lint/SKILL.md +37 -23
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +24 -12
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
|
@@ -19,8 +19,8 @@ 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
24
|
/* append to the content of an info line (" " if empty) the hint how to show the
|
|
25
25
|
hidden key hints again, so it need not be remembered while the key hints are hidden */
|
|
26
26
|
const keysHint = (surface, info) => surface.keys ? info : info === " " ? "?: show key hints" :
|
|
@@ -39,8 +39,7 @@ export const fitGroups = (groups, collapsed, first, width) => {
|
|
|
39
39
|
widths.push(w);
|
|
40
40
|
used += gap + w;
|
|
41
41
|
}
|
|
42
|
-
const expanded = widths.
|
|
43
|
-
.filter((i) => i >= 0);
|
|
42
|
+
const expanded = [...widths.keys()].filter((i) => !collapsed.includes(groups[first + i].title));
|
|
44
43
|
if (expanded.length > 0) {
|
|
45
44
|
const extra = Math.max(0, width - used);
|
|
46
45
|
const each = Math.floor(extra / expanded.length);
|
|
@@ -535,22 +534,22 @@ export const renderAgents = (ctx) => {
|
|
|
535
534
|
const thumbH = Math.max(1, Math.round(logH * Math.min(1, logH / Math.max(1, logLines.length))));
|
|
536
535
|
const thumbY = logMax === 0 ? 0 : Math.round((logH - thumbH) * logFirst / logMax);
|
|
537
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)));
|
|
538
|
-
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));
|
|
539
538
|
const op = h(Box, { key: "op", height: agentOpHeight(agentOp), flexShrink: 0 }, pane("operation", "Operation", () => [
|
|
540
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) :
|
|
541
540
|
agentOp.desc.length === 0 ? h(Text, cx("dim"), "(no operation title and description)") : ""),
|
|
542
541
|
...agentOp.desc.map((line, i) => h(Text, { key: `desc-${i}`, ...cx("normal", "truncate", dim && "dimmed") }, line))
|
|
543
542
|
], true));
|
|
544
|
-
const info = h(Box, { key: "info", height: AGENT_INFO_H, flexShrink: 0 }, pane("process", "Process", () => [
|
|
545
|
-
field("host:", clean(
|
|
546
|
-
field("pid:", String(
|
|
547
|
-
field("uid:", clean(agentUser(
|
|
548
|
-
field("cwd:", clean(
|
|
549
|
-
], true, true), pane("accounting", "Accounting", () => [
|
|
550
|
-
field("uptime:", agentUptime(
|
|
551
|
-
field("heartbeat:", `${agentDuration(now -
|
|
552
|
-
field("usage (total):", agentUsage(
|
|
553
|
-
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)
|
|
554
553
|
], false, true));
|
|
555
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)] :
|
|
556
555
|
lines.length === 0 ? [h(Text, { key: "none", ...cx("dim", dim && "dimmed") }, "(no processing log lines yet)")] :
|
|
@@ -570,8 +569,8 @@ export const renderAgents = (ctx) => {
|
|
|
570
569
|
];
|
|
571
570
|
};
|
|
572
571
|
/* the heights of the buttons of the time ranges (with a blank line below them), and of the
|
|
573
|
-
frames "Task Progress" (and "Open Tasks"
|
|
574
|
-
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
|
|
575
574
|
with an inner blank line at its top and bottom (except for the column charts), of the
|
|
576
575
|
statistics view */
|
|
577
576
|
const RANGES_H = 2;
|
|
@@ -619,15 +618,60 @@ const statsBar = (value, max, width) => {
|
|
|
619
618
|
const n = max > 0 ? Math.round(value / max * width) : 0;
|
|
620
619
|
return "█".repeat(n).padEnd(width, "░");
|
|
621
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
|
+
};
|
|
622
666
|
/* render the statistics view: the framed column chart "Tasks per State" of the numbers of tasks per
|
|
623
667
|
lifecycle state (labeled by state, its effort, and phase, in the colors of their lanes, with the framed column
|
|
624
|
-
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
|
|
625
669
|
"Task Progress" of the cancelled, finished, work-in-progress, and open tasks (with the framed
|
|
626
|
-
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"
|
|
627
671
|
of the total times, tokens, and costs of the tasks (with the framed column chart "Effort"
|
|
628
|
-
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
|
|
629
673
|
tasks of the highest usage (with the framed "Finished Tasks" of the usage per finished task and
|
|
630
|
-
the framed "Efficiency" of the AI usage
|
|
674
|
+
the framed "Efficiency" of the AI usage beside it), below the buttons of the time ranges */
|
|
631
675
|
export const renderStats = (ctx) => {
|
|
632
676
|
const { stats, usage, range, rangeRef, columns, boardH, dim, mouse, filter, agents } = ctx;
|
|
633
677
|
const innerW = Math.max(20, columns - 6);
|
|
@@ -640,55 +684,15 @@ export const renderStats = (ctx) => {
|
|
|
640
684
|
/* the number of columns the frames "Finished Tasks" and "Efficiency" are each grown by
|
|
641
685
|
(at the expense of the frame "Top-5 Tasks") */
|
|
642
686
|
const avgGrow = 4;
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
const frame = frameOf(false);
|
|
646
|
-
const accentFrame = frameOf(true);
|
|
687
|
+
const frame = statsFrame(dim, false);
|
|
688
|
+
const accentFrame = statsFrame(dim, true);
|
|
647
689
|
if (stats === null)
|
|
648
690
|
return [h(Box, { key: "stats", height: boardH }), h(Text, { key: "info" }, " "), h(Text, { key: "keys1" }, " "), h(Text, { key: "keys2" }, " ")];
|
|
649
|
-
/*
|
|
650
|
-
for its number, each number sits on top of its column (being at least a thin line,
|
|
651
|
-
even without any number, like in the web board), the columns stand on a blank line above
|
|
652
|
-
their labels (each cut to its first five characters, without an ellipsis, for
|
|
653
|
-
readability) and their sub-labels below them (like the effort of a state, cut with
|
|
654
|
-
an ellipsis, and blank if empty, to keep the columns of all charts aligned), and each
|
|
655
|
-
group of columns is labeled below its line spanning its columns
|
|
656
|
-
(like in the web board), followed by a footer line (blank if empty, to keep the columns
|
|
657
|
-
of all charts aligned), where the columns start directly at the left padding of the
|
|
658
|
-
frame, are separated by a gap of one column, and leave the remaining width on the right,
|
|
659
|
-
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 */
|
|
660
692
|
const chartH = Math.max(9, boardH - RANGES_H - PROGRESS_H - USAGE_H - TOP_H);
|
|
661
|
-
const colChart = (key, title, width, cols, groups, footer = "", height = chartH, compact = false) => {
|
|
662
|
-
const areaH = Math.max(1, height - (compact ? 4 : 8));
|
|
663
|
-
const colW = Math.max(2, Math.floor((width + 1) / cols.length));
|
|
664
|
-
const lineW = colW - 1;
|
|
665
|
-
const fitW = Math.max(1, Math.min(14, lineW));
|
|
666
|
-
const barW = fitW > 1 && (lineW - fitW) % 2 !== 0 ? fitW - 1 : fitW;
|
|
667
|
-
const barL = Math.floor((lineW - barW) / 2);
|
|
668
|
-
const max = Math.max(1, ...cols.map((c) => c.count));
|
|
669
|
-
const gapOf = (i, n) => i < n - 1 ? " " : "";
|
|
670
|
-
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));
|
|
671
|
-
const rows = Array.from({ length: areaH }, (_x, r) => {
|
|
672
|
-
const level = areaH - 1 - r;
|
|
673
|
-
return h(Box, { key: `row${r}` }, h(Text, { wrap: "truncate" }, ...cols.map((c, i) => {
|
|
674
|
-
const fill = Math.max(1, Math.round(c.count / max * (areaH - 1) * 8));
|
|
675
|
-
const cell = Math.max(0, Math.min(8, fill - level * 8));
|
|
676
|
-
const text = String(c.count);
|
|
677
|
-
if (cell === 0 && level === Math.ceil(fill / 8) && text.length > barW)
|
|
678
|
-
return h(Text, { key: i, color: tint(c.tone), dimColor: dim }, center(text, lineW) + gapOf(i, cols.length));
|
|
679
|
-
return column(i, c.tone, cell > 0 ? lowerEighths[cell].repeat(barW) :
|
|
680
|
-
level === Math.ceil(fill / 8) ? center(text, barW) : " ".repeat(barW));
|
|
681
|
-
})));
|
|
682
|
-
});
|
|
683
|
-
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)))));
|
|
684
|
-
const foot = h(Box, { key: "footer" }, h(Text, { ...cx("dim", dim && "dimmed"), wrap: "truncate" }, center(footer, Math.max(0, cols.length * colW - 1))));
|
|
685
|
-
if (compact)
|
|
686
|
-
return frame(key, height, title, 0, ...rows, labels, foot);
|
|
687
|
-
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);
|
|
688
|
-
};
|
|
689
693
|
/* the column chart of the number of tasks per state (labeled by the states and below by the
|
|
690
694
|
effort of their estimated tasks, grouped by their phases, and with the total number of tasks
|
|
691
|
-
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
|
|
692
696
|
their week numbers, prefixed with "W" if fitting, and grouped by all weeks with the total
|
|
693
697
|
and average number of finished tasks, and with the forecast of the backlog as its footer),
|
|
694
698
|
grown like the frames "Finished Tasks" and "Efficiency" together, to align with them */
|
|
@@ -704,10 +708,10 @@ export const renderStats = (ctx) => {
|
|
|
704
708
|
const weekW = Math.max(2, Math.floor(weeksW / Math.max(1, stats.throughput.length))) - 1;
|
|
705
709
|
const weekSum = stats.throughput.reduce((n, w) => n + w.count, 0);
|
|
706
710
|
const weekAvg = stats.throughput.length > 0 ? weekSum / stats.throughput.length : 0;
|
|
707
|
-
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) => ({
|
|
708
712
|
label: s.status, count: s.count, tone: s.tone,
|
|
709
713
|
sub: s.effort.estimated > 0 ? statsHoursN(s.effort.minutes) : ""
|
|
710
|
-
})), 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);
|
|
711
715
|
/* a framed stacked bar of the numbers of tasks of parts, with its legend */
|
|
712
716
|
const stacked = (key, title, width, parts) => {
|
|
713
717
|
const total = parts.reduce((n, p) => n + p.count, 0);
|
|
@@ -785,9 +789,9 @@ export const renderStats = (ctx) => {
|
|
|
785
789
|
[h(Text, { key: "loading", ...cx("dim", dim && "dimmed") }, "loading …")] : [head, ...lines]));
|
|
786
790
|
/* the compact column chart of the distribution of the effort sizes (the numbers of the
|
|
787
791
|
estimated tasks per size, from the smallest to the largest size), with the numbers of
|
|
788
|
-
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 */
|
|
789
793
|
const effort = stats.states.reduce((sum, s) => ({ estimated: sum.estimated + s.effort.estimated, tasks: sum.tasks + s.effort.tasks }), { estimated: 0, tasks: 0 });
|
|
790
|
-
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);
|
|
791
795
|
/* the table of the tasks of the highest usage: the id (inverse behind "≡", in the color of
|
|
792
796
|
its lane), the title (cut with an ellipsis), a bar of its share of the total costs (resp.
|
|
793
797
|
tokens), the costs, and the input and output tokens (as "<input> ▷ <output>", with
|
|
@@ -808,7 +812,7 @@ export const renderStats = (ctx) => {
|
|
|
808
812
|
stats.top.length === 0 ? [h(Text, { key: "none", ...cx("dim", dim && "dimmed") }, "no usage")] :
|
|
809
813
|
[topHead, ...stats.top.map((t, i) => {
|
|
810
814
|
const id = fit(t.id, idW - 4).trimEnd();
|
|
811
|
-
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 +
|
|
812
816
|
`${statsTokens(t.input).padStart(inW)} ▷ ${statsTokens(t.output).padStart(outW)}`.padStart(tokW));
|
|
813
817
|
})];
|
|
814
818
|
/* the averages and lead times of the finished tasks and the efficiency of the AI usage (the
|