entropy-machines 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +68 -0
  3. package/agents/isolated-worker.md +128 -0
  4. package/agents/verifier.md +158 -0
  5. package/bin/dispatch +700 -0
  6. package/bin/doclint +460 -0
  7. package/bin/drain +507 -0
  8. package/bin/drain-pick.py +168 -0
  9. package/bin/drain-prompt.md +67 -0
  10. package/bin/drain-run.sh +342 -0
  11. package/bin/entropy-machines-init +285 -0
  12. package/bin/handoff +1151 -0
  13. package/bin/init +232 -0
  14. package/bin/post-fold-audit +377 -0
  15. package/bin/serve +724 -0
  16. package/bin/status +208 -0
  17. package/bin/tracker +153 -0
  18. package/docs/AGENT-QUICKSTART.md +86 -0
  19. package/docs/CONFIG.md +68 -0
  20. package/docs/NPM.md +91 -0
  21. package/docs/SERVE.md +74 -0
  22. package/docs/TRACKER-ADAPTER.md +66 -0
  23. package/doctrine/HANDOFF-PROMPT.md +63 -0
  24. package/doctrine/README.md +62 -0
  25. package/doctrine/ROLES.md +27 -0
  26. package/doctrine/WORKFLOW.md +87 -0
  27. package/hooks/commit-msg +24 -0
  28. package/hooks/post-checkout +354 -0
  29. package/hooks/pre-commit +33 -0
  30. package/lib/PRD-001-orientation.html +1180 -0
  31. package/lib/REPORT-TEMPLATE.html +413 -0
  32. package/lib/changelog-collate.mjs +328 -0
  33. package/lib/changelog-guard.sh +157 -0
  34. package/lib/changelog-new.mjs +70 -0
  35. package/lib/config.mjs +283 -0
  36. package/lib/config.py +317 -0
  37. package/lib/doc-template.html +807 -0
  38. package/lib/entropy-drain.plist.in +59 -0
  39. package/lib/entropy-drain.service.in +53 -0
  40. package/lib/entropy-drain.timer.in +36 -0
  41. package/lib/fail-first.mjs +901 -0
  42. package/lib/handoff-guard.sh +623 -0
  43. package/lib/install-hooks.sh +169 -0
  44. package/lib/notes.py +675 -0
  45. package/lib/preflight-tree.mjs +82 -0
  46. package/lib/roots.sh +212 -0
  47. package/lib/themes/daylight.css +84 -0
  48. package/lib/themes/high-contrast.css +36 -0
  49. package/lib/tracker-file +333 -0
  50. package/lib/tracker-view.py +784 -0
  51. package/package.json +38 -0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Refuse to run a suite from a tree that cannot resolve its own dependencies.
4
+ *
5
+ *
6
+ * THE FAILURE THIS EXISTS FOR. A git worktree has no `node_modules`. Node then
7
+ * walks UP from the worktree looking for one — and on 2026-08-17 a dispatched
8
+ * agent's `npm run e2e` resolved `playwright` out of a SIBLING agent's worktree
9
+ * and ran with that worktree as cwd. It tested someone else's code and reported
10
+ * the result as its own. Green, confident, wrong. The agent caught it only by
11
+ * watching the output; nothing in the tooling would have told it, or the
12
+ * session landing its work.
13
+ *
14
+ * That is worse than the already-known symptom (an empty ENOENT from a missing
15
+ * .bin/tsc), because a spurious red gets investigated and a spurious green does
16
+ * not. A worktree isolates the filesystem; it does not isolate node resolution.
17
+ *
18
+ * THE CHECK. Every package a suite spawns must be present under THIS tree's own
19
+ * node_modules. A symlink to the main checkout satisfies that and is the fix we
20
+ * recommend — what must never happen is no entry at all, because that is the
21
+ * only case where resolution leaves the tree.
22
+ *
23
+ * Silent and fast on the happy path: one `git rev-parse` and four `stat`s.
24
+ */
25
+ import { execFileSync } from 'node:child_process';
26
+ import { existsSync } from 'node:fs';
27
+ import { dirname, join } from 'node:path';
28
+
29
+ // Anything a suite spawns or imports from the tree root. Keep this list to
30
+ // things whose absence produces a WRONG ANSWER rather than a clean crash.
31
+ const NEEDED = ['playwright', 'vitest', 'typescript', 'esbuild'];
32
+
33
+ function git(...args) {
34
+ return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
35
+ }
36
+
37
+ let root;
38
+ try {
39
+ root = git('rev-parse', '--show-toplevel');
40
+ } catch {
41
+ process.exit(0); // not a git checkout — nothing to be confused about
42
+ }
43
+
44
+ const nodeModules = join(root, 'node_modules');
45
+ const missing = existsSync(nodeModules)
46
+ ? NEEDED.filter((p) => !existsSync(join(nodeModules, p)))
47
+ : NEEDED;
48
+
49
+ if (missing.length === 0) process.exit(0);
50
+
51
+ // Where a worktree should point. --git-common-dir is the MAIN checkout's .git
52
+ // even when called from a worktree, so its parent is the tree that has the
53
+ // real node_modules.
54
+ let mainTree = null;
55
+ try {
56
+ const commonDir = git('rev-parse', '--path-format=absolute', '--git-common-dir');
57
+ const candidate = dirname(commonDir);
58
+ if (candidate !== root && existsSync(join(candidate, 'node_modules'))) mainTree = candidate;
59
+ } catch {
60
+ /* older git without --path-format; the message still works without it */
61
+ }
62
+
63
+ const lines = [
64
+ '',
65
+ 'preflight: this tree cannot resolve its own dependencies — refusing to run.',
66
+ '',
67
+ ` tree: ${root}`,
68
+ ` missing: ${missing.join(', ')}${existsSync(nodeModules) ? ' (node_modules exists but is incomplete)' : ' (no node_modules at all)'}`,
69
+ '',
70
+ ' Node would search UPWARD from here and can land in another worktree, which',
71
+ ' means testing someone else\'s code and reporting it as yours. That has',
72
+ ' happened once already.',
73
+ '',
74
+ ];
75
+ lines.push(
76
+ mainTree
77
+ ? ` Fix: ln -s ${JSON.stringify(join(mainTree, 'node_modules'))} ${JSON.stringify(nodeModules)}`
78
+ : ' Fix: symlink or install node_modules in this tree (npm ci).'
79
+ );
80
+ lines.push('');
81
+ console.error(lines.join('\n'));
82
+ process.exit(1);
package/lib/roots.sh ADDED
@@ -0,0 +1,212 @@
1
+ # roots.sh — resolve the ONE root every entry point needs. SOURCE it, do not
2
+ # run it.
3
+ #
4
+ # . "$(dirname "$0")/../lib/roots.sh"
5
+ # ENTROPY_MACHINES_HOME=$(entropy_machines_home "$0")
6
+ # entropy_machines_require_root <tool-name> # sets+exports ENTROPY_MACHINES_ROOT,
7
+ # # refusing a nested clone first
8
+ #
9
+ # THERE IS ONE ROOT: THE GIT REPOSITORY.
10
+ #
11
+ # The harness is VENDORED AS PLAIN TRACKED FILES inside the project it works
12
+ # on and committed alongside the project's code — exactly as a `scripts/`
13
+ # directory is. There is no nested `.git`, so `git` from anywhere inside the
14
+ # project always answers with the project. config.json, .entropy-machines/, the
15
+ # harness's own bin/ and lib/ are all inside one repository.
16
+ #
17
+ # ENTROPY_MACHINES_ROOT the repository's MAIN checkout. .entropy-machines/
18
+ # lives here. This is THE root.
19
+ # ENTROPY_MACHINES_HOME the directory the harness's own files sit in (bin/, lib/,
20
+ # hooks/, doctrine/, config.json). NOT a root and not a repo —
21
+ # just a path, derived from the calling script's own $0, so a
22
+ # script can find its sibling lib/ files whether the harness is
23
+ # vendored at the repo root or in a subdirectory. It is always
24
+ # inside ENTROPY_MACHINES_ROOT, or equal to it when the harness
25
+ # is its own project.
26
+ #
27
+ # WHAT USED TO BE HERE AND WHY IT IS GONE. This file used to resolve TWO
28
+ # roots, because the install layout was a NESTED GIT CLONE (`cd my-project &&
29
+ # git clone <harness> entropy-machines`). A nested repo shadows its parent for
30
+ # every git query, so "which repo am I in" had two answers depending on cwd.
31
+ # That one fact produced all of: a second root, drift detection
32
+ # (`entropy_harness_drift`), drift refusals in bin/init, gitignoring the
33
+ # harness directory, and a worktree-resolution stanza to tell the two apart.
34
+ # The vendored layout deletes the premise, so all of it is deleted. If you
35
+ # find yourself re-adding a "which repo is this really" check, the layout has
36
+ # regressed, not the code.
37
+ #
38
+ # THE NESTED CLONE IS REFUSED, NOT RESOLVED. Deleting the two-root machinery
39
+ # removed the harness's ability to COPE with a nested clone; it did not stop
40
+ # anyone creating one (`cd my-project && git clone <harness> tools/` is the
41
+ # instinct, and is what the README said to do until recently). A nested repo
42
+ # shadows its parent for every git query, so every command run from inside it
43
+ # resolves to the HARNESS as the project: the tracker writes .entropy-machines/ into
44
+ # the harness, dispatch and handoff operate on the wrong repo, and nothing
45
+ # says a word. Three agents in a sibling project were lost to exactly that on
46
+ # 2026-08-17. entropy_machines_refuse_nested_clone() says so and exits. It is a
47
+ # REFUSAL, not a resolution scheme — if it ever grows a way to keep working
48
+ # in that layout, the expensive mistake has been re-made.
49
+ #
50
+ # ENTROPY_MACHINES_ROOT USES --git-common-dir, NOT --show-toplevel. THIS IS
51
+ # LOAD-BEARING. From inside a LINKED WORKTREE --show-toplevel prints the
52
+ # WORKTREE's own path. Tracker state lives under .entropy-machines/, which is
53
+ # gitignored and therefore absent from every worktree — resolving it that way
54
+ # hands a dispatched worker an EMPTY tracker instead of the project's, which
55
+ # reads as "no issues" rather than as an error. --git-common-dir names the
56
+ # main checkout's .git from anywhere, including from inside a worktree, which
57
+ # is the question actually being asked. A previous bug here sent state to the
58
+ # wrong place; do not "simplify" this to --show-toplevel.
59
+
60
+ # entropy_machines_home <path-to-calling-script> — echoes the harness directory.
61
+ entropy_machines_home() {
62
+ CDPATH= cd -- "$(dirname -- "$1")/.." 2>/dev/null && pwd -P
63
+ }
64
+
65
+ # entropy_machines_git_root [dir] — echoes the main checkout of the git repository
66
+ # containing <dir> (default: the current directory), or nothing (exit 1).
67
+ #
68
+ # GIT ONLY: no config.json walk-up. entropy_machines_root() adds that fallback; the
69
+ # nested-clone check must not have it, because it asks specifically "WHICH GIT
70
+ # REPOSITORY owns this directory" and a walk-up would answer a different
71
+ # question with a path that looks like an answer.
72
+ entropy_machines_git_root() {
73
+ _egr_dir=${1:-}
74
+ if [ -n "$_egr_dir" ]; then
75
+ _egr_common=$(CDPATH= cd -- "$_egr_dir" 2>/dev/null && git rev-parse --git-common-dir 2>/dev/null) || _egr_common=""
76
+ else
77
+ # No argument: run git in the AMBIENT cwd rather than cd-ing to "$PWD",
78
+ # so entropy_machines_root() below keeps the exact behaviour it had before this
79
+ # helper was extracted out of it.
80
+ _egr_dir=$PWD
81
+ _egr_common=$(git rev-parse --git-common-dir 2>/dev/null) || _egr_common=""
82
+ fi
83
+ if [ -n "$_egr_common" ]; then
84
+ # --git-common-dir may print a path relative to the directory git ran in.
85
+ case "$_egr_common" in
86
+ /*) ;;
87
+ *) _egr_common="$_egr_dir/$_egr_common" ;;
88
+ esac
89
+ if _egr_top=$(CDPATH= cd -- "$_egr_common/.." 2>/dev/null && pwd -P); then
90
+ printf '%s\n' "$_egr_top"
91
+ unset _egr_dir _egr_common _egr_top
92
+ return 0
93
+ fi
94
+ fi
95
+ unset _egr_dir _egr_common
96
+ return 1
97
+ }
98
+
99
+ # entropy_machines_root — echoes the repository's main checkout, or nothing (exit 1).
100
+ #
101
+ # No environment override. Nothing in this harness needs one any more: every
102
+ # caller either runs with a cwd inside the project (git answers), or is a git
103
+ # hook (git guarantees the cwd) or a scheduled unit that cd's into the harness
104
+ # directory first — which is inside the project. The old ENTROPY_PROJECT
105
+ # override existed to paper over the nested clone, and papering is exactly
106
+ # what made the wrong answer survivable.
107
+ entropy_machines_root() {
108
+ if _er_top=$(entropy_machines_git_root); then
109
+ printf '%s\n' "$_er_top"
110
+ unset _er_top
111
+ return 0
112
+ fi
113
+
114
+ # No git. Walk up for config.json so the harness still works in a plain
115
+ # directory — the config loader's own tests do exactly this.
116
+ _er_d=$PWD
117
+ while [ -n "$_er_d" ] && [ "$_er_d" != "/" ]; do
118
+ if [ -f "$_er_d/config.json" ]; then
119
+ printf '%s\n' "$_er_d"
120
+ unset _er_d
121
+ return 0
122
+ fi
123
+ _er_d=$(dirname -- "$_er_d")
124
+ done
125
+
126
+ unset _er_d
127
+ return 1
128
+ }
129
+
130
+ # entropy_machines_refuse_nested_clone <tool-name> — exits 2 if the harness is a
131
+ # NESTED GIT REPOSITORY inside the project. A REFUSAL, not a resolution: it
132
+ # never tries to make that layout work (see the header).
133
+ #
134
+ # The harness is vendored — plain files committed into the project — so
135
+ # $ENTROPY_MACHINES_HOME/.git normally does not exist at all and this returns
136
+ # immediately. When it DOES exist there is exactly one innocent explanation:
137
+ # the harness IS the project, either developed standalone or vendored at the
138
+ # repo root. That still holds from inside a linked worktree, where the .git
139
+ # is a FILE pointing at the main checkout.
140
+ #
141
+ # So: nested iff a DIFFERENT repository encloses the harness's parent
142
+ # directory than the one that owns the harness directory itself. Deliberately
143
+ # phrased without ENTROPY_MACHINES_ROOT, which is resolved from the CWD and is
144
+ # therefore the shadowed, wrong answer in the very case being detected.
145
+ entropy_machines_refuse_nested_clone() {
146
+ _rnc_tool=${1:-entropy-machines}
147
+ _rnc_home=${ENTROPY_MACHINES_HOME:-}
148
+ if [ -z "$_rnc_home" ] || [ ! -e "$_rnc_home/.git" ]; then
149
+ unset _rnc_tool _rnc_home
150
+ return 0
151
+ fi
152
+
153
+ _rnc_owner=$(entropy_machines_git_root "$_rnc_home") || _rnc_owner=""
154
+ _rnc_outer=$(entropy_machines_git_root "$_rnc_home/..") || _rnc_outer=""
155
+
156
+ if [ -z "$_rnc_outer" ] || [ "$_rnc_outer" = "$_rnc_owner" ]; then
157
+ unset _rnc_tool _rnc_home _rnc_owner _rnc_outer
158
+ return 0
159
+ fi
160
+
161
+ echo "$_rnc_tool: REFUSED — the harness is a nested git repository." >&2
162
+ echo " $_rnc_home has its own .git, and it sits inside another" >&2
163
+ echo " repository ($_rnc_outer)." >&2
164
+ echo "" >&2
165
+ echo " A nested repo shadows its parent for every git query, so every" >&2
166
+ echo " command run from inside it resolves to the HARNESS as the project:" >&2
167
+ echo " the tracker writes .entropy-machines/ into the harness, and dispatch and" >&2
168
+ echo " handoff operate on the wrong repository. Nothing has been written." >&2
169
+ echo "" >&2
170
+ echo " This harness is VENDORED, not cloned — plain files committed into" >&2
171
+ echo " the project alongside its code, exactly as a scripts/ directory is." >&2
172
+ echo " Fix it either way:" >&2
173
+ echo " rm -rf $_rnc_home/.git" >&2
174
+ echo " git -C $_rnc_outer add $_rnc_home && git -C $_rnc_outer commit" >&2
175
+ echo " or re-vendor: delete $_rnc_home and copy the harness's files in" >&2
176
+ echo " (no .git), then commit them into $_rnc_outer." >&2
177
+ unset _rnc_tool _rnc_home _rnc_owner _rnc_outer
178
+ exit 2
179
+ }
180
+
181
+ # entropy_machines_require_root <tool-name> — sets and exports ENTROPY_MACHINES_ROOT, or exits 2
182
+ # with a message that names the directory actually looked in.
183
+ #
184
+ # config.json is still the project's contract with the harness, and its
185
+ # absence is still a refusal: every other entry point would otherwise have to
186
+ # guess at paths, suite commands and protected paths it has not been told.
187
+ # bin/init is the one command that runs without it, because it is the thing
188
+ # that writes it.
189
+ entropy_machines_require_root() {
190
+ _err_tool=${1:-entropy-machines}
191
+ # Before anything is resolved: in a nested clone every answer below is the
192
+ # harness's, not the project's, and they all look plausible.
193
+ entropy_machines_refuse_nested_clone "$_err_tool"
194
+ if ! ENTROPY_MACHINES_ROOT=$(entropy_machines_root); then
195
+ echo "$_err_tool: REFUSED — not inside a git repository, and no config.json" >&2
196
+ echo " found by walking up from $PWD." >&2
197
+ echo " cd into the project you are running the factory on." >&2
198
+ unset _err_tool
199
+ exit 2
200
+ fi
201
+ if [ ! -f "${ENTROPY_MACHINES_HOME:-$ENTROPY_MACHINES_ROOT}/config.json" ]; then
202
+ echo "$_err_tool: REFUSED — no config.json at ${ENTROPY_MACHINES_HOME:-$ENTROPY_MACHINES_ROOT}." >&2
203
+ echo " That is this project's contract with the harness: every path," >&2
204
+ echo " command and suite the harness would otherwise hardcode lives in" >&2
205
+ echo " it. See ${ENTROPY_MACHINES_HOME:-<harness>}/docs/CONFIG.md." >&2
206
+ echo " To create a starter one: ${ENTROPY_MACHINES_HOME:-<harness>}/bin/init" >&2
207
+ unset _err_tool
208
+ exit 2
209
+ fi
210
+ export ENTROPY_MACHINES_ROOT
211
+ unset _err_tool
212
+ }
@@ -0,0 +1,84 @@
1
+ /* daylight — the long-reading theme. LIGHT FIRST.
2
+ The bare :root is the light palette and [data-theme=dark] is the override,
3
+ which is the mirror image of high-contrast.css. Both directions of the
4
+ toggle therefore work under either theme: whichever state a theme does not
5
+ name falls through to its own bare :root.
6
+
7
+ EVERY TOKEN HAS A VALUE IN THE BARE :root. A token defined only under
8
+ [data-theme=dark] renders unstyled in the default state — that is the
9
+ classic bug in a theme file and the reason the test compares the two files'
10
+ token NAME sets rather than eyeballing them.
11
+
12
+ WHAT IT IS, AND WHY IT IS NOT A RECOLOURED hc-light. hc-light maximises
13
+ legibility: pure white ground, saturated 4.5:1-and-up ink on every element,
14
+ a border hue loud enough to be a signal on its own. daylight is for reading
15
+ a long report end to end, so it trades some of that for comfort:
16
+
17
+ * The ground is warm paper (#FAF8F4), not #fff, and the ink is a
18
+ blue-leaning near-black (#23262E), not #292929 grey — a slight hue bias
19
+ in both directions rather than a pure neutral, which is what stops a
20
+ full page of prose reading as printer output.
21
+ * The rules are quiet. --line sits at 3.25:1 on the ground: a real
22
+ boundary you can see, not a stripe that competes with the text. The
23
+ structural CSS still separates with borders and never with tints, so
24
+ quieting the line is the whole difference between the two looks.
25
+ * The type scale is bigger and further apart — 13/16/20/28 against
26
+ high-contrast's 12/14/16/20. Body sits at the browser default rather
27
+ than below it, and the title is more than twice the small step, so a
28
+ heading reads as a heading in a page of running text.
29
+ * --accent (#2A5DB0 light / #7FB2F0 dark) is one blue that works on both
30
+ grounds, so a link does not change character with the toggle.
31
+ * --focus is amber (#B45309 / #F0A868) — the one hue no other token
32
+ occupies in either state, because focus has to be unmistakable, and the
33
+ structural CSS draws it as a border and never as a fill.
34
+
35
+ SEMANTICS DO NOT RELY ON HUE. ok is a teal-green, warn an amber, bad a red;
36
+ under deuteranopia or protanopia the teal stays visibly cool while the other
37
+ two collapse together, so the three are also separated by LIGHTNESS — in the
38
+ light state ok/warn/bad differ by 1.40:1, 1.47:1 and 2.05:1 in luminance,
39
+ and in the dark state by 1.85:1, 1.29:1 and 1.43:1. Every one of them clears
40
+ 4.5:1 against its own ground. The templates pair each of these colours with
41
+ a word ("open", "ok"); keep that true — the colour is the second signal, not
42
+ the only one.
43
+
44
+ Body text on ground: 14.26:1 light, 14.13:1 dark. Muted text (--dim):
45
+ 6.29:1 light, 7.43:1 dark.
46
+
47
+ A THEME FILE IS TOKENS ONLY — no layout, no component rules, no @font-face,
48
+ no remote URL. Selected by docs.theme in config.json; bin/serve inlines it
49
+ between a doc's entropy-machines-theme:begin and entropy-machines-theme:end markers and never
50
+ links to it. See docs/CONFIG.md. */
51
+ :root{
52
+ --bg:#FAF8F4; /* warm paper, not #fff */
53
+ --panel:#FFFFFF; /* one step up from the ground, for insets */
54
+ --ink:#23262E; /* blue-leaning near-black — 14.26:1 on --bg */
55
+ --dim:#5A5C63; /* 6.29:1 — a real value, never opacity */
56
+ --line:#8E8A7E; /* 3.25:1 — visible, quiet */
57
+ --focus:#B45309; /* amber; no other token uses this hue */
58
+ --accent:#2A5DB0; /* one link blue for both grounds */
59
+ --ok:#0B6E5A; /* teal-green: stays cool for a red-green reader */
60
+ --warn:#A06E12;
61
+ --bad:#8C1D18; /* darkest of the three here, so it shouts */
62
+ --alt:#6D4AA8;
63
+ --fs-sm:13px; --fs-base:16px; --fs-head:20px; --fs-title:28px;
64
+ --mono:ui-monospace,SFMono-Regular,Menlo,monospace;
65
+ --positive:var(--ok); --good:var(--ok); --muted:var(--dim); --border:var(--line);
66
+ --caution:var(--warn); --warn-deep:var(--warn); --code-bg:var(--panel);
67
+ }
68
+ /* The dark state. Ink-on-slate rather than white-on-black: the same trade as
69
+ the light state, one stop softer than high contrast at both ends. The type
70
+ scale does not change with the state, so it is not restated. The aliases are
71
+ var() references and re-resolve here on their own. */
72
+ :root[data-theme=dark]{
73
+ --bg:#14161A; /* slate, not #000 */
74
+ --panel:#1B1E24;
75
+ --ink:#E6E3DC; /* warm off-white — 14.13:1 on --bg */
76
+ --dim:#A8A69F; /* 7.43:1 */
77
+ --line:#626973; /* 3.27:1 */
78
+ --focus:#F0A868;
79
+ --accent:#7FB2F0;
80
+ --ok:#34A98D;
81
+ --warn:#F2C87B;
82
+ --bad:#FF8C82; /* lightest of the three here, so it shouts */
83
+ --alt:#C79BEE;
84
+ }
@@ -0,0 +1,36 @@
1
+ /* high-contrast — the house style, and the default.
2
+ VS Code High Contrast: hc-black as the bare :root, hc-light on the toggle.
3
+ Lifted verbatim from what lib/REPORT-TEMPLATE.html has always shipped; the
4
+ values are established house style, not up for improvement here.
5
+
6
+ A THEME FILE IS TOKENS ONLY. No layout, no component rules, no @font-face
7
+ and no remote URL of any kind — the structural CSS stays in the templates
8
+ so both themes get every structural fix for free, and bin/doclint refuses
9
+ any doc that references something off this machine.
10
+
11
+ Selected by docs.theme in config.json (see docs/CONFIG.md). bin/serve
12
+ INLINES this file between a doc's entropy-machines-theme:begin and entropy-machines-theme:end
13
+ markers, and into its own dashboard. It is never linked to as a stylesheet:
14
+ a doc is a standalone local file that must still look right when it is
15
+ opened straight off disk or moved somewhere else. */
16
+ :root{
17
+ --bg:#000; --panel:#000;
18
+ --ink:#fff; --dim:rgba(255,255,255,.7);
19
+ --line:#6FC3DF; /* contrastBorder */
20
+ --focus:#F38518; /* focusBorder / activeContrastBorder */
21
+ --accent:#21A6FF; /* textLink.foreground */
22
+ --ok:#23D18B; --warn:#F5F543; --bad:#F48771; --alt:#D670D6;
23
+ --fs-sm:12px; --fs-base:14px; --fs-head:16px; --fs-title:20px;
24
+ --mono:ui-monospace,SFMono-Regular,Menlo,monospace;
25
+ --positive:var(--ok); --good:var(--ok); --muted:var(--dim); --border:var(--line);
26
+ --caution:var(--warn); --warn-deep:var(--warn); --code-bg:var(--panel);
27
+ }
28
+ /* The toggle stamps data-theme on the root element. hc-black is the bare
29
+ :root, so only the light state needs restating; [data-theme=dark] matches
30
+ nothing here and correctly falls through to the default. */
31
+ :root[data-theme=light]{
32
+ --bg:#fff; --panel:#fff;
33
+ --ink:#292929; --dim:rgba(41,41,41,.75);
34
+ --line:#0F4A85; --focus:#006BBD; --accent:#0F4A85;
35
+ --ok:#0A5C21; --warn:#7A4A00; --bad:#A81C0B; --alt:#6B21A8;
36
+ }