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.
- package/LICENSE +93 -0
- package/README.md +68 -0
- package/agents/isolated-worker.md +128 -0
- package/agents/verifier.md +158 -0
- package/bin/dispatch +700 -0
- package/bin/doclint +460 -0
- package/bin/drain +507 -0
- package/bin/drain-pick.py +168 -0
- package/bin/drain-prompt.md +67 -0
- package/bin/drain-run.sh +342 -0
- package/bin/entropy-machines-init +285 -0
- package/bin/handoff +1151 -0
- package/bin/init +232 -0
- package/bin/post-fold-audit +377 -0
- package/bin/serve +724 -0
- package/bin/status +208 -0
- package/bin/tracker +153 -0
- package/docs/AGENT-QUICKSTART.md +86 -0
- package/docs/CONFIG.md +68 -0
- package/docs/NPM.md +91 -0
- package/docs/SERVE.md +74 -0
- package/docs/TRACKER-ADAPTER.md +66 -0
- package/doctrine/HANDOFF-PROMPT.md +63 -0
- package/doctrine/README.md +62 -0
- package/doctrine/ROLES.md +27 -0
- package/doctrine/WORKFLOW.md +87 -0
- package/hooks/commit-msg +24 -0
- package/hooks/post-checkout +354 -0
- package/hooks/pre-commit +33 -0
- package/lib/PRD-001-orientation.html +1180 -0
- package/lib/REPORT-TEMPLATE.html +413 -0
- package/lib/changelog-collate.mjs +328 -0
- package/lib/changelog-guard.sh +157 -0
- package/lib/changelog-new.mjs +70 -0
- package/lib/config.mjs +283 -0
- package/lib/config.py +317 -0
- package/lib/doc-template.html +807 -0
- package/lib/entropy-drain.plist.in +59 -0
- package/lib/entropy-drain.service.in +53 -0
- package/lib/entropy-drain.timer.in +36 -0
- package/lib/fail-first.mjs +901 -0
- package/lib/handoff-guard.sh +623 -0
- package/lib/install-hooks.sh +169 -0
- package/lib/notes.py +675 -0
- package/lib/preflight-tree.mjs +82 -0
- package/lib/roots.sh +212 -0
- package/lib/themes/daylight.css +84 -0
- package/lib/themes/high-contrast.css +36 -0
- package/lib/tracker-file +333 -0
- package/lib/tracker-view.py +784 -0
- package/package.json +38 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- ==================== entropy-machines report template ====================
|
|
3
|
+
A sprint report, plan, or audit the owner READS AND ANSWERS. Copy this file
|
|
4
|
+
into the project's docs directory (docs.dir in config.json, default
|
|
5
|
+
entropy-machines-docs/), replace the placeholders, and serve it with bin/serve. The
|
|
6
|
+
owner answers in the browser and the save button writes those answers back
|
|
7
|
+
into this file on disk.
|
|
8
|
+
|
|
9
|
+
------------------------------------------------------------------ THE RULE
|
|
10
|
+
A REPORT IS A LOCAL FILE. It is never published to, and never depends on,
|
|
11
|
+
an external website. Everything it needs is inside it: no stylesheet link,
|
|
12
|
+
no script tag, no font, no image loaded from an off-machine origin. A doc
|
|
13
|
+
that phones out stops working the moment it leaves this machine, or the
|
|
14
|
+
moment the machine is offline, and it turns a private sprint report into a
|
|
15
|
+
request to somebody else's server.
|
|
16
|
+
|
|
17
|
+
Enforced by bin/doclint, which refuses any reference (an href, a src, a CSS
|
|
18
|
+
url(), a string literal in a script) pointing at http://, https://, or a
|
|
19
|
+
protocol-relative //host/path. Prose that merely MENTIONS a URL is fine —
|
|
20
|
+
text does not fetch anything. Data URIs, relative paths and same-document
|
|
21
|
+
anchors are fine; embed images as data URIs rather than linking them.
|
|
22
|
+
|
|
23
|
+
------------------------------------------------------ THE OTHER HALF: ANSWERS
|
|
24
|
+
A report with nowhere to answer is a broadcast, not a dialogue. bin/doclint
|
|
25
|
+
also refuses a doc that:
|
|
26
|
+
* has no answer box under some h2 — every section the owner is expected to
|
|
27
|
+
respond to gets one;
|
|
28
|
+
* gives two boxes the same key — the second silently overwrites the first
|
|
29
|
+
on save;
|
|
30
|
+
* has an answer box with no textarea in it;
|
|
31
|
+
* is missing the save button or the saved-answers JSON block, either of
|
|
32
|
+
which means the owner's typing dies in the browser.
|
|
33
|
+
|
|
34
|
+
An answer box is a div with class `response` carrying a unique, stable key
|
|
35
|
+
attribute, and containing a label, a div with class `discuss` posing an
|
|
36
|
+
actual question, and one textarea. The key is the identity bin/serve uses to
|
|
37
|
+
write an answer back into this file — renaming one orphans the answer that
|
|
38
|
+
was already saved under the old name. The saved answers are mirrored into a
|
|
39
|
+
JSON script block near the end of the body.
|
|
40
|
+
|
|
41
|
+
DO NOT PASTE A LIVE ANSWER-BOX ATTRIBUTE INTO A COMMENT. That is why the
|
|
42
|
+
paragraph above describes the markup instead of showing it. Every tool that
|
|
43
|
+
reads these docs counts those keys with a regex — bin/serve's dashboard,
|
|
44
|
+
bin/status, bin/doclint — and a documented example got counted as a real
|
|
45
|
+
question. Each doc built from a template with a literal example reported one
|
|
46
|
+
phantom unanswered question nobody could ever answer, so "fully answered"
|
|
47
|
+
was an unreachable state. The readers strip comments now; not writing the
|
|
48
|
+
example is still the fix that matters.
|
|
49
|
+
|
|
50
|
+
------------------------------------------------------------------ THE SPINE
|
|
51
|
+
Landed, then Verified, then Still open, in that order. Put anything you like
|
|
52
|
+
between them — a decision the owner has to make, a table, a war story. Those
|
|
53
|
+
three are the ones a reader knows how to look for. Nothing checks the order;
|
|
54
|
+
it is the convention, and a reader who cannot find "Still open" will assume
|
|
55
|
+
there is nothing open.
|
|
56
|
+
|
|
57
|
+
The citation band under the header carries one link per issue this report
|
|
58
|
+
DELIVERS and one per issue it OPENS, so a reader can get from the report to
|
|
59
|
+
the tracker without reading the prose.
|
|
60
|
+
|
|
61
|
+
--------------------------------------------------------------- THE LOOK
|
|
62
|
+
The palette below is VS Code High Contrast (hc-black, with an hc-light
|
|
63
|
+
override on the theme toggle), the fonts are the system stack, and there are
|
|
64
|
+
four type steps: 12 / 14 / 16 / 20. That is this project's house style and a
|
|
65
|
+
reasonable default — it renders identically offline on any machine and needs
|
|
66
|
+
no font download. IT IS NOT A CONFORMANCE TARGET. Restyle it however you
|
|
67
|
+
like; bin/doclint does not look at colours, fonts or sizes. Only local-only
|
|
68
|
+
and answerable are enforced.
|
|
69
|
+
|
|
70
|
+
YOU DO NOT HAVE TO EDIT THIS FILE TO CHANGE THE LOOK. The token block below
|
|
71
|
+
sits between two markers, and bin/serve swaps that span for
|
|
72
|
+
lib/themes/<name>.css when config.json says docs.theme: "<name>" — ships
|
|
73
|
+
with "high-contrast" (this one, the default) and "daylight", a softer
|
|
74
|
+
light-first theme for long reading. Everything below the end marker is
|
|
75
|
+
structure and is shared by every theme, so a layout fix is made once. Adding
|
|
76
|
+
a third theme is a new file in lib/themes/ defining the same token names; see
|
|
77
|
+
docs/CONFIG.md.
|
|
78
|
+
|
|
79
|
+
If you keep the house style, it holds together on three rules: separate with
|
|
80
|
+
borders, never background tints; focus and hover are the focus hue as a
|
|
81
|
+
border, never a fill; state is never carried by opacity alone — use weight,
|
|
82
|
+
case, or a colour plus a word.
|
|
83
|
+
|
|
84
|
+
There is no live-reload poller in here. Add the one from
|
|
85
|
+
lib/doc-template.html if you want an open tab to pick up an agent's reply.
|
|
86
|
+
========================================================================= -->
|
|
87
|
+
<html lang="en"><head>
|
|
88
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
89
|
+
<title>{{TITLE}}</title>
|
|
90
|
+
<style>
|
|
91
|
+
/* THEME TOKENS. Everything between the two markers below is exactly
|
|
92
|
+
lib/themes/high-contrast.css, inlined so this file still looks right when
|
|
93
|
+
it is opened straight off disk. bin/serve REPLACES that span with
|
|
94
|
+
lib/themes/<docs.theme>.css when docs.theme names another theme (see
|
|
95
|
+
docs/CONFIG.md) — the markers are what it looks for, so keep them, keep
|
|
96
|
+
the block between them tokens-only, and put no layout in there. Nothing
|
|
97
|
+
below the end marker is themed: structural CSS is shared by every theme,
|
|
98
|
+
which is the point of having themes instead of a second template. */
|
|
99
|
+
/* entropy-machines-theme:begin */
|
|
100
|
+
/* high-contrast — the house style, and the default.
|
|
101
|
+
VS Code High Contrast: hc-black as the bare :root, hc-light on the toggle.
|
|
102
|
+
Lifted verbatim from what lib/REPORT-TEMPLATE.html has always shipped; the
|
|
103
|
+
values are established house style, not up for improvement here.
|
|
104
|
+
|
|
105
|
+
A THEME FILE IS TOKENS ONLY. No layout, no component rules, no @font-face
|
|
106
|
+
and no remote URL of any kind — the structural CSS stays in the templates
|
|
107
|
+
so both themes get every structural fix for free, and bin/doclint refuses
|
|
108
|
+
any doc that references something off this machine.
|
|
109
|
+
|
|
110
|
+
Selected by docs.theme in config.json (see docs/CONFIG.md). bin/serve
|
|
111
|
+
INLINES this file between a doc's entropy-machines-theme:begin and entropy-machines-theme:end
|
|
112
|
+
markers, and into its own dashboard. It is never linked to as a stylesheet:
|
|
113
|
+
a doc is a standalone local file that must still look right when it is
|
|
114
|
+
opened straight off disk or moved somewhere else. */
|
|
115
|
+
:root{
|
|
116
|
+
--bg:#000; --panel:#000;
|
|
117
|
+
--ink:#fff; --dim:rgba(255,255,255,.7);
|
|
118
|
+
--line:#6FC3DF; /* contrastBorder */
|
|
119
|
+
--focus:#F38518; /* focusBorder / activeContrastBorder */
|
|
120
|
+
--accent:#21A6FF; /* textLink.foreground */
|
|
121
|
+
--ok:#23D18B; --warn:#F5F543; --bad:#F48771; --alt:#D670D6;
|
|
122
|
+
--fs-sm:12px; --fs-base:14px; --fs-head:16px; --fs-title:20px;
|
|
123
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
124
|
+
--positive:var(--ok); --good:var(--ok); --muted:var(--dim); --border:var(--line);
|
|
125
|
+
--caution:var(--warn); --warn-deep:var(--warn); --code-bg:var(--panel);
|
|
126
|
+
}
|
|
127
|
+
/* The toggle stamps data-theme on the root element. hc-black is the bare
|
|
128
|
+
:root, so only the light state needs restating; [data-theme=dark] matches
|
|
129
|
+
nothing here and correctly falls through to the default. */
|
|
130
|
+
:root[data-theme=light]{
|
|
131
|
+
--bg:#fff; --panel:#fff;
|
|
132
|
+
--ink:#292929; --dim:rgba(41,41,41,.75);
|
|
133
|
+
--line:#0F4A85; --focus:#006BBD; --accent:#0F4A85;
|
|
134
|
+
--ok:#0A5C21; --warn:#7A4A00; --bad:#A81C0B; --alt:#6B21A8;
|
|
135
|
+
}
|
|
136
|
+
/* entropy-machines-theme:end */
|
|
137
|
+
*{box-sizing:border-box}
|
|
138
|
+
body{margin:0;background:var(--bg);color:var(--ink);
|
|
139
|
+
font:var(--fs-base)/1.6 ui-sans-serif,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
140
|
+
main{max-width:860px;margin:0 auto;padding:24px 20px 80px}
|
|
141
|
+
a{color:var(--accent)}
|
|
142
|
+
:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
|
|
143
|
+
|
|
144
|
+
h1{font-size:var(--fs-title);font-weight:700;margin:0 0 4px;line-height:1.3}
|
|
145
|
+
h2{font-size:var(--fs-head);font-weight:700;margin:32px 0 8px;letter-spacing:.2px}
|
|
146
|
+
h3{font-size:var(--fs-base);font-weight:700;margin:20px 0 6px}
|
|
147
|
+
p,li,td,th{font-size:var(--fs-base)}
|
|
148
|
+
.sub,.meta,figcaption{font-size:var(--fs-sm);color:var(--ink)}
|
|
149
|
+
.eyebrow{font-size:var(--fs-sm);font-weight:700;text-transform:uppercase;
|
|
150
|
+
letter-spacing:.09em;margin:0 0 6px}
|
|
151
|
+
code{font-family:var(--mono);font-size:var(--fs-sm);border:1px solid var(--line);
|
|
152
|
+
border-radius:2px;padding:0 4px}
|
|
153
|
+
h1 code{font-size:var(--fs-head)}
|
|
154
|
+
pre{font-family:var(--mono);font-size:var(--fs-sm);border:1px solid var(--line);
|
|
155
|
+
border-radius:4px;padding:10px 12px;overflow-x:auto;line-height:1.5}
|
|
156
|
+
pre code{border:0;padding:0;font-size:inherit}
|
|
157
|
+
|
|
158
|
+
header{border-bottom:1px solid var(--line);padding-bottom:12px;margin-bottom:8px;
|
|
159
|
+
display:flex;gap:12px;align-items:baseline;flex-wrap:wrap}
|
|
160
|
+
header .sub{margin:0}
|
|
161
|
+
#theme{margin-left:auto;background:var(--panel);border:1px solid var(--line);
|
|
162
|
+
color:var(--ink);border-radius:4px;padding:6px 10px;cursor:pointer;
|
|
163
|
+
font-family:inherit;font-size:var(--fs-sm);font-weight:600}
|
|
164
|
+
#theme:hover{box-shadow:inset 0 0 0 2px var(--focus)}
|
|
165
|
+
|
|
166
|
+
.pill{display:inline-block;font-size:var(--fs-sm);font-weight:700;padding:1px 8px;
|
|
167
|
+
border-radius:10px;border:1px solid var(--line);color:var(--ink)}
|
|
168
|
+
.pill.ok{color:var(--ok);border-color:var(--ok)}
|
|
169
|
+
.pill.warn{color:var(--warn);border-color:var(--warn)}
|
|
170
|
+
.pill.bad{color:var(--bad);border-color:var(--bad)}
|
|
171
|
+
.pill.alt{color:var(--alt);border-color:var(--alt)}
|
|
172
|
+
|
|
173
|
+
.card{border:1px solid var(--line);border-radius:4px;padding:12px 14px;margin:12px 0}
|
|
174
|
+
.card > :first-child{margin-top:0}
|
|
175
|
+
.card > :last-child{margin-bottom:0}
|
|
176
|
+
.card.flag{border-color:var(--focus)}
|
|
177
|
+
table{width:100%;border-collapse:collapse;margin:12px 0;display:block;overflow-x:auto}
|
|
178
|
+
th,td{border:1px solid var(--line);padding:6px 10px;text-align:left;vertical-align:top}
|
|
179
|
+
th{font-weight:700}
|
|
180
|
+
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
|
|
181
|
+
ul,ol{padding-left:20px}
|
|
182
|
+
li{margin:3px 0}
|
|
183
|
+
hr{border:0;border-top:1px solid var(--line);margin:28px 0}
|
|
184
|
+
|
|
185
|
+
nav{border:1px solid var(--line);padding:10px 12px;margin:0 0 22px;
|
|
186
|
+
display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px}
|
|
187
|
+
nav .brand{font-size:var(--fs-sm);font-weight:700;text-transform:uppercase;
|
|
188
|
+
letter-spacing:.09em}
|
|
189
|
+
nav a{font-size:var(--fs-sm);text-decoration:none;border-bottom:1px solid var(--line)}
|
|
190
|
+
nav a:hover{border-bottom-color:var(--focus)}
|
|
191
|
+
|
|
192
|
+
.links{border:1px solid var(--line);padding:8px 12px;margin:0 0 18px;
|
|
193
|
+
display:flex;flex-wrap:wrap;gap:6px 14px;align-items:baseline}
|
|
194
|
+
.links .k{font-size:var(--fs-sm);font-weight:700;text-transform:uppercase;
|
|
195
|
+
letter-spacing:.09em;color:var(--dim)}
|
|
196
|
+
.links a{font-family:var(--mono);font-size:var(--fs-sm);text-decoration:none;
|
|
197
|
+
border:1px solid var(--line);padding:1px 6px}
|
|
198
|
+
.links a:hover{border-color:currentColor}
|
|
199
|
+
.links .grp{display:flex;flex-wrap:wrap;gap:4px;align-items:baseline}
|
|
200
|
+
|
|
201
|
+
.response{border:1px solid var(--line);padding:12px 14px;margin:16px 0}
|
|
202
|
+
.response label{display:block;font-size:var(--fs-sm);font-weight:700;
|
|
203
|
+
text-transform:uppercase;letter-spacing:.09em;margin:0 0 6px}
|
|
204
|
+
.response .discuss{font-size:var(--fs-base);margin:0 0 10px}
|
|
205
|
+
.response textarea{display:block;width:100%;min-height:76px;padding:8px;
|
|
206
|
+
background:var(--bg);color:var(--ink);border:1px solid var(--line);
|
|
207
|
+
font:var(--fs-base)/1.5 var(--mono);resize:vertical}
|
|
208
|
+
.response textarea:focus-visible{outline:none;border-color:var(--focus);
|
|
209
|
+
box-shadow:inset 0 0 0 1px var(--focus)}
|
|
210
|
+
.savebar{position:fixed;bottom:14px;right:19px;display:flex;align-items:center;
|
|
211
|
+
gap:11px;z-index:50;background:var(--bg);border:1px solid var(--line);
|
|
212
|
+
padding:7px 9px 7px 16px}
|
|
213
|
+
.savebar .stat{font-size:var(--fs-sm)}
|
|
214
|
+
.savebar button{font:700 var(--fs-base) inherit;cursor:pointer;
|
|
215
|
+
border:1px solid var(--line);background:none;color:var(--ink);padding:6px 14px}
|
|
216
|
+
.savebar button:hover{box-shadow:inset 0 0 0 2px var(--focus)}
|
|
217
|
+
.savebar button.ghost{padding:6px 10px}
|
|
218
|
+
</style></head>
|
|
219
|
+
<body>
|
|
220
|
+
<main>
|
|
221
|
+
|
|
222
|
+
<nav aria-label="document">
|
|
223
|
+
<div class="brand">{{KIND}} · {{DATE}}</div>
|
|
224
|
+
<a href="#landed">Landed</a>
|
|
225
|
+
<a href="#verified">Verified</a>
|
|
226
|
+
<a href="#call">Your call</a>
|
|
227
|
+
<a href="#open">Still open</a>
|
|
228
|
+
</nav>
|
|
229
|
+
|
|
230
|
+
<header>
|
|
231
|
+
<div>
|
|
232
|
+
<h1>{{HEADLINE — what this sprint was for, in the owner's words}}</h1>
|
|
233
|
+
<p class="sub">{{N}} commits on <code>{{BRANCH}}</code> · verified by re-running, not by relaying</p>
|
|
234
|
+
</div>
|
|
235
|
+
<button id="theme">◐ theme</button>
|
|
236
|
+
</header>
|
|
237
|
+
|
|
238
|
+
<!-- Citation band: one link per issue DELIVERED, one per issue OPENED. The
|
|
239
|
+
delivered ones point at the tracker ids; the opened ones point at the
|
|
240
|
+
anchors in "Still open" below, so a reader can jump straight to them. -->
|
|
241
|
+
<div class="links">
|
|
242
|
+
<span class="k">delivers</span>
|
|
243
|
+
<span class="grp">
|
|
244
|
+
<a href="#i-delivered-one">{{i-delivered-one}}</a>
|
|
245
|
+
<a href="#i-delivered-two">{{i-delivered-two}}</a>
|
|
246
|
+
</span>
|
|
247
|
+
<span class="k">opens</span>
|
|
248
|
+
<span class="grp">
|
|
249
|
+
<a href="#i-opened-one">{{i-opened-one}}</a>
|
|
250
|
+
</span>
|
|
251
|
+
<span class="k">rulings</span>
|
|
252
|
+
<span class="grp"><a href="{{SOME-OTHER-DOC}}.html">{{that doc}}</a></span>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<p><span class="pill ok">{{one-word verdict}}</span> {{One paragraph: what a
|
|
256
|
+
reader gets out of this sprint, and the single biggest risk in it. Say the risk
|
|
257
|
+
plainly — it does not shrink by being phrased carefully.}}</p>
|
|
258
|
+
|
|
259
|
+
<h2 id="landed">Landed</h2>
|
|
260
|
+
|
|
261
|
+
<p>{{What changed and why it was worth doing. Lead with the thing that would
|
|
262
|
+
surprise the owner, not with the chronology.}}</p>
|
|
263
|
+
|
|
264
|
+
<ul>
|
|
265
|
+
<li id="i-delivered-one"><code>{{i-delivered-one}}</code> — {{what it does now}}</li>
|
|
266
|
+
<li id="i-delivered-two"><code>{{i-delivered-two}}</code> — {{what it does now}}</li>
|
|
267
|
+
</ul>
|
|
268
|
+
|
|
269
|
+
<div class="response" data-resp="s-landed">
|
|
270
|
+
<label>On what landed</label>
|
|
271
|
+
<div class="discuss">{{Ask an actual question about a trade you made — not
|
|
272
|
+
"any thoughts?". A box under a question the owner cannot answer is the same as
|
|
273
|
+
no box.}}</div>
|
|
274
|
+
<textarea placeholder="Type your answer…"></textarea>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<h2 id="verified">Verified</h2>
|
|
278
|
+
|
|
279
|
+
<table>
|
|
280
|
+
<thead><tr><th>Check</th><th>Command</th><th class="num">Result</th></tr></thead>
|
|
281
|
+
<tbody>
|
|
282
|
+
<tr><td>{{what was checked}}</td><td><code>{{the command you ran}}</code></td><td class="num">{{result}}</td></tr>
|
|
283
|
+
<tr><td>{{what was checked}}</td><td><code>{{the command you ran}}</code></td><td class="num">{{result}}</td></tr>
|
|
284
|
+
</tbody>
|
|
285
|
+
</table>
|
|
286
|
+
|
|
287
|
+
<p class="meta"><strong>Not run:</strong> {{Name the gaps. An unmentioned gap
|
|
288
|
+
reads as a passing check.}}</p>
|
|
289
|
+
|
|
290
|
+
<div class="response" data-resp="s-verified">
|
|
291
|
+
<label>On the verification</label>
|
|
292
|
+
<div class="discuss">{{What would you have wanted run that was not?}}</div>
|
|
293
|
+
<textarea placeholder="Type your answer…"></textarea>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<h2 id="call">Your call</h2>
|
|
297
|
+
|
|
298
|
+
<div class="card flag">
|
|
299
|
+
<p class="eyebrow">Decision needed</p>
|
|
300
|
+
<p><strong>{{The fork, as a question with two named options.}}</strong></p>
|
|
301
|
+
<p>{{Which one you would take, and the one-line cost of each. Recommend one
|
|
302
|
+
unless both are bad.}}</p>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div class="response" data-resp="s-call">
|
|
306
|
+
<label>On the decision</label>
|
|
307
|
+
<div class="discuss">{{Option A or option B? Say which and I'll do it.}}</div>
|
|
308
|
+
<textarea placeholder="Type your answer…"></textarea>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<h2 id="open">Still open</h2>
|
|
312
|
+
|
|
313
|
+
<ul>
|
|
314
|
+
<li id="i-opened-one"><span class="pill bad">open</span>
|
|
315
|
+
<code>{{i-opened-one}}</code> — {{what is unresolved, and what it blocks}}
|
|
316
|
+
<span class="meta">{{status · effort · gate if any}}</span></li>
|
|
317
|
+
</ul>
|
|
318
|
+
|
|
319
|
+
<div class="response" data-resp="s-open">
|
|
320
|
+
<label>On what is still open</label>
|
|
321
|
+
<div class="discuss">{{Which of these should jump the queue before anyone
|
|
322
|
+
else depends on this?}}</div>
|
|
323
|
+
<textarea placeholder="Type your answer…"></textarea>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<hr>
|
|
327
|
+
<pre><code>{{Paste the real output of the suite run and the commit list.
|
|
328
|
+
Numbers, not adjectives.}}</code></pre>
|
|
329
|
+
|
|
330
|
+
</main>
|
|
331
|
+
|
|
332
|
+
<div class="savebar">
|
|
333
|
+
<span class="stat" id="saveStat">Unsaved changes…</span>
|
|
334
|
+
<button class="ghost" id="clearBtn" title="Clear">✕</button>
|
|
335
|
+
<button id="saveBtn">💾 Save</button>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- Mirrors what has been written to disk. bin/serve rewrites this block on
|
|
339
|
+
save; it is also what the answer counters read. Leave it as {} in a new
|
|
340
|
+
doc. -->
|
|
341
|
+
<script type="application/json" id="responses-data">{}</script>
|
|
342
|
+
|
|
343
|
+
<script>
|
|
344
|
+
// Autosave to localStorage between saves, so typing survives a reload. The
|
|
345
|
+
// browser copy is a convenience only — nothing outside this tab can read it,
|
|
346
|
+
// so the answer is not real until 💾 writes it into the file.
|
|
347
|
+
(function(){
|
|
348
|
+
var file=(location.pathname.split('/').pop()||'report').split('?')[0];
|
|
349
|
+
var KEY='entropy-machines-'+file;
|
|
350
|
+
var boxes=[].slice.call(document.querySelectorAll('.response[data-resp] textarea'));
|
|
351
|
+
try{
|
|
352
|
+
var disk=JSON.parse(document.getElementById('responses-data').textContent||'{}');
|
|
353
|
+
var saved=JSON.parse(localStorage.getItem(KEY)||'{}');
|
|
354
|
+
boxes.forEach(function(t){
|
|
355
|
+
var k=t.closest('.response').getAttribute('data-resp');
|
|
356
|
+
if(!t.value.trim()) t.value = disk[k] || saved[k] || '';
|
|
357
|
+
});
|
|
358
|
+
}catch(e){}
|
|
359
|
+
function dump(){
|
|
360
|
+
var o={}; boxes.forEach(function(t){ o[t.closest('.response').getAttribute('data-resp')]=t.value; });
|
|
361
|
+
try{ localStorage.setItem(KEY, JSON.stringify(o)); }catch(e){}
|
|
362
|
+
}
|
|
363
|
+
boxes.forEach(function(t){ t.addEventListener('input', dump); });
|
|
364
|
+
var clear=document.getElementById('clearBtn');
|
|
365
|
+
if(clear) clear.addEventListener('click', function(){
|
|
366
|
+
boxes.forEach(function(t){ t.value=''; }); dump();
|
|
367
|
+
var st=document.getElementById('saveStat'); if(st) st.textContent='Cleared — press 💾 to write it';
|
|
368
|
+
});
|
|
369
|
+
})();
|
|
370
|
+
</script>
|
|
371
|
+
|
|
372
|
+
<script>
|
|
373
|
+
const r = document.documentElement;
|
|
374
|
+
try { if (localStorage.reportTheme) r.dataset.theme = localStorage.reportTheme; } catch(e){}
|
|
375
|
+
document.getElementById('theme').onclick = () => {
|
|
376
|
+
const next = r.dataset.theme === 'light' ? 'dark' : 'light';
|
|
377
|
+
r.dataset.theme = next;
|
|
378
|
+
try { localStorage.reportTheme = next; } catch(e){}
|
|
379
|
+
};
|
|
380
|
+
</script>
|
|
381
|
+
|
|
382
|
+
<script>
|
|
383
|
+
// 💾 posts to bin/serve, which merges the answers into THIS file on disk.
|
|
384
|
+
// The endpoint takes the filename in the query string and a flat
|
|
385
|
+
// {key: answer} body — see docs/SERVE.md. bin/serve also injects its own
|
|
386
|
+
// copy of this handler on every GET and wins the click, so a doc can never
|
|
387
|
+
// be served with a Downloads-folder save as the only way to save; this one
|
|
388
|
+
// is here so the contract is visible in the file the author is reading.
|
|
389
|
+
(function(){
|
|
390
|
+
var btn=document.getElementById('saveBtn'), st=document.getElementById('saveStat');
|
|
391
|
+
if(!btn) return;
|
|
392
|
+
btn.addEventListener('click', function(){
|
|
393
|
+
var o={};
|
|
394
|
+
document.querySelectorAll('.response[data-resp]').forEach(function(el){
|
|
395
|
+
var t=el.querySelector('textarea'); if(!t) return;
|
|
396
|
+
var v=(t.value||''); if(v.trim()) o[el.getAttribute('data-resp')]=v;
|
|
397
|
+
});
|
|
398
|
+
var file=(location.pathname.split('/').pop()||'').split('?')[0];
|
|
399
|
+
if(st) st.textContent='Saving…';
|
|
400
|
+
fetch('/__save?file='+encodeURIComponent(file),
|
|
401
|
+
{method:'POST',headers:{'Content-Type':'application/json'},
|
|
402
|
+
body:JSON.stringify(o)})
|
|
403
|
+
.then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
|
|
404
|
+
.then(function(j){
|
|
405
|
+
if(st) st.textContent='Saved '+j.count+' answer(s) to '+file;
|
|
406
|
+
var el=document.getElementById('responses-data');
|
|
407
|
+
if(el) el.textContent=JSON.stringify(o,null,1);
|
|
408
|
+
})
|
|
409
|
+
.catch(function(e){ if(st) st.textContent='SAVE FAILED: '+e.message+' — open it through bin/serve'; });
|
|
410
|
+
});
|
|
411
|
+
})();
|
|
412
|
+
</script>
|
|
413
|
+
</body></html>
|