entropy-machines 0.1.3 → 0.1.7
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/README.md +1 -0
- package/agents/designer.md +41 -0
- package/bin/auto-verify +123 -0
- package/bin/cycle +29 -0
- package/bin/dispatch +184 -101
- package/bin/entropy-machines-init +58 -3
- package/bin/handoff +79 -0
- package/bin/migrate-db +38 -0
- package/bin/no-fork-gate +9 -0
- package/bin/post-fold-audit +2 -5
- package/bin/reply +24 -0
- package/bin/serve +353 -160
- package/bin/sprint-report +424 -0
- package/bin/status +2 -31
- package/docs/AGENT-QUICKSTART.md +3 -6
- package/docs/CONFIG.md +4 -3
- package/docs/SERVE.md +29 -1
- package/doctrine/ROLES.md +3 -1
- package/lib/PRD-001-orientation.html +516 -284
- package/lib/PRD-005-navigation.html +508 -0
- package/lib/PRD-006-react-migration.html +811 -0
- package/lib/REPORT-TEMPLATE.html +970 -80
- package/lib/api.py +387 -0
- package/lib/cycle.py +147 -0
- package/lib/doc-template.html +487 -130
- package/lib/docstate.py +557 -0
- package/lib/doctracker.py +709 -0
- package/lib/enhance.py +1568 -0
- package/lib/migrate_db.py +724 -0
- package/lib/reply.py +275 -0
- package/lib/schema.sql +99 -0
- package/lib/sprint-report-template.html +367 -0
- package/lib/tracker-file +11 -1
- package/lib/tracker-view.py +176 -104
- package/package.json +5 -1
- package/lib/themes/daylight.css +0 -84
- package/lib/themes/high-contrast.css +0 -36
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ the cycle repeats.
|
|
|
11
11
|
|
|
12
12
|
**A PRD creates issues, not the other way round.** The questions in a PRD are
|
|
13
13
|
the decisions only you can make. Everything downstream follows from your answers.
|
|
14
|
+
The tracker backend is a six-operation contract and can be swapped — see [TRACKER-ADAPTER.md](docs/TRACKER-ADAPTER.md).
|
|
14
15
|
|
|
15
16
|
```
|
|
16
17
|
YOU ANSWER A PRD
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designer
|
|
3
|
+
description: UX alignment agent for dialogue docs and PRD layout. Produces visual aids (mermaid diagrams, component mockups), pressure-tests UX decisions, and ensures docs are scannable with clear options and tight feedback loops. Read-only — no code changes, no commits.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are reviewing UX alignment for dialogue docs in this repo.
|
|
7
|
+
|
|
8
|
+
Your job is to make sure docs are **scannable**, options are **clear**, and
|
|
9
|
+
feedback loops are **tight**. You produce visual aids and challenge UX
|
|
10
|
+
decisions — you do not implement code.
|
|
11
|
+
|
|
12
|
+
## What you do
|
|
13
|
+
|
|
14
|
+
- **Visual aids**: mermaid sequence/flow diagrams, ASCII component mockups,
|
|
15
|
+
annotated screenshots. Anything that helps the owner see the system.
|
|
16
|
+
- **Layout review**: page flow, response box placement, discuss block
|
|
17
|
+
clarity, navigation structure. Flag when a page buries its question or
|
|
18
|
+
makes the owner hunt for what needs attention.
|
|
19
|
+
- **Option clarity**: every multi-option discuss block should have exactly
|
|
20
|
+
one marked as recommended, trade-offs stated as bullets not paragraphs,
|
|
21
|
+
and the question itself answerable without reading the whole page.
|
|
22
|
+
- **Grill-me challenge**: when asked, pressure-test a UX decision. Ask the
|
|
23
|
+
hard questions: what happens when there are 20 of these? What does the
|
|
24
|
+
owner see after ignoring this for a week? Where does this break on
|
|
25
|
+
mobile / in a narrow terminal? The point is to find the weak spots
|
|
26
|
+
before the owner does.
|
|
27
|
+
|
|
28
|
+
## What you never do
|
|
29
|
+
|
|
30
|
+
- Implement code changes
|
|
31
|
+
- Commit, push, or merge
|
|
32
|
+
- Override owner UX rulings — you advise, the owner decides
|
|
33
|
+
- Edit files outside docs (no lib/, no bin/, no tests/)
|
|
34
|
+
|
|
35
|
+
## Output
|
|
36
|
+
|
|
37
|
+
Your final message reports:
|
|
38
|
+
|
|
39
|
+
- What you reviewed and what you found
|
|
40
|
+
- Any diagrams or mockups you produced (as mermaid code blocks or inline)
|
|
41
|
+
- Specific recommendations, each as one bullet with a clear action
|
package/bin/auto-verify
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/auto-verify — zero-cost poller that emits a verifier Agent call.
|
|
3
|
+
#
|
|
4
|
+
# bin/auto-verify <issue-id>
|
|
5
|
+
#
|
|
6
|
+
# Polls for HANDOFF.md in the worker's worktree (matched by .dispatch-issue
|
|
7
|
+
# marker). Zero tokens while polling — pure shell. When found, outputs a
|
|
8
|
+
# structured Agent JSON block with the worktree path baked in. The
|
|
9
|
+
# orchestrator receives this via task notification and pastes it.
|
|
10
|
+
#
|
|
11
|
+
# Run via Bash(run_in_background: true) alongside the worker Agent call.
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
id="$1"
|
|
16
|
+
if [ -z "$id" ]; then
|
|
17
|
+
echo "usage: bin/auto-verify <issue-id>" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
ENTROPY_MACHINES_ROOT=$(git rev-parse --show-toplevel)
|
|
22
|
+
ctx="$ENTROPY_MACHINES_ROOT/.dispatch-context/$id.md"
|
|
23
|
+
NOTES_PY="$ENTROPY_MACHINES_ROOT/lib/notes.py"
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# POLL — zero tokens. Pure shell, 30-second intervals, 20-minute timeout.
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
main_wt="$ENTROPY_MACHINES_ROOT"
|
|
29
|
+
worker_wt=""
|
|
30
|
+
polls=0
|
|
31
|
+
max_polls=40
|
|
32
|
+
|
|
33
|
+
echo "auto-verify($id): polling for .dispatch-issue=$id + HANDOFF.md (30s intervals, max ${max_polls})"
|
|
34
|
+
while [ -z "$worker_wt" ] && [ "$polls" -lt "$max_polls" ]; do
|
|
35
|
+
for wt in $(git worktree list --porcelain | grep '^worktree ' | sed 's/worktree //'); do
|
|
36
|
+
[ "$wt" = "$main_wt" ] && continue
|
|
37
|
+
if [ -f "$wt/.dispatch-issue" ] && [ "$(cat "$wt/.dispatch-issue" | tr -d '[:space:]')" = "$id" ]; then
|
|
38
|
+
if [ -f "$wt/HANDOFF.md" ]; then
|
|
39
|
+
worker_wt="$wt"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
if [ -z "$worker_wt" ]; then
|
|
45
|
+
sleep 30
|
|
46
|
+
polls=$((polls + 1))
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
if [ -z "$worker_wt" ]; then
|
|
51
|
+
echo "auto-verify($id): TIMEOUT — no HANDOFF.md after $max_polls polls" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "auto-verify($id): HANDOFF.md found at $worker_wt"
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# EMIT — a structured Agent call for the verifier. The orchestrator pastes
|
|
59
|
+
# this to the Agent tool. The verifier runs in the SAME session, so it can
|
|
60
|
+
# SendMessage to the worker for real interrogation.
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
prompt_file="$ENTROPY_MACHINES_ROOT/.dispatch-context/.verifier-prompt-$id.tmp"
|
|
63
|
+
trap 'rm -f "$prompt_file"' EXIT
|
|
64
|
+
|
|
65
|
+
cat > "$prompt_file" << VEOF
|
|
66
|
+
VERIFIER for issue $id.
|
|
67
|
+
|
|
68
|
+
A worker agent named "$id" completed implementation in worktree: $worker_wt
|
|
69
|
+
Dispatch context (full issue detail): $ctx
|
|
70
|
+
|
|
71
|
+
Read BOTH files before doing anything else.
|
|
72
|
+
|
|
73
|
+
## STEP 1 — INTERROGATE THE WORKER
|
|
74
|
+
|
|
75
|
+
Run: bin/handoff $id --interrogate
|
|
76
|
+
This prints the questions. Use ListAgents to find the worker agent named
|
|
77
|
+
"$id" and get its address. Send ALL questions via SendMessage:
|
|
78
|
+
SendMessage({to: "<agent-address>", message: "<the questions>"})
|
|
79
|
+
Wait for the worker to respond (it resumes). Record the answers:
|
|
80
|
+
bin/handoff $id --record-interrogation --answer "<answers>"
|
|
81
|
+
|
|
82
|
+
If the worker cannot be reached (no longer listed), answer from artifacts:
|
|
83
|
+
1. Read $worker_wt/HANDOFF.md (changed/found/assumed/next)
|
|
84
|
+
2. Read the changed files in the worktree
|
|
85
|
+
At least one answer must be substantive.
|
|
86
|
+
|
|
87
|
+
## STEP 2 — LIFT
|
|
88
|
+
|
|
89
|
+
Run: bin/handoff $id --from $worker_wt --lift
|
|
90
|
+
If any files are rejected (main drifted), merge manually: read the worker's
|
|
91
|
+
version and main's current version, apply the semantic diff onto main.
|
|
92
|
+
|
|
93
|
+
## STEP 3 — TEST
|
|
94
|
+
|
|
95
|
+
Run: tests/run
|
|
96
|
+
All tests must pass. Fix failures caused by the lift.
|
|
97
|
+
|
|
98
|
+
## STEP 4 — VERIFY (do NOT commit)
|
|
99
|
+
|
|
100
|
+
Run: bin/handoff $id --from $worker_wt --verified "auto-verified: tests passed"
|
|
101
|
+
If HANDOFF.md had found: none AND next: none, add --clean.
|
|
102
|
+
If it had real found: or next: values, pass --found "..." --next "..."
|
|
103
|
+
|
|
104
|
+
DO NOT stage, commit, or push. Report what you verified and stop.
|
|
105
|
+
The orchestrator is the last pass — they review your work and commit.
|
|
106
|
+
VEOF
|
|
107
|
+
|
|
108
|
+
echo
|
|
109
|
+
echo "--- AGENT CALL ---"
|
|
110
|
+
python3 -c '
|
|
111
|
+
import json, sys
|
|
112
|
+
prompt = open(sys.argv[1]).read()
|
|
113
|
+
call = {
|
|
114
|
+
"subagent_type": "general-purpose",
|
|
115
|
+
"name": "verify-" + sys.argv[2],
|
|
116
|
+
"description": "Verify and land " + sys.argv[2],
|
|
117
|
+
"prompt": prompt
|
|
118
|
+
}
|
|
119
|
+
print(json.dumps(call, indent=2))
|
|
120
|
+
' "$prompt_file" "$id"
|
|
121
|
+
echo "--- END AGENT CALL ---"
|
|
122
|
+
echo
|
|
123
|
+
echo "Pass the JSON block above to the Agent tool to start verification."
|
package/bin/cycle
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Review-cycle CLI — thin wrapper around lib/cycle.py.
|
|
3
|
+
#
|
|
4
|
+
# bin/cycle list every handle + status + ball
|
|
5
|
+
# bin/cycle resolve "<phrase>" handle → file (fuzzy)
|
|
6
|
+
# bin/cycle responses <handle> read the user's saved answers
|
|
7
|
+
# bin/cycle start <handle> mark 'in-review' (agent is on it)
|
|
8
|
+
# bin/cycle done <handle> [label] mark 'open' (ball back to user) + snapshot
|
|
9
|
+
# bin/cycle set <handle> <status> any status transition
|
|
10
|
+
# bin/cycle new <handle> <file> <title> [status] register a new doc
|
|
11
|
+
# bin/cycle history <handle> list this doc's versions
|
|
12
|
+
# bin/cycle sync regenerate STATE.md + INDEX.html from manifest
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
. "$(dirname "$0")/../lib/roots.sh"
|
|
16
|
+
ENTROPY_MACHINES_HOME=$(entropy_machines_home "$0")
|
|
17
|
+
entropy_machines_require_root cycle
|
|
18
|
+
|
|
19
|
+
CONFIG_PY="$ENTROPY_MACHINES_HOME/lib/config.py"
|
|
20
|
+
|
|
21
|
+
docs_dir=$(python3 "$CONFIG_PY" get docs.dir 2>/dev/null) || docs_dir="entropy-machines-docs"
|
|
22
|
+
case "$docs_dir" in
|
|
23
|
+
/*) docs_path="$docs_dir" ;;
|
|
24
|
+
*) docs_path="$ENTROPY_MACHINES_ROOT/$docs_dir" ;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
export ENTROPY_MACHINES_DOCS="$docs_path"
|
|
28
|
+
export PYTHONPATH="$ENTROPY_MACHINES_HOME/lib${PYTHONPATH:+:$PYTHONPATH}"
|
|
29
|
+
exec python3 "$ENTROPY_MACHINES_HOME/lib/cycle.py" "$@"
|
package/bin/dispatch
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
#
|
|
4
4
|
# bin/dispatch i-foo --files "src/x.ts tests/x.test.ts" --brief "one-line task"
|
|
5
5
|
#
|
|
6
|
-
# Exit 0 means SAFE TO DISPATCH; anything else names the hazard. It
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Exit 0 means SAFE TO DISPATCH; anything else names the hazard. It emits
|
|
7
|
+
# TWO structured JSON blocks (--- AGENT CALL --- / --- END AGENT CALL ---)
|
|
8
|
+
# with the exact Agent tool parameters — the WORKER (implements in a
|
|
9
|
+
# worktree) and the VERIFIER (polls for HANDOFF.md, then runs the full
|
|
10
|
+
# handoff pipeline and commits). The orchestrator pastes both blocks in one
|
|
11
|
+
# message and walks away — nothing to remember, no manual landing. It refuses
|
|
12
|
+
# the mistakes that have each burned a real run:
|
|
9
13
|
#
|
|
10
14
|
# 1. NON-ROOT CWD. A worktree is created in whatever repo the dispatching
|
|
11
15
|
# session's cwd belongs to. A session parked one directory off from the
|
|
@@ -21,7 +25,11 @@
|
|
|
21
25
|
# none of this agent's business.
|
|
22
26
|
# 3. ALREADY-LANDED WORK. If git log already mentions the issue id, the
|
|
23
27
|
# task may be partly or fully done. The audit comes before the dispatch.
|
|
24
|
-
# 4.
|
|
28
|
+
# 4. RECENTLY-TOUCHED FILES. If any declared file was touched in the last
|
|
29
|
+
# 5 commits on any branch, that file is hot — another change may still
|
|
30
|
+
# be landing, or the agent's base commit may already contain the fix.
|
|
31
|
+
# The commits are printed and the dispatch requires --force to proceed.
|
|
32
|
+
# 5. UNRECORDED BRIEFS. The brief + file scope are appended to the tracker
|
|
25
33
|
# notes log in the same act, so any session can see what was handed out
|
|
26
34
|
# and scopes can be checked for overlap before agents collide in a file.
|
|
27
35
|
#
|
|
@@ -78,19 +86,21 @@
|
|
|
78
86
|
# with no obvious symptom until the agent can't find its own files. Check 1
|
|
79
87
|
# is what closes that.
|
|
80
88
|
#
|
|
81
|
-
# --anyway skips check 3 only (the git-log hits are still printed);
|
|
82
|
-
#
|
|
83
|
-
#
|
|
89
|
+
# --anyway skips check 3 only (the git-log hits are still printed); --force
|
|
90
|
+
# skips check 4 only (the file-history hits are still printed); checks 1 and
|
|
91
|
+
# 2 have no override — there is no correct dispatch from a wrong cwd or over
|
|
92
|
+
# your own uncommitted edits.
|
|
84
93
|
set -e
|
|
85
94
|
|
|
86
|
-
usage() { echo "usage: bin/dispatch <issue-id> --files \"<paths>\" --brief \"<text>\" [--anyway] [--dry-run]" >&2; exit 2; }
|
|
95
|
+
usage() { echo "usage: bin/dispatch <issue-id> --files \"<paths>\" --brief \"<text>\" [--anyway] [--force] [--dry-run]" >&2; exit 2; }
|
|
87
96
|
|
|
88
|
-
id=""; files=""; brief=""; anyway=0; dry=0
|
|
97
|
+
id=""; files=""; brief=""; anyway=0; force=0; dry=0
|
|
89
98
|
while [ $# -gt 0 ]; do
|
|
90
99
|
case "$1" in
|
|
91
100
|
--files) files="$2"; shift 2 ;;
|
|
92
101
|
--brief) brief="$2"; shift 2 ;;
|
|
93
102
|
--anyway) anyway=1; shift ;;
|
|
103
|
+
--force) force=1; shift ;;
|
|
94
104
|
# Run every check and print the brief block, but write nothing to the
|
|
95
105
|
# log. Added after a smoke test of this script left a phantom DISPATCH
|
|
96
106
|
# note behind — a note that can be annotated but never removed, and that
|
|
@@ -189,6 +199,29 @@ if [ -n "$hits" ]; then
|
|
|
189
199
|
fi
|
|
190
200
|
fi
|
|
191
201
|
|
|
202
|
+
# CHECK 4 — recently-touched files. A declared file that was touched in the
|
|
203
|
+
# last 5 commits on any branch is hot: the change may still be landing, the
|
|
204
|
+
# agent may be re-doing work that already shipped, or the base commit already
|
|
205
|
+
# contains the fix. The commits are printed unconditionally; the dispatch is
|
|
206
|
+
# refused unless --force is given.
|
|
207
|
+
file_history_hits=""
|
|
208
|
+
for f in $files; do
|
|
209
|
+
fhits=$(git log -5 --all --oneline -- "$f" 2>/dev/null)
|
|
210
|
+
if [ -n "$fhits" ]; then
|
|
211
|
+
file_history_hits="${file_history_hits}${file_history_hits:+
|
|
212
|
+
} $f:
|
|
213
|
+
$(echo "$fhits" | sed 's/^/ /')"
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
216
|
+
if [ -n "$file_history_hits" ]; then
|
|
217
|
+
echo "dispatch: declared files were touched in the last 5 commits on some branch:" >&2
|
|
218
|
+
echo "$file_history_hits" >&2
|
|
219
|
+
if [ "$force" != "1" ]; then
|
|
220
|
+
echo " Review the commits above, then rerun with --force to proceed." >&2
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
192
225
|
# ---------------------------------------------------------------------------
|
|
193
226
|
# THE DENYLIST — what OTHER agents are holding right now
|
|
194
227
|
# ---------------------------------------------------------------------------
|
|
@@ -405,6 +438,20 @@ Whether the tracker is reachable from inside your worktree depends on this
|
|
|
405
438
|
project's config.json worktree.linkPaths and on whether this clone's hooks
|
|
406
439
|
are installed. Ask the dispatching session for the issue text if you need it."
|
|
407
440
|
|
|
441
|
+
# Extract the issue description from the tracker JSON for context-file
|
|
442
|
+
# inclusion. Falls back to empty if unavailable or the JSON has no
|
|
443
|
+
# description field — the section is omitted rather than printing a
|
|
444
|
+
# confusing "(none)".
|
|
445
|
+
issue_description=$(printf '%s\n' "$issue_json" | python3 -c '
|
|
446
|
+
import json, sys
|
|
447
|
+
try:
|
|
448
|
+
data = json.loads(sys.stdin.read())
|
|
449
|
+
desc = data.get("description") or ""
|
|
450
|
+
print(desc)
|
|
451
|
+
except Exception:
|
|
452
|
+
pass
|
|
453
|
+
' 2>/dev/null || true)
|
|
454
|
+
|
|
408
455
|
notes_rendered=$(printf '%s\n' "$notes_raw" | render_notes)
|
|
409
456
|
|
|
410
457
|
# Not capped: an id with a long history is exactly the case the audit is
|
|
@@ -434,7 +481,22 @@ for s in suites:
|
|
|
434
481
|
link_paths=$(cfg_get worktree.linkPaths)
|
|
435
482
|
|
|
436
483
|
mkdir -p "$ctxdir"
|
|
484
|
+
# PRESERVE EXISTING CONTENT. The orchestrator may have already written a
|
|
485
|
+
# spec into this file before dispatching — that spec is the agent's primary
|
|
486
|
+
# input and must appear FIRST and intact. Read it before the redirect
|
|
487
|
+
# truncates the file; if present, it is emitted before the dispatch
|
|
488
|
+
# boilerplate under a clear separator.
|
|
489
|
+
existing_ctx=""
|
|
490
|
+
if [ -s "$ctx" ]; then
|
|
491
|
+
existing_ctx=$(cat "$ctx")
|
|
492
|
+
fi
|
|
437
493
|
{
|
|
494
|
+
if [ -n "$existing_ctx" ]; then
|
|
495
|
+
printf '%s\n' "$existing_ctx"
|
|
496
|
+
echo
|
|
497
|
+
echo "---"
|
|
498
|
+
echo
|
|
499
|
+
fi
|
|
438
500
|
echo "# Dispatch context — $id"
|
|
439
501
|
echo
|
|
440
502
|
echo "Written by bin/dispatch on $(date -u '+%Y-%m-%dT%H:%M:%SZ') from $ENTROPY_MACHINES_ROOT ($branch @ $head)."
|
|
@@ -503,6 +565,12 @@ for s in suites:
|
|
|
503
565
|
echo "has not read it. It is recorded for the audit trail and it is NOT the"
|
|
504
566
|
echo "limit of what you may write. The enforced limit is the claimed list above."
|
|
505
567
|
echo
|
|
568
|
+
if [ -n "$issue_description" ]; then
|
|
569
|
+
echo "## Issue description"
|
|
570
|
+
echo
|
|
571
|
+
echo "$issue_description"
|
|
572
|
+
echo
|
|
573
|
+
fi
|
|
506
574
|
echo "## Your brief"
|
|
507
575
|
echo
|
|
508
576
|
echo " $brief"
|
|
@@ -596,101 +664,116 @@ if [ "$dry" -eq 1 ]; then
|
|
|
596
664
|
else
|
|
597
665
|
echo "dispatch: OK — brief + scope recorded on $id."
|
|
598
666
|
fi
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
echo
|
|
609
|
-
# Pasted verbatim into the agent's brief. Four keys, one line each, and the
|
|
610
|
-
# instruction says so twice — an agent given a free-form "write up what you
|
|
611
|
-
# found" returns prose the lander then has to read and compress, which is
|
|
612
|
-
# the work this was meant to remove. The file is gitignored, so the agent
|
|
613
|
-
# cannot accidentally add it to the tree.
|
|
614
|
-
echo "PASTE INTO THE AGENT'S BRIEF:"
|
|
615
|
-
echo " BRIEF: $brief"
|
|
616
|
-
echo
|
|
617
|
-
# FIRST LINE after the brief, and the load-bearing one — see the measured
|
|
618
|
-
# numbers in the header. This holds regardless of whether your runner
|
|
619
|
-
# auto-loads a project-instructions file into subagents: proximity to the
|
|
620
|
-
# prompt is what changes behaviour, not presence in context.
|
|
621
|
-
echo " FIRST, before anything else: read this file, in full, with one Read:"
|
|
622
|
-
echo " $ctx"
|
|
623
|
-
echo " It is your issue text, what already landed under this id, your scope and"
|
|
624
|
-
echo " brief, what earlier agents left you, and this project's suites. It is in"
|
|
625
|
-
echo " the MAIN checkout, NOT in your worktree; the path above is absolute, use"
|
|
626
|
-
echo " it as-is."
|
|
627
|
-
if [ "$dry" -eq 1 ]; then
|
|
628
|
-
echo " (--dry-run: that file was NOT written. Rerun without --dry-run.)"
|
|
629
|
-
fi
|
|
630
|
-
# The cwd check, stated to the AGENT. This script already refuses a
|
|
631
|
-
# non-root cwd, but that runs when the brief is written and the dispatch
|
|
632
|
-
# call happens after — a `cd` in between still misroutes the worktree, and
|
|
633
|
-
# has once taken out three agents at once this way. Nothing in the tool
|
|
634
|
-
# chain can catch that on the way in, so the agent checks on the way out.
|
|
667
|
+
# ---------------------------------------------------------------------------
|
|
668
|
+
# STRUCTURED AGENT CALL
|
|
669
|
+
# ---------------------------------------------------------------------------
|
|
670
|
+
# The prompt is assembled and emitted as part of a JSON block containing the
|
|
671
|
+
# exact Agent tool parameters. The orchestrator passes these directly — no
|
|
672
|
+
# manual assembly, no remembering the subagent_type. The same measured
|
|
673
|
+
# numbers from the header (pasted ~96%, referenced ~38%) shaped what goes
|
|
674
|
+
# into the prompt: every load-bearing rule is INSIDE the prompt text, not
|
|
675
|
+
# merely referenced.
|
|
635
676
|
#
|
|
636
|
-
#
|
|
637
|
-
#
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
# path, which can never equal the main repo's path, so it can never confirm
|
|
643
|
-
# which repo the worktree belongs to.
|
|
644
|
-
echo " Your scope is a DENYLIST, not an allowlist. These files are claimed by"
|
|
645
|
-
echo " another agent RIGHT NOW and you must NOT write them: $deny_field"
|
|
646
|
-
echo " Everything else is yours if the fix needs it. If your fix needs a CLAIMED"
|
|
647
|
-
echo " file: STOP — do not write it, and do NOT ship the half you were allowed"
|
|
648
|
-
echo " to write. Leave the in-scope work coherent and name the exact file and"
|
|
649
|
-
echo " what it must contain in HANDOFF.md's found: line. The context file above"
|
|
650
|
-
echo " has the full protocol and says what does and does not enforce it."
|
|
651
|
-
echo " SECOND, before you write anything: run \`git rev-parse --git-common-dir\`."
|
|
652
|
-
echo " It prints the MAIN repo's .git — which repo your worktree branched from."
|
|
653
|
-
echo " It MUST contain /$(basename "$ENTROPY_MACHINES_ROOT")/. A bare relative \`.git\` means you"
|
|
654
|
-
echo " are not in a worktree at all: report that."
|
|
655
|
-
echo " If either is wrong, STOP and report it. NEVER cd to make it pass and"
|
|
656
|
-
echo " never write to the shared checkout — a cd is the failure, not the fix."
|
|
657
|
-
echo " Do NOT use --show-toplevel here: yours is the worktree path, so it can"
|
|
658
|
-
echo " never end in /$(basename "$ENTROPY_MACHINES_ROOT") and tells you nothing about the repo."
|
|
659
|
-
# THE SCRATCHPAD, IN THE PASTE BLOCK RATHER THAN ONLY IN A DOC. The same
|
|
660
|
-
# measurement that shapes the rest of this block puts a pasted rule at ~96%
|
|
661
|
-
# and one sitting only in a doc the agent was told to go read at far less —
|
|
662
|
-
# and a scratchpad the worker never hears about is the shared one it was
|
|
663
|
-
# already using.
|
|
664
|
-
echo " YOUR PRIVATE SCRATCHPAD is \`.scratch\` at the root of your worktree —"
|
|
665
|
-
echo " mutators, probe harnesses, captured suite output go THERE, not in any"
|
|
666
|
-
echo " scratchpad shared with the agents running beside you. Two concurrent"
|
|
667
|
-
echo " agents have overwritten each other's throwaway files by both reaching"
|
|
668
|
-
echo " for a shared scratch directory instead. If a tool refuses to write"
|
|
669
|
-
echo " through .scratch, say so in HANDOFF.md — do not fall back to a shared"
|
|
670
|
-
echo " scratchpad silently."
|
|
671
|
-
# Earlier agents' carry-forward lines, INSIDE the paste block rather than
|
|
672
|
-
# only in the context file above it, for the same measured reason.
|
|
673
|
-
if [ -n "$carry" ]; then
|
|
674
|
-
echo " WHAT EARLIER WORK ON THIS ISSUE LEFT YOU (you may not be able to look"
|
|
675
|
-
echo " this up yourself — see the context file for how to check):"
|
|
676
|
-
echo "$carry"
|
|
677
|
+
# The prompt is written to a temp file so Python can JSON-encode it with
|
|
678
|
+
# proper escaping. The file is cleaned up on exit.
|
|
679
|
+
prompt_tmp="$ctxdir/.prompt-$id.tmp"
|
|
680
|
+
trap 'rm -f "$prompt_tmp"' EXIT
|
|
681
|
+
{
|
|
682
|
+
echo "BRIEF: $brief"
|
|
677
683
|
echo
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
echo "
|
|
683
|
-
echo "
|
|
684
|
-
echo "
|
|
685
|
-
echo "
|
|
686
|
-
echo "
|
|
687
|
-
echo "
|
|
688
|
-
|
|
689
|
-
echo "
|
|
690
|
-
|
|
691
|
-
|
|
684
|
+
# FIRST LINE after the brief, and the load-bearing one — see the measured
|
|
685
|
+
# numbers in the header. This holds regardless of whether your runner
|
|
686
|
+
# auto-loads a project-instructions file into subagents: proximity to the
|
|
687
|
+
# prompt is what changes behaviour, not presence in context.
|
|
688
|
+
echo "FIRST, before anything else: read this file, in full, with one Read:"
|
|
689
|
+
echo " $ctx"
|
|
690
|
+
echo "It is your issue text, what already landed under this id, your scope and"
|
|
691
|
+
echo "brief, what earlier agents left you, and this project's suites. It is in"
|
|
692
|
+
echo "the MAIN checkout, NOT in your worktree; the path above is absolute, use"
|
|
693
|
+
echo "it as-is."
|
|
694
|
+
if [ "$dry" -eq 1 ]; then
|
|
695
|
+
echo "(--dry-run: that file was NOT written. Rerun without --dry-run.)"
|
|
696
|
+
fi
|
|
697
|
+
# The cwd check, stated to the AGENT. This script already refuses a
|
|
698
|
+
# non-root cwd, but that runs when the brief is written and the dispatch
|
|
699
|
+
# call happens after — a `cd` in between still misroutes the worktree, and
|
|
700
|
+
# has once taken out three agents at once this way. Nothing in the tool
|
|
701
|
+
# chain can catch that on the way in, so the agent checks on the way out.
|
|
702
|
+
#
|
|
703
|
+
# --git-common-dir answers WHICH REPO IS THIS A WORKTREE OF. From a linked
|
|
704
|
+
# worktree it resolves to the MAIN repo's .git as an ABSOLUTE path, so it
|
|
705
|
+
# carries the repo's own path. From a non-worktree checkout at its root it
|
|
706
|
+
# prints a bare relative `.git`, which is why the wording calls that out as
|
|
707
|
+
# its own reportable state rather than leaving the agent to interpret it.
|
|
708
|
+
# --show-toplevel is the wrong check here: it prints the WORKTREE's own
|
|
709
|
+
# path, which can never equal the main repo's path, so it can never confirm
|
|
710
|
+
# which repo the worktree belongs to.
|
|
711
|
+
echo "Your scope is a DENYLIST, not an allowlist. These files are claimed by"
|
|
712
|
+
echo "another agent RIGHT NOW and you must NOT write them: $deny_field"
|
|
713
|
+
echo "Everything else is yours if the fix needs it. If your fix needs a CLAIMED"
|
|
714
|
+
echo "file: STOP — do not write it, and do NOT ship the half you were allowed"
|
|
715
|
+
echo "to write. Leave the in-scope work coherent and name the exact file and"
|
|
716
|
+
echo "what it must contain in HANDOFF.md's found: line. The context file above"
|
|
717
|
+
echo "has the full protocol and says what does and does not enforce it."
|
|
718
|
+
echo "SECOND, before you write anything: run \`git rev-parse --git-common-dir\`."
|
|
719
|
+
echo "It prints the MAIN repo's .git — which repo your worktree branched from."
|
|
720
|
+
echo "It MUST contain /$(basename "$ENTROPY_MACHINES_ROOT")/. A bare relative \`.git\` means you"
|
|
721
|
+
echo "are not in a worktree at all: report that."
|
|
722
|
+
# THE SCRATCHPAD, IN THE PASTE BLOCK RATHER THAN ONLY IN A DOC. The same
|
|
723
|
+
# measurement that shapes the rest of this block puts a pasted rule at ~96%
|
|
724
|
+
# and one sitting only in a doc the agent was told to go read at far less —
|
|
725
|
+
# and a scratchpad the worker never hears about is the shared one it was
|
|
726
|
+
# already using.
|
|
727
|
+
echo "YOUR PRIVATE SCRATCHPAD is \`.scratch\` at the root of your worktree."
|
|
728
|
+
# Earlier agents' carry-forward lines, INSIDE the paste block rather than
|
|
729
|
+
# only in the context file above it, for the same measured reason.
|
|
730
|
+
if [ -n "$carry" ]; then
|
|
731
|
+
echo "WHAT EARLIER WORK ON THIS ISSUE LEFT YOU:"
|
|
732
|
+
echo "$carry"
|
|
733
|
+
echo
|
|
734
|
+
fi
|
|
735
|
+
echo "THIRD, write the file .dispatch-issue at the root of your worktree,"
|
|
736
|
+
echo "containing ONLY this line: $id"
|
|
737
|
+
echo "This identifies your worktree to the auto-verifier polling for it."
|
|
738
|
+
echo "Before you finish, write HANDOFF.md at the root of your worktree."
|
|
739
|
+
echo "Exactly these four keys, ONE LINE each, no prose:"
|
|
740
|
+
echo " changed: what you changed"
|
|
741
|
+
echo " found: seen but not fixed, out of scope — or: none"
|
|
742
|
+
echo " assumed: what you took as given — or: none"
|
|
743
|
+
echo " next: what the next agent needs to know — or: none"
|
|
744
|
+
} > "$prompt_tmp"
|
|
745
|
+
|
|
746
|
+
# Emit the structured agent call — the orchestrator passes these fields
|
|
747
|
+
# directly to the Agent tool. subagent_type and isolation are set here;
|
|
748
|
+
# the orchestrator must not override them.
|
|
749
|
+
echo
|
|
750
|
+
echo "--- AGENT CALL ---"
|
|
751
|
+
python3 -c '
|
|
752
|
+
import json, sys
|
|
753
|
+
prompt = open(sys.argv[1]).read()
|
|
754
|
+
call = {
|
|
755
|
+
"subagent_type": "general-purpose",
|
|
756
|
+
"isolation": "worktree",
|
|
757
|
+
"name": sys.argv[2],
|
|
758
|
+
"description": "Implement " + sys.argv[2],
|
|
759
|
+
"prompt": prompt
|
|
760
|
+
}
|
|
761
|
+
print(json.dumps(call, indent=2))
|
|
762
|
+
' "$prompt_tmp" "$id"
|
|
763
|
+
echo "--- END AGENT CALL ---"
|
|
764
|
+
echo
|
|
765
|
+
echo "--- AUTO-VERIFY ---"
|
|
766
|
+
echo "bin/auto-verify $id"
|
|
767
|
+
echo "--- END AUTO-VERIFY ---"
|
|
768
|
+
echo
|
|
769
|
+
echo "Pass the AGENT block to the Agent tool AND run the AUTO-VERIFY command"
|
|
770
|
+
echo "via the Bash tool (run_in_background: true) — both in ONE message."
|
|
771
|
+
echo "The worker implements; bin/auto-verify polls for HANDOFF.md at zero token"
|
|
772
|
+
echo "cost, then emits a verifier Agent call. Paste that call when notified."
|
|
773
|
+
echo "The verifier runs interrogate → lift → test → verify. YOU review and commit."
|
|
774
|
+
echo " issue: $id | advisory --files: $files | ENFORCED denylist: $deny_field"
|
|
692
775
|
echo
|
|
693
|
-
echo "
|
|
776
|
+
echo "To skip auto-verification and land manually:"
|
|
694
777
|
echo " bin/handoff $id --interrogate # the questions; send them to the LIVE agent"
|
|
695
778
|
echo " bin/handoff $id --record-interrogation --answer \"...\" # what it said"
|
|
696
779
|
echo " bin/handoff $id --from <agent-worktree> --lift # copies the files IN, refusing any that drifted"
|
|
@@ -21,9 +21,15 @@
|
|
|
21
21
|
// against a clone does).
|
|
22
22
|
//
|
|
23
23
|
// Deliberately NOT here, because the owner deleted a 1072-line installer to
|
|
24
|
-
// get rid of them: managed blocks, hash fences, install receipts,
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// get rid of them: managed blocks, hash fences, install receipts, an
|
|
25
|
+
// uninstall path, and any write into the user's .claude/ or CLAUDE.md.
|
|
26
|
+
// Undoing this is `rm -rf` of one directory.
|
|
27
|
+
//
|
|
28
|
+
// VERSION STAMPING is the one piece that came back: a .version file in the
|
|
29
|
+
// vendored directory lets `start` detect a stale copy and auto-update the
|
|
30
|
+
// harness code (bin/, lib/, etc.) without re-running bin/init or touching
|
|
31
|
+
// config.json. Without it, `npx entropy-machines start` after a version bump
|
|
32
|
+
// silently ran the old code and the user had to know to delete and re-vendor.
|
|
27
33
|
|
|
28
34
|
const fs = require('node:fs');
|
|
29
35
|
const path = require('node:path');
|
|
@@ -204,6 +210,9 @@ function init(argv) {
|
|
|
204
210
|
fs.copyFileSync(path.join(PKG_ROOT, 'LICENSE'), licenseDst);
|
|
205
211
|
}
|
|
206
212
|
|
|
213
|
+
// Stamp the version so `start` can detect a stale vendored copy.
|
|
214
|
+
fs.writeFileSync(path.join(dest, '.version'), PKG.version + '\n');
|
|
215
|
+
|
|
207
216
|
const shown = path.relative(root, dest) || '.';
|
|
208
217
|
console.log(`${GREEN}entropy-machines ${PKG.version}: vendored into ${dest}${RESET}`);
|
|
209
218
|
for (const d of HARNESS_DIRS) console.log(` ${DIM}${path.join(shown, d)}/${RESET}`);
|
|
@@ -257,6 +266,22 @@ function findHarness(root) {
|
|
|
257
266
|
return null;
|
|
258
267
|
}
|
|
259
268
|
|
|
269
|
+
// Re-copy harness code (bin/, lib/, etc.) without re-running bin/init or
|
|
270
|
+
// touching config.json. Called by start() when the vendored .version is older
|
|
271
|
+
// than the running package.
|
|
272
|
+
function updateHarness(loc) {
|
|
273
|
+
const dest = loc.dir;
|
|
274
|
+
for (const d of HARNESS_DIRS) {
|
|
275
|
+
fs.cpSync(path.join(PKG_ROOT, d), path.join(dest, d), {
|
|
276
|
+
recursive: true,
|
|
277
|
+
filter: copyFilter,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// LICENSE: overwrite — it's ours, not user-edited.
|
|
281
|
+
fs.copyFileSync(path.join(PKG_ROOT, 'LICENSE'), path.join(dest, 'LICENSE'));
|
|
282
|
+
fs.writeFileSync(path.join(dest, '.version'), PKG.version + '\n');
|
|
283
|
+
}
|
|
284
|
+
|
|
260
285
|
function start(argv) {
|
|
261
286
|
const root = repoRoot();
|
|
262
287
|
if (!root) die(['entropy-machines: not inside a git repository.']);
|
|
@@ -270,10 +295,40 @@ function start(argv) {
|
|
|
270
295
|
if (!loc) {
|
|
271
296
|
die(['entropy-machines: config.json not found at root or in ' + DEFAULT_DIR + '/.']);
|
|
272
297
|
}
|
|
298
|
+
|
|
299
|
+
// AUTO-UPDATE: if the vendored copy is older than this package, re-copy
|
|
300
|
+
// the harness code. config.json and the docs directory are untouched.
|
|
301
|
+
const versionFile = path.join(loc.dir, '.version');
|
|
302
|
+
let vendored = '';
|
|
303
|
+
try { vendored = fs.readFileSync(versionFile, 'utf8').trim(); } catch {}
|
|
304
|
+
if (vendored !== PKG.version) {
|
|
305
|
+
const from = vendored || 'unknown';
|
|
306
|
+
console.log(`${GREEN}entropy-machines: updating ${loc.shown}/ from ${from} → ${PKG.version}${RESET}`);
|
|
307
|
+
updateHarness(loc);
|
|
308
|
+
console.log(`${DIM} (config.json and docs left alone)${RESET}\n`);
|
|
309
|
+
}
|
|
310
|
+
|
|
273
311
|
const serve = path.join(loc.dir, 'bin', 'serve');
|
|
274
312
|
if (!fs.existsSync(serve)) {
|
|
275
313
|
die([`entropy-machines: ${path.join(loc.shown, 'bin', 'serve')} not found.`]);
|
|
276
314
|
}
|
|
315
|
+
// Agent onboarding notice — only if no agent config file mentions
|
|
316
|
+
// entropy-machines yet. Checks every common agent instruction file.
|
|
317
|
+
const agentConfigPaths = [
|
|
318
|
+
path.join(root, 'CLAUDE.md'),
|
|
319
|
+
path.join(root, '.claude', 'CLAUDE.md'),
|
|
320
|
+
path.join(root, '.cursorrules'),
|
|
321
|
+
path.join(root, '.github', 'copilot-instructions.md'),
|
|
322
|
+
path.join(root, '.windsurfrules'),
|
|
323
|
+
];
|
|
324
|
+
const agentOnboarded = agentConfigPaths.some((p) => {
|
|
325
|
+
try { return fs.readFileSync(p, 'utf8').includes('entropy-machines'); } catch { return false; }
|
|
326
|
+
});
|
|
327
|
+
if (!agentOnboarded) {
|
|
328
|
+
console.log(`\n${BOLD}${YELLOW} ⚠ your coding agent doesn't know about entropy-machines yet.${RESET}`);
|
|
329
|
+
console.log(` Point it at ${CYAN}${path.join(loc.shown, 'docs', 'AGENT-QUICKSTART.md')}${RESET} before answering the PRD.\n`);
|
|
330
|
+
}
|
|
331
|
+
|
|
277
332
|
console.log(`\n${BOLD}entropy-machines: starting serve from ${loc.shown}/${RESET}\n`);
|
|
278
333
|
const r = spawnSync('sh', [serve], { cwd: loc.dir, stdio: 'inherit' });
|
|
279
334
|
process.exit(r.status ?? 1);
|