@oneie/claude 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/abm-strategist.md +67 -1
  3. package/agents/ads-meta.md +67 -1
  4. package/agents/analyst.md +67 -1
  5. package/agents/animator.md +108 -0
  6. package/agents/architect.md +269 -20
  7. package/agents/brand-guardian.md +67 -1
  8. package/agents/brand-strategist.md +67 -1
  9. package/agents/campaign-content.md +67 -1
  10. package/agents/campaign-email.md +67 -1
  11. package/agents/campaign-sms.md +67 -1
  12. package/agents/campaign-social.md +67 -1
  13. package/agents/cco.md +83 -2
  14. package/agents/ceo.md +108 -11
  15. package/agents/chairman.md +197 -0
  16. package/agents/cmo.md +82 -2
  17. package/agents/community-greeter.md +67 -1
  18. package/agents/community-moderator.md +67 -1
  19. package/agents/compliance.md +67 -1
  20. package/agents/copywriter.md +67 -1
  21. package/agents/creative-strategist.md +67 -1
  22. package/agents/cro.md +81 -1
  23. package/agents/cto.md +266 -28
  24. package/agents/customer-interviewer.md +67 -1
  25. package/agents/customer-researcher.md +67 -1
  26. package/agents/customer-success-manager.md +67 -1
  27. package/agents/customer-trainer.md +67 -1
  28. package/agents/cxo.md +82 -1
  29. package/agents/demand-creator.md +67 -1
  30. package/agents/demo-mover.md +67 -1
  31. package/agents/demo-specialist.md +67 -1
  32. package/agents/demo-thai-family-law.md +67 -1
  33. package/agents/designer.md +67 -1
  34. package/agents/discovery-caller.md +67 -1
  35. package/agents/doctor.md +269 -0
  36. package/agents/educate-coach.md +67 -1
  37. package/agents/elevate-tutor.md +67 -1
  38. package/agents/email-lifecycle-marketer.md +67 -1
  39. package/agents/engage-specialist.md +67 -1
  40. package/agents/events-coordinator.md +67 -1
  41. package/agents/foundation-builder.md +67 -1
  42. package/agents/funnel-architect.md +67 -1
  43. package/agents/gift-creator.md +67 -1
  44. package/agents/google-ads.md +67 -1
  45. package/agents/guide.md +67 -1
  46. package/agents/helpdesk-dispatcher.md +67 -1
  47. package/agents/hook-specialist.md +67 -1
  48. package/agents/identify-optimizer.md +67 -1
  49. package/agents/implementer.md +313 -45
  50. package/agents/incident-commander.md +67 -1
  51. package/agents/insights-lead.md +87 -1
  52. package/agents/journey-runner.md +67 -1
  53. package/agents/linkedin-ads.md +67 -1
  54. package/agents/live-sales-chat.md +67 -1
  55. package/agents/market-researcher.md +67 -1
  56. package/agents/media-buyer.md +67 -1
  57. package/agents/memory-keeper.md +195 -0
  58. package/agents/movers-customer-researcher.md +67 -1
  59. package/agents/movers-foundation-builder.md +67 -1
  60. package/agents/movers-market-researcher.md +67 -1
  61. package/agents/movers-pricing-strategist.md +67 -1
  62. package/agents/nurture-architect.md +67 -1
  63. package/agents/offer-architect.md +67 -1
  64. package/agents/onboarder.md +67 -1
  65. package/agents/onboarding-specialist.md +67 -1
  66. package/agents/operations-dashboard.md +87 -1
  67. package/agents/perf-engineer.md +333 -37
  68. package/agents/playbook-writer.md +67 -1
  69. package/agents/plg-strategist.md +67 -1
  70. package/agents/positioning-architect.md +67 -1
  71. package/agents/press-officer.md +67 -1
  72. package/agents/pricing-strategist.md +67 -1
  73. package/agents/privacy-officer.md +67 -1
  74. package/agents/referral-manager.md +67 -1
  75. package/agents/refine-analyst.md +67 -1
  76. package/agents/release-manager.md +446 -39
  77. package/agents/renewals-upsell-rep.md +67 -1
  78. package/agents/review-engineer.md +319 -45
  79. package/agents/rewards-steward.md +67 -1
  80. package/agents/sales-call-coach.md +67 -1
  81. package/agents/sales-closer.md +67 -1
  82. package/agents/security-auditor.md +343 -48
  83. package/agents/sell-closer.md +67 -1
  84. package/agents/share-amplifier.md +67 -1
  85. package/agents/social-media-manager.md +67 -1
  86. package/agents/storyteller.md +301 -0
  87. package/agents/strategist.md +67 -1
  88. package/agents/strategy-aligner.md +67 -1
  89. package/agents/support-agent.md +67 -1
  90. package/agents/tagger.md +327 -0
  91. package/agents/tech-writer.md +195 -22
  92. package/agents/test-engineer.md +398 -29
  93. package/agents/tiktok-ads.md +67 -1
  94. package/agents/tracking-engineer.md +67 -1
  95. package/agents/trailkeeper.md +181 -0
  96. package/agents/upsell-strategist.md +67 -1
  97. package/agents/voice.md +67 -1
  98. package/agents/w1-recon.md +1 -1
  99. package/agents/w2-decide.md +1 -1
  100. package/agents/w3-edit.md +8 -2
  101. package/agents/w4-verify.md +13 -0
  102. package/agents/workflow-optimiser.md +81 -1
  103. package/commands/close.md +916 -160
  104. package/commands/deploy.md +102 -724
  105. package/commands/do.md +58 -2
  106. package/commands/sweep.md +159 -0
  107. package/commands/tasks.md +222 -0
  108. package/hooks/scripts/dev-only.sh +135 -0
  109. package/hooks/scripts/git-add-guard.sh +37 -2
  110. package/hooks/scripts/session-start.sh +32 -4
  111. package/package.json +1 -1
  112. package/rules/scripts.md +85 -0
  113. package/scripts/CLAUDE.md +315 -0
  114. package/scripts/ad-copy-lint.sh +656 -0
  115. package/scripts/agent-actor-parity.sh +129 -0
  116. package/scripts/blocks-manifest-cached.sh +100 -0
  117. package/scripts/chat-context-check.sh +89 -0
  118. package/scripts/chrome.mjs +18 -0
  119. package/scripts/close-metrics.sh +587 -0
  120. package/scripts/close-owner.sh +326 -0
  121. package/scripts/db-sync-lock-check.sh +116 -0
  122. package/scripts/deploy-emit.sh +311 -0
  123. package/scripts/deploy-gate-check.sh +155 -0
  124. package/scripts/deploy-ready.sh +78 -0
  125. package/scripts/deploy-record.sh +605 -0
  126. package/scripts/deploy-schema-check.sh +58 -0
  127. package/scripts/deploy.sh +393 -243
  128. package/scripts/do-auto.sh +127 -26
  129. package/scripts/do-board.sh +429 -0
  130. package/scripts/do-close.sh +1184 -0
  131. package/scripts/do-consumer-sweep.sh +18 -1
  132. package/scripts/do-decide.sh +476 -0
  133. package/scripts/do-fleet.sh +8 -2
  134. package/scripts/do-plan-json.mjs +110 -12
  135. package/scripts/do-prove-selftest.sh +108 -0
  136. package/scripts/do-prove.sh +86 -10
  137. package/scripts/do-rank.py +200 -3
  138. package/scripts/do-reconcile.sh +73 -12
  139. package/scripts/do-signal.sh +101 -23
  140. package/scripts/do-smoke.sh +18 -1
  141. package/scripts/do-w4-gates.sh +11 -1
  142. package/scripts/do-world-check.sh +153 -0
  143. package/scripts/download-stats.sh +172 -0
  144. package/scripts/factory-brief-check.sh +330 -0
  145. package/scripts/factory-check.sh +18 -1
  146. package/scripts/factory-close-check.sh +257 -0
  147. package/scripts/factory-emit.sh +211 -0
  148. package/scripts/factory-executor-check.mjs +353 -0
  149. package/scripts/factory-peak.sh +301 -0
  150. package/scripts/factory-repo.sh +71 -0
  151. package/scripts/factory-review-check.mjs +61 -0
  152. package/scripts/factory-tasks-check.sh +18 -1
  153. package/scripts/fixtures/factory-brief-real.md +44 -0
  154. package/scripts/flywheel-outcome.sh +63 -0
  155. package/scripts/gate-reaper-check.sh +98 -0
  156. package/scripts/gate-reaper.sh +9 -0
  157. package/scripts/gate-watchdog.sh +619 -0
  158. package/scripts/gc-content-check.sh +142 -0
  159. package/scripts/gh-traffic-capture.sh +153 -0
  160. package/scripts/govern-order-check.sh +202 -0
  161. package/scripts/governor-doors-check.sh +86 -5
  162. package/scripts/health.sh +448 -0
  163. package/scripts/id-inventory.mjs +418 -0
  164. package/scripts/incident.sh +212 -0
  165. package/scripts/land.sh +755 -45
  166. package/scripts/lib/gc-finished.sh +77 -0
  167. package/scripts/livekit-ratchet.sh +18 -1
  168. package/scripts/machine-check.sh +1 -1
  169. package/scripts/memory-index-budget.sh +79 -0
  170. package/scripts/npm-downloads.sh +109 -0
  171. package/scripts/one-agents.mjs +204 -8
  172. package/scripts/one-resume.sh +31 -3
  173. package/scripts/pr-body.sh +335 -0
  174. package/scripts/preview-fd-check.sh +289 -0
  175. package/scripts/redirect-lint.sh +169 -0
  176. package/scripts/release.sh +40 -6
  177. package/scripts/resume-lost-sessions.sh +68 -0
  178. package/scripts/shoot-pages.mjs +140 -0
  179. package/scripts/signal-meta-backfill.ts +451 -0
  180. package/scripts/signal-watch.sh +63 -6
  181. package/scripts/speed-cache-check.sh +12 -2
  182. package/scripts/sweep.sh +426 -0
  183. package/scripts/task-titles-dump.ts +101 -0
  184. package/scripts/test-cached.sh +47 -10
  185. package/scripts/test-lanes.sh +14 -0
  186. package/scripts/thread-name-backfill.ts +215 -0
  187. package/scripts/triage-shape-check.sh +149 -0
  188. package/scripts/tsc-cached.sh +155 -8
  189. package/scripts/typedb-flake-check.sh +3 -1
  190. package/scripts/urls-lint.sh +8 -0
  191. package/scripts/verify-board-doors.sh +80 -0
  192. package/scripts/verify-fast.sh +159 -6
  193. package/scripts/worktree-up.sh +21 -3
  194. package/skills/astro/SKILL.md +9 -3
  195. package/skills/astro/optimize-performance.md +3 -2
  196. package/skills/cloudflare/SKILL.md +3 -2
  197. package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
  198. package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
  199. package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
  200. package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  201. package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  202. package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  203. package/skills/cloudflare-security-audit/HUNTING.md +251 -0
  204. package/skills/cloudflare-security-audit/LICENSE +21 -0
  205. package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  206. package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  207. package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
  208. package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
  209. package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  210. package/skills/cloudflare-security-audit/SKILL.md +192 -0
  211. package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  212. package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
  213. package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  214. package/skills/cloudflare-security-audit/report-schema.json +461 -0
  215. package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
  216. package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
  217. package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
  218. package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
  219. package/skills/deploy/REFERENCE.md +713 -0
  220. package/skills/deploy/SKILL.md +140 -0
  221. package/skills/fleet-audit/SKILL.md +58 -0
  222. package/skills/meeting/SKILL.md +220 -0
  223. package/skills/planning/SKILL.md +256 -0
  224. package/skills/shadcn/SKILL.md +1 -1
  225. package/skills/typedb/SKILL.md +7 -0
  226. package/skills/voice/SKILL.md +94 -6
  227. package/skills/voice/corpus-check.sh +87 -0
  228. package/templates/template-agent.md +7 -1
  229. package/templates/template-feature.md +9 -0
  230. package/templates/template-todo.md +29 -0
@@ -51,22 +51,50 @@ done < <(find "$PROJ" -path '*/subagents/workflows/wf_*/journal.jsonl' -mtime -3
51
51
 
52
52
  echo
53
53
  echo "${DIM}══ branches ══${OFF}"
54
+ # WHY the yardstick is `dev` and not `main`:
55
+ # land.sh merges a branch into TRUNK, and TRUNK is `dev` (land.sh:117 —
56
+ # "TRUNK is what a branch LANDS INTO; PR_BASE is what dev is RELEASED into").
57
+ # Measuring "ready to land" against main therefore counts every commit that
58
+ # is ALREADY INTEGRATED on dev and merely waiting on the dev->main PR, and
59
+ # reports it as unlanded work. Measured 2026-09-15: feat/dashboard,
60
+ # feat/fix-og-screenshot-fullpage and feat/tasks-bulk each read
61
+ # "38 / 20 / 11 commit(s) ready to land" while all three were 0 ahead of
62
+ # origin/dev and already merged — and `--deploy` would have re-run land.sh
63
+ # on all three. This is the same stale-yardstick class as do-auto.sh --gc,
64
+ # but the opposite direction: --gc measures against main and KEEPS too much
65
+ # (safe); this INVENTED work (unsafe), and re-launching landed work is the
66
+ # most expensive mistake the board offers.
67
+ # Remote refs first: a local `main`/`dev` can sit hundreds of commits behind
68
+ # origin while `git status` reports the tree clean.
69
+ _ref() { git rev-parse --verify -q "origin/$1" >/dev/null 2>&1 && echo "origin/$1" || echo "$1"; }
70
+ TRUNK_REF="$(_ref "${LAND_TRUNK:-dev}")" # what a branch lands INTO
71
+ BASE_REF="$(_ref main)" # what dev is RELEASED into
54
72
  READY=()
55
73
  while IFS= read -r br; do
56
74
  [[ -z "$br" ]] && continue
57
- ahead="$(git rev-list --count "main..$br" 2>/dev/null || echo 0)"
75
+ ahead="$(git rev-list --count "$TRUNK_REF..$br" 2>/dev/null || echo 0)"
58
76
  if [[ "$ahead" == "0" ]]; then
59
- printf ' %-34s %sno commits beyond main%s\n' "$br" "$DIM" "$OFF"
77
+ printf ' %-34s %sno commits beyond %s%s\n' "$br" "$DIM" "$TRUNK_REF" "$OFF"
60
78
  else
61
- printf ' %-34s %s%s commit(s) ready to land%s\n' "$br" "$GRN" "$ahead" "$OFF"
79
+ printf ' %-34s %s%s commit(s) ready to land%s %s(-> %s)%s\n' \
80
+ "$br" "$GRN" "$ahead" "$OFF" "$DIM" "$TRUNK_REF" "$OFF"
62
81
  READY+=("$br")
63
82
  fi
64
83
  done < <(git for-each-ref --format='%(refname:short)' --sort=-committerdate refs/heads/feat 2>/dev/null | head -20)
65
84
 
85
+ # The other half of the loop: what is integrated but not yet released.
86
+ rel="$(git rev-list --count "$BASE_REF..$TRUNK_REF" 2>/dev/null || echo 0)"
87
+ if [[ "$rel" != "0" ]]; then
88
+ printf '\n %-34s %s%s commit(s) ready to release%s %s(%s -> %s, by PR)%s\n' \
89
+ "${LAND_TRUNK:-dev}" "$YEL" "$rel" "$OFF" "$DIM" "$TRUNK_REF" "$BASE_REF" "$OFF"
90
+ fi
91
+
66
92
  echo
67
93
  echo "${DIM}══ next ══${OFF}"
68
94
  if (( ${#READY[@]} )); then
69
95
  echo " bash .claude/scripts/land.sh ${READY[*]} --deploy"
96
+ elif [[ "$rel" != "0" ]]; then
97
+ echo " gh pr create --base main --head ${LAND_TRUNK:-dev} # $rel commit(s) integrated, not released"
70
98
  else
71
99
  echo " nothing to land yet — resume a fleet above, or ./deploy dev"
72
100
  fi
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env bash
2
+ # pr-body.sh — the pull request a finished branch deserves, derived, never written.
3
+ #
4
+ # bash .claude/scripts/pr-body.sh feat/editor-panel-tabs
5
+ # bash .claude/scripts/pr-body.sh do/my-plan --base main --gate "fast lane (green)"
6
+ # bash .claude/scripts/pr-body.sh feat/x --dev "https://dev.one.ie — 2 routes proven"
7
+ # bash .claude/scripts/pr-body.sh feat/x --title # just the title line
8
+ # bash .claude/scripts/pr-body.sh --self-test # the red proofs
9
+ #
10
+ # WHY THIS EXISTS: a /do branch arrives at the merge door carrying no explanation.
11
+ # Context isolation throws away each cycle's reasoning to save 90-150k tokens, so
12
+ # the prose that would have justified the diff is gone by the time a human reads
13
+ # it. `do-auto.sh`'s `_merge_digest` solved that for the LOCAL reader by writing
14
+ # `.do-digest.md` at trunk root. A pull request is the same document with an
15
+ # audience: same facts, same sources, addressed to a reviewer instead of to the
16
+ # operator standing in the tree.
17
+ #
18
+ # EVERY FIELD IS DERIVED FROM GIT AT CALL TIME. It deliberately does NOT read
19
+ # `.do-digest.md`, even when one is sitting there: that file lives at trunk root,
20
+ # is uncommitted, is written per-slug and is overwritten by the next cycle, so a
21
+ # body that were "the digest when present, git otherwise" would be right
22
+ # sometimes and stale sometimes with nothing on its face to say which. Deriving
23
+ # every time is slower by milliseconds and always true.
24
+ #
25
+ # THE DIFF IS `merge-base..BR`, NEVER `BASE..BR`. Two-dot from the base conflates
26
+ # what this branch added with what trunk advanced while it was building — the
27
+ # latter reads as spurious reversions and inflates every count in the body. Same
28
+ # rule `_merge_digest` follows, and the same reason.
29
+ #
30
+ # A MEASUREMENT THAT IS NOT THERE IS NOT RENDERED. The trust score, the open
31
+ # improvements and the cycle count come from gitignored artifacts that exist only
32
+ # beside a live worktree; the plan title comes from a file that only a /do branch
33
+ # has. When one is absent its line is omitted — never printed as 0, "?" or
34
+ # "unknown", which read as measured facts.
35
+ #
36
+ # Exit codes: 0 emitted · 2 bad usage · 3 no such branch · 4 self-test failed.
37
+ set -uo pipefail
38
+ # The repo this reads. Overridable ONLY through a dedicated name so the
39
+ # self-test can point it at a scratch repo; a generic `ROOT` would inherit
40
+ # whatever a calling script happened to export and read the wrong tree.
41
+ ROOT="${PR_BODY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
42
+
43
+ BR="" BASE="main" GATE="" DEV="" TITLE_ONLY=0 SELF_TEST=0
44
+ while [ $# -gt 0 ]; do
45
+ case "$1" in
46
+ --base) BASE="${2:-main}"; shift 2 ;;
47
+ --gate) GATE="${2:-}"; shift 2 ;;
48
+ --dev) DEV="${2:-}"; shift 2 ;;
49
+ --title) TITLE_ONLY=1; shift ;;
50
+ --self-test) SELF_TEST=1; shift ;;
51
+ -h|--help) sed -n '2,10p' "${BASH_SOURCE[0]}"; exit 0 ;;
52
+ -*) echo "pr-body.sh: unknown flag '$1'" >&2; exit 2 ;;
53
+ *) BR="$1"; shift ;;
54
+ esac
55
+ done
56
+
57
+ # --- the plan behind a branch ------------------------------------------------
58
+ # `feat/editor-panel-tabs` and `do/editor-panel-tabs` are both the slug
59
+ # `editor-panel-tabs`; a bare branch name is its own slug.
60
+ slug_of() { printf '%s' "${1#*/}"; }
61
+
62
+ # The worktree holding a branch, if any. The gitignored build artifacts
63
+ # (.do-trust.json, .w4-improvements.json) live there and nowhere else, so a
64
+ # branch with no worktree simply reports fewer facts.
65
+ worktree_of() {
66
+ git worktree list --porcelain \
67
+ | awk -v b="refs/heads/$1" '/^worktree /{w=$2} /^branch /{if ($2==b) print w}' \
68
+ | head -1
69
+ }
70
+
71
+ # THE PLAN IS READ FROM THE BRANCH, NOT FROM THE TREE THIS RUNS IN.
72
+ #
73
+ # A /do branch commits its own `text/<slug>-todo.md`, and that copy is the one
74
+ # whose cycles this branch actually closed. The checked-out tree may not have the
75
+ # file at all (a plan authored entirely on the branch) or may have a STALE copy —
76
+ # `do-auto.sh` blind-syncs `<slug>-plan.md` from main every iteration, so a tree
77
+ # copy is not evidence about a branch. Caught by the throwaway proof: reading
78
+ # $ROOT silently dropped the title, the cycle counts and the kill-switch, and the
79
+ # body still looked complete because every one of those lines is optional.
80
+ #
81
+ # Falls back to the working tree so a branch with no committed plan (a hand-made
82
+ # `feat/*`) can still pick up a plan that exists only locally.
83
+ todo_for() {
84
+ local br="$1" slug="$2" out
85
+ out="$(mktemp)"
86
+ if git -C "$ROOT" show "${br}:text/${slug}-todo.md" > "$out" 2>/dev/null && [ -s "$out" ]; then
87
+ printf '%s' "$out"; return 0
88
+ fi
89
+ rm -f "$out"
90
+ [ -f "$ROOT/text/${slug}-todo.md" ] && { printf '%s' "$ROOT/text/${slug}-todo.md"; return 0; }
91
+ return 1
92
+ }
93
+
94
+ # `title: Some Words` out of a todo's frontmatter. Frontmatter only — a `title:`
95
+ # in the body of a 900-line plan is somebody's YAML example, not the plan's name.
96
+ plan_title() {
97
+ local f="$1"
98
+ [ -f "$f" ] || return 1
99
+ awk '
100
+ NR==1 && $0=="---" { inside=1; next }
101
+ inside && $0=="---" { exit }
102
+ inside && /^title:[[:space:]]/ { sub(/^title:[[:space:]]*/, ""); print; exit }
103
+ ' "$f"
104
+ }
105
+
106
+ emit() {
107
+ local br="$1" base="$2"
108
+ local mb behind shortstat slug todo wt title
109
+ slug="$(slug_of "$br")"
110
+ todo="$(todo_for "$br" "$slug" || true)"
111
+ wt="$(worktree_of "$br")"
112
+
113
+ mb="$(git -C "$ROOT" merge-base "$base" "$br" 2>/dev/null || printf '%s' "$base")"
114
+ behind="$(git -C "$ROOT" rev-list --count "$mb..$base" 2>/dev/null || echo 0)"
115
+ shortstat="$(git -C "$ROOT" diff --shortstat "$mb..$br" 2>/dev/null || true)"
116
+
117
+ title="$(plan_title "$todo" 2>/dev/null || true)"
118
+ # No plan, or a plan with no title: the branch's own first commit says what it
119
+ # set out to do, which is the next most honest sentence available.
120
+ [ -n "$title" ] || title="$(git -C "$ROOT" log --format=%s "$mb..$br" 2>/dev/null | tail -1)"
121
+ [ -n "$title" ] || title="$br"
122
+
123
+ if [ "$TITLE_ONLY" = "1" ]; then printf '%s\n' "$title"; return 0; fi
124
+
125
+ echo "## What this is"
126
+ echo
127
+ echo "Branch \`${br}\` → \`${base}\`. Generated by \`.claude/scripts/pr-body.sh\` from git —"
128
+ echo "every number below is derived at PR time, not written by hand or by a model."
129
+ echo
130
+ [ -n "$todo" ] && [ -f "$todo" ] && echo "Plan: \`text/${slug}-todo.md\`"
131
+ [ -n "$todo" ] && [ -f "$todo" ] && echo
132
+
133
+ # --- the counts ------------------------------------------------------------
134
+ echo "## Receipts"
135
+ echo
136
+ if [ -n "$todo" ] && [ -f "$todo" ]; then
137
+ local closed total
138
+ closed="$(grep -cE '\[[xX]\] \*{0,2}C[0-9]+' "$todo" 2>/dev/null || true)"
139
+ total="$(grep -cE '\[[ xX]\] \*{0,2}C[0-9]+' "$todo" 2>/dev/null || true)"
140
+ [ "${closed:-0}" != "0" ] || [ "${total:-0}" != "0" ] \
141
+ && echo "- **Cycles closed:** ${closed:-0} of ${total:-0}"
142
+ fi
143
+ echo "- **This branch's changes (merge-base..${br}):** ${shortstat:-none}"
144
+ [ "${behind:-0}" -gt 0 ] \
145
+ && echo "- **Trunk advanced during build:** ${behind} commit(s) — the diff above excludes them"
146
+
147
+ # Mergeability WITHOUT mutating anything. `--write-tree` computes the merge in
148
+ # the object store and says whether it conflicts; it does not touch a worktree,
149
+ # an index or a ref, which is why this is safe to run against a shared trunk.
150
+ if git -C "$ROOT" merge-tree --write-tree "$base" "$br" >/dev/null 2>&1; then
151
+ echo "- **Merges cleanly into \`${base}\`:** yes"
152
+ else
153
+ echo "- **Merges cleanly into \`${base}\`:** NO — conflicts, resolve before merging"
154
+ fi
155
+
156
+ # Gate result is PASSED IN by the caller that actually ran one. This script
157
+ # runs no gate, so it must never imply it did: no --gate, no line.
158
+ [ -n "$GATE" ] && echo "- **Gate:** ${GATE}"
159
+
160
+ # Same rule, one door further along: whether this branch was ever RUN, and
161
+ # where. `--dev` is passed only by a caller that actually shipped the branch
162
+ # to dev.one.ie and probed it. No flag, no line — a PR that says nothing
163
+ # about dev is a PR whose branch was never run, and that is the honest read.
164
+ [ -n "$DEV" ] && echo "- **Ran on dev:** ${DEV}"
165
+
166
+ if [ -n "$wt" ] && [ -f "$wt/.do-trust.json" ]; then
167
+ local trust composite
168
+ trust="$(jq -r '.level // empty' "$wt/.do-trust.json" 2>/dev/null || true)"
169
+ composite="$(jq -r '.composite // empty' "$wt/.do-trust.json" 2>/dev/null || true)"
170
+ [ -n "$trust$composite" ] \
171
+ && echo "- **Rubric:** trust=${trust:-—} · composite=${composite:-—}"
172
+ fi
173
+ if [ -n "$wt" ] && [ -f "$wt/.w4-improvements.json" ]; then
174
+ local open
175
+ open="$(jq -r 'if type=="array" then length elif type=="object" then ([.[]?]|length) else 0 end' \
176
+ "$wt/.w4-improvements.json" 2>/dev/null || true)"
177
+ [ -n "$open" ] && echo "- **Open improvements carried:** ${open}"
178
+ fi
179
+
180
+ # The kill-switch, quoted so a reviewer can run the same command the plan
181
+ # gates on rather than taking "it works" on trust.
182
+ if [ -n "$todo" ] && [ -f "$todo" ]; then
183
+ local outcome
184
+ outcome="$(awk '
185
+ NR==1 && $0=="---" { inside=1; next }
186
+ inside && $0=="---" { exit }
187
+ inside && /^outcome:[[:space:]]/ { sub(/^outcome:[[:space:]]*/, ""); print; exit }
188
+ ' "$todo" 2>/dev/null | sed 's/^"//; s/"$//')"
189
+ if [ -n "$outcome" ]; then
190
+ echo
191
+ echo "**Outcome check** — the plan's own kill-switch, exits 0 when the goal is met:"
192
+ echo
193
+ echo '```bash'
194
+ echo "$outcome"
195
+ echo '```'
196
+ fi
197
+ fi
198
+
199
+ echo
200
+ echo "## Files changed (this branch only)"
201
+ echo
202
+ echo '```'
203
+ git -C "$ROOT" diff --stat "$mb..$br" 2>/dev/null || true
204
+ echo '```'
205
+ echo
206
+ echo "## Commits"
207
+ echo
208
+ echo '```'
209
+ git -C "$ROOT" log --oneline "$mb..$br" 2>/dev/null || true
210
+ echo '```'
211
+ }
212
+
213
+ # --- self-test ---------------------------------------------------------------
214
+ # The point is that each check can go RED. A body generator that emits a
215
+ # plausible paragraph no matter what it was handed proves nothing, and every
216
+ # claim below is one this script got wrong at least once while being written.
217
+ self_test() {
218
+ local tmp fails=0
219
+ tmp="$(mktemp -d)"
220
+ trap 'rm -rf "$tmp"' RETURN
221
+ t_ok() { printf ' ✓ %s\n' "$1"; }
222
+ t_bad() { printf ' ✗ %s\n' "$1"; fails=$((fails+1)); }
223
+
224
+ # A scratch repo, so the checks assert on a history they built themselves
225
+ # rather than on whatever this repo happens to look like today.
226
+ git init -q "$tmp/r" 2>/dev/null
227
+ (
228
+ cd "$tmp/r" || exit 1
229
+ git config user.email t@t; git config user.name t; git config commit.gpgsign false
230
+ echo base > a.txt; git add a.txt; git commit -qm "base commit"
231
+ git checkout -q -b feat/scratch
232
+ echo branch > b.txt; git add b.txt; git commit -qm "the branch's own first commit"
233
+ git checkout -q master 2>/dev/null || git checkout -q main
234
+ echo trunk >> a.txt; git add a.txt; git commit -qm "trunk moved on"
235
+ )
236
+ local basebr; basebr="$(cd "$tmp/r" && git rev-parse --abbrev-ref HEAD)"
237
+ local out
238
+ out="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" feat/scratch --base "$basebr" 2>/dev/null)"
239
+
240
+ # 1. The diff is merge-base scoped: the branch added b.txt and NOTHING else.
241
+ # A two-dot BASE..BR diff would also show a.txt as reverted.
242
+ grep -q 'b.txt' <<<"$out" && ! grep -q 'a.txt' <<<"$out" \
243
+ && t_ok "diff is merge-base scoped (trunk's own commit excluded)" \
244
+ || t_bad "diff leaked trunk's changes — merge-base scoping is broken"
245
+
246
+ # 2. Trunk drift is reported, not silently folded into the diff.
247
+ grep -q 'Trunk advanced during build:\*\* 1 commit' <<<"$out" \
248
+ && t_ok "trunk drift counted" || t_bad "trunk drift not reported"
249
+
250
+ # 3. With no plan file and no worktree, the absent measurements are ABSENT —
251
+ # not rendered as 0 or "?". This is the claim that stops a body from
252
+ # reporting "Cycles closed: 0" about a branch that never had cycles.
253
+ ! grep -qE 'Cycles closed|Rubric:|Open improvements' <<<"$out" \
254
+ && t_ok "absent measurements are omitted, not zeroed" \
255
+ || t_bad "rendered a measurement it never took"
256
+
257
+ # 4. No --gate ⇒ no gate line. This script runs no gate and must not imply it.
258
+ ! grep -q 'Gate:' <<<"$out" && t_ok "claims no gate it did not run" || t_bad "invented a gate result"
259
+
260
+ # Same for --dev. A body that implies the branch was run on dev when nobody
261
+ # shipped it is the same dishonesty as an invented gate, one door later.
262
+ ! grep -q 'Ran on dev:' <<<"$out" \
263
+ && t_ok "claims no dev run it did not do" || t_bad "invented a dev run"
264
+ outd="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" feat/scratch --base "$basebr" --dev "https://dev.one.ie — 1 route proven" 2>/dev/null)"
265
+ grep -q 'Ran on dev:\*\* https://dev.one.ie' <<<"$outd" \
266
+ && t_ok "reports the dev run it was handed" || t_bad "dropped the caller's dev result"
267
+ local outg
268
+ outg="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" feat/scratch --base "$basebr" --gate "fast lane (green)" 2>/dev/null)"
269
+ grep -q 'Gate:\*\* fast lane (green)' <<<"$outg" \
270
+ && t_ok "reports the gate it was handed" || t_bad "dropped the caller's gate result"
271
+
272
+ # 5. Title falls back to the branch's FIRST commit, not its last.
273
+ local tl
274
+ tl="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" feat/scratch --base "$basebr" --title 2>/dev/null)"
275
+ [ "$tl" = "the branch's own first commit" ] \
276
+ && t_ok "title falls back to the branch's first commit" \
277
+ || t_bad "title fallback wrong: got '$tl'"
278
+
279
+ # 6. A conflicting branch is reported as conflicting. Without this the
280
+ # mergeability line is a constant that always says yes.
281
+ (
282
+ cd "$tmp/r" || exit 1
283
+ git checkout -q -b feat/conflict "$basebr"
284
+ printf 'totally different\n' > a.txt; git add a.txt; git commit -qm "rewrite a.txt"
285
+ git checkout -q "$basebr"
286
+ printf 'trunk rewrote it too\n' > a.txt; git add a.txt; git commit -qm "trunk rewrote a.txt"
287
+ )
288
+ local outc
289
+ outc="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" feat/conflict --base "$basebr" 2>/dev/null)"
290
+ grep -q 'Merges cleanly into .*:\*\* NO' <<<"$outc" \
291
+ && t_ok "a conflicting branch reads NO" || t_bad "conflict not detected — the line is a constant"
292
+ grep -q 'Merges cleanly into .*:\*\* yes' <<<"$out" \
293
+ && t_ok "a clean branch reads yes" || t_bad "clean branch misreported as conflicting"
294
+
295
+ # 7. The plan is read from the BRANCH, not from the checked-out tree. This is
296
+ # the bug the throwaway PR proof caught: reading $ROOT silently dropped the
297
+ # title, the cycle counts and the kill-switch, and the body still looked
298
+ # complete, because every one of those lines is optional. Non-vacuous by
299
+ # construction — the plan exists ONLY on the branch, never in the tree.
300
+ (
301
+ cd "$tmp/r" || exit 1
302
+ git switch -q -c do/planned "$basebr"
303
+ mkdir -p text
304
+ printf -- '---\ntitle: The Plan Name\nslug: planned\noutcome: "true"\n---\n\n- [x] C1 one\n- [ ] C2 two\n' > text/planned-todo.md
305
+ git add text/planned-todo.md; git commit -qm "add the plan"
306
+ git switch -q "$basebr"
307
+ )
308
+ local outp tlp
309
+ [ ! -f "$tmp/r/text/planned-todo.md" ] \
310
+ && t_ok "the plan is absent from the working tree (check is non-vacuous)" \
311
+ || t_bad "self-test setup wrong — plan is in the tree, so this proves nothing"
312
+ outp="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" do/planned --base "$basebr" 2>/dev/null)"
313
+ grep -q 'Cycles closed:\*\* 1 of 2' <<<"$outp" \
314
+ && t_ok "cycle counts read from the branch's own plan" \
315
+ || t_bad "cycle counts not read from the branch"
316
+ grep -q 'kill-switch' <<<"$outp" \
317
+ && t_ok "kill-switch read from the branch's own plan" \
318
+ || t_bad "kill-switch not read from the branch"
319
+ tlp="$(PR_BODY_ROOT="$tmp/r" bash "${BASH_SOURCE[0]}" do/planned --base "$basebr" --title 2>/dev/null)"
320
+ [ "$tlp" = "The Plan Name" ] \
321
+ && t_ok "title read from the branch's own plan" \
322
+ || t_bad "title not read from the branch: got '$tlp'"
323
+
324
+ printf '\n'
325
+ [ "$fails" -eq 0 ] && { echo "pr-body.sh: all checks pass"; return 0; }
326
+ echo "pr-body.sh: ${fails} check(s) FAILED"; return 1
327
+ }
328
+
329
+ if [ "$SELF_TEST" = "1" ]; then self_test || exit 4; exit 0; fi
330
+
331
+ [ -n "$BR" ] || { echo "pr-body.sh: name a branch" >&2; exit 2; }
332
+ git -C "$ROOT" show-ref --verify --quiet "refs/heads/$BR" \
333
+ || { echo "pr-body.sh: no such branch '$BR'" >&2; exit 3; }
334
+
335
+ emit "$BR" "$BASE"