@rse/ase 1.0.7 → 2.0.1
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-api.d.ts +184 -0
- package/dst/ase-agent-definition-api.js +6 -0
- package/dst/ase-agent-definition-builtin/condenser.js +42 -0
- package/dst/ase-agent-definition-builtin/editor.js +45 -0
- package/dst/ase-agent-definition-builtin/estimator.js +98 -0
- package/dst/ase-agent-definition-builtin/implementer.js +121 -0
- package/dst/ase-agent-definition-builtin/integrator.js +56 -0
- package/dst/ase-agent-definition-builtin/qualifier.js +61 -0
- package/dst/ase-agent-definition-builtin/rebooter.js +41 -0
- package/dst/ase-agent-definition-builtin/researcher.js +77 -0
- package/dst/ase-agent-definition-core.js +827 -0
- package/dst/ase-agent-fleet.js +772 -0
- package/dst/ase-agent-process.js +232 -0
- package/dst/ase-agent-relay-client.js +509 -0
- package/dst/ase-agent-relay-core.js +213 -0
- package/dst/ase-agent-relay-server-bind.js +242 -0
- package/dst/ase-agent-relay-server-cli.js +50 -0
- package/dst/ase-agent-runtime-claim.js +189 -0
- package/dst/ase-agent-runtime-engine.js +1100 -0
- package/dst/ase-agent-runtime-exec.js +275 -0
- package/dst/ase-agent-runtime-phase.js +581 -0
- package/dst/ase-agent-runtime-request.js +272 -0
- package/dst/ase-agent-runtime-tool.js +207 -0
- package/dst/ase-agent-runtime.js +1316 -0
- package/dst/ase-agent.js +65 -0
- package/dst/ase-artifact.js +6 -5
- package/dst/ase-config-core.js +465 -0
- package/dst/ase-config-schema.js +357 -0
- package/dst/ase-config-scope.js +172 -0
- package/dst/ase-config.js +25 -585
- package/dst/ase-hook.js +228 -111
- package/dst/ase-lib-daemon-cli.js +130 -0
- package/dst/ase-lib-hash.js +20 -0
- package/dst/{ase-log.js → ase-lib-log.js} +40 -7
- package/dst/ase-lib-server-bind.js +115 -0
- package/dst/ase-lib-server-cli.js +474 -0
- package/dst/{ase-table.js → ase-lib-table.js} +1 -1
- package/dst/{ase-version.js → ase-lib-version.js} +8 -1
- package/dst/ase-man.js +96 -0
- package/dst/ase-mcp.js +166 -13
- package/dst/{ase-getopt.js → ase-service-getopt.js} +82 -80
- package/dst/{ase-markdown.js → ase-service-markdown.js} +18 -11
- package/dst/ase-service-otel.js +1246 -0
- package/dst/{ase-skills.js → ase-service-skills.js} +29 -64
- package/dst/ase-service-state.js +101 -0
- package/dst/ase-service.js +269 -81
- package/dst/ase-setup-common.js +141 -0
- package/dst/ase-setup-mcp.js +359 -0
- package/dst/ase-setup-settings.js +837 -0
- package/dst/ase-setup.js +113 -869
- package/dst/ase-spec.js +31 -19
- package/dst/ase-statusline.js +108 -43
- package/dst/ase-task-board-core.js +739 -0
- package/dst/ase-task-board-filter.js +127 -0
- package/dst/ase-task-board-flow.js +237 -0
- package/dst/ase-task-board-graph.js +497 -0
- package/dst/ase-task-board-stats.js +263 -0
- package/dst/ase-task-board-tui-control.js +775 -0
- package/dst/ase-task-board-tui-model.js +1008 -0
- package/dst/ase-task-board-tui-popup.js +626 -0
- package/dst/ase-task-board-tui-style.js +106 -0
- package/dst/ase-task-board-tui-view.js +847 -0
- package/dst/ase-task-board-tui.js +189 -0
- package/dst/ase-task-board-web-client.css +1 -0
- package/dst/ase-task-board-web-client.html +20 -0
- package/dst/ase-task-board-web-client.js +81 -0
- package/dst/ase-task-board-web-server.js +823 -0
- package/dst/ase-task-board.js +134 -0
- package/dst/ase-task-format.js +757 -0
- package/dst/ase-task-request.js +208 -0
- package/dst/ase-task-store-client.js +495 -0
- package/dst/ase-task-store-core.js +804 -0
- package/dst/ase-task-store-issues.js +237 -0
- package/dst/ase-task-store-plugin-api.js +6 -0
- package/dst/ase-task-store-plugin-ase.js +484 -0
- package/dst/ase-task-store-plugin-delegate.js +137 -0
- package/dst/ase-task-store-plugin-gitea.js +666 -0
- package/dst/ase-task-store-plugin-github.js +696 -0
- package/dst/ase-task-store-plugin-gitlab.js +668 -0
- package/dst/ase-task-store-server-bind.js +538 -0
- package/dst/ase-task-store-server-cli.js +154 -0
- package/dst/ase-task.js +1335 -357
- package/dst/{ase-compat.js → ase-util-compat.js} +2 -2
- package/dst/{ase-diagram.js → ase-util-diagram.js} +107 -32
- package/dst/{ase-meta.js → ase-util-meta.js} +2 -2
- package/dst/{ase-metric.js → ase-util-metric.js} +2 -2
- package/dst/{ase-mint.js → ase-util-mint.js} +19 -12
- package/dst/{ase-worktree.js → ase-util-worktree.js} +30 -19
- package/dst/ase-util.js +20 -0
- package/dst/ase.js +108 -32
- package/dst/man/ase-agent-adhoc.1 +515 -0
- package/dst/man/ase-agent-condenser.1 +193 -0
- package/dst/man/ase-agent-editor.1 +212 -0
- package/dst/man/ase-agent-estimator.1 +214 -0
- package/dst/man/ase-agent-fleet.1 +185 -0
- package/dst/man/ase-agent-implementer.1 +233 -0
- package/dst/man/ase-agent-integrator.1 +213 -0
- package/dst/man/ase-agent-qualifier.1 +214 -0
- package/dst/man/ase-agent-rebooter.1 +193 -0
- package/dst/man/ase-agent-relay.1 +128 -0
- package/dst/man/ase-agent-researcher.1 +217 -0
- package/dst/man/ase-agent.1 +264 -0
- package/dst/man/ase-artifact-list.1 +38 -0
- package/dst/man/ase-artifact-name.1 +39 -0
- package/dst/man/ase-artifact.1 +56 -0
- package/dst/man/ase-config-delete.1 +45 -0
- package/dst/man/ase-config-edit.1 +48 -0
- package/dst/man/ase-config-get.1 +46 -0
- package/dst/man/ase-config-init.1 +60 -0
- package/dst/man/ase-config-list.1 +42 -0
- package/dst/man/ase-config-set.1 +52 -0
- package/dst/man/ase-config.1 +231 -0
- package/dst/man/ase-hook-permission-request.1 +36 -0
- package/dst/man/ase-hook-pre-tool-use.1 +59 -0
- package/dst/man/ase-hook-session-end.1 +47 -0
- package/dst/man/ase-hook-session-persona.1 +37 -0
- package/dst/man/ase-hook-session-start.1 +81 -0
- package/dst/man/ase-hook-stop.1 +34 -0
- package/dst/man/ase-hook-user-prompt-submit.1 +34 -0
- package/dst/man/ase-hook.1 +51 -0
- package/dst/man/ase-man.1 +67 -0
- package/dst/man/ase-mcp.1 +61 -0
- package/dst/man/ase-service-log.1 +43 -0
- package/dst/man/ase-service-send.1 +47 -0
- package/dst/man/ase-service-start.1 +54 -0
- package/dst/man/ase-service-status.1 +46 -0
- package/dst/man/ase-service-stop.1 +37 -0
- package/dst/man/ase-service.1 +85 -0
- package/dst/man/ase-setup-disable.1 +44 -0
- package/dst/man/ase-setup-enable.1 +45 -0
- package/dst/man/ase-setup-install.1 +73 -0
- package/dst/man/ase-setup-mcp.1 +124 -0
- package/dst/man/ase-setup-status.1 +60 -0
- package/dst/man/ase-setup-statusline.1 +92 -0
- package/dst/man/ase-setup-telemetry.1 +92 -0
- package/dst/man/ase-setup-uninstall.1 +59 -0
- package/dst/man/ase-setup-update.1 +64 -0
- package/dst/man/ase-setup.1 +93 -0
- package/dst/man/ase-spec-export.1 +62 -0
- package/dst/man/ase-spec-lint.1 +40 -0
- package/dst/man/ase-spec-preview.1 +40 -0
- package/dst/man/ase-spec.1 +46 -0
- package/dst/man/ase-statusline.1 +189 -0
- package/dst/man/ase-task-assign.1 +71 -0
- package/dst/man/ase-task-board.1 +384 -0
- package/dst/man/ase-task-delete.1 +35 -0
- package/dst/man/ase-task-edit.1 +46 -0
- package/dst/man/ase-task-idscheme.1 +65 -0
- package/dst/man/ase-task-lifecycle.1 +57 -0
- package/dst/man/ase-task-list.1 +47 -0
- package/dst/man/ase-task-load.1 +36 -0
- package/dst/man/ase-task-newid.1 +61 -0
- package/dst/man/ase-task-purge.1 +37 -0
- package/dst/man/ase-task-rename.1 +44 -0
- package/dst/man/ase-task-request.1 +61 -0
- package/dst/man/ase-task-save.1 +48 -0
- package/dst/man/ase-task-status.1 +55 -0
- package/dst/man/ase-task-store.1 +194 -0
- package/dst/man/ase-task-view.1 +40 -0
- package/dst/man/ase-task.1 +160 -0
- package/dst/man/ase-util-compat.1 +25 -0
- package/dst/man/ase-util-diagram.1 +76 -0
- package/dst/man/ase-util-meta.1 +45 -0
- package/dst/man/ase-util-metric.1 +54 -0
- package/dst/man/ase-util-mint.1 +59 -0
- package/dst/man/ase-util-worktree.1 +72 -0
- package/dst/man/ase-util.1 +50 -0
- package/dst/man/ase.1 +124 -0
- package/package.json +81 -21
- 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/agents/ase-meta-diagram.md +12 -8
- package/plugin/agents/ase-repo-review.md +322 -0
- package/plugin/hooks/hooks-codex.json +5 -0
- package/plugin/hooks/hooks-copilot.json +5 -0
- package/plugin/hooks/hooks.json +8 -0
- package/plugin/meta/ase-common-approach.md +117 -0
- package/plugin/meta/ase-common-changeset.md +256 -0
- package/plugin/meta/ase-common-code.md +10 -173
- package/plugin/meta/ase-common-grill.md +317 -0
- package/plugin/meta/ase-common-resolve.md +305 -0
- package/plugin/meta/ase-common-task.md +83 -4
- package/plugin/meta/ase-constitution.md +0 -2
- package/plugin/meta/ase-format-specbook.md +208 -25
- package/plugin/meta/ase-format-specbook.yaml +242 -851
- package/plugin/meta/ase-format-task.md +669 -125
- package/plugin/meta/ase-persona.md +3 -4
- package/plugin/meta/ase-skill.md +9 -0
- package/plugin/mods/ase-mods.ts +409 -0
- package/plugin/mods/claude-code.d.ts +13417 -0
- package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
- package/plugin/skills/ase-arch-analyze/help.md +2 -2
- package/plugin/skills/ase-code-analyze/SKILL.md +59 -8
- package/plugin/skills/ase-code-analyze/help.md +29 -4
- package/plugin/skills/ase-code-edit/SKILL.md +107 -181
- package/plugin/skills/ase-code-edit/help.md +118 -31
- package/plugin/skills/ase-code-lint/SKILL.md +64 -13
- package/plugin/skills/ase-code-lint/help.md +32 -2
- package/plugin/skills/ase-docs-proofread/SKILL.md +4 -4
- package/plugin/skills/ase-docs-proofread/help.md +1 -1
- package/plugin/skills/ase-docs-refine/SKILL.md +4 -4
- package/plugin/skills/ase-docs-shorten/SKILL.md +4 -4
- package/plugin/skills/ase-help-intent/data.md +2164 -1087
- package/plugin/skills/ase-help-intent/help.md +1 -1
- package/plugin/skills/ase-help-skill/catalog.md +12 -9
- package/plugin/skills/ase-meta-brainstorm/SKILL.md +1 -1
- package/plugin/skills/ase-meta-config/SKILL.md +3 -4
- package/plugin/skills/ase-meta-config/help.md +31 -10
- package/plugin/skills/ase-meta-mint/SKILL.md +5 -5
- package/plugin/skills/ase-meta-mint/help.md +10 -5
- package/plugin/skills/ase-meta-why/help.md +1 -1
- package/plugin/skills/ase-meta-workflow/SKILL.md +7 -7
- package/plugin/skills/ase-meta-workflow/help.md +4 -4
- package/plugin/skills/ase-meta-workflow/sample.md +3 -3
- package/plugin/skills/ase-meta-workflow/workflow.txt +22 -18
- package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/SKILL.md +4 -3
- package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/help.md +5 -5
- package/plugin/skills/{ase-meta-commit → ase-repo-commit}/SKILL.md +6 -6
- package/plugin/skills/{ase-meta-commit → ase-repo-commit}/help.md +5 -5
- package/plugin/skills/{ase-meta-diff → ase-repo-diff}/SKILL.md +6 -6
- package/plugin/skills/{ase-meta-diff → ase-repo-diff}/help.md +8 -8
- package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/SKILL.md +19 -19
- package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/help.md +10 -12
- package/plugin/skills/ase-repo-merge/SKILL.md +346 -0
- package/plugin/skills/ase-repo-merge/help.md +107 -0
- package/plugin/skills/ase-repo-resolve/SKILL.md +158 -0
- package/plugin/skills/ase-repo-resolve/help.md +102 -0
- package/plugin/skills/ase-repo-review/SKILL.md +406 -0
- package/plugin/skills/ase-repo-review/help.md +181 -0
- package/plugin/skills/ase-spec-edit/SKILL.md +117 -183
- package/plugin/skills/ase-spec-edit/help.md +107 -26
- package/plugin/skills/ase-sync-reconcile/help.md +1 -1
- package/plugin/skills/ase-task-assign/SKILL.md +111 -0
- package/plugin/skills/ase-task-assign/help.md +119 -0
- package/plugin/skills/ase-task-condense/SKILL.md +21 -21
- package/plugin/skills/ase-task-condense/help.md +12 -10
- package/plugin/skills/ase-task-delete/SKILL.md +1 -1
- package/plugin/skills/ase-task-dissect/SKILL.md +71 -48
- package/plugin/skills/ase-task-dissect/help.md +10 -9
- package/plugin/skills/ase-task-edit/SKILL.md +379 -94
- package/plugin/skills/ase-task-edit/help.md +95 -22
- package/plugin/skills/ase-task-estimate/SKILL.md +492 -0
- package/plugin/skills/ase-task-estimate/help.md +158 -0
- package/plugin/skills/ase-task-grill/SKILL.md +246 -138
- package/plugin/skills/ase-task-grill/help.md +129 -26
- package/plugin/skills/ase-task-id/SKILL.md +8 -1
- package/plugin/skills/ase-task-id/help.md +6 -3
- package/plugin/skills/ase-task-implement/SKILL.md +535 -145
- package/plugin/skills/ase-task-implement/help.md +138 -47
- package/plugin/skills/ase-task-integrate/SKILL.md +549 -0
- package/plugin/skills/ase-task-integrate/help.md +187 -0
- package/plugin/skills/ase-task-list/SKILL.md +55 -23
- package/plugin/skills/ase-task-list/help.md +45 -21
- package/plugin/skills/ase-task-reboot/SKILL.md +84 -43
- package/plugin/skills/ase-task-reboot/help.md +28 -8
- package/plugin/skills/ase-task-rename/SKILL.md +10 -2
- package/plugin/skills/ase-task-research/SKILL.md +386 -0
- package/plugin/skills/ase-task-research/help.md +102 -0
- package/plugin/skills/ase-task-status/SKILL.md +137 -0
- package/plugin/skills/ase-task-status/help.md +80 -0
- package/plugin/skills/ase-task-view/SKILL.md +49 -24
- package/plugin/skills/ase-task-view/help.md +19 -9
- package/plugin/agents/ase-meta-review.md +0 -187
- package/plugin/skills/ase-code-craft/SKILL.md +0 -264
- package/plugin/skills/ase-code-craft/help.md +0 -122
- package/plugin/skills/ase-code-refactor/SKILL.md +0 -232
- package/plugin/skills/ase-code-refactor/help.md +0 -109
- package/plugin/skills/ase-code-resolve/SKILL.md +0 -289
- package/plugin/skills/ase-code-resolve/help.md +0 -116
- package/plugin/skills/ase-meta-review/SKILL.md +0 -210
- package/plugin/skills/ase-meta-review/help.md +0 -96
- package/plugin/skills/ase-task-preflight/SKILL.md +0 -166
- package/plugin/skills/ase-task-preflight/help.md +0 -82
- /package/dst/{ase-ignore.js → ase-lib-ignore.js} +0 -0
- /package/dst/{ase-stdio.js → ase-lib-stdio.js} +0 -0
- /package/dst/{ase-kv.js → ase-service-kv.js} +0 -0
- /package/dst/{ase-sleep.js → ase-service-sleep.js} +0 -0
- /package/dst/{ase-timestamp.js → ase-service-timestamp.js} +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
|
+
*/
|
|
6
|
+
import { DateTime } from "luxon";
|
|
7
|
+
import { Config } from "./ase-config-core.js";
|
|
8
|
+
import { configSchema } from "./ase-config-schema.js";
|
|
9
|
+
import * as TaskFormat from "./ase-task-format.js";
|
|
10
|
+
export const statsRanges = ["total", "30d", "7d"];
|
|
11
|
+
/* the labels of the time ranges (for their buttons) */
|
|
12
|
+
export const statsRangeLabels = {
|
|
13
|
+
"total": "total",
|
|
14
|
+
"30d": "within last 30 days",
|
|
15
|
+
"7d": "within last 7 days"
|
|
16
|
+
};
|
|
17
|
+
/* the label of a time range (for its button), where the time range "total" is
|
|
18
|
+
suffixed with its number of days */
|
|
19
|
+
export const statsRangeLabel = (range, days) => range === "total" ? `${statsRangeLabels[range]} (${days} day${days === 1 ? "" : "s"})` : statsRangeLabels[range];
|
|
20
|
+
/* the number of tasks of the highest usage */
|
|
21
|
+
const TOP_N = 5;
|
|
22
|
+
/* the number of weeks of the throughput (a quarter) */
|
|
23
|
+
const WEEKS_N = 12;
|
|
24
|
+
/* the fields of the AI usage, by the amounts of the usage attachment */
|
|
25
|
+
const usageFields = [
|
|
26
|
+
["timeAI", "time.ai"], ["timeHR", "time.hr"], ["timeReal", "time.real"],
|
|
27
|
+
["cread", "token.cread"], ["cwrite", "token.cwrite"], ["input", "token.input"], ["output", "token.output"],
|
|
28
|
+
["costAI", "cost.ai"], ["costHR", "cost.hr"]
|
|
29
|
+
];
|
|
30
|
+
/* the scale of the effort sizes (the upper band bounds of "XS" to "L" in human minutes) of the
|
|
31
|
+
"project.task.effort.scale" configuration, else (if unset or invalid) of the default one */
|
|
32
|
+
export const effortScale = (log) => {
|
|
33
|
+
try {
|
|
34
|
+
const cfg = new Config("config", configSchema, log);
|
|
35
|
+
cfg.read();
|
|
36
|
+
const value = cfg.get("project.task.effort.scale");
|
|
37
|
+
return TaskFormat.parseEffortScale(typeof value === "string" ? value : TaskFormat.EFFORT_SCALE);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return TaskFormat.parseEffortScale(TaskFormat.EFFORT_SCALE);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/* parse a timestamp of a task (in milliseconds, or NaN if absent or unparsable) */
|
|
44
|
+
const timeOf = (text) => {
|
|
45
|
+
const dt = DateTime.fromFormat(text.trim(), "yyyy-LL-dd HH:mm");
|
|
46
|
+
return dt.isValid ? dt.toMillis() : DateTime.fromISO(text.trim()).toMillis();
|
|
47
|
+
};
|
|
48
|
+
/* the share of an activity period (from its creation to its last modification) within the time
|
|
49
|
+
range of the statistics (from now back to its start): null if it does not overlap with the time
|
|
50
|
+
range, else its overlapping part (where a period without extent overlaps entirely) */
|
|
51
|
+
const shareOf = (createdAt, modifiedAt, range, now) => {
|
|
52
|
+
if (range === "total")
|
|
53
|
+
return 1;
|
|
54
|
+
const from = now - (range === "7d" ? 7 : 30) * 24 * 60 * 60 * 1000;
|
|
55
|
+
let created = timeOf(createdAt);
|
|
56
|
+
let modified = timeOf(modifiedAt);
|
|
57
|
+
if (Number.isNaN(created))
|
|
58
|
+
created = modified;
|
|
59
|
+
if (Number.isNaN(modified))
|
|
60
|
+
modified = created;
|
|
61
|
+
if (Number.isNaN(created) || modified < from || Math.min(created, modified) > now)
|
|
62
|
+
return null;
|
|
63
|
+
if (modified <= created)
|
|
64
|
+
return 1;
|
|
65
|
+
return Math.max(0, Math.min(modified, now) - Math.max(created, from)) / (modified - created);
|
|
66
|
+
};
|
|
67
|
+
/* the throughput of the last weeks: the finished tasks per (ISO) week, by their last
|
|
68
|
+
modification as the (approximated) time of their completion */
|
|
69
|
+
const throughputOf = (board, kindOf, now) => {
|
|
70
|
+
const thisWeek = DateTime.fromMillis(now).startOf("week");
|
|
71
|
+
const throughput = Array.from({ length: WEEKS_N }, (_x, i) => {
|
|
72
|
+
const start = thisWeek.minus({ weeks: WEEKS_N - 1 - i });
|
|
73
|
+
return { week: start.weekNumber, start: start.toFormat("yyyy-LL-dd"), count: 0 };
|
|
74
|
+
});
|
|
75
|
+
for (const card of board.cards.values()) {
|
|
76
|
+
if (board.context.has(card.id) || kindOf.get(card.status) !== "finished")
|
|
77
|
+
continue;
|
|
78
|
+
const time = timeOf(card.modified);
|
|
79
|
+
if (Number.isNaN(time) || time > now)
|
|
80
|
+
continue;
|
|
81
|
+
const ago = Math.round(thisWeek.diff(DateTime.fromMillis(time).startOf("week"), "weeks").weeks);
|
|
82
|
+
if (ago >= 0 && ago < WEEKS_N)
|
|
83
|
+
throughput[WEEKS_N - 1 - ago].count++;
|
|
84
|
+
}
|
|
85
|
+
return throughput;
|
|
86
|
+
};
|
|
87
|
+
/* the number of days from the creation (resp. the last modification without any creation)
|
|
88
|
+
of the oldest task until now (zero without any task) */
|
|
89
|
+
const daysOf = (board, now) => {
|
|
90
|
+
let oldest = now;
|
|
91
|
+
for (const card of board.cards.values()) {
|
|
92
|
+
if (board.context.has(card.id))
|
|
93
|
+
continue;
|
|
94
|
+
let time = timeOf(card.created);
|
|
95
|
+
if (Number.isNaN(time))
|
|
96
|
+
time = timeOf(card.modified);
|
|
97
|
+
if (!Number.isNaN(time))
|
|
98
|
+
oldest = Math.min(oldest, time);
|
|
99
|
+
}
|
|
100
|
+
return Math.ceil((now - oldest) / (24 * 60 * 60 * 1000));
|
|
101
|
+
};
|
|
102
|
+
/* the lead times of the finished tasks <within> the time range (in minutes, sorted ascending),
|
|
103
|
+
by their last modification as the (approximated) time of their completion (like the throughput) */
|
|
104
|
+
const leadsOf = (board, within, kindOf) => {
|
|
105
|
+
const leads = [];
|
|
106
|
+
for (const id of within) {
|
|
107
|
+
const card = board.cards.get(id);
|
|
108
|
+
if (kindOf.get(card.status) !== "finished")
|
|
109
|
+
continue;
|
|
110
|
+
const created = timeOf(card.created);
|
|
111
|
+
const modified = timeOf(card.modified);
|
|
112
|
+
if (!Number.isNaN(created) && !Number.isNaN(modified) && modified >= created)
|
|
113
|
+
leads.push((modified - created) / (60 * 1000));
|
|
114
|
+
}
|
|
115
|
+
return leads.sort((a, b) => a - b);
|
|
116
|
+
};
|
|
117
|
+
/* a percentile <p> (0-1) of the ascending <values> (linearly interpolated
|
|
118
|
+
between their closest ranks), or null without any values */
|
|
119
|
+
const percentileOf = (values, p) => {
|
|
120
|
+
if (values.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
const pos = (values.length - 1) * p;
|
|
123
|
+
const low = Math.floor(pos);
|
|
124
|
+
const high = Math.ceil(pos);
|
|
125
|
+
return Math.round(values[low] + (values[high] - values[low]) * (pos - low));
|
|
126
|
+
};
|
|
127
|
+
/* compute the statistics of the (possibly filtered) tasks of a board within a time range, where
|
|
128
|
+
a task counts if the period of its plan (its "Created" and "Modified" keys) overlaps with the
|
|
129
|
+
time range, and its usage counts with the overlapping part of the period of its usage attachment
|
|
130
|
+
(its "Created" and "Modified" keys) -- as the usage attachment does not track when the usage
|
|
131
|
+
accrued, the usage is taken as being evenly spread over this period, i.e., the usage within a
|
|
132
|
+
time range other than "total" is just an approximation, and where the effort of a task counts
|
|
133
|
+
with the representative human minutes of its effort size on a scale (see "effortScale"), and
|
|
134
|
+
where the readiness of the tasks is determined on the unfiltered board "all" */
|
|
135
|
+
export const statsOf = (board, all, usage, range = "total", scale = TaskFormat.parseEffortScale(TaskFormat.EFFORT_SCALE), now = Date.now()) => {
|
|
136
|
+
/* the tasks within the time range */
|
|
137
|
+
const within = new Set();
|
|
138
|
+
for (const card of board.cards.values())
|
|
139
|
+
if (!board.context.has(card.id) && shareOf(card.created, card.modified, range, now) !== null)
|
|
140
|
+
within.add(card.id);
|
|
141
|
+
/* the effort of tasks within the time range (without the explicit epics) */
|
|
142
|
+
const effortOf = (cards) => {
|
|
143
|
+
const sum = { minutes: 0, estimated: 0, tasks: 0 };
|
|
144
|
+
for (const card of cards) {
|
|
145
|
+
if (!within.has(card.id) || card.group === card.id)
|
|
146
|
+
continue;
|
|
147
|
+
sum.tasks++;
|
|
148
|
+
if (card.effort !== "") {
|
|
149
|
+
sum.estimated++;
|
|
150
|
+
sum.minutes += TaskFormat.effortMinutes(card.effort, scale);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return sum;
|
|
154
|
+
};
|
|
155
|
+
/* the tasks per state and their progress and effort,
|
|
156
|
+
and the tones and categories of the lanes */
|
|
157
|
+
const states = [];
|
|
158
|
+
const progress = { finished: 0, cancelled: 0, wip: 0, open: 0 };
|
|
159
|
+
const toneOf = new Map();
|
|
160
|
+
const kindOf = new Map();
|
|
161
|
+
for (const g of board.groups) {
|
|
162
|
+
for (const l of g.lanes) {
|
|
163
|
+
const tone = l.kind === "terminal" ? "done" : l.active ? "active" : "idle";
|
|
164
|
+
const kind = l.kind === "terminal" ? (l.status === "CANCELLED" ? "cancelled" : "finished") : l.active ? "wip" : "open";
|
|
165
|
+
const cards = board.lanes.get(l.status) ?? [];
|
|
166
|
+
const count = cards.filter((c) => within.has(c.id)).length;
|
|
167
|
+
states.push({ phase: g.title, status: l.status, count, tone, effort: effortOf(cards) });
|
|
168
|
+
progress[kind] += count;
|
|
169
|
+
toneOf.set(l.status, tone);
|
|
170
|
+
kindOf.set(l.status, kind);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/* the readiness of the open tasks, by the states of their (known) predecessors
|
|
174
|
+
(independent of the time range and the filter, as they block the tasks nevertheless) */
|
|
175
|
+
const done = (id) => {
|
|
176
|
+
const card = all.cards.get(id);
|
|
177
|
+
return card === undefined || toneOf.get(card.status) === "done";
|
|
178
|
+
};
|
|
179
|
+
const readiness = { ready: 0, blocked: 0 };
|
|
180
|
+
for (const id of within)
|
|
181
|
+
if (kindOf.get(board.cards.get(id).status) === "open")
|
|
182
|
+
readiness[(all.pred.get(id) ?? []).every(done) ? "ready" : "blocked"]++;
|
|
183
|
+
/* the distribution of the effort sizes of the estimated tasks within the time range
|
|
184
|
+
(without the explicit epics, like their effort), from the smallest to the largest size */
|
|
185
|
+
const sizes = TaskFormat.effortSizes.map((size) => ({ size, count: 0 }));
|
|
186
|
+
for (const id of within) {
|
|
187
|
+
const card = board.cards.get(id);
|
|
188
|
+
if (card.effort !== "" && card.group !== card.id)
|
|
189
|
+
sizes[TaskFormat.effortSizes.indexOf(card.effort)].count++;
|
|
190
|
+
}
|
|
191
|
+
/* the total AI usage of the tasks (proportionally to the overlapping parts of their usage
|
|
192
|
+
periods), the usage of each task, and the total usage of the finished tasks */
|
|
193
|
+
const zero = () => ({ timeAI: 0, timeHR: 0, timeReal: 0, cread: 0, cwrite: 0, input: 0, output: 0, costAI: 0, costHR: 0 });
|
|
194
|
+
const total = zero();
|
|
195
|
+
const finished = zero();
|
|
196
|
+
const tops = [];
|
|
197
|
+
for (const card of board.cards.values()) {
|
|
198
|
+
const u = usage.get(card.id);
|
|
199
|
+
if (u === undefined || board.context.has(card.id))
|
|
200
|
+
continue;
|
|
201
|
+
const share = shareOf(u.period.created, u.period.modified, range, now);
|
|
202
|
+
if (share === null)
|
|
203
|
+
continue;
|
|
204
|
+
const task = zero();
|
|
205
|
+
for (const [field, key] of usageFields)
|
|
206
|
+
task[field] = (u.usage[key] ?? 0) * share;
|
|
207
|
+
for (const [field] of usageFields) {
|
|
208
|
+
total[field] += task[field];
|
|
209
|
+
if (within.has(card.id) && kindOf.get(card.status) === "finished")
|
|
210
|
+
finished[field] += task[field];
|
|
211
|
+
}
|
|
212
|
+
const input = Math.round(task.input);
|
|
213
|
+
const output = Math.round(task.output);
|
|
214
|
+
const cost = Number((task.costAI + task.costHR).toFixed(4));
|
|
215
|
+
if (input + output > 0 || cost > 0)
|
|
216
|
+
tops.push({ id: card.id, title: card.title, tone: toneOf.get(card.status) ?? "idle", cost, input, output, share: 0 });
|
|
217
|
+
}
|
|
218
|
+
for (const [field, key] of usageFields)
|
|
219
|
+
total[field] = Number(total[field].toFixed(key.startsWith("cost.") ? 4 : 0));
|
|
220
|
+
/* the tasks of the highest usage: by their costs, then by their input and output
|
|
221
|
+
tokens, with their shares of the total costs (resp. input and output tokens) */
|
|
222
|
+
const top = tops.sort((a, b) => b.cost - a.cost || (b.input + b.output) - (a.input + a.output)).slice(0, TOP_N);
|
|
223
|
+
const costs = total.costAI + total.costHR;
|
|
224
|
+
const tokens = total.input + total.output;
|
|
225
|
+
for (const t of top)
|
|
226
|
+
t.share = costs > 0 ? t.cost / costs : tokens > 0 ? (t.input + t.output) / tokens : 0;
|
|
227
|
+
/* the lead times of the finished tasks (see "leadsOf") */
|
|
228
|
+
const leads = leadsOf(board, within, kindOf);
|
|
229
|
+
/* the efficiency of the AI usage */
|
|
230
|
+
const n = progress.finished;
|
|
231
|
+
const inputs = total.cread + total.cwrite + total.input;
|
|
232
|
+
const efficiency = {
|
|
233
|
+
timeRatio: total.timeHR > 0 ? Number((total.timeAI / total.timeHR).toFixed(2)) : null,
|
|
234
|
+
costRatio: total.costHR > 0 ? Number((total.costAI / total.costHR).toFixed(2)) : null,
|
|
235
|
+
cacheRatio: inputs > 0 ? Number((total.cread / inputs).toFixed(4)) : null,
|
|
236
|
+
costAI: n > 0 ? Number((finished.costAI / n).toFixed(4)) : 0,
|
|
237
|
+
costHR: n > 0 ? Number((finished.costHR / n).toFixed(4)) : 0,
|
|
238
|
+
input: n > 0 ? Math.round(finished.input / n) : 0,
|
|
239
|
+
output: n > 0 ? Math.round(finished.output / n) : 0,
|
|
240
|
+
leadMedian: percentileOf(leads, 0.50),
|
|
241
|
+
leadP85: percentileOf(leads, 0.85)
|
|
242
|
+
};
|
|
243
|
+
/* the throughput of the last weeks (independent of the time range,
|
|
244
|
+
as the weeks are a time range on their own) */
|
|
245
|
+
const throughput = throughputOf(board, kindOf, now);
|
|
246
|
+
/* the forecast of the backlog: all unfinished tasks, divided by the average throughput
|
|
247
|
+
of the last weeks (independent of the time range, like the throughput, as all
|
|
248
|
+
unfinished tasks have to be finished nevertheless) */
|
|
249
|
+
let backlog = 0;
|
|
250
|
+
for (const card of board.cards.values()) {
|
|
251
|
+
const kind = kindOf.get(card.status);
|
|
252
|
+
if (!board.context.has(card.id) && (kind === "wip" || kind === "open"))
|
|
253
|
+
backlog++;
|
|
254
|
+
}
|
|
255
|
+
const perWeek = throughput.reduce((sum, w) => sum + w.count, 0) / WEEKS_N;
|
|
256
|
+
const forecast = {
|
|
257
|
+
backlog,
|
|
258
|
+
weeks: backlog === 0 ? 0 : perWeek > 0 ? Number((backlog / perWeek).toFixed(1)) : null
|
|
259
|
+
};
|
|
260
|
+
/* the number of days of the time range "total" */
|
|
261
|
+
const days = daysOf(board, now);
|
|
262
|
+
return { range, days, states, progress, readiness, sizes, usage: total, top, efficiency, throughput, forecast };
|
|
263
|
+
};
|