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
package/docs/SERVE.md ADDED
@@ -0,0 +1,74 @@
1
+ # bin/serve — the factory lights
2
+
3
+ bin/serve [port] # default 8787, 127.0.0.1 only
4
+
5
+ A local web server, not a CLI. The owner's surface is a browser tab: dialogue
6
+ docs with response boxes answered in place, plus a dashboard.
7
+
8
+ | Route | What |
9
+ |---|---|
10
+ | `GET /` | Dashboard — ready issues, in-flight claims, per-doc answered counts, recent events. |
11
+ | `GET /<doc>.html` | A doc from `docs.dir`, save patch and theme injected at serve time. |
12
+ | `GET /__docversion?file=…` | `{"reviews": "<hash>"}`, polled by the live-reload watcher. |
13
+ | `POST /__save?file=…` | Body `{"<data-resp>": "<answer>"}`, merged atomically. |
14
+
15
+ Everything else 404s — no listing, nothing outside `docs.dir`.
16
+
17
+ ## The rule
18
+
19
+ **No report, plan or PRD is ever published to an external site, and none may
20
+ depend on one** — a doc pulling a font or stylesheet from someone else's server
21
+ breaks offline and tells that server it was opened. And every section the owner
22
+ is asked about has a box to answer in.
23
+
24
+ **`bin/doclint` enforces exactly those two things.** Run it before handing over
25
+ a URL. Exit 1 names file, line and problem for an off-machine origin in an
26
+ `href`, `src`, CSS `url()` or script literal (prose mentioning a URL passes, as
27
+ do `data:` URIs and relative paths), or for an `<h2>` with no answer box, two
28
+ boxes sharing a key, or a missing `#saveBtn` / `#responses-data`.
29
+ `data-informational` on an `<h2>` skips that section — no whole-file exemption.
30
+ Exit 2 is a refusal, not a pass: it could not read its input. **The look is not
31
+ checked** — `lib/REPORT-TEMPLATE.html` is a default, not a conformance target.
32
+
33
+ ## Docs and themes
34
+
35
+ `docs.dir` (default `entropy-machines-docs/`) is created if absent; docs sit directly in
36
+ it, since the save-path check refuses any `file` containing `/`. Reports start
37
+ from `lib/REPORT-TEMPLATE.html`, PRDs from `lib/doc-template.html`, whose header
38
+ comment authoritatively describes the response-box machinery. `docs.theme` picks
39
+ a stylesheet from `lib/themes/` (`high-contrast`, `daylight`), inlined on each
40
+ `GET` between the `entropy-machines-theme` markers; an unknown name is refused by name.
41
+
42
+ ## Saving
43
+
44
+ A save endpoint taking a path is a whole-disk write primitive, so `file` is
45
+ validated on both routes: no `/`, no `..`, ends `.html`, already in `docs.dir`.
46
+ Refusals return plain 4xx JSON. A valid save merges into `#responses-data` and
47
+ the matching `<textarea>`s via temp-file-plus-`os.replace`.
48
+
49
+ **The save patch is injected on every `GET`, never written into the file** —
50
+ otherwise an older doc falls back to `showSaveFilePicker`, writing to
51
+ `~/Downloads` and leaving the reviewed file untouched. It runs in the click
52
+ capture phase with `stopImmediatePropagation`, so it wins whatever else is bound.
53
+
54
+ ## Hot reload
55
+
56
+ The doc polls `__docversion` every 5s: a clean tab reloads itself, a dirty one
57
+ shows a bar instead. Typed text is never clobbered.
58
+
59
+ `reviews` is **not** a whole-file hash — your own 💾 rewrites `#responses-data`
60
+ and the answered `<textarea>`s, which would read back as an incoming reply. It
61
+ is a SHA-256 with exactly those two regions blanked. **Consequence:**
62
+ overwriting an existing answer is indistinguishable from the reader typing and
63
+ triggers no reload; land a reply as new content outside the answer regions.
64
+
65
+ ## Dashboard, port, root
66
+
67
+ `GET /` uses only state already tracked: `tracker ready`; `tracker notes` through
68
+ `lib/notes.py claims --hours 24`, the same derivation `bin/dispatch` uses for its
69
+ denylist; each doc's own counts; the notes tail. An unreachable tracker says so
70
+ rather than rendering empty — unreadable and empty are different facts.
71
+
72
+ A busy port refuses with the next port to try, not a traceback. One root,
73
+ resolved via `--git-common-dir` so it is the MAIN checkout even from a linked
74
+ worktree; no environment override, so `cd` to serve another project.
@@ -0,0 +1,66 @@
1
+ # The tracker adapter
2
+
3
+ The harness needs **six** operations from an issue tracker. That is the whole
4
+ dependency. Anything richer belongs to your tracker, not to us.
5
+
6
+ A backend is any executable that implements this CLI. `tracker.backend` in
7
+ `config.json` selects one.
8
+
9
+ | Command | Contract |
10
+ |---|---|
11
+ | `show <id>` | Print one issue as JSON on stdout. Exit 3 if no such issue. |
12
+ | `notes [--issue <id>]` | Print the note log as JSONL, one record per line, oldest first. With `--issue`, only that issue's records. |
13
+ | `remember --issue <id> <text>` | Append one note record. Must be atomic under concurrent writers. |
14
+ | `claim <id>` | Mark in-progress and record the claimant. Exit 4 if already claimed by someone else. |
15
+ | `ready` | Print claimable issues as JSONL — no open blockers, not held, not gated. |
16
+ | `set <id> <key>=<value>` | Set a field. Exit 2 on an unknown key. |
17
+
18
+ ## The note record
19
+
20
+ The original harness wrote notes as free text with em-dash delimiters and
21
+ re-parsed that format in four independent places. Any format change needed
22
+ four synchronised edits, so the format could never change.
23
+
24
+ Notes are JSONL here. One record per line, one parser, `lib/notes`:
25
+
26
+ ```json
27
+ {"ts":"2026-08-27T19:04:11Z","verb":"DISPATCH","issue":"i-foo","actor":"orchestrator",
28
+ "fields":{"scope":["src/a.ts","tests/a.test.ts"],"brief":"one-line task"}}
29
+ ```
30
+
31
+ - `verb` is the record type: `DISPATCH`, `HANDOFF`, `INTERROGATION`, or a
32
+ backend-defined string. Consumers MUST ignore verbs they do not know.
33
+ - `fields` is free-form per verb. `scope` is an ARRAY — the original parsed
34
+ comma-vs-space-delimited strings differently in different call sites.
35
+ - Records are append-only. Nothing rewrites or deletes one.
36
+
37
+ ## What we deliberately do not require
38
+
39
+ Milestones, priorities, assignees, sprints, epics, time tracking. If your
40
+ tracker has them, good; the harness will not read them.
41
+
42
+ ## Two states a general tracker usually lacks
43
+
44
+ A `command` backend that cannot express these will degrade: `ready` will
45
+ over-report, and the orchestrator has to catch it by eye.
46
+
47
+ - **held** — decided not to do now, WITH a reason. Distinct from closed: a
48
+ held issue is a decision, not a completion, and `ready` must not return it.
49
+ - **gated** — blocked on an open question outside the issue graph (a design
50
+ decision, an owner ruling). Distinct from blocked-by, which points at
51
+ another issue. `ready` must not return a gated issue, and must fail closed
52
+ when the gate handle is unknown.
53
+
54
+ **Neither is a `status` value.** `status` holds only `notstarted`, `progress`
55
+ or `done`. Held-ness is the presence of a `heldWhy` field; gated-ness is the
56
+ presence of a `gate` field. This is deliberate. Modelling them as statuses
57
+ makes the reason optional — a bare `status: held` is expressible, and a held
58
+ issue whose reason nobody recorded is indistinguishable from one somebody
59
+ forgot. Carrying the reason in the field that defines the state makes the
60
+ reasonless version unrepresentable.
61
+
62
+ It also keeps the two orthogonal to progress: an issue can be half-implemented
63
+ AND held, which a single status enum forces you to lose.
64
+
65
+ The built-in `file` backend enforces this — `set <id> status=held` is refused
66
+ with a message naming the right field.
@@ -0,0 +1,63 @@
1
+ # Reshare prompt
2
+
3
+ Paste the block below into a fresh session to hand this work off. It is
4
+ self-contained — it assumes the reader knows nothing about the repo or this
5
+ conversation.
6
+
7
+ Use it if you want the doctrine extended or revised by an agent that is not
8
+ carrying a sprint's worth of context.
9
+
10
+ ---
11
+
12
+ ```
13
+ Read `doctrine/README.md`, `doctrine/ROLES.md` and `doctrine/WORKFLOW.md` in
14
+ this repo, plus `agents/verifier.md` and `agents/isolated-worker.md`. They
15
+ document how work moves through this project: issues → worked → verified →
16
+ folded → sprint report → issues.
17
+
18
+ Four roles: the owner (decides, ticks, says when held work may merge), workers
19
+ (one scoped issue each, in isolated git worktrees, commit nothing), a verifier
20
+ (ONE sweep across all finished work for a sprint, on a clean tree, before
21
+ anything is folded), and orchestrators (fold, land, report, file issues, talk to
22
+ the owner — the only committers).
23
+
24
+ The design intent, in the owner's words: verification is a quick check across
25
+ the workstreams for a sprint — not one verifier per worker — to confirm nothing
26
+ is glaringly wrong and the work is in an acceptable, testable, healthy state.
27
+ The orchestrator's job is the higher order: rather than polluting its context
28
+ with nitty-gritty verification, it inherits healthier work to fold in, which
29
+ frees orchestrators to work more fully with the owner instead of getting buried.
30
+
31
+ YOUR TASK: <state it here>
32
+
33
+ Things to know before you change any of it:
34
+
35
+ - Every rule in those files exists because something went wrong once, and most
36
+ say so. Do not shorten a rule into a slogan — the incident IS the argument.
37
+ If you remove one, say what now prevents the incident.
38
+ - The repo convention is that a new working rule names what enforces it (a
39
+ wrapper, a gate, a refusal, a CI job) or states plainly that nothing does.
40
+ - Depth belongs to the orchestrator, breadth to the verifier. The verifier
41
+ checks a test EXISTS and plausibly bites; proving a test is pinned to
42
+ behaviour — by mutating one half of a fix and watching the right subset go
43
+ red — stays with the orchestrator, on risky changes only. Do not collapse
44
+ those two into one role.
45
+ - A project can keep its own local, gitignored docs directory outside this
46
+ one; whether it is reachable from inside an agent's worktree depends on
47
+ whether it is listed in `config.json`'s `worktree.linkPaths` AND whether
48
+ this clone's git hooks were installed (`hooks/post-checkout` is what
49
+ symlinks it in). Don't assume either way — paste what an agent needs into
50
+ its brief regardless: proximity, not reachability, is what gets a rule
51
+ followed (96% vs 7%, measured on an earlier version of this workflow).
52
+ - Agent definitions are picked up at SESSION START only in Claude Code, which
53
+ is what this doctrine's reference implementation targets — `subagent_type`
54
+ needs a restart to see a new one. A different agent runner may behave
55
+ differently; check it before assuming this restart requirement carries
56
+ over.
57
+
58
+ Open question the owner raised and I could not resolve: they described the
59
+ roles as "4 — workers, verifiers, orchestrators", which names three. I
60
+ documented the fourth as the OWNER, since the owner holds decisions no agent may
61
+ take (the sprint-close tick has no code path, and held work merges only on their
62
+ say-so). If a different fourth role was meant, ask.
63
+ ```
@@ -0,0 +1,62 @@
1
+ # The factory
2
+
3
+ How work moves through this repo: the cycle, the roles, and the rules that
4
+ exist because something went wrong once.
5
+
6
+ This directory is the home for workflow documentation. A project can keep its
7
+ own local, gitignored docs directory for product and design material
8
+ alongside it; this directory is committed, because it governs how anyone —
9
+ human or agent — is supposed to operate here.
10
+
11
+ - **[WORKFLOW.md](WORKFLOW.md)** — the cycle, stage by stage, with what must be
12
+ true to leave each stage.
13
+ - **[ROLES.md](ROLES.md)** — who does what, and what each role must never do.
14
+
15
+ ---
16
+
17
+ ## The cycle
18
+
19
+ ```
20
+ ┌──────────────────────────────────────────────────────────┐
21
+ │ │
22
+ ▼ │
23
+ ISSUES ──▶ WORKED ──▶ VERIFIED ──▶ FOLDED ──▶ SPRINT REPORT ──┘
24
+ (tracker) (workers) (verifier) (orchestr.) (orchestrator +
25
+ owner ticks)
26
+ ```
27
+
28
+ Issues get worked. Finished work is verified as a sprint, on a clean tree,
29
+ before anything is folded. The orchestrator folds it in and lands it. The sprint
30
+ ends in a report the owner ticks. Rulings from that report — plus whatever
31
+ arrives from other workstreams — become the next issues, and round it goes.
32
+
33
+ ## The one-paragraph version
34
+
35
+ **Workers** implement one scoped issue each, in isolated git worktrees, and
36
+ commit nothing. **A verifier** sweeps all of their finished output together,
37
+ once, on a clean tree, and reports whether it is healthy. **An orchestrator**
38
+ folds the verified work into `main`, re-proving anything risky by its own
39
+ mutation, and is the only committer. The orchestrator then writes the sprint
40
+ report, the owner ticks it, and the rulings in it become issues.
41
+
42
+ ## Why it is shaped like this
43
+
44
+ **Verification is a sprint-level sweep, not a per-agent gate.** One verifier
45
+ across the whole sprint is cheaper than one per worker, and it is the only
46
+ vantage point that can see two changes interacting. It also keeps the
47
+ orchestrator's attention on the owner rather than on nitty-gritty re-running.
48
+
49
+ **The orchestrator still owns depth.** The verifier checks that a test exists
50
+ and plausibly bites. Proving a test is pinned to *behaviour* — by mutating one
51
+ half of a fix at a time and watching the right subset go red — stays with the
52
+ orchestrator, on the risky changes only. A subagent's proof can be vacuous in a
53
+ way that re-running it cannot reveal; only an independent mutation shows what a
54
+ test is actually pinned to.
55
+
56
+ **Work is landed at the behest of the owner** If they're working wtith you directly likely they'll indicate what to do, to be mindful of other agents, when pulling work in. You should also be intentional about surfacing risks of collission and surfacing what you can in advance of merging work.
57
+
58
+ **The clean tree is the source of truth.** The orchestrator's checkout is
59
+ usually dirty with other sessions' work. A worker's worktree branches from
60
+ committed HEAD, so it is clean. When a worker reports a failure the
61
+ orchestrator cannot reproduce, **the worker is right** — see the incident in
62
+ [WORKFLOW.md](WORKFLOW.md#the-clean-tree-is-the-truth).
@@ -0,0 +1,27 @@
1
+ # Roles
2
+
3
+ Four roles: three agents and the owner. Every prohibition is here because
4
+ something went wrong once.
5
+
6
+ | Role | Does | Never |
7
+ |---|---|---|
8
+ | **Owner** | Rules on open questions in served docs, ticks the sprint closed, says when held work merges. | Delegates the ready tick — there is no code path to it. |
9
+ | **Worker**<br>`agents/isolated-worker.md` | One scoped issue in its own worktree: the change, its tests, its `changelog.d/` fragment, and a `HANDOFF.md` — `changed` / `found` / `assumed` / `next`. | Commits, pushes, merges, runs the full build, edits outside its scope, or touches another worktree. |
10
+ | **Verifier**<br>`agents/verifier.md` | One sweep per sprint (not per worker) on a clean tree: applies every finished patch together, builds, regenerates, runs the suites, checks each change has a test that bites. Verdict with real numbers. | Fixes, lands, commits, reviews design, edits a worker's worktree, or repeats a number it did not re-run. |
11
+ | **Orchestrator**<br>the session talking to the owner | Dispatches, folds, lands, reports, files rulings as issues. The **only committer**. | Lands held work without the owner, relays a subagent's verification as its own, or lets its cwd leave the repo root. |
12
+
13
+ **Worker, hard-won:** write `HANDOFF.md` early — a dead worker otherwise
14
+ leaves only a diff. Check `git rev-parse --git-common-dir` first — not
15
+ `--show-toplevel`, which prints the worktree's own path and cannot confirm
16
+ which repo you branched from. Symlink
17
+ `worktree.linkPaths` before running anything. Report what you did **not** run.
18
+
19
+ | Job | Agent |
20
+ |---|---|
21
+ | Implement a scoped issue | `isolated-worker` |
22
+ | Sprint verification sweep | `verifier` |
23
+ | Read-only investigation | `Explore` — no worktree cost |
24
+ | Fold, land, report, file | the orchestrator itself |
25
+
26
+ Written against Claude Code: `agents/*.md` are picked up **at session start
27
+ only**. Check your runner's equivalent.
@@ -0,0 +1,87 @@
1
+ # The workflow
2
+
3
+ ```
4
+ ISSUES ──▶ WORKED ──▶ VERIFIED ──▶ FOLDED ──▶ SPRINT REPORT ──▶ ISSUES
5
+ ```
6
+
7
+ ## 1. Issues
8
+ The tracker is the state of the world, not the docs (`../docs/TRACKER-ADAPTER.md`).
9
+ **Leave when:** issues are chosen, non-overlapping in file scope, none gated on an
10
+ open decision. Held means decided, not forgotten. Two issues on one file serialize.
11
+
12
+ ## 2. Worked
13
+ One worker per issue, each in its own worktree. `dispatch <id> --files "…" --brief
14
+ "…"` **before** spawning anything — it refuses a non-root cwd, refuses uncommitted
15
+ edits inside the worker's scope, claims the issue, records the brief.
16
+ **Everything the worker needs goes in the brief:** over 225 subagent runs a rule
17
+ pasted into the brief was followed 96% of the time, the same rule left in the
18
+ project instructions file 7%. Prompt position, not access.
19
+ **Leave when:** every worker has finished or stopped *with* a `HANDOFF.md`. A
20
+ stopped worker is fine; one with no handoff is a loss.
21
+
22
+ ## 3. Verified
23
+ One verifier for the sprint. It gets every finished patch as a file and sweeps them
24
+ together. **Leave when:** `healthy` or `healthy with caveats` and the caveats are
25
+ understood. `not ready` goes back to stage 2.
26
+
27
+ ## 4. Folded
28
+ The orchestrator, and only the orchestrator, merges. Per change: apply to `main`;
29
+ run `generate` and confirm **nothing moves**; for the risky ones **mutate one half
30
+ of the fix at a time** and check the failure set is the one that half owns —
31
+ replaying a worker's own revert only proves the test noticed *that* revert. Then
32
+ `handoff <id> --from <worktree> --verified "<what YOU ran>"` and commit.
33
+ **Leave when:** committed on `main` with a fragment, the issue `done`, suites green
34
+ **on the final tree** — not on each patch. Nothing is folded while work is HELD.
35
+ Because folding is serial, the verifier blessed a combination, not each merge: **a
36
+ conflict resolution is the orchestrator's own code and needs its own proof.**
37
+
38
+ ## 5. Sprint report
39
+ Names the issues delivered and the issues opened, and gives the owner somewhere to
40
+ respond section by section. **A local HTML file in the project's docs directory** —
41
+ served by `bin/serve`, answered in the browser, saved back to disk; never published
42
+ to an external site, never loading from one. `bin/doclint` gates that plus
43
+ answerability. **Leave when:** the owner ticks the sprint closed — no code path.
44
+
45
+ ## 6. Back to issues
46
+ Rulings become tracker issues. `remember --issue <id> "…"` puts a finding where the
47
+ next session reads it. A ruling living only in chat is lost; only in a doc, nearly.
48
+
49
+ ## Standing rules
50
+
51
+ ### The clean tree is the truth
52
+ A worker's worktree branches from committed HEAD; the orchestrator's checkout is
53
+ dirty. **When a worker reports a failure the orchestrator cannot reproduce, the
54
+ worker is right.** Dismissing that once hid a fixture set broken at HEAD.
55
+
56
+ - **A new rule names what enforces it** — wrapper, gate, refusal or CI job, or say
57
+ plainly that nothing does.
58
+ - **Work is held until the owner says merge.**
59
+ - **Never run the full build in a worker or verifier** — release commands rewrite
60
+ committed bookkeeping. Use `config.json`'s `suites`.
61
+ - **Symlink `worktree.linkPaths` in every worktree.** Unlinked, a runtime walks up
62
+ and resolves a *sibling worktree* — you test someone else's code and report it as
63
+ your own. Green, confident, wrong.
64
+
65
+ ### Isolation is the runner's, and it can silently not happen
66
+ `isolation: worktree` branches the repo the **calling session's cwd** is in, not the
67
+ one the harness is vendored in. Orchestrate project A from a shell sitting in
68
+ project B and every worker gets a worktree of B, without the files it was sent for.
69
+ On 2026-08-30 the fallback from that put every agent in one shared checkout: they
70
+ could see each other's uncommitted edits, and one `git add -A` swept a live lane's
71
+ work into an unrelated commit.
72
+
73
+ **The worker's check, before it writes anything:** `git rev-parse --git-common-dir`
74
+ must contain the project's directory name; a bare relative `.git` means no worktree
75
+ at all. Not `--show-toplevel` — that prints the *worktree's* path, which never
76
+ equals the main repo's. `dispatch` pastes this into every brief.
77
+
78
+ **Orchestrating without isolation:** confirm cwd is the project root before every
79
+ dispatch, give concurrent agents strictly disjoint file scopes, and **never `git add
80
+ -A` while a lane is live** — stage explicit paths, every time.
81
+
82
+ **What enforces it: nothing.** `bin/dispatch` refuses a cwd that is not
83
+ `ENTROPY_MACHINES_ROOT`, which catches a session already drifted *when the brief is written*.
84
+ The runner creates the worktree later, from whatever the cwd is then, so a `cd` in
85
+ between is invisible to every gate — and no shell script can reach inside the
86
+ runner's isolation to check what it actually did. The worker's own check is all
87
+ that is downstream of it.
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # Repo commit-msg hook — VERSION-CONTROLLED, like its pre-commit sibling.
3
+ #
4
+ # It exists separately from pre-commit because handoff-guard keys on the issue
5
+ # id in the commit message, and pre-commit runs before a message exists.
6
+ #
7
+ # ONE ROOT. lib/handoff-guard.sh is a harness file living one directory up from
8
+ # this one, inside the same repository as the commit being made. The shim
9
+ # exports ENTROPY_MACHINES_HOME (it lives in .git/hooks and cannot find lib/
10
+ # relatively); the fallback below only matters when this file is run directly.
11
+ # $1, the message file path git passes in, is already project-relative or
12
+ # absolute as git gives it — nothing here needs to touch it.
13
+ #
14
+ # Keep it fast. $1 is the path to the message file.
15
+
16
+ set -euo pipefail
17
+
18
+ . "$(dirname "$0")/../lib/roots.sh"
19
+
20
+ : "${ENTROPY_MACHINES_HOME:=$(entropy_machines_home "$0")}"
21
+ export ENTROPY_MACHINES_HOME
22
+ entropy_machines_require_root commit-msg
23
+
24
+ exec "$ENTROPY_MACHINES_HOME/lib/handoff-guard.sh" --message "$1"