@bompus/house-rules 0.0.0-stage → 0.11.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/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +347 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +119 -0
- package/composition.mjs +353 -0
- package/config-view.mjs +194 -0
- package/config.mjs +320 -0
- package/docs/assets/banner-dark.png +0 -0
- package/docs/assets/banner-light.png +0 -0
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +193 -0
- package/docs/npm-release.md +45 -0
- package/docs/skill-names.md +50 -0
- package/examples/person/house-rules.json +15 -0
- package/examples/person/rules/10-tooling.md +8 -0
- package/examples/person/rules/20-reporting.md +9 -0
- package/examples/project/AGENTS.md +18 -0
- package/package.json +40 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +431 -0
- package/rules/modifiers/coded-offers.md +40 -0
- package/rules/modifiers/effort-estimates.md +41 -0
- package/rules/modifiers/land-when-done.md +12 -0
- package/rules/modifiers/low-quota-handoff.md +19 -0
- package/rules/modifiers/multi-agent.md +34 -0
- package/rules/modifiers/no-attribution.md +10 -0
- package/rules/modifiers/plan-files.md +50 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +13 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +48 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/swarmail.md +12 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +47 -0
- package/rules/references/release-batching.md +43 -0
- package/rules/references/squash-landing.md +44 -0
- package/setup.mjs +375 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +86 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +68 -0
- package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
- package/skills/hr-api-exposure-check/SKILL.md +159 -0
- package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
- package/skills/hr-audit-choices/SKILL.md +67 -0
- package/skills/hr-audit-choices/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/NOTICE.md +36 -0
- package/skills/hr-benchmarking/SKILL.md +316 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +17 -0
- package/skills/hr-benchmarking/references/priority-findings-20261006.md +115 -0
- package/skills/hr-benchmarking/references/tools.md +160 -0
- package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
- package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
- package/skills/hr-better-accessibility/LICENSE +22 -0
- package/skills/hr-better-accessibility/NOTICE.md +46 -0
- package/skills/hr-better-accessibility/SKILL.md +119 -0
- package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
- package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
- package/skills/hr-better-accessibility/forms.md +82 -0
- package/skills/hr-better-accessibility/hit-areas.md +98 -0
- package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
- package/skills/hr-better-accessibility/screen-readers.md +101 -0
- package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
- package/skills/hr-better-colors/LICENSE +21 -0
- package/skills/hr-better-colors/NOTICE.md +37 -0
- package/skills/hr-better-colors/SKILL.md +114 -0
- package/skills/hr-better-colors/agents/openai.yaml +3 -0
- package/skills/hr-better-colors/color-formats.md +90 -0
- package/skills/hr-better-colors/color-usage.md +140 -0
- package/skills/hr-better-colors/contrast.md +80 -0
- package/skills/hr-better-colors/palette-generation.md +104 -0
- package/skills/hr-better-colors/palette-structure.md +76 -0
- package/skills/hr-better-colors/token-naming.md +97 -0
- package/skills/hr-better-layout/LICENSE +22 -0
- package/skills/hr-better-layout/NOTICE.md +48 -0
- package/skills/hr-better-layout/SKILL.md +92 -0
- package/skills/hr-better-layout/agents/openai.yaml +3 -0
- package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
- package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
- package/skills/hr-better-typography/LICENSE +22 -0
- package/skills/hr-better-typography/NOTICE.md +46 -0
- package/skills/hr-better-typography/SKILL.md +172 -0
- package/skills/hr-better-typography/agents/openai.yaml +3 -0
- package/skills/hr-better-typography/choosing-fonts.md +69 -0
- package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
- package/skills/hr-better-typography/details-and-accessibility.md +119 -0
- package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
- package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
- package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
- package/skills/hr-better-writing/LICENSE +22 -0
- package/skills/hr-better-writing/NOTICE.md +46 -0
- package/skills/hr-better-writing/SKILL.md +122 -0
- package/skills/hr-better-writing/agents/openai.yaml +3 -0
- package/skills/hr-change-impact/SKILL.md +113 -0
- package/skills/hr-change-impact/agents/openai.yaml +5 -0
- package/skills/hr-code-review/LICENSE +21 -0
- package/skills/hr-code-review/NOTICE.md +26 -0
- package/skills/hr-code-review/SKILL.md +147 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-design-exploration/SKILL.md +82 -0
- package/skills/hr-design-exploration/agents/openai.yaml +4 -0
- package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
- package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
- package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/hr-diagnosing-bugs/references/reporting.md +72 -0
- package/skills/hr-explain-code/NOTICE.md +37 -0
- package/skills/hr-explain-code/SKILL.md +110 -0
- package/skills/hr-explain-code/agents/openai.yaml +5 -0
- package/skills/hr-explain-code/references/helper-brief.md +39 -0
- package/skills/hr-extract-shared-steps/SKILL.md +134 -0
- package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
- package/skills/hr-handoff/SKILL.md +91 -0
- package/skills/hr-handoff/agents/openai.yaml +5 -0
- package/skills/hr-handoff/assets/ledger.example.json +10 -0
- package/skills/hr-handoff/assets/receipt.example.json +76 -0
- package/skills/hr-handoff/assets/source.example.json +20 -0
- package/skills/hr-handoff/references/reconciliation.md +105 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +282 -0
- package/skills/hr-house-rules-setup/SKILL.md +100 -0
- package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
- package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
- package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
- package/skills/hr-lean-plan/SKILL.md +142 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +176 -0
- package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
- package/skills/hr-manual-qa/NOTICE.md +36 -0
- package/skills/hr-manual-qa/SKILL.md +37 -0
- package/skills/hr-manual-qa/agents/openai.yaml +3 -0
- package/skills/hr-navigation-retro/LICENSE +21 -0
- package/skills/hr-navigation-retro/NOTICE.md +5 -0
- package/skills/hr-navigation-retro/SKILL.md +86 -0
- package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
- package/skills/hr-navigation-retro/references/inputs.md +67 -0
- package/skills/hr-navigation-retro/scripts/retro.py +552 -0
- package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
- package/skills/hr-ordering-tests/SKILL.md +51 -0
- package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
- package/skills/hr-plain-prose/SKILL.md +261 -0
- package/skills/hr-plain-prose/agents/openai.yaml +3 -0
- package/skills/hr-plain-prose/scripts/check.mjs +331 -0
- package/skills/hr-pr-followup/LICENSE +21 -0
- package/skills/hr-pr-followup/NOTICE.md +9 -0
- package/skills/hr-pr-followup/SKILL.md +71 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +90 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
- package/skills/hr-progress-report/SKILL.md +49 -0
- package/skills/hr-progress-report/agents/openai.yaml +5 -0
- package/skills/hr-read-reddit/SKILL.md +44 -0
- package/skills/hr-read-reddit/agents/openai.yaml +2 -0
- package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
- package/skills/hr-read-x-links/SKILL.md +42 -0
- package/skills/hr-read-x-links/agents/openai.yaml +2 -0
- package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
- package/skills/hr-split-to-prs/LICENSE +21 -0
- package/skills/hr-split-to-prs/NOTICE.md +9 -0
- package/skills/hr-split-to-prs/SKILL.md +61 -0
- package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
- package/skills/hr-stock-ui-audit/SKILL.md +132 -0
- package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
- package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
- package/skills/hr-tdd/LICENSE +21 -0
- package/skills/hr-tdd/NOTICE.md +10 -0
- package/skills/hr-tdd/SKILL.md +40 -0
- package/skills/hr-tdd/agents/openai.yaml +3 -0
- package/skills/hr-tdd/mocking.md +39 -0
- package/skills/hr-tdd/tests.md +77 -0
- package/skills/hr-test-audit/SKILL.md +146 -0
- package/skills/hr-test-audit/agents/openai.yaml +3 -0
- package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
- package/skills/hr-what-next/SKILL.md +61 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +68 -0
- package/skills/hr-writing-for-agents/NOTICE.md +46 -0
- package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
- package/skills/hr-writing-for-agents/SKILL.md +108 -0
- package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
- package/skills/hr-writing-pr/NOTICE.md +48 -0
- package/skills/hr-writing-pr/SKILL.md +104 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# CPU priority findings: 2026-10-06
|
|
2
|
+
|
|
3
|
+
Read this record before answering whether negative, zero or positive nice values
|
|
4
|
+
improved the tested workload. Reuse these results for the same question; a new
|
|
5
|
+
experiment needs a materially different workload, contention regime or hypothesis.
|
|
6
|
+
Follow the host's authorization rules before changing priority or measuring again.
|
|
7
|
+
|
|
8
|
+
## Result and scope
|
|
9
|
+
|
|
10
|
+
Negative nice did not establish a timing or memory benefit in this small WSL
|
|
11
|
+
allocation experiment. Verified effective nice 0 was the only setting that met
|
|
12
|
+
the preset variation criterion for both runtimes. It is a baseline candidate for
|
|
13
|
+
comparable isolated measurements, not a universal priority policy.
|
|
14
|
+
|
|
15
|
+
The workload performed 16,384 warmup and 65,536 measured `Buffer.alloc(size, 1)`
|
|
16
|
+
operations, cycling 1,000, 16,000, 128,000 and 1,000,000-byte buffers and retaining
|
|
17
|
+
at most 128 references. Timing and CPU cover the measured loop; peak RSS covers
|
|
18
|
+
the whole process. This allocation case is also reported in
|
|
19
|
+
[Bun issue 44661](https://github.com/oven-sh/bun/issues/44661).
|
|
20
|
+
This is a priority sensitivity comparison, not a general Bun-versus-Node ranking,
|
|
21
|
+
a contention-resistance test or an explanation of that issue's root cause.
|
|
22
|
+
|
|
23
|
+
## Recorded experiment
|
|
24
|
+
|
|
25
|
+
The measured interval was 2026-10-06 23:42:55Z–23:45:40Z. Bun 1.4.2 and
|
|
26
|
+
Node 26.10.0 used unchanged sources and binaries. Three independent processes
|
|
27
|
+
per runtime/priority cell ran in counterbalanced order, for 24 processes total.
|
|
28
|
+
All passed correctness, effective-control and quiet-host admission checks;
|
|
29
|
+
none was rejected. The preset criterion was `(maximum - minimum) / median <= 10%`.
|
|
30
|
+
|
|
31
|
+
Controls were Windows-hosted WSL, Linux kernel 7.2.8 with an unverified custom
|
|
32
|
+
scheduler implementation, ordinary fair scheduling, unprivileged workload UID
|
|
33
|
+
1000, CPU affinity 0–7 and identical 704 MiB `memory.high`/`memory.max` limits.
|
|
34
|
+
No CPU quota was present at inspected cgroup ancestors. The launcher set absolute
|
|
35
|
+
priority before dropping privileges and verified it afterward; the workload did
|
|
36
|
+
not run as root. Main-thread scheduler wait included warmup and cannot establish
|
|
37
|
+
timed-region or all-GC-thread scheduling effects.
|
|
38
|
+
|
|
39
|
+
| Runtime | Nice | Median wall ms | Wall range ms | Span | Median CPU ms | Median peak RSS MiB | Variation passes |
|
|
40
|
+
| ------- | ---: | -------------: | ------------: | -----: | ------------: | ------------------: | ---------------- |
|
|
41
|
+
| bun | +10 | 1445.9 | 1195.6–1467.9 | 18.83% | 1812.3 | 191.5 | no |
|
|
42
|
+
| bun | +0 | 1356.4 | 1326.5–1412.4 | 6.34% | 1778.9 | 191.3 | yes |
|
|
43
|
+
| bun | -10 | 1416.1 | 1291.2–1468.1 | 12.49% | 1781.2 | 189.2 | no |
|
|
44
|
+
| bun | -20 | 1346.1 | 1287.4–1497.2 | 15.58% | 1691.4 | 190.1 | no |
|
|
45
|
+
| node | +10 | 1196.5 | 1083.8–1231.8 | 12.37% | 1849.8 | 193.5 | no |
|
|
46
|
+
| node | +0 | 1136.2 | 1133.3–1138.5 | 0.46% | 1735.8 | 192.1 | yes |
|
|
47
|
+
| node | -10 | 1148.1 | 1097.3–1152.7 | 4.82% | 1747.0 | 191.9 | yes |
|
|
48
|
+
| node | -20 | 1191.7 | 1189.9–1277.3 | 7.33% | 1825.9 | 192.7 | yes |
|
|
49
|
+
|
|
50
|
+
Bun +10, -10 and -20 failed the variation criterion. Node -10 overlapped nice 0;
|
|
51
|
+
Node -20 was slower. These three-process samples support no confirmed negative
|
|
52
|
+
priority gain. The small quiet-host cohort does not predict behavior under
|
|
53
|
+
contention or on another scheduler. The per-process measurement values below
|
|
54
|
+
make the reported aggregates auditable; their scope limits still apply.
|
|
55
|
+
|
|
56
|
+
## Per-process measurement values
|
|
57
|
+
|
|
58
|
+
These are the 24 retained output measurements used above, with path and process
|
|
59
|
+
identity fields omitted. Repetition numbers identify each process within its
|
|
60
|
+
runtime/priority cell; they are not execution order. CPU is in microseconds and
|
|
61
|
+
peak RSS in KiB to retain the source units. Wall values retain nine decimal
|
|
62
|
+
places for aggregate recalculation, not a claim of clock accuracy.
|
|
63
|
+
|
|
64
|
+
Recalculation from these samples matches every aggregate before display rounding.
|
|
65
|
+
Each source output passed correctness and effective-control checks before and
|
|
66
|
+
after the workload. No process was rejected. The broader host-admission receipts
|
|
67
|
+
remain outside this bundle; this table provides sample-level auditability for
|
|
68
|
+
the reported wall, CPU, RSS and variation values, not complete host reproduction.
|
|
69
|
+
|
|
70
|
+
| Runtime | Nice | Repetition | Wall ms | CPU us | Peak RSS KiB |
|
|
71
|
+
| ------- | ---: | ---------: | -------------: | ------: | -----------: |
|
|
72
|
+
| bun | +10 | 1 | 1195.596419000 | 1505936 | 196116 |
|
|
73
|
+
| bun | +10 | 2 | 1445.887323000 | 1812345 | 190948 |
|
|
74
|
+
| bun | +10 | 3 | 1467.863081000 | 1852217 | 197132 |
|
|
75
|
+
| bun | +0 | 1 | 1326.456355000 | 1659644 | 195900 |
|
|
76
|
+
| bun | +0 | 2 | 1412.394017000 | 1778913 | 189540 |
|
|
77
|
+
| bun | +0 | 3 | 1356.414170000 | 1779749 | 199772 |
|
|
78
|
+
| bun | -10 | 1 | 1291.186539000 | 1638686 | 190644 |
|
|
79
|
+
| bun | -10 | 2 | 1468.095042000 | 1847875 | 195332 |
|
|
80
|
+
| bun | -10 | 3 | 1416.096128000 | 1781215 | 193692 |
|
|
81
|
+
| bun | -20 | 1 | 1287.427399000 | 1628746 | 194676 |
|
|
82
|
+
| bun | -20 | 2 | 1497.167736000 | 1893806 | 191196 |
|
|
83
|
+
| bun | -20 | 3 | 1346.090557000 | 1691437 | 195036 |
|
|
84
|
+
| node | +10 | 1 | 1083.811791000 | 1655921 | 200988 |
|
|
85
|
+
| node | +10 | 2 | 1196.507378000 | 1849814 | 198100 |
|
|
86
|
+
| node | +10 | 3 | 1231.812016000 | 1913550 | 197184 |
|
|
87
|
+
| node | +0 | 1 | 1138.547173000 | 1717254 | 196524 |
|
|
88
|
+
| node | +0 | 2 | 1136.151985000 | 1735839 | 197480 |
|
|
89
|
+
| node | +0 | 3 | 1133.265512000 | 1736305 | 196716 |
|
|
90
|
+
| node | -10 | 1 | 1097.328563000 | 1684817 | 196008 |
|
|
91
|
+
| node | -10 | 2 | 1152.692612000 | 1789165 | 197780 |
|
|
92
|
+
| node | -10 | 3 | 1148.141853000 | 1746972 | 196516 |
|
|
93
|
+
| node | -20 | 1 | 1277.348138000 | 1994581 | 196600 |
|
|
94
|
+
| node | -20 | 2 | 1191.670413000 | 1825925 | 197344 |
|
|
95
|
+
| node | -20 | 3 | 1189.945316000 | 1821113 | 198976 |
|
|
96
|
+
|
|
97
|
+
## Omitting nice versus requesting zero
|
|
98
|
+
|
|
99
|
+
A child inherits its parent's nice value, and `exec` preserves it. Omitting
|
|
100
|
+
`nice` therefore yields zero only when the parent already has effective nice 0.
|
|
101
|
+
GNU `nice -n 0 COMMAND` adds zero to the inherited value; it does not reset +10
|
|
102
|
+
to zero. Bare `nice COMMAND` applies the default +10 adjustment. `setpriority`
|
|
103
|
+
sets an absolute value, subject to permissions; verify the effective value
|
|
104
|
+
rather than treating the requested command as proof.
|
|
105
|
+
|
|
106
|
+
Positive nice lowers CPU scheduling priority; negative nice raises it. Neither
|
|
107
|
+
reserves a core or replaces host admission and resource controls. Keep effective
|
|
108
|
+
values equal across ordinary comparison arms. General +10 background/build
|
|
109
|
+
policies serve responsiveness and are separate from this experiment; these
|
|
110
|
+
results do not authorize removing them or changing installed settings.
|
|
111
|
+
|
|
112
|
+
Sources: [Linux getpriority/setpriority](https://man7.org/linux/man-pages/man2/setpriority.2.html)
|
|
113
|
+
for inheritance, absolute values and permissions; [GNU nice](https://www.gnu.org/software/coreutils/manual/html_node/nice-invocation.html)
|
|
114
|
+
for relative adjustment. GNU online retrieval was unavailable during verification;
|
|
115
|
+
the installed command's help confirmed the adjustment and default +10.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Benchmarking and investigation tools
|
|
2
|
+
|
|
3
|
+
These defaults come from tools used in our work. Prefer a suitable runner
|
|
4
|
+
already used by the project. Check the official version's documentation before
|
|
5
|
+
relying on flags or interpreting output.
|
|
6
|
+
|
|
7
|
+
## Timing and resource measurement
|
|
8
|
+
|
|
9
|
+
| Task | Default and reason | Limits to record |
|
|
10
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| Whole CLI commands | [Hyperfine](https://github.com/sharkdp/hyperfine): repeated runs with wall time, CPU time and peak memory, warmup/preparation and JSON output. | Works across Linux, macOS and Windows. Declare warm/cold conditions. Since 2.0 commands run without a shell unless `-S` or `--shell` is given; 1.x needs `-N` for that. Peak memory and `--metrics` hardware counters are measured on Linux and macOS only; on Windows use another tool for memory. Check `schema_version` before reading exported JSON; 2.0 changed its layout. |
|
|
12
|
+
| JavaScript operations | [Tinybench](https://github.com/tinylibs/tinybench), directly or through the project's benchmark runner: warmup and task latency/throughput statistics. | The unit is a task invocation. For 1,000-operation batches, batch p99 divided by 1,000 is the p99 of batch-average costs, not mean operation cost or individual-operation p99. |
|
|
13
|
+
| HTTP load, including JS/TS servers | [oha](https://github.com/hatoo/oha): a native load generator with concurrency/rate controls, latency distributions and JSON output. | Declare protocol, connections, rate, deadlines, keepalive and error handling. Check generator CPU/network limits. Latency correction requires rate limiting with `-q`. |
|
|
14
|
+
| Command CPU and memory | [GNU time](https://www.gnu.org/software/time/manual/time.html), commonly `/usr/bin/time -v`: wall time, CPU and maximum RSS. | GNU options differ from shell `time` and BSD/macOS tools. RSS is a lifetime high-water mark for the observed process; it does not isolate retained cache memory. |
|
|
15
|
+
| Host or process contention | Linux [mpstat/pidstat](https://github.com/sysstat/sysstat): processor utilization and process CPU, memory and I/O observations. | Sampling can miss short bursts; some counters need kernel support. These explain interference, not application latency. |
|
|
16
|
+
|
|
17
|
+
On Linux with compressed swap enabled, observe compressed-swap activity as
|
|
18
|
+
well as disk swap I/O. Unchanged `pswpin`/`pswpout` counters do not establish
|
|
19
|
+
absence of swapping. For [zswap](https://www.kernel.org/doc/html/latest/admin-guide/mm/zswap.html),
|
|
20
|
+
record available `zswpin`/`zswpout`/`zswpwb` deltas from the benchmark cgroup's
|
|
21
|
+
`memory.stat` over the measured interval. Label host-wide deltas as system-wide;
|
|
22
|
+
they cannot attribute activity to the workload.
|
|
23
|
+
|
|
24
|
+
Use [cgroup swap and zswap accounting](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html)
|
|
25
|
+
(`memory.swap.current`, `memory.zswap.current`) to distinguish the workload
|
|
26
|
+
from other jobs. These occupancy values do not measure activity by themselves.
|
|
27
|
+
Record missing counters and preserve the run plan's declared pressure criteria.
|
|
28
|
+
|
|
29
|
+
## Process attribution
|
|
30
|
+
|
|
31
|
+
Prefer `pidstat -u -r -d -p ALL 1 20` for the standard Linux process view.
|
|
32
|
+
For receipts that retain PID birth identity, use the bundled collector:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
python3 scripts/process-attribution.py --seconds 20 --output /disk/path/receipt.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run from the skill directory. Linux receipts contain raw one-second snapshots
|
|
39
|
+
and CPU deltas ranked by busy cores, with process name, parent PID, birth ticks,
|
|
40
|
+
RSS pages and unmatched boundary counts. They omit command arguments.
|
|
41
|
+
|
|
42
|
+
Each row also reports `reapedChildCores`: CPU of children the process reaped
|
|
43
|
+
during the interval, attributed to that parent. It shows which process ran
|
|
44
|
+
commands too short-lived to appear in a snapshot, not the commands themselves,
|
|
45
|
+
and can include CPU the children used before the interval started.
|
|
46
|
+
|
|
47
|
+
Each interval reports `cpuBusyCores` per logical CPU for pinned runs (see
|
|
48
|
+
[fenced-core arm](#fenced-core-arm)). Busy CPU excludes idle, iowait and steal
|
|
49
|
+
time. A CPU brought online or offline during the interval reports `null`; the
|
|
50
|
+
fenced-core gate rejects that interval.
|
|
51
|
+
The collector uses its own `/proc/self/status` `VmHWM` for peak RSS;
|
|
52
|
+
`ru_maxrss` can retain a launcher's inherited high-water mark.
|
|
53
|
+
It records observer CPU and scan duration. Include those costs in the declared
|
|
54
|
+
measurement budget; no negligible-impact claim follows from successful collection.
|
|
55
|
+
|
|
56
|
+
On Windows, `scripts/process-attribution.ps1` accepts mandatory `OutputPath`
|
|
57
|
+
and `StopPath` arguments. It saves process PID, start time, name and cumulative
|
|
58
|
+
CPU, plus host activity and observer CPU/working set. Match PID and start time
|
|
59
|
+
before computing deltas. It stops when the stop file exists or after 120 samples;
|
|
60
|
+
choose a fresh stop path. Use the host's approved PowerShell invocation without
|
|
61
|
+
changing execution policy.
|
|
62
|
+
|
|
63
|
+
Neither collector stops jobs, takes a lock or grants admission. Boundary samples
|
|
64
|
+
miss processes that start and exit between them; unreadable processes are counted
|
|
65
|
+
as missing. CPU contributors explain observed activity, not whether it changed
|
|
66
|
+
application latency. Keep host/resource gates and owner coordination separate.
|
|
67
|
+
|
|
68
|
+
## Scheduling controls
|
|
69
|
+
|
|
70
|
+
These Linux controls change resource allocation under competition; they do not
|
|
71
|
+
establish isolation. Check support for the actual kernel, controller and I/O
|
|
72
|
+
path. Record effective values and unavailable controls for every arm; requesting
|
|
73
|
+
a setting does not prove it took effect.
|
|
74
|
+
|
|
75
|
+
| Control | Effect and limit |
|
|
76
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| [Nice level](https://man7.org/linux/man-pages/man2/nice.2.html) | Lower numeric values raise fair CPU scheduling priority. Permissions and autogroup policy affect the result. This does not reserve a core. |
|
|
78
|
+
| CPU affinity (`taskset`) | Restricts available CPUs and can affect worker counts and parallelism. It does not reserve exclusive cores. Use it for a declared CPU configuration or a [fenced-core arm](#fenced-core-arm), not automatic noise reduction. |
|
|
79
|
+
| [CPU and I/O cgroup weights](https://docs.kernel.org/admin-guide/cgroup-v2.html) | Adjust relative shares among active sibling groups. Parent limits and controller/device support still apply; a higher weight is not a capacity guarantee. |
|
|
80
|
+
| [Process I/O priority](https://man7.org/linux/man-pages/man2/ioprio_set.2.html) | Depends on the supporting I/O scheduler and path. Process-specific priority does not cover asynchronous writes. |
|
|
81
|
+
| [Memory protection](https://docs.kernel.org/admin-guide/cgroup-v2.html) | `memory.low` and `memory.min` protect against reclaim, not memory-bandwidth competition. Excessive hard protection can cause OOM. Memory caps remain limits, not reservations. |
|
|
82
|
+
|
|
83
|
+
### Fenced-core arm
|
|
84
|
+
|
|
85
|
+
On a shared host where brief bursts from other sessions keep failing host-wide
|
|
86
|
+
admission, a fenced-core arm keeps other work off chosen CPUs for a finite phase. It is a
|
|
87
|
+
declared sensitivity arm that needs the user's authorization, because it changes
|
|
88
|
+
other sessions' CPU placement:
|
|
89
|
+
|
|
90
|
+
1. Choose the benchmark CPUs as whole physical cores (include SMT siblings) and pin
|
|
91
|
+
every arm, its browser or runtime and its children to them.
|
|
92
|
+
2. Move every other thread you are permitted to change off the benchmark CPUs.
|
|
93
|
+
Record each thread's identity (PID, thread ID, birth ticks) and original
|
|
94
|
+
affinity. Stop and signal nothing. A thread that sets its own affinity, or
|
|
95
|
+
is created with an explicit mask, can stay eligible on the benchmark CPUs;
|
|
96
|
+
the gate still counts its CPU.
|
|
97
|
+
3. Gate on foreign CPU on the benchmark CPUs: their busy time (`cpuBusyCores`)
|
|
98
|
+
minus the interval CPU of the benchmark's own live processes (`busyCores`).
|
|
99
|
+
Do not subtract `reapedChildCores`: it can include CPU used before the
|
|
100
|
+
interval, so it would understate foreign load. Process CPU is not per-CPU,
|
|
101
|
+
so the subtraction holds only while the whole tree stays on the benchmark
|
|
102
|
+
CPUs: check every thread's effective affinity at the start and end of each
|
|
103
|
+
batch, and reject the batch if any ran elsewhere. Boundary checks miss a
|
|
104
|
+
thread that widens and restores its affinity within the batch; when the
|
|
105
|
+
workload can change its own affinity, confine the tree with a cpuset it
|
|
106
|
+
cannot widen. In the same census, record every other permitted thread
|
|
107
|
+
still eligible on the benchmark CPUs as unfenced. Report host-wide load as
|
|
108
|
+
context.
|
|
109
|
+
4. At the phase boundary, restore recorded threads after checking their birth
|
|
110
|
+
ticks. Threads started during the window inherited the fence; give them the
|
|
111
|
+
CPU set they would otherwise have inherited, usually all CPUs. A still-fenced
|
|
112
|
+
thread can start another while you restore, so repeat the census until a
|
|
113
|
+
full pass finds no permitted thread left fenced; threads started after their
|
|
114
|
+
creator is restored inherit the restored set. Do not freeze processes to stop
|
|
115
|
+
thread creation. Run this cleanup even when the phase fails.
|
|
116
|
+
|
|
117
|
+
Report results as constrained to the pinned setting with the effective
|
|
118
|
+
affinities. Processes you cannot change (other users, system services) stay
|
|
119
|
+
unfenced; record them. In a virtual machine, guest affinity does not reserve
|
|
120
|
+
host cores; host activity, shared cache, memory bandwidth and power limits
|
|
121
|
+
still apply.
|
|
122
|
+
|
|
123
|
+
Avoid realtime [CPU](https://man7.org/linux/man-pages/man7/sched.7.html) or
|
|
124
|
+
[I/O](https://man7.org/linux/man-pages/man1/ionice.1.html) classes for generic
|
|
125
|
+
benchmark noise reduction; they can starve dependencies or monitoring.
|
|
126
|
+
Preserve the host's scheduling policy unless it authorizes a different regime.
|
|
127
|
+
|
|
128
|
+
## Profiling and debugging
|
|
129
|
+
|
|
130
|
+
Use a separate diagnostic run; instrumentation changes the work it observes.
|
|
131
|
+
|
|
132
|
+
| Question | Tool and reason | Limits to record |
|
|
133
|
+
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| Where does JavaScript CPU time go? | Runtime CPU profiler: [Node](https://nodejs.org/api/cli.html#--cpu-prof) or [Bun](https://bun.com/docs/project/benchmarking#cpu-profiling). | Match runtime/version and profile settings. Samples locate hotspots; verify improvements with unprofiled runs. |
|
|
135
|
+
| Which JS objects are allocated or retained? | Runtime allocation profiles and heap snapshots: [Node](https://nodejs.org/api/cli.html#--heap-prof), [Bun](https://bun.com/docs/project/benchmarking#heap-profiling). | Matching flags do not guarantee matching metrics; see the distinction below. |
|
|
136
|
+
| Where does native/kernel CPU work go? | Linux [perf stat](https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-stat.txt) and [perf record](https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-record.txt): counters, samples and call graphs. | Permissions, hardware/kernel support, symbols and stack unwinding affect evidence. Frame-pointer unwinding needs compatible binaries. |
|
|
137
|
+
| Which system calls fail or wait? | Linux [strace](https://github.com/strace/strace/blob/master/doc/strace.1.in): syscall results, errors and ordering. | Interception changes execution; summaries are not ordinary throughput. Traces may contain sensitive paths or payloads. |
|
|
138
|
+
| Which native allocations churn? | Linux [heaptrack](https://github.com/KDE/heaptrack): allocation counts, temporary allocations and allocation stacks. | Symbols improve attribution. Pool allocators need annotations; native allocation traces do not identify individual GC-managed JS objects. |
|
|
139
|
+
|
|
140
|
+
Node `--heap-prof` samples allocation activity. Bun's documentation describes
|
|
141
|
+
a full V8-format heap snapshot at exit, not the same sampled allocation history;
|
|
142
|
+
its accepted interval flag does not add allocation sampling. Record which kind
|
|
143
|
+
of artifact was captured and verify semantics for the version used.
|
|
144
|
+
|
|
145
|
+
For a concrete crash, hang or event-tracing question, use the available
|
|
146
|
+
[debugger](https://www.sourceware.org/gdb/documentation/) or
|
|
147
|
+
[system tracing tool](https://bpftrace.org/docs). They are optional diagnostic
|
|
148
|
+
tools, not benchmark prerequisites; platform and permissions affect availability.
|
|
149
|
+
|
|
150
|
+
For TypeScript compiler versus application timing, read [the language reference](javascript.md).
|
|
151
|
+
|
|
152
|
+
## Correctness and audit evidence
|
|
153
|
+
|
|
154
|
+
Use the repository's lint, type checks, focused tests and relevant static or
|
|
155
|
+
security analysis. These establish intended behavior and detect defects; passing
|
|
156
|
+
them does not establish speed or memory use. State their coverage limits.
|
|
157
|
+
|
|
158
|
+
Add a tool after using it successfully on a real task. Record the question it
|
|
159
|
+
answered, why it helped, its platform and measurement limits, and the official
|
|
160
|
+
documentation. Keep untried candidates in research notes until then.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
param([Parameter(Mandatory=$true)][string]$OutputPath,[Parameter(Mandatory=$true)][string]$StopPath)
|
|
2
|
+
$ErrorActionPreference='Stop'
|
|
3
|
+
$cpuCounter=New-Object System.Diagnostics.PerformanceCounter('Processor','% Processor Time','_Total')
|
|
4
|
+
$memoryCounter=New-Object System.Diagnostics.PerformanceCounter('Memory','Available MBytes')
|
|
5
|
+
$pageCounter=New-Object System.Diagnostics.PerformanceCounter('Memory','Pages Input/sec')
|
|
6
|
+
$records=@()
|
|
7
|
+
try {
|
|
8
|
+
[void]$cpuCounter.NextValue()
|
|
9
|
+
for($i=0;$i -lt 120;$i++) {
|
|
10
|
+
$items=@(); $missing=0
|
|
11
|
+
foreach($p in Get-Process) {
|
|
12
|
+
try { if($null -ne $p.CPU) { $items+=@{pid=$p.Id;name=$p.ProcessName;cpuSeconds=$p.CPU;started=$p.StartTime.ToUniversalTime().ToString('o')} } } catch { $missing++ }
|
|
13
|
+
}
|
|
14
|
+
$self=Get-Process -Id $PID
|
|
15
|
+
$records+=@{utc=[DateTime]::UtcNow.ToString('o');totalCpuPercent=$cpuCounter.NextValue();availableBytes=$memoryCounter.NextValue()*1MB;pagesInputPerSec=$pageCounter.NextValue();processes=$items;missingProcesses=$missing;observerCpuSeconds=$self.CPU;observerWorkingSet=$self.WorkingSet64}
|
|
16
|
+
if(Test-Path $StopPath) { break }
|
|
17
|
+
[Threading.Thread]::Sleep(1000)
|
|
18
|
+
}
|
|
19
|
+
ConvertTo-Json -InputObject $records -Depth 5 -Compress | Set-Content -LiteralPath $OutputPath -Encoding utf8
|
|
20
|
+
} finally { $cpuCounter.Dispose();$memoryCounter.Dispose();$pageCounter.Dispose() }
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Finite Linux CPU attribution; does not acquire admission, stop processes or print argv."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import resource
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_process(stat):
|
|
12
|
+
end = stat.rfind(")")
|
|
13
|
+
fields = stat[end + 2:].split()
|
|
14
|
+
return {"name": stat[stat.find("(") + 1:end], "parentPid": int(fields[1]),
|
|
15
|
+
"cpuTicks": int(fields[11]) + int(fields[12]),
|
|
16
|
+
"reapedChildCpuTicks": int(fields[13]) + int(fields[14]), "birthTicks": fields[19],
|
|
17
|
+
"rssPages": int(fields[21])}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def snapshot(proc=Path("/proc")):
|
|
21
|
+
processes = {}
|
|
22
|
+
missing = 0
|
|
23
|
+
started = time.monotonic()
|
|
24
|
+
for path in proc.iterdir():
|
|
25
|
+
if not path.name.isdigit():
|
|
26
|
+
continue
|
|
27
|
+
try:
|
|
28
|
+
processes[int(path.name)] = read_process((path / "stat").read_text(encoding="utf-8", errors="replace"))
|
|
29
|
+
except (OSError, ValueError, IndexError):
|
|
30
|
+
missing += 1
|
|
31
|
+
cpu_lines = [line.split() for line in (proc / "stat").read_text().splitlines() if line.startswith("cpu")]
|
|
32
|
+
ticks = list(map(int, cpu_lines[0][1:9]))
|
|
33
|
+
per_cpu = {line[0][3:]: list(map(int, line[1:9])) for line in cpu_lines[1:]}
|
|
34
|
+
peak = int(next(line for line in (proc / "self/status").read_text().splitlines()
|
|
35
|
+
if line.startswith("VmHWM:")).split()[1]) * 1024
|
|
36
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
37
|
+
return {"monotonic": time.monotonic(), "utc": datetime.now(timezone.utc).isoformat(),
|
|
38
|
+
# Busy excludes idle, iowait and steal: steal is time the hypervisor ran
|
|
39
|
+
# something else, not work by any guest thread.
|
|
40
|
+
"hostBusyTicks": sum(ticks) - ticks[3] - ticks[4] - ticks[7],
|
|
41
|
+
"cpuBusyTicks": {cpu: sum(t) - t[3] - t[4] - t[7] for cpu, t in per_cpu.items()},
|
|
42
|
+
"processes": processes,
|
|
43
|
+
"missingProcesses": missing, "scanSeconds": time.monotonic() - started,
|
|
44
|
+
"observerCpuSeconds": usage.ru_utime + usage.ru_stime,
|
|
45
|
+
"observerPeakRssBytes": peak}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cpu_busy_cores(before, after, scale):
|
|
49
|
+
# A CPU brought online or offline between snapshots has no interval value;
|
|
50
|
+
# report it as None so a gate cannot read it as idle.
|
|
51
|
+
return {cpu: (after[cpu] - before[cpu]) / scale if cpu in before and cpu in after else None
|
|
52
|
+
for cpu in sorted(before.keys() | after.keys(), key=int)}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def compare(before, after, hz):
|
|
56
|
+
seconds = after["monotonic"] - before["monotonic"]
|
|
57
|
+
rows = []
|
|
58
|
+
prior = before["processes"]
|
|
59
|
+
current = after["processes"]
|
|
60
|
+
matched = set()
|
|
61
|
+
for pid, process in current.items():
|
|
62
|
+
old = prior.get(pid)
|
|
63
|
+
if old is None or old["birthTicks"] != process["birthTicks"]:
|
|
64
|
+
continue
|
|
65
|
+
matched.add(pid)
|
|
66
|
+
cpu = process["cpuTicks"] - old["cpuTicks"]
|
|
67
|
+
# When the parent reaps children, their total CPU is added to its
|
|
68
|
+
# cutime/cstime. The delta attributes that CPU to the parent; it can
|
|
69
|
+
# include CPU the children used before the interval started.
|
|
70
|
+
reaped = process.get("reapedChildCpuTicks", 0) - old.get("reapedChildCpuTicks", 0)
|
|
71
|
+
if cpu > 0 or reaped > 0:
|
|
72
|
+
rows.append({"pid": pid, **process, "busyCores": cpu / hz / seconds,
|
|
73
|
+
"reapedChildCores": reaped / hz / seconds})
|
|
74
|
+
return {"startUtc": before["utc"], "endUtc": after["utc"], "seconds": seconds,
|
|
75
|
+
"hostBusyCores": (after["hostBusyTicks"] - before["hostBusyTicks"]) / hz / seconds,
|
|
76
|
+
"cpuBusyCores": cpu_busy_cores(before.get("cpuBusyTicks", {}), after.get("cpuBusyTicks", {}),
|
|
77
|
+
hz * seconds),
|
|
78
|
+
"unmatchedBefore": len(prior) - len(matched),
|
|
79
|
+
"unmatchedAfter": len(current) - len(matched),
|
|
80
|
+
"processCpu": sorted(rows, key=lambda row: -row["busyCores"])}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main():
|
|
84
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
85
|
+
parser.add_argument("--seconds", type=int, default=20)
|
|
86
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
87
|
+
args = parser.parse_args()
|
|
88
|
+
if not 1 <= args.seconds <= 300:
|
|
89
|
+
parser.error("seconds must be between 1 and 300")
|
|
90
|
+
hz = os.sysconf("SC_CLK_TCK")
|
|
91
|
+
samples = [snapshot()]
|
|
92
|
+
for _ in range(args.seconds):
|
|
93
|
+
time.sleep(1)
|
|
94
|
+
samples.append(snapshot())
|
|
95
|
+
intervals = [compare(a, b, hz) for a, b in zip(samples, samples[1:])]
|
|
96
|
+
result = {"version": 2, "observerPid": os.getpid(), "clockTicksPerSecond": hz,
|
|
97
|
+
"bootId": Path("/proc/sys/kernel/random/boot_id").read_text().strip(),
|
|
98
|
+
"samples": samples, "intervals": intervals,
|
|
99
|
+
"limits": "Snapshots miss processes that start and exit between samples. reapedChildCores attributes children's CPU to the parent in the interval it reaps them, including CPU they used before the interval. Busy CPU excludes idle, iowait and steal. CPU attribution does not prove interference or grant admission. Observer overhead is included; no negligible-impact claim."}
|
|
100
|
+
args.output.write_text(json.dumps(result, indent=2))
|
|
101
|
+
print(json.dumps({"output": str(args.output), "intervals": len(intervals),
|
|
102
|
+
"peakHostBusyCores": max(row["hostBusyCores"] for row in intervals),
|
|
103
|
+
"observerPeakRssBytes": samples[-1]["observerPeakRssBytes"]}))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
main()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Krehel
|
|
4
|
+
Copyright (c) 2026 Emil Kowalski
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Source notices
|
|
2
|
+
|
|
3
|
+
This notice accompanies the hr-better-accessibility bundle when copied on its own.
|
|
4
|
+
|
|
5
|
+
## jakubkrehel/skills
|
|
6
|
+
|
|
7
|
+
Source: https://github.com/jakubkrehel/skills (compared at revision `267330e1adfc66a718fb65fa6918c1f06d0a689e`)
|
|
8
|
+
|
|
9
|
+
- `skills/hr-better-accessibility/`, `skills/hr-better-colors/`, `skills/hr-better-layout/`,
|
|
10
|
+
`skills/hr-better-typography/` and `skills/hr-better-writing/`: adapted skill bundles,
|
|
11
|
+
including their supporting references and discovery metadata.
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Jakub Krehel
|
|
14
|
+
|
|
15
|
+
## emilkowalski/skills
|
|
16
|
+
|
|
17
|
+
Source: https://github.com/emilkowalski/skills (compared at revision `e8a175de22ae1e49370fc144c1f3bb9aeedf988d`)
|
|
18
|
+
|
|
19
|
+
- `skills/hr-better-accessibility/` and `skills/hr-better-layout/`: mobile behavior and
|
|
20
|
+
viewport guidance adapted from `skills/mobile-native/SKILL.md`.
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2026 Emil Kowalski
|
|
23
|
+
|
|
24
|
+
## house-rules and MIT permission notice
|
|
25
|
+
|
|
26
|
+
MIT License
|
|
27
|
+
|
|
28
|
+
Copyright (c) 2026 Aaron Queen
|
|
29
|
+
|
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
32
|
+
in the Software without restriction, including without limitation the rights
|
|
33
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
34
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
35
|
+
furnished to do so, subject to the following conditions:
|
|
36
|
+
|
|
37
|
+
The above copyright notice and this permission notice shall be included in all
|
|
38
|
+
copies or substantial portions of the Software.
|
|
39
|
+
|
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
42
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
43
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
44
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
45
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
46
|
+
SOFTWARE.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-better-accessibility
|
|
3
|
+
description: "Accessibility rules for web UI: native elements, keyboard and focus, form labels and errors, accessible names, live regions, reduced motion, zoom and reflow. Use when building or reviewing web markup, forms, dialogs or interactive controls."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
upstream: https://github.com/jakubkrehel/skills
|
|
7
|
+
upstream_ref: 267330e1adfc66a718fb65fa6918c1f06d0a689e
|
|
8
|
+
upstream_path: skills/better-accessibility/
|
|
9
|
+
local_patches: "Use-when description; reduced-motion kill switch spares spinners; 320px reflow keeps the 2D-content exception; carousel pan-y, touch-callout and real-hardware check (from emilkowalski/skills mobile-native)"
|
|
10
|
+
house_rules_patches: "Portable provenance and review verdict"
|
|
11
|
+
mobile_native_source: https://github.com/emilkowalski/skills/blob/e8a175de22ae1e49370fc144c1f3bb9aeedf988d/skills/mobile-native/SKILL.md
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Accessibility
|
|
15
|
+
|
|
16
|
+
Most accessibility is free if you use the platform. Native elements ship with keyboard support, real labels announce themselves and a visible focus ring is one CSS rule.
|
|
17
|
+
|
|
18
|
+
Write every fix in the project's styling system, and use the exact values below rather than familiar-looking substitutes.
|
|
19
|
+
|
|
20
|
+
Reviewing means two walks. Keyboard-only, where every flow completes without a mouse. Then screen-reader, where every control announces a name, a role and its state. When unsure, take the platform default over a custom rebuild, and remove ARIA rather than add it.
|
|
21
|
+
|
|
22
|
+
Contrast measurement and color fixes belong to `hr-better-colors`. Text sizing and iOS input zoom belong to `hr-better-typography`. Spatial RTL layout belongs to `hr-better-layout`.
|
|
23
|
+
|
|
24
|
+
## Native elements first
|
|
25
|
+
|
|
26
|
+
The first rule of ARIA: don't use ARIA when a native element exists. `<button>` for actions, `<a href>` for navigation, never `<div onClick>`. A real link must support Cmd/Ctrl/middle-click. No ARIA is better than bad ARIA. See [semantics-and-aria.md](semantics-and-aria.md) for landmarks, button-vs-link and disabled states.
|
|
27
|
+
|
|
28
|
+
## Visible focus rings
|
|
29
|
+
|
|
30
|
+
Style `:focus-visible`, not bare `:focus`. Keyboard users get a ring and mouse users usually don't. Prefer the browser's unmodified indicator.
|
|
31
|
+
|
|
32
|
+
A custom ring needs a project focus token or another explicit color. Verify the whole indicator against every adjacent color it crosses, `currentColor` included. Use at least a `2px` solid perimeter or an equivalent visible area. Never use `outline: none` without a verified replacement, and preserve system colors in forced-colors mode. Recipes are in [focus-and-keyboard.md](focus-and-keyboard.md).
|
|
33
|
+
|
|
34
|
+
## Full keyboard support
|
|
35
|
+
|
|
36
|
+
Every pointer interaction needs a keyboard path. Follow the ARIA APG patterns: Escape closes overlays, arrow keys move within composite widgets, Tab moves between widgets, Enter and Space activate.
|
|
37
|
+
|
|
38
|
+
Use only `tabindex="0"` to join the natural tab order and `tabindex="-1"` for programmatic focus. Positive values break that order. Composite widgets use roving tabindex, where the active item is `0` and every other is `-1`.
|
|
39
|
+
|
|
40
|
+
## Trap and restore focus
|
|
41
|
+
|
|
42
|
+
Modals set `inert` on the background content, move focus inside on open and return focus to the trigger on close. Add `overscroll-behavior: contain` so background content doesn't scroll.
|
|
43
|
+
|
|
44
|
+
## Minimum hit area
|
|
45
|
+
|
|
46
|
+
WCAG 2.5.8's Level AA baseline is a 24×24 CSS-pixel target, or one of its spacing, equivalent-control, inline, user-agent and essential exceptions. Aim for 44×44px on touch and 40×40px on desktop where density permits. Extend with a pseudo-element when the visible element should stay smaller.
|
|
47
|
+
|
|
48
|
+
Never let extended hit areas overlap. Give decorative layers `pointer-events: none`, so a glow never swallows the clicks meant for the control beneath it. Sizes and collision rules are in [hit-areas.md](hit-areas.md).
|
|
49
|
+
|
|
50
|
+
## Label and type every control
|
|
51
|
+
|
|
52
|
+
Every input gets a `<label for>` or a wrapping `<label>`. A placeholder is never a label. Label and control share one hit target, with no dead zone between a checkbox and its text.
|
|
53
|
+
|
|
54
|
+
Add `autocomplete` with a meaningful `name`, plus the `type` and `inputmode` that summon the right keyboard. Never block paste; users paste passwords and one-time codes. See [forms.md](forms.md).
|
|
55
|
+
|
|
56
|
+
## Errors that announce
|
|
57
|
+
|
|
58
|
+
Keep submit enabled until the request starts, then disable with a spinner and the original label. Validate on submit. Mark failing fields `aria-invalid="true"`, point `aria-describedby` at the inline error text and focus the first invalid field.
|
|
59
|
+
|
|
60
|
+
Use native `disabled` when a control is genuinely unavailable. Reach for `aria-disabled="true"` only when it should stay focusable, then block pointer, keyboard and form behavior in code and style the state explicitly.
|
|
61
|
+
|
|
62
|
+
## Accessible names everywhere
|
|
63
|
+
|
|
64
|
+
Icon-only buttons need a descriptive `aria-label`. Visible label text must appear in the accessible name. Decorative elements get `aria-hidden="true"`, never on a focusable element.
|
|
65
|
+
|
|
66
|
+
## Don't rely on color alone
|
|
67
|
+
|
|
68
|
+
Status needs a redundant cue: an icon, text, or an underline alongside the color. Work out which WCAG contrast requirement applies, then use `hr-better-colors` to measure the rendered pair. When it fails, report the pair and the requirement it misses, and leave the colors alone unless asked.
|
|
69
|
+
|
|
70
|
+
## Honor prefers-reduced-motion
|
|
71
|
+
|
|
72
|
+
Wrap motion in `@media (prefers-reduced-motion: no-preference)` so it is opt-in. Under reduced motion, replace slides and scales with opacity crossfades, and kill parallax and autoplay entirely.
|
|
73
|
+
|
|
74
|
+
Two rules hold regardless of the preference. Autoplaying media needs a visible pause control, and toasts carrying an action or an error stay until dismissed. See [motion-and-zoom.md](motion-and-zoom.md).
|
|
75
|
+
|
|
76
|
+
## Announce dynamic content
|
|
77
|
+
|
|
78
|
+
Three mechanisms, three jobs. `aria-describedby` carries field-specific validation. A polite live region (`role="status"`) carries non-urgent updates not tied to a control, such as toasts and result counts. `role="alert"` carries urgent untied errors and nothing else.
|
|
79
|
+
|
|
80
|
+
Repeated polite announcements need a stable empty region rendered before its text updates. Dynamically inserted alerts vary in support, so test them on the screen readers you target. See [screen-readers.md](screen-readers.md).
|
|
81
|
+
|
|
82
|
+
## Alt text by purpose
|
|
83
|
+
|
|
84
|
+
Decorative images get `alt=""`. Informative images describe the meaning. Functional images describe the action: a search icon button is `alt="Search"`, not `alt="magnifying glass"`.
|
|
85
|
+
|
|
86
|
+
## Structure is navigation
|
|
87
|
+
|
|
88
|
+
Use headings that describe their sections and form a coherent outline. Give the page one `<h1>` and nest the levels below it without skipping. Expose one visible primary `<main>` landmark. When repeated navigation or chrome precedes it, make a "Skip to content" link the first focusable element. Anchored headings get `scroll-margin-top`.
|
|
89
|
+
|
|
90
|
+
## Survive zoom and text resize
|
|
91
|
+
|
|
92
|
+
The page must work at 200% zoom and reflow at 320px width without horizontal scrolling. Tables, maps and code blocks may scroll inside their own container. Use `min-height` rather than fixed `height` on text containers. Prefer `rem` breakpoints where they fit the codebase, and never let the viewport meta cap how far the reader can zoom.
|
|
93
|
+
|
|
94
|
+
## Before you finish
|
|
95
|
+
|
|
96
|
+
| Mistake | Fix |
|
|
97
|
+
| ----------------------------------------------- | ---------------------------------------------------------------- |
|
|
98
|
+
| Custom focus color assumed to work everywhere | Verify it against every adjacent color and in forced-colors mode |
|
|
99
|
+
| Repeated polite update inconsistently announced | Keep a stable empty status region and update its text |
|
|
100
|
+
| `assertive` live region for a routine toast | Use `polite`; reserve `assertive` for errors |
|
|
101
|
+
| `aria-hidden="true"` on a focusable element | Remove it or make the element non-focusable |
|
|
102
|
+
| Submit disabled until the form is valid | Keep it enabled; validate on submit and focus the first error |
|
|
103
|
+
| Hover treatment stuck after a tap on touch | Gate hover styling with `@media (hover: hover)` |
|
|
104
|
+
| Tooltip on a natively `disabled` control | Text beside it, or `aria-disabled` so it stays focusable |
|
|
105
|
+
|
|
106
|
+
## Reporting
|
|
107
|
+
|
|
108
|
+
**Severity.** `HIGH` prevents a task, hides content from assistive technology, or creates a systemic failure. `MEDIUM` makes an interaction meaningfully harder. `LOW` is isolated polish.
|
|
109
|
+
|
|
110
|
+
**Verification.** Without a browser: accessible names on every interactive element, keyboard handlers on non-native controls, focus styles, `prefers-reduced-motion` guards and form labels bound to their inputs. With one: tab the flow in order, read computed names and roles from the accessibility tree, confirm a visible focus indicator at every stop and run an automated audit. Report every check you could not run as `Not verified`.
|
|
111
|
+
|
|
112
|
+
**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
|
|
113
|
+
|
|
114
|
+
| Severity | Location | Before | After | Why |
|
|
115
|
+
| -------- | -------- | ------ | ----- | --- |
|
|
116
|
+
|
|
117
|
+
`Location` is `path/to/file:line`. `Why` names the principle and the user impact.
|
|
118
|
+
|
|
119
|
+
Report `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable accessibility findings" and report verification.
|