@bompus/house-rules 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +83 -0
- package/README.md +91 -60
- package/compose.mjs +8 -2
- package/composition.mjs +11 -3
- package/config-view.mjs +18 -3
- package/config.mjs +74 -13
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +85 -9
- package/package.json +4 -2
- package/rules/core.md +118 -209
- package/rules/modifiers/backlog-outline.md +25 -0
- package/rules/modifiers/backlog-visual.md +16 -0
- package/rules/modifiers/coded-offers.md +47 -17
- package/rules/modifiers/cross-session-relay.md +23 -0
- package/rules/modifiers/low-quota-handoff.md +4 -3
- package/rules/modifiers/model-default-questions.md +33 -0
- package/rules/modifiers/multi-agent.md +6 -2
- package/rules/modifiers/no-attribution.md +7 -1
- package/rules/modifiers/periodic-updates.md +25 -0
- package/rules/modifiers/plan-files.md +35 -3
- package/rules/modifiers/release-batching.md +4 -2
- package/rules/modifiers/shared-host-load.md +13 -42
- package/rules/modifiers/squash-landing.md +1 -1
- package/rules/modifiers/upstream-contributions.md +45 -0
- package/rules/modifiers/web-image-formats.md +35 -0
- package/rules/references/backlog-view.md +154 -0
- package/rules/references/landing.md +14 -14
- package/rules/references/multi-agent.md +6 -2
- package/rules/references/release-batching.md +27 -4
- package/rules/references/squash-landing.md +10 -4
- package/rules/references/thread-visuals.md +45 -0
- package/setup.mjs +87 -17
- package/skills/hr-agent-guidance-audit/SKILL.md +10 -5
- package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +1 -1
- package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +25 -57
- package/skills/hr-agent-guidance-refresh/references/verification.md +1 -1
- package/skills/hr-audit-choices/SKILL.md +1 -1
- package/skills/hr-benchmarking/SKILL.md +122 -159
- package/skills/hr-benchmarking/references/javascript.md +113 -0
- package/skills/hr-benchmarking/references/tools.md +62 -57
- package/skills/hr-better-accessibility/SKILL.md +1 -1
- package/skills/hr-better-accessibility/screen-readers.md +1 -0
- package/skills/hr-code-review/SKILL.md +19 -3
- package/skills/hr-code-review/agents/openai.yaml +1 -1
- package/skills/hr-code-review/references/acp-client.md +43 -0
- package/skills/{hr-change-impact/SKILL.md → hr-code-review/references/impact.md} +2 -12
- package/skills/hr-code-review/references/panel-adjudication.md +58 -0
- package/skills/hr-code-review/references/panel-plan.md +61 -0
- package/skills/hr-code-review/references/panel-recommendations.json +105 -0
- package/skills/hr-code-review/references/panel-recommendations.md +46 -0
- package/skills/hr-code-review/references/panel-setup.md +54 -0
- package/skills/hr-code-review/references/reviewer-role.md +31 -0
- package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
- package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
- package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
- package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
- package/skills/hr-design-exploration/SKILL.md +24 -1
- package/skills/hr-design-exploration/agents/openai.yaml +2 -2
- package/skills/hr-diagnosing-bugs/SKILL.md +2 -2
- package/skills/hr-diagnosing-bugs/references/reporting.md +29 -4
- package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
- package/skills/hr-explain-code/SKILL.md +1 -1
- package/skills/hr-extract-shared-steps/SKILL.md +1 -1
- package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
- package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
- package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
- package/skills/hr-handoff/LICENSE +21 -0
- package/skills/hr-handoff/NOTICE.md +29 -0
- package/skills/hr-handoff/SKILL.md +24 -1
- package/skills/hr-handoff/references/reconciliation.md +25 -6
- package/skills/hr-handoff/references/successor-thread.md +76 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +48 -6
- package/skills/hr-house-rules-setup/SKILL.md +22 -8
- package/skills/hr-lean-plan/SKILL.md +4 -0
- package/skills/hr-maintainability-review/SKILL.md +14 -4
- package/skills/hr-maintainability-review/agents/openai.yaml +1 -1
- package/skills/hr-manual-qa/SKILL.md +1 -1
- package/skills/hr-ordering-tests/SKILL.md +1 -1
- package/skills/hr-plain-prose/SKILL.md +7 -4
- package/skills/hr-pr-followup/SKILL.md +6 -3
- package/skills/hr-pr-followup/references/review-helpers.md +21 -0
- package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
- package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
- package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
- package/skills/hr-read-reddit/SKILL.md +1 -1
- package/skills/hr-research-shared-links/SKILL.md +103 -0
- package/skills/hr-sqlite/SKILL.md +174 -0
- package/skills/hr-sqlite/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/references/evidence.md +94 -0
- package/skills/hr-sqlite/references/tool-decisions.md +39 -0
- package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
- package/skills/hr-stock-ui-audit/SKILL.md +1 -1
- package/skills/hr-test-audit/LICENSE +21 -0
- package/skills/hr-test-audit/NOTICE.md +67 -0
- package/skills/hr-usage-report/SKILL.md +90 -0
- package/skills/hr-usage-report/agents/openai.yaml +2 -0
- package/skills/hr-usage-report/assets/example-report.json +125 -0
- package/skills/hr-usage-report/assets/report-template.html +377 -0
- package/skills/hr-usage-report/scripts/render-report.ts +86 -0
- package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
- package/skills/hr-what-next/SKILL.md +100 -10
- package/skills/hr-what-next/references/reporting.md +13 -22
- package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
- package/skills/hr-writing-pr/SKILL.md +29 -14
- package/skills/hr-writing-pr/scripts/benefit-chart.py +200 -0
- package/docs/npm-release.md +0 -45
- package/rules/modifiers/swarmail.md +0 -12
- package/skills/hr-benchmarking/references/priority-findings-20261006.md +0 -115
- package/skills/hr-change-impact/agents/openai.yaml +0 -5
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Source notices
|
|
2
|
+
|
|
3
|
+
This notice accompanies the hr-test-audit bundle when copied on its own.
|
|
4
|
+
Each source below is published under the MIT License. The licence text at the
|
|
5
|
+
end applies to each of them, with the copyright line shown for that source.
|
|
6
|
+
|
|
7
|
+
## obra/superpowers
|
|
8
|
+
|
|
9
|
+
Source: https://github.com/obra/superpowers (compared at revision `8ca22db`)
|
|
10
|
+
|
|
11
|
+
- `SKILL.md`: cites `writing-good-tests.md`.
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2025 Jesse Vincent
|
|
14
|
+
|
|
15
|
+
## addyosmani/agent-skills
|
|
16
|
+
|
|
17
|
+
Source: https://github.com/addyosmani/agent-skills (compared at revision `9d0c60d`)
|
|
18
|
+
|
|
19
|
+
- `scripts/floor-guard.ts`: adapted from constraint-driven-development.
|
|
20
|
+
|
|
21
|
+
Copyright (c) 2025 Addy Osmani
|
|
22
|
+
|
|
23
|
+
## openclaw/openclaw
|
|
24
|
+
|
|
25
|
+
Source: https://github.com/openclaw/openclaw (linked at revision `65f1e4d`, compared at `1fda703`)
|
|
26
|
+
|
|
27
|
+
- `SKILL.md`: the authoring gate, junk patterns, retention bar and evidence
|
|
28
|
+
fields are adapted from `.agents/skills/test-audit/`.
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2026 OpenClaw Foundation
|
|
31
|
+
|
|
32
|
+
## no-human-ai/no_human
|
|
33
|
+
|
|
34
|
+
Source: https://github.com/no-human-ai/no_human (compared at revision `9b50bf6`)
|
|
35
|
+
|
|
36
|
+
- `SKILL.md`: the rule that weakening a test counts as removing it is adapted
|
|
37
|
+
from its tamper guard.
|
|
38
|
+
|
|
39
|
+
Copyright (c) 2026 Eyal Golan
|
|
40
|
+
|
|
41
|
+
## omkamal/pypict-claude-skill
|
|
42
|
+
|
|
43
|
+
Source: https://github.com/omkamal/pypict-claude-skill (compared at revision `fbda212`)
|
|
44
|
+
|
|
45
|
+
- `SKILL.md`: the pairwise-coverage guidance is adapted from this skill.
|
|
46
|
+
|
|
47
|
+
Copyright (c) 2025 pypict-claude-skill contributors
|
|
48
|
+
|
|
49
|
+
## MIT License text
|
|
50
|
+
|
|
51
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
52
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
53
|
+
in the Software without restriction, including without limitation the rights
|
|
54
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
55
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
56
|
+
furnished to do so, subject to the following conditions:
|
|
57
|
+
|
|
58
|
+
The above copyright notice and this permission notice shall be included in all
|
|
59
|
+
copies or substantial portions of the Software.
|
|
60
|
+
|
|
61
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
62
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
63
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
64
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
65
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
66
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
67
|
+
SOFTWARE.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-usage-report
|
|
3
|
+
description: Measure where Claude Code, Codex and OpenCode usage went by provider, model and role, and chart model comparisons as HTML. Use for quota or cost questions and to show a model or role recommendation visually.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Usage report
|
|
7
|
+
|
|
8
|
+
Answer quota and model-role questions from measured usage and published
|
|
9
|
+
evidence, then show the result as charts and tables. Run the scripts from this
|
|
10
|
+
skill's directory with Bun.
|
|
11
|
+
|
|
12
|
+
## 1. Measure where usage went
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun scripts/usage-by-model.ts --days 7 [--providers claude,codex,opencode] [--claude-projects <dir,dir>] [--prices <api.json>] [--json]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
It reads Claude Code transcripts (`projects` under `~/.claude` and under every
|
|
19
|
+
other `~/.claude_*` or `~/.claude-*` configuration directory, so sessions run
|
|
20
|
+
with a separate `CLAUDE_CONFIG_DIR` count; `--claude-projects` replaces that
|
|
21
|
+
list), Codex rollouts
|
|
22
|
+
(`~/.codex/sessions`) and the OpenCode database, all read-only and local. It
|
|
23
|
+
prices each request at [models.dev](https://models.dev) list rates, including
|
|
24
|
+
long-context tiers, and splits usage by provider, model and role: main session,
|
|
25
|
+
subagent, advisor. Without `--prices` it downloads `https://models.dev/api.json`;
|
|
26
|
+
that request carries no local data. Use `--since <ISO>` for a fixed start.
|
|
27
|
+
|
|
28
|
+
Read the output beside the provider's live quota readings:
|
|
29
|
+
|
|
30
|
+
- **Shares, not dollars.** List price stands in for subscription quota; no
|
|
31
|
+
provider publishes the conversion. Say so in the report.
|
|
32
|
+
- **Cache reads.** When cache reads dominate a row's cost, context length, not
|
|
33
|
+
the model's output, drives usage. Compare the main-request context median
|
|
34
|
+
with the session-start median: the gap is conversation growth; the start is
|
|
35
|
+
fixed overhead (system prompt, rules, tool schemas).
|
|
36
|
+
- **Advisor rows.** Each advisor call re-reads the whole transcript uncached at
|
|
37
|
+
the advisor's rates; a few calls can outweigh many main turns.
|
|
38
|
+
- **Cross-provider work.** The same table shows what the author's sessions and
|
|
39
|
+
another vendor's reviewer each cost.
|
|
40
|
+
- **Unpriced models.** The summary lists models models.dev has no price for;
|
|
41
|
+
report them rather than dropping them.
|
|
42
|
+
|
|
43
|
+
## 2. Pick evidence by question
|
|
44
|
+
|
|
45
|
+
| Question | Evidence | Read |
|
|
46
|
+
| ------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
47
|
+
| Quality per dollar for implementation | Agentic coding boards that publish effort, harness and cost per task | Score against cost at every effort; cost per solved task |
|
|
48
|
+
| Review and bug finding | Bug-finding boards with run counts per row | The effort curve; treat single runs as noisy |
|
|
49
|
+
| Raw speed | Output tokens per second | Generation speed only |
|
|
50
|
+
| Task speed | Boards that time whole tasks | Time to finish; a model that generates faster can still take longer |
|
|
51
|
+
| Fit for your own work | A known-answer trial on your own tasks (`hr-benchmarking`) | Correctness and model time per task shape |
|
|
52
|
+
|
|
53
|
+
Compare models at the same effort and harness, or say they differ. A vendor
|
|
54
|
+
table that sets one model's top effort against another's lower effort is not a
|
|
55
|
+
matched comparison. Keep token speed and task time in separate charts; never
|
|
56
|
+
infer one from the other.
|
|
57
|
+
|
|
58
|
+
## 3. Render the report
|
|
59
|
+
|
|
60
|
+
Write a data file, then:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bun scripts/render-report.ts <data.json> <out.html>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The page is self-contained, follows the host's theme variables when it is
|
|
67
|
+
embedded, and falls back to a dark surface. `assets/example-report.json` uses
|
|
68
|
+
every section type.
|
|
69
|
+
|
|
70
|
+
The data file declares `series` once, so each model keeps one color across
|
|
71
|
+
every chart in the report: up to three colored series with slots 0 to 2, and at
|
|
72
|
+
most one `"reference": true` series drawn dashed and neutral. The renderer
|
|
73
|
+
rejects a fourth colored series because no fourth hue passes the color-vision
|
|
74
|
+
check on the dark surface against the other three. Compare more models in a
|
|
75
|
+
second report file.
|
|
76
|
+
|
|
77
|
+
Section types:
|
|
78
|
+
|
|
79
|
+
- `curve`: points with `x`, `y`, `label`, `tip` per series; `xLog`, domains,
|
|
80
|
+
ticks and axis affixes. Use for score against cost across efforts.
|
|
81
|
+
- `ladder`: `categories` (usually efforts) and `values` per series, `null` for a
|
|
82
|
+
missing row, optional `notes` such as run counts.
|
|
83
|
+
- `bars`: `rows` of `label`, `key`, `value`, optional `display` and `tip`.
|
|
84
|
+
- `pair`: two sections side by side, such as token speed beside task time.
|
|
85
|
+
- `table`: `columns` and `rows`; a cell may be `{ "text", "verdict": "yes" | "maybe" | "no" }`;
|
|
86
|
+
`numeric` lists right-aligned column indexes.
|
|
87
|
+
|
|
88
|
+
End with one verdict table per model or role: role, key evidence with numbers,
|
|
89
|
+
verdict. Name the evidence date in the title, and apply any model or effort
|
|
90
|
+
limits the user has set before recommending a configuration.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Example: three models across effort levels (illustrative data)",
|
|
3
|
+
"series": {
|
|
4
|
+
"small": { "name": "Model S", "slot": 0 },
|
|
5
|
+
"mid": { "name": "Model M", "slot": 1 },
|
|
6
|
+
"large": { "name": "Model L", "slot": 2 },
|
|
7
|
+
"previous": { "name": "Model L (previous)", "reference": true }
|
|
8
|
+
},
|
|
9
|
+
"sections": [
|
|
10
|
+
{
|
|
11
|
+
"type": "curve",
|
|
12
|
+
"title": "Score against cost per task, every effort level",
|
|
13
|
+
"sub": "Illustrative values. Each line runs low to max on a log cost axis.",
|
|
14
|
+
"xLog": true,
|
|
15
|
+
"xDomain": [0.05, 20],
|
|
16
|
+
"yDomain": [20, 60],
|
|
17
|
+
"xTicks": [0.1, 0.3, 1, 3, 10],
|
|
18
|
+
"xPrefix": "$",
|
|
19
|
+
"yTicks": [20, 30, 40, 50, 60],
|
|
20
|
+
"ySuffix": "%",
|
|
21
|
+
"xLabel": "cost per task (log)",
|
|
22
|
+
"series": [
|
|
23
|
+
{
|
|
24
|
+
"key": "small",
|
|
25
|
+
"points": [
|
|
26
|
+
{ "label": "low", "x": 0.08, "y": 28, "tip": "28% · $0.08/task" },
|
|
27
|
+
{ "label": "medium", "x": 0.2, "y": 34, "tip": "34% · $0.20/task" },
|
|
28
|
+
{ "label": "max", "x": 1, "y": 45, "tip": "45% · $1.00/task" }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"key": "mid",
|
|
33
|
+
"points": [
|
|
34
|
+
{ "label": "low", "x": 0.5, "y": 35, "tip": "35% · $0.50/task" },
|
|
35
|
+
{ "label": "medium", "x": 0.8, "y": 40, "tip": "40% · $0.80/task" },
|
|
36
|
+
{ "label": "max", "x": 8, "y": 54, "tip": "54% · $8.00/task" }
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"key": "large",
|
|
41
|
+
"points": [
|
|
42
|
+
{ "label": "low", "x": 1.2, "y": 44, "tip": "44% · $1.20/task" },
|
|
43
|
+
{ "label": "medium", "x": 3, "y": 52, "tip": "52% · $3.00/task" },
|
|
44
|
+
{ "label": "max", "x": 12, "y": 57, "tip": "57% · $12.00/task" }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"key": "previous",
|
|
49
|
+
"points": [
|
|
50
|
+
{ "label": "low", "x": 5, "y": 43, "tip": "43% · $5.00/task" },
|
|
51
|
+
{ "label": "max", "x": 16, "y": 50, "tip": "50% · $16.00/task" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "ladder",
|
|
58
|
+
"title": "Review: bugs found per effort level",
|
|
59
|
+
"sub": "Illustrative values; notes carry run counts.",
|
|
60
|
+
"categories": ["low", "medium", "high", "max"],
|
|
61
|
+
"yDomain": [0, 50],
|
|
62
|
+
"yTicks": [0, 25, 50],
|
|
63
|
+
"series": [
|
|
64
|
+
{
|
|
65
|
+
"key": "small",
|
|
66
|
+
"values": [10, 12, 16, null],
|
|
67
|
+
"notes": ["1 run", "1 run", "2 runs", "no row"]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"key": "mid",
|
|
71
|
+
"values": [18, 23, 34, 45],
|
|
72
|
+
"notes": ["3 runs", "3 runs", "3 runs", "3 runs"]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"key": "large",
|
|
76
|
+
"values": [22, 30, 32, 41],
|
|
77
|
+
"notes": ["3 runs", "3 runs", "3 runs", "3 runs"]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "pair",
|
|
83
|
+
"items": [
|
|
84
|
+
{
|
|
85
|
+
"type": "bars",
|
|
86
|
+
"title": "Generation speed: output tokens/s",
|
|
87
|
+
"max": 400,
|
|
88
|
+
"rows": [
|
|
89
|
+
{ "label": "Model S", "key": "small", "value": 380 },
|
|
90
|
+
{ "label": "Model M", "key": "mid", "value": 220 },
|
|
91
|
+
{ "label": "Model L", "key": "large", "value": 125 }
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"type": "bars",
|
|
96
|
+
"title": "Task time: minutes per task",
|
|
97
|
+
"max": 3,
|
|
98
|
+
"rows": [
|
|
99
|
+
{ "label": "Model S", "key": "small", "value": 2.6, "display": "2:36" },
|
|
100
|
+
{ "label": "Model M", "key": "mid", "value": 1, "display": "1:00" },
|
|
101
|
+
{ "label": "Model L", "key": "large", "value": 2.1, "display": "2:06" }
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"type": "table",
|
|
108
|
+
"title": "Model S: where it fits",
|
|
109
|
+
"columns": ["Role", "Key evidence", "Verdict"],
|
|
110
|
+
"rows": [
|
|
111
|
+
[
|
|
112
|
+
"Narrow read-only lookups",
|
|
113
|
+
"Equal accuracy on known-answer lookups at a fraction of the cost",
|
|
114
|
+
{ "text": "Yes", "verdict": "yes" }
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
"Mechanical edits",
|
|
118
|
+
"Matches Model M medium at lower cost; untested on your work",
|
|
119
|
+
{ "text": "Try on a named task", "verdict": "maybe" }
|
|
120
|
+
],
|
|
121
|
+
["Review", "Finds about half of what Model L finds", { "text": "No", "verdict": "no" }]
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<!-- Model comparison report. scripts/render-report.ts fills #report-data from a data file;
|
|
6
|
+
section types and rules: SKILL.md. Colors: up to three categorical series
|
|
7
|
+
(validated dark-surface slots) plus one dashed neutral "reference" series. -->
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--s1: #3987e5;
|
|
11
|
+
--s2: #d95926;
|
|
12
|
+
--s3: #199e70;
|
|
13
|
+
--ref: #a8a7a0;
|
|
14
|
+
--fg: var(--foreground, #f2f2f0);
|
|
15
|
+
--mut: var(--muted-foreground, #b5b4ad);
|
|
16
|
+
--bd: var(--border, #3a3a38);
|
|
17
|
+
--bg: var(--background, #1a1a19);
|
|
18
|
+
--grid: color-mix(in srgb, var(--fg) 12%, transparent);
|
|
19
|
+
}
|
|
20
|
+
html,
|
|
21
|
+
body {
|
|
22
|
+
background: var(--bg);
|
|
23
|
+
}
|
|
24
|
+
body {
|
|
25
|
+
font-family: var(--font-sans, system-ui, sans-serif);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
.wrap {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 22px;
|
|
33
|
+
}
|
|
34
|
+
h2 {
|
|
35
|
+
font-size: 18px;
|
|
36
|
+
margin: 0;
|
|
37
|
+
}
|
|
38
|
+
h3 {
|
|
39
|
+
font-size: 15px;
|
|
40
|
+
margin: 0 0 2px;
|
|
41
|
+
}
|
|
42
|
+
.sub {
|
|
43
|
+
color: var(--mut);
|
|
44
|
+
font-size: 12.5px;
|
|
45
|
+
margin: 0 0 8px;
|
|
46
|
+
line-height: 1.4;
|
|
47
|
+
}
|
|
48
|
+
.legend {
|
|
49
|
+
display: flex;
|
|
50
|
+
gap: 14px;
|
|
51
|
+
flex-wrap: wrap;
|
|
52
|
+
font-size: 12.5px;
|
|
53
|
+
color: var(--mut);
|
|
54
|
+
margin-bottom: 6px;
|
|
55
|
+
}
|
|
56
|
+
.legend span {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 6px;
|
|
60
|
+
}
|
|
61
|
+
.sw {
|
|
62
|
+
width: 18px;
|
|
63
|
+
height: 0;
|
|
64
|
+
border-top: 2px solid;
|
|
65
|
+
}
|
|
66
|
+
.grid2 {
|
|
67
|
+
display: grid;
|
|
68
|
+
grid-template-columns: 1fr 1fr;
|
|
69
|
+
gap: 18px;
|
|
70
|
+
}
|
|
71
|
+
@media (max-width: 560px) {
|
|
72
|
+
.grid2 {
|
|
73
|
+
grid-template-columns: 1fr;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
svg {
|
|
77
|
+
display: block;
|
|
78
|
+
width: 100%;
|
|
79
|
+
overflow: visible;
|
|
80
|
+
}
|
|
81
|
+
svg text {
|
|
82
|
+
font-family: inherit;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
fill: var(--mut);
|
|
85
|
+
}
|
|
86
|
+
svg .lbl {
|
|
87
|
+
fill: var(--fg);
|
|
88
|
+
}
|
|
89
|
+
.tip {
|
|
90
|
+
position: fixed;
|
|
91
|
+
pointer-events: none;
|
|
92
|
+
background: var(--popover, #262624);
|
|
93
|
+
color: var(--popover-foreground, var(--fg));
|
|
94
|
+
border: 1px solid var(--bd);
|
|
95
|
+
border-radius: 6px;
|
|
96
|
+
padding: 6px 8px;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
opacity: 0;
|
|
99
|
+
transition: opacity 0.08s;
|
|
100
|
+
z-index: 5;
|
|
101
|
+
max-width: 260px;
|
|
102
|
+
}
|
|
103
|
+
table {
|
|
104
|
+
border-collapse: collapse;
|
|
105
|
+
width: 100%;
|
|
106
|
+
font-size: 12.5px;
|
|
107
|
+
}
|
|
108
|
+
th,
|
|
109
|
+
td {
|
|
110
|
+
text-align: left;
|
|
111
|
+
padding: 6px 8px;
|
|
112
|
+
border-bottom: 1px solid var(--bd);
|
|
113
|
+
vertical-align: top;
|
|
114
|
+
}
|
|
115
|
+
th {
|
|
116
|
+
color: var(--mut);
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
}
|
|
119
|
+
td.n {
|
|
120
|
+
font-variant-numeric: tabular-nums;
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
}
|
|
123
|
+
.yes {
|
|
124
|
+
color: var(--success, #3fb37f);
|
|
125
|
+
}
|
|
126
|
+
.no {
|
|
127
|
+
color: var(--mut);
|
|
128
|
+
}
|
|
129
|
+
.maybe {
|
|
130
|
+
color: var(--warning, #e0a63a);
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
<div class="wrap" id="root"></div>
|
|
136
|
+
<div class="tip" id="tip"></div>
|
|
137
|
+
<script id="report-data" type="application/json">
|
|
138
|
+
__REPORT_DATA__
|
|
139
|
+
</script>
|
|
140
|
+
<script>
|
|
141
|
+
const DATA = JSON.parse(document.getElementById("report-data").textContent);
|
|
142
|
+
const SLOTS = ["var(--s1)", "var(--s2)", "var(--s3)"];
|
|
143
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
144
|
+
const tip = document.getElementById("tip");
|
|
145
|
+
const esc = (s) =>
|
|
146
|
+
String(s).replace(
|
|
147
|
+
/[&<>"]/g,
|
|
148
|
+
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c],
|
|
149
|
+
);
|
|
150
|
+
function showTip(e, h) {
|
|
151
|
+
tip.innerHTML = h;
|
|
152
|
+
tip.style.opacity = 1;
|
|
153
|
+
tip.style.left = Math.min(e.clientX + 12, innerWidth - 270) + "px";
|
|
154
|
+
tip.style.top = e.clientY + 12 + "px";
|
|
155
|
+
}
|
|
156
|
+
function hideTip() {
|
|
157
|
+
tip.style.opacity = 0;
|
|
158
|
+
}
|
|
159
|
+
function el(t, a, p) {
|
|
160
|
+
const n = document.createElementNS(NS, t);
|
|
161
|
+
for (const k in a) n.setAttribute(k, a[k]);
|
|
162
|
+
if (p) p.appendChild(n);
|
|
163
|
+
return n;
|
|
164
|
+
}
|
|
165
|
+
function hover(node, html) {
|
|
166
|
+
node.addEventListener("mousemove", (e) => showTip(e, html));
|
|
167
|
+
node.addEventListener("mouseleave", hideTip);
|
|
168
|
+
}
|
|
169
|
+
// Color follows the entity: a series key keeps its slot across every chart in the report.
|
|
170
|
+
const keys = DATA.series || {};
|
|
171
|
+
const color = (k) => (keys[k]?.reference ? "var(--ref)" : SLOTS[keys[k]?.slot ?? 0]);
|
|
172
|
+
const isRef = (k) => !!keys[k]?.reference;
|
|
173
|
+
const name = (k) => keys[k]?.name ?? k;
|
|
174
|
+
const legend = (ks) =>
|
|
175
|
+
`<div class="legend">${ks.map((k) => `<span><i class="sw" style="border-color:${color(k)};${isRef(k) ? "border-top-style:dashed" : ""}"></i>${esc(name(k))}</span>`).join("")}</div>`;
|
|
176
|
+
function marker(svg, x, y, k, html) {
|
|
177
|
+
el(
|
|
178
|
+
"circle",
|
|
179
|
+
{
|
|
180
|
+
cx: x,
|
|
181
|
+
cy: y,
|
|
182
|
+
r: 4.5,
|
|
183
|
+
fill: isRef(k) ? "var(--bg)" : color(k),
|
|
184
|
+
stroke: isRef(k) ? color(k) : "var(--bg)",
|
|
185
|
+
"stroke-width": 2,
|
|
186
|
+
},
|
|
187
|
+
svg,
|
|
188
|
+
);
|
|
189
|
+
hover(el("circle", { cx: x, cy: y, r: 11, fill: "transparent" }, svg), html);
|
|
190
|
+
}
|
|
191
|
+
function endLabels(svg, items) {
|
|
192
|
+
items.sort((a, b) => a.y - b.y);
|
|
193
|
+
let last = -99;
|
|
194
|
+
for (const it of items) {
|
|
195
|
+
const y = Math.max(it.y, last + 13);
|
|
196
|
+
last = y;
|
|
197
|
+
el("text", { x: it.x + 8, y: y + 4, class: "lbl" }, svg).textContent = it.text;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const draw = {
|
|
201
|
+
// Score against cost (or any two measures): one polyline per series through its effort points.
|
|
202
|
+
curve(s, svg) {
|
|
203
|
+
const W = svg.clientWidth,
|
|
204
|
+
H = s.height || 300,
|
|
205
|
+
m = { l: 44, r: 74, t: 10, b: 34 };
|
|
206
|
+
svg.setAttribute("height", H);
|
|
207
|
+
const xs = s.series.flatMap((r) => r.points.map((p) => p.x)),
|
|
208
|
+
ys = s.series.flatMap((r) => r.points.map((p) => p.y));
|
|
209
|
+
const [x0, x1] = s.xDomain || [Math.min(...xs) * 0.7, Math.max(...xs) * 1.4],
|
|
210
|
+
[y0, y1] = s.yDomain || [
|
|
211
|
+
Math.floor(Math.min(...ys) / 10) * 10,
|
|
212
|
+
Math.ceil(Math.max(...ys) / 10) * 10,
|
|
213
|
+
];
|
|
214
|
+
const tx = (v) =>
|
|
215
|
+
(s.xLog ? Math.log10(v) - Math.log10(x0) : v - x0) /
|
|
216
|
+
(s.xLog ? Math.log10(x1) - Math.log10(x0) : x1 - x0);
|
|
217
|
+
const x = (v) => m.l + tx(v) * (W - m.l - m.r),
|
|
218
|
+
y = (v) => H - m.b - ((v - y0) / (y1 - y0)) * (H - m.t - m.b);
|
|
219
|
+
for (const v of s.xTicks || []) {
|
|
220
|
+
el("line", { x1: x(v), x2: x(v), y1: m.t, y2: H - m.b, stroke: "var(--grid)" }, svg);
|
|
221
|
+
el("text", { x: x(v), y: H - m.b + 15, "text-anchor": "middle" }, svg).textContent =
|
|
222
|
+
(s.xPrefix || "") + v + (s.xSuffix || "");
|
|
223
|
+
}
|
|
224
|
+
for (const v of s.yTicks || []) {
|
|
225
|
+
el("line", { x1: m.l, x2: W - m.r, y1: y(v), y2: y(v), stroke: "var(--grid)" }, svg);
|
|
226
|
+
el("text", { x: m.l - 6, y: y(v) + 4, "text-anchor": "end" }, svg).textContent =
|
|
227
|
+
v + (s.ySuffix || "");
|
|
228
|
+
}
|
|
229
|
+
el("text", { x: W / 2, y: H - 2, "text-anchor": "middle" }, svg).textContent =
|
|
230
|
+
s.xLabel || "";
|
|
231
|
+
const ends = [];
|
|
232
|
+
for (const r of s.series) {
|
|
233
|
+
el(
|
|
234
|
+
"polyline",
|
|
235
|
+
{
|
|
236
|
+
points: r.points.map((p) => x(p.x) + "," + y(p.y)).join(" "),
|
|
237
|
+
fill: "none",
|
|
238
|
+
stroke: color(r.key),
|
|
239
|
+
"stroke-width": 2,
|
|
240
|
+
"stroke-dasharray": isRef(r.key) ? "5 4" : "",
|
|
241
|
+
},
|
|
242
|
+
svg,
|
|
243
|
+
);
|
|
244
|
+
for (const p of r.points)
|
|
245
|
+
marker(
|
|
246
|
+
svg,
|
|
247
|
+
x(p.x),
|
|
248
|
+
y(p.y),
|
|
249
|
+
r.key,
|
|
250
|
+
`<b>${esc(name(r.key))} ${esc(p.label)}</b><br>${esc(p.tip || "")}`,
|
|
251
|
+
);
|
|
252
|
+
const L = r.points[r.points.length - 1];
|
|
253
|
+
ends.push({ x: x(L.x), y: y(L.y), text: name(r.key).split(" ")[0] });
|
|
254
|
+
}
|
|
255
|
+
endLabels(svg, ends);
|
|
256
|
+
},
|
|
257
|
+
// One value per category per series (effort ladders).
|
|
258
|
+
ladder(s, svg) {
|
|
259
|
+
const W = svg.clientWidth,
|
|
260
|
+
H = s.height || 250,
|
|
261
|
+
m = { l: 34, r: 74, t: 10, b: 26 },
|
|
262
|
+
n = s.categories.length;
|
|
263
|
+
svg.setAttribute("height", H);
|
|
264
|
+
const [y0, y1] = s.yDomain,
|
|
265
|
+
x = (i) => m.l + (i / (n - 1)) * (W - m.l - m.r),
|
|
266
|
+
y = (v) => H - m.b - ((v - y0) / (y1 - y0)) * (H - m.t - m.b);
|
|
267
|
+
for (const v of s.yTicks || []) {
|
|
268
|
+
el("line", { x1: m.l, x2: W - m.r, y1: y(v), y2: y(v), stroke: "var(--grid)" }, svg);
|
|
269
|
+
el("text", { x: m.l - 6, y: y(v) + 4, "text-anchor": "end" }, svg).textContent = v;
|
|
270
|
+
}
|
|
271
|
+
s.categories.forEach((c, i) => {
|
|
272
|
+
el("text", { x: x(i), y: H - m.b + 16, "text-anchor": "middle" }, svg).textContent = c;
|
|
273
|
+
});
|
|
274
|
+
const ends = [];
|
|
275
|
+
for (const r of s.series) {
|
|
276
|
+
const pts = r.values.map((v, i) => [i, v]).filter((p) => p[1] !== null);
|
|
277
|
+
el(
|
|
278
|
+
"polyline",
|
|
279
|
+
{
|
|
280
|
+
points: pts.map(([i, v]) => x(i) + "," + y(v)).join(" "),
|
|
281
|
+
fill: "none",
|
|
282
|
+
stroke: color(r.key),
|
|
283
|
+
"stroke-width": 2,
|
|
284
|
+
"stroke-dasharray": isRef(r.key) ? "5 4" : "",
|
|
285
|
+
},
|
|
286
|
+
svg,
|
|
287
|
+
);
|
|
288
|
+
for (const [i, v] of pts)
|
|
289
|
+
marker(
|
|
290
|
+
svg,
|
|
291
|
+
x(i),
|
|
292
|
+
y(v),
|
|
293
|
+
r.key,
|
|
294
|
+
`<b>${esc(name(r.key))} ${esc(s.categories[i])}</b><br>${esc(v)}${r.notes?.[i] ? " · " + esc(r.notes[i]) : ""}`,
|
|
295
|
+
);
|
|
296
|
+
const L = pts[pts.length - 1];
|
|
297
|
+
ends.push({ x: x(L[0]), y: y(L[1]), text: name(r.key).split(" ")[0] });
|
|
298
|
+
}
|
|
299
|
+
endLabels(svg, ends);
|
|
300
|
+
},
|
|
301
|
+
// Horizontal bars; rows name their series for color.
|
|
302
|
+
bars(s, svg) {
|
|
303
|
+
const W = svg.clientWidth,
|
|
304
|
+
H = s.height || 190,
|
|
305
|
+
lw = s.labelWidth || 96,
|
|
306
|
+
rh = (H - 6) / s.rows.length,
|
|
307
|
+
max = s.max || Math.max(...s.rows.map((r) => r.value)) * 1.1;
|
|
308
|
+
svg.setAttribute("height", H);
|
|
309
|
+
s.rows.forEach((r, i) => {
|
|
310
|
+
const w = ((W - lw - 46) * r.value) / max,
|
|
311
|
+
yy = i * rh + 4,
|
|
312
|
+
bh = rh - 12;
|
|
313
|
+
el("text", { x: 0, y: yy + bh / 2 + 4 }, svg).textContent = r.label;
|
|
314
|
+
const b = el(
|
|
315
|
+
"rect",
|
|
316
|
+
{
|
|
317
|
+
x: lw,
|
|
318
|
+
y: yy,
|
|
319
|
+
width: Math.max(w, 2),
|
|
320
|
+
height: bh,
|
|
321
|
+
rx: 4,
|
|
322
|
+
fill: isRef(r.key) ? "none" : color(r.key),
|
|
323
|
+
stroke: isRef(r.key) ? color(r.key) : "none",
|
|
324
|
+
"stroke-dasharray": isRef(r.key) ? "5 4" : "",
|
|
325
|
+
"stroke-width": isRef(r.key) ? 1.5 : 0,
|
|
326
|
+
},
|
|
327
|
+
svg,
|
|
328
|
+
);
|
|
329
|
+
el("text", { x: lw + w + 6, y: yy + bh / 2 + 4, class: "lbl" }, svg).textContent =
|
|
330
|
+
r.display ?? r.value;
|
|
331
|
+
hover(b, `<b>${esc(r.label)}</b><br>${esc(r.tip || "")}`);
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
function section(s) {
|
|
336
|
+
const box = document.createElement("section");
|
|
337
|
+
box.innerHTML = `<h3>${esc(s.title || "")}</h3>${s.sub ? `<p class="sub">${esc(s.sub)}</p>` : ""}`;
|
|
338
|
+
if (s.type === "pair") {
|
|
339
|
+
box.className = "grid2";
|
|
340
|
+
box.innerHTML = "";
|
|
341
|
+
for (const c of s.items) box.appendChild(section(c));
|
|
342
|
+
return box;
|
|
343
|
+
}
|
|
344
|
+
if (s.type === "table") {
|
|
345
|
+
const cls = (v) => ({ yes: "yes", no: "no", maybe: "maybe" })[v] || "";
|
|
346
|
+
box.innerHTML += `<table><thead><tr>${s.columns.map((c) => `<th>${esc(c)}</th>`).join("")}</tr></thead><tbody>${s.rows
|
|
347
|
+
.map(
|
|
348
|
+
(row) =>
|
|
349
|
+
`<tr>${row
|
|
350
|
+
.map((c, i) => {
|
|
351
|
+
const v = typeof c === "object" && c ? c : { text: c };
|
|
352
|
+
return `<td class="${s.numeric?.includes(i) ? "n" : ""} ${cls(v.verdict)}">${esc(v.text)}</td>`;
|
|
353
|
+
})
|
|
354
|
+
.join("")}</tr>`,
|
|
355
|
+
)
|
|
356
|
+
.join("")}</tbody></table>`;
|
|
357
|
+
return box;
|
|
358
|
+
}
|
|
359
|
+
const ks = s.series ? s.series.map((r) => r.key) : [];
|
|
360
|
+
if (ks.length > 1) box.insertAdjacentHTML("beforeend", legend(ks));
|
|
361
|
+
const svg = el("svg", { height: s.height || 200 }, box);
|
|
362
|
+
box._draw = () => {
|
|
363
|
+
svg.innerHTML = "";
|
|
364
|
+
draw[s.type](s, svg);
|
|
365
|
+
};
|
|
366
|
+
return box;
|
|
367
|
+
}
|
|
368
|
+
const root = document.getElementById("root");
|
|
369
|
+
if (DATA.title) root.insertAdjacentHTML("beforeend", `<h2>${esc(DATA.title)}</h2>`);
|
|
370
|
+
const nodes = DATA.sections.map(section);
|
|
371
|
+
nodes.forEach((n) => root.appendChild(n));
|
|
372
|
+
const redraw = () => root.querySelectorAll("section").forEach((n) => n._draw && n._draw());
|
|
373
|
+
redraw();
|
|
374
|
+
addEventListener("resize", redraw);
|
|
375
|
+
</script>
|
|
376
|
+
</body>
|
|
377
|
+
</html>
|