@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
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bash
2
+ # do-world-check.sh — assert every CODE surface a promise's `world.code:` block
3
+ # declares actually EXISTS on disk. Zero LLM; the path's presence is the verdict.
4
+ #
5
+ # WHY: a tag is an address, and the family named after it is what `world:`
6
+ # manifests. `/do` already presence-checks the block's agents, skills, workflow
7
+ # steps and views — but nothing checked for a `.ts`, `.astro` or `.tql` at the
8
+ # tag's name, so a promise could name a code surface it never built and settle
9
+ # green. `world.code:` declares those paths; this gate answers missing/present
10
+ # with the same rule already applied to agents: missing → a cycle · present → skip.
11
+ #
12
+ # SCOPE — `world.code.*` ONLY, and the line is deliberate. The other four kinds
13
+ # (agents · skills · workflow/lifecycle · views) stay the PROSE walk an agent runs
14
+ # at DESIGN/PLAN (.claude/commands/do.md § world, .claude/agents/w1-recon.md);
15
+ # they are named entities, not one deterministic path each. Every key under
16
+ # `code:` is a repo-root-relative path, so every key under `code:` is checkable.
17
+ # Not wired into do-w4-gates.sh or do-promise-settle.sh yet — that is the follow-on.
18
+ #
19
+ # The --self-test fixtures name paths under fixtures/ rather than schema/ only
20
+ # because factory-repo.sh:44 (MONOREPO_PATH_RE) refuses a `portable` script that
21
+ # names a monorepo path. The RED shape is identical either way; the demo C6 asks
22
+ # for — `code.tql` naming an absent schema/<tag>.tql — is recorded verbatim in
23
+ # text/deploy-todo.md § C6.
24
+ #
25
+ # Usage:
26
+ # do-world-check.sh <slug> # text/<slug>.md, or $PROMISE_FILE
27
+ # do-world-check.sh --self-test # temp fixtures, zero network
28
+ # exit: 0 all present (or no code: block) · 1 a surface missing · 2 no promise file · 4 bad usage
29
+ set -u
30
+
31
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
32
+
33
+ # print "<key>\t<path>" for every path declared under world: → code:.
34
+ # Two levels of nesting, so the col-0 terminator alone is not enough: the walk
35
+ # tracks indentation. Both YAML list forms are read — flow (`tql: [a, b]`) and
36
+ # block (`tql:` then `- a`) — and a trailing ` # comment` is stripped.
37
+ code_entries() { # $1 = promise file
38
+ awk '
39
+ function indent(s, n) { n = match(s, /[^ ]/); return n ? n - 1 : -1 }
40
+ function clean(v) {
41
+ sub(/[ \t]+#.*$/, "", v) # trailing comment
42
+ gsub(/^[ \t]+|[ \t]+$/, "", v)
43
+ gsub(/^["'"'"']|["'"'"']$/, "", v) # one layer of quoting
44
+ return v
45
+ }
46
+ function emit(k, v) { v = clean(v); if (v != "") printf "%s\t%s\n", k, v }
47
+ function emit_flow(k, v, n, i, parts) {
48
+ sub(/^\[/, "", v); sub(/\].*$/, "", v)
49
+ n = split(v, parts, ",")
50
+ for (i = 1; i <= n; i++) emit(k, parts[i])
51
+ }
52
+ /^---[ \t]*$/ { fm++; if (fm >= 2) exit; next }
53
+ fm != 1 { next }
54
+ /^world:[ \t]*$/ { inw = 1; next }
55
+ inw && /^[^ \t#]/ { inw = 0; inc = 0; key = "" } # a col-0 key ends world:
56
+ !inw { next }
57
+ /^[ \t]*$/ { next }
58
+ /^[ \t]*#/ { next }
59
+ {
60
+ i = indent($0)
61
+ if (inc && i <= 2) { inc = 0; key = "" } # a sibling of code: ends it
62
+ if (!inc) { if ($0 ~ /^[ \t]*code:[ \t]*(#.*)?$/ && i == 2) { inc = 1; key = "" } ; next }
63
+ if (i < 4) next
64
+ if ($0 ~ /^[ \t]*-[ \t]/) { # block list item
65
+ if (key == "") next
66
+ line = $0; sub(/^[ \t]*-[ \t]*/, "", line); emit(key, line); next
67
+ }
68
+ if (match($0, /^[ \t]*[A-Za-z][A-Za-z0-9_-]*:/)) { # a code: sub-key
69
+ key = $0; sub(/^[ \t]*/, "", key); sub(/:.*$/, "", key)
70
+ rest = $0; sub(/^[ \t]*[A-Za-z][A-Za-z0-9_-]*:[ \t]*/, "", rest)
71
+ if (rest ~ /^\[/) { emit_flow(key, rest); key = "" }
72
+ else emit(key, rest)
73
+ }
74
+ }
75
+ ' "$1"
76
+ }
77
+
78
+ check() { # $1 = slug
79
+ local slug="$1"
80
+ local file="${PROMISE_FILE:-$ROOT/text/$slug.md}" missing=0 checked=0 line key path
81
+ if [ ! -f "$file" ]; then
82
+ echo "[world-check] $slug — no promise file at text/$slug.md"; return 2
83
+ fi
84
+ while IFS="$(printf '\t')" read -r key path; do
85
+ [ -z "${path:-}" ] && continue
86
+ checked=$((checked+1))
87
+ if [ -e "$ROOT/$path" ]; then
88
+ echo "[world-check] ok: world.code.$key → $path"
89
+ else
90
+ echo "[world-check] MISSING: world.code.$key declares $path — no such path"
91
+ missing=$((missing+1))
92
+ fi
93
+ done <<EOF
94
+ $(code_entries "$file")
95
+ EOF
96
+ if [ "$checked" -eq 0 ]; then
97
+ echo "[world-check] $slug — no world.code: surfaces declared (nothing to check)"; return 0
98
+ fi
99
+ if [ "$missing" -gt 0 ]; then
100
+ echo "[world-check] $slug — $missing/$checked declared code surface(s) missing"; return 1
101
+ fi
102
+ echo "[world-check] $slug — ok ($checked declared code surface(s) present)"; return 0
103
+ }
104
+
105
+ if [ "${1:-}" = "--self-test" ]; then
106
+ dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
107
+ mkdir -p "$dir/text" "$dir/fixtures"
108
+ fails=0
109
+ : >"$dir/fixtures/green.tql"; : >"$dir/fixtures/green.astro"
110
+ # green: flow list + block list, every declared path on disk
111
+ cat >"$dir/text/green.md" <<'YAML'
112
+ ---
113
+ slug: green
114
+ world:
115
+ agents:
116
+ - { name: doctor, subscribes: [health] }
117
+ code: # a trailing comment on the key must not hide the block
118
+ tql: [fixtures/green.tql]
119
+ astro:
120
+ - fixtures/green.astro # a trailing comment must not break the path
121
+ tracking:
122
+ marks: ["a nested sibling at indent 4 must not read as a code surface"]
123
+ views: []
124
+ ---
125
+ YAML
126
+ # red: a declared .tql that was never written
127
+ cat >"$dir/text/red.md" <<'YAML'
128
+ ---
129
+ slug: red
130
+ world:
131
+ code:
132
+ tql: [fixtures/nonexistent-zzz.tql]
133
+ ---
134
+ YAML
135
+ # bare: a world: block with no code: — passes, checks nothing
136
+ printf -- '---\nslug: bare\nworld:\n agents: []\n---\n' >"$dir/text/bare.md"
137
+ out="$(ROOT="$dir" PROMISE_FILE= check green 2>&1)"; rc=$?
138
+ [ "$rc" -eq 0 ] || { echo "FAIL green should pass: $out"; fails=$((fails+1)); }
139
+ case "$out" in *"ok (2 declared"*) ;; *) echo "FAIL green must count 2 surfaces: $out"; fails=$((fails+1)) ;; esac
140
+ out="$(ROOT="$dir" PROMISE_FILE= check red 2>&1)"; rc=$?
141
+ [ "$rc" -eq 1 ] || { echo "FAIL red should fail: $out"; fails=$((fails+1)); }
142
+ case "$out" in *"MISSING: world.code.tql"*) ;; *) echo "FAIL red must name the path: $out"; fails=$((fails+1)) ;; esac
143
+ out="$(ROOT="$dir" PROMISE_FILE= check bare 2>&1)"; rc=$?
144
+ [ "$rc" -eq 0 ] || { echo "FAIL bare should pass: $out"; fails=$((fails+1)); }
145
+ case "$out" in *"nothing to check"*) ;; *) echo "FAIL bare must say nothing to check: $out"; fails=$((fails+1)) ;; esac
146
+ echo "[world-check] self-test: 3 fixtures, $fails failed"
147
+ exit "$fails"
148
+ fi
149
+
150
+ SLUG="${1:-}"
151
+ [ -z "$SLUG" ] && { echo "usage: do-world-check.sh <slug> | --self-test" >&2; exit 4; }
152
+ PROMISE_FILE="${PROMISE_FILE:-}"
153
+ check "$SLUG"
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env bash
2
+ # download-stats.sh — every lifetime reach number we can prove, in one table.
3
+ #
4
+ # bash .claude/scripts/download-stats.sh # print
5
+ # bash .claude/scripts/download-stats.sh --write # + record to text/download-stats.md
6
+ # bash .claude/scripts/download-stats.sh --json
7
+ # bash .claude/scripts/download-stats.sh --self-test
8
+ #
9
+ # manifest: needs-env — (npm + gh + network; writes only text/download-stats.md)
10
+ #
11
+ # FOLDS IN npm-downloads.sh, which this replaces. Both of that script's traps
12
+ # are live here and neither is obvious:
13
+ #
14
+ # 1. npm's downloads API REFUSES a range longer than 18 months — and answers
15
+ # that refusal with HTTP 200 and an `error` field. A naive all-time query
16
+ # therefore returns nothing useful without failing. All-time is a WALK: from
17
+ # each package's first publish (registry `time.created`), 17 months at a
18
+ # time, summed. Read `.error`, never the status code.
19
+ # 2. A scoped name must be percent-encoded — `@oneie%2Fsdk`. Unencoded, the
20
+ # slash reads as a path separator and the API 404s, which deserialises to a
21
+ # zero indistinguishable from a real one. Encoded once, in enc().
22
+ #
23
+ # And one about WHICH packages: the list comes from the REGISTRY (search by
24
+ # maintainer), not from manifests on disk and not from `npm access list
25
+ # packages` — that returns EMPTY under a granular token (measured 2026-09-14:
26
+ # "packages in scope: 0" while all 30 were readable). Reading manifests misses
27
+ # every package not in this repo, which was 10 of 30 — including `oneie` at
28
+ # 13,117 downloads, the single most-downloaded package we have.
29
+ #
30
+ # WHY A RECORDED .md: npm's per-day history is queryable back to a package's
31
+ # first publish, so npm needs no ledger. GitHub traffic is different — it keeps
32
+ # FOURTEEN DAYS and offers no backfill (see gh-traffic-capture.sh). The .md is
33
+ # the durable, committed, human-readable record; the gitignored ledger is the
34
+ # daily machine one. A snapshot appended here survives a dead laptop.
35
+ #
36
+ # HONESTY, because these numbers are for social proof and will be checked:
37
+ # npm counts mirrors, CI and crawlers. "57,819 downloads" is defensible;
38
+ # "57,819 developers" is a lie. The table separates the current @oneie scope
39
+ # from retired lines so a headline can be sourced to the right subtotal.
40
+ set -uo pipefail
41
+
42
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
43
+ OUT="$ROOT/text/download-stats.md"
44
+ MAINTAINER="${NPM_MAINTAINER:-oneie}"
45
+ ORG="${GH_ORG:-one-ie}"
46
+ MODE=print
47
+
48
+ while [[ $# -gt 0 ]]; do
49
+ case "$1" in
50
+ --write) MODE=write ;;
51
+ --json) MODE=json ;;
52
+ --self-test) MODE=selftest ;;
53
+ -h|--help) sed -n '2,8p' "${BASH_SOURCE[0]}"; exit 0 ;;
54
+ *) echo "download-stats.sh: unknown flag '$1'" >&2; exit 2 ;;
55
+ esac
56
+ shift
57
+ done
58
+
59
+ command -v jq >/dev/null || { echo "download-stats.sh: needs jq" >&2; exit 2; }
60
+
61
+ enc() { printf '%s' "${1/\//%2F}"; } # @oneie/sdk -> @oneie%2Fsdk
62
+ today() { date -u '+%Y-%m-%d'; }
63
+ add_months() { date -j -v+"$2"m -f '%Y-%m-%d' "$1" '+%Y-%m-%d' 2>/dev/null || date -d "$1 + $2 months" '+%Y-%m-%d' 2>/dev/null; }
64
+
65
+ # all_time <pkg> — the 17-month walk described above.
66
+ all_time() {
67
+ local p="$1" e c created start end total=0 body err n
68
+ e="$(enc "$p")"
69
+ created="$(npm view "$p" time.created 2>/dev/null | head -1 | cut -c1-10)"
70
+ [[ -n "$created" ]] || created="2024-01-01"
71
+ start="$created"; end="$(today)"; cursor="$start"
72
+ while [[ "$cursor" < "$end" ]]; do
73
+ stop="$(add_months "$cursor" 17)"
74
+ [[ -z "$stop" || "$stop" > "$end" ]] && stop="$end"
75
+ body="$(curl -sS --max-time 25 "https://api.npmjs.org/downloads/point/${cursor}:${stop}/${e}" 2>/dev/null)"
76
+ err="$(printf '%s' "$body" | jq -r '.error // empty' 2>/dev/null)"
77
+ if [[ -z "$err" ]]; then
78
+ n="$(printf '%s' "$body" | jq -r '.downloads // 0' 2>/dev/null)"
79
+ [[ "$n" =~ ^[0-9]+$ ]] && total=$(( total + n ))
80
+ fi
81
+ [[ "$stop" == "$end" ]] && break
82
+ cursor="$stop"
83
+ done
84
+ printf '%s|%s' "$total" "$created"
85
+ }
86
+
87
+ if [[ "$MODE" == selftest ]]; then
88
+ fails=0
89
+ [[ "$(enc '@oneie/sdk')" == '@oneie%2Fsdk' ]] || { echo " FAIL: scoped name not encoded"; fails=1; }
90
+ [[ "$(enc 'oneie')" == 'oneie' ]] || { echo " FAIL: unscoped name mangled"; fails=1; }
91
+ [[ -n "$(add_months 2026-01-01 17)" ]] || { echo " FAIL: add_months returned nothing"; fails=1; }
92
+ # the 18-month refusal must be READ, not inferred from the status code
93
+ b="$(curl -sS --max-time 20 "https://api.npmjs.org/downloads/point/2000-01-01:$(today)/npm" 2>/dev/null)"
94
+ [[ -n "$(printf '%s' "$b" | jq -r '.error // empty')" ]] \
95
+ || { echo " NOTE: the >18mo range did not error — the walk is still correct, the guard is just untested here"; }
96
+ (( fails )) && { echo "download-stats.sh: self-test FAILED"; exit 1; }
97
+ echo "download-stats.sh: self-test PASS"; exit 0
98
+ fi
99
+
100
+ # ── npm ──────────────────────────────────────────────────────────────────────
101
+ pkgs="$(curl -sS --max-time 30 "https://registry.npmjs.org/-/v1/search?text=maintainer:${MAINTAINER}&size=250" 2>/dev/null | jq -r '.objects[].package.name' | sort)"
102
+ [[ -n "$pkgs" ]] || { echo "download-stats.sh: registry search returned no packages for maintainer:${MAINTAINER}" >&2; exit 1; }
103
+
104
+ rows="[]"
105
+ while IFS= read -r p; do
106
+ [[ -n "$p" ]] || continue
107
+ at="$(all_time "$p")"
108
+ wk="$(curl -sS --max-time 15 "https://api.npmjs.org/downloads/point/last-week/$(enc "$p")" 2>/dev/null | jq -r '.downloads // 0')"
109
+ ver="$(npm view "$p" version 2>/dev/null | tail -1)"
110
+ rows="$(printf '%s' "$rows" | jq --arg p "$p" --arg v "$ver" --arg c "${at##*|}" \
111
+ --argjson t "${at%%|*}" --argjson w "${wk:-0}" \
112
+ '. + [{package:$p,version:$v,since:$c,all_time:$t,last_week:$w,scoped:($p|startswith("@"))}]')"
113
+ done <<< "$pkgs"
114
+
115
+ NPM_ALL="$(printf '%s' "$rows" | jq '[.[].all_time]|add // 0')"
116
+ NPM_WK="$(printf '%s' "$rows" | jq '[.[].last_week]|add // 0')"
117
+ NPM_SCOPED="$(printf '%s' "$rows" | jq '[.[]|select(.scoped)|.all_time]|add // 0')"
118
+ NPM_COUNT="$(printf '%s' "$rows" | jq 'length')"
119
+
120
+ # ── github ───────────────────────────────────────────────────────────────────
121
+ repos="$(gh api "orgs/$ORG/repos?per_page=100&type=public" 2>/dev/null)"
122
+ GH_STARS="$(printf '%s' "$repos" | jq '[.[].stargazers_count]|add // 0')"
123
+ GH_FORKS="$(printf '%s' "$repos" | jq '[.[].forks_count]|add // 0')"
124
+ GH_REPOS="$(printf '%s' "$repos" | jq 'length')"
125
+ GH_CLONES=0; GH_UNIQ=0
126
+ while IFS= read -r r; do
127
+ [[ -n "$r" ]] || continue
128
+ t="$(gh api "repos/$r/traffic/clones" --jq '"\(.count) \(.uniques)"' 2>/dev/null)" || continue
129
+ GH_CLONES=$(( GH_CLONES + ${t%% *} )); GH_UNIQ=$(( GH_UNIQ + ${t##* } ))
130
+ done < <(printf '%s' "$repos" | jq -r '.[].full_name')
131
+
132
+ if [[ "$MODE" == json ]]; then
133
+ jq -n --argjson pkgs "$rows" --arg d "$(today)" \
134
+ --argjson na "$NPM_ALL" --argjson nw "$NPM_WK" --argjson ns "$NPM_SCOPED" --argjson nc "$NPM_COUNT" \
135
+ --argjson gs "$GH_STARS" --argjson gf "$GH_FORKS" --argjson gr "$GH_REPOS" \
136
+ --argjson gc "$GH_CLONES" --argjson gu "$GH_UNIQ" \
137
+ '{date:$d, npm:{packages:$nc, all_time:$na, scope_all_time:$ns, last_week:$nw, rows:$pkgs},
138
+ github:{repos:$gr, stars:$gs, forks:$gf, clones_14d:$gc, unique_cloners_14d:$gu}}'
139
+ exit 0
140
+ fi
141
+
142
+ emit() {
143
+ printf '# Download stats\n\n'
144
+ printf '_Measured %s by `.claude/scripts/download-stats.sh`. Every figure is re-derivable — no number here was typed by hand._\n\n' "$(today)"
145
+ printf '## Headline\n\n| metric | value | source |\n|---|---:|---|\n'
146
+ printf '| npm downloads, all time | **%s** | downloads API, per package since first publish |\n' "$NPM_ALL"
147
+ printf '| npm downloads, `@%s` scope | **%s** | the current product line only |\n' "$MAINTAINER" "$NPM_SCOPED"
148
+ printf '| npm downloads, last week | %s | downloads API |\n' "$NPM_WK"
149
+ printf '| packages published | **%s** | registry, maintainer `%s` |\n' "$NPM_COUNT" "$MAINTAINER"
150
+ printf '| GitHub stars | **%s** | %s public repos, org-wide |\n' "$GH_STARS" "$GH_REPOS"
151
+ printf '| GitHub forks | %s | org-wide |\n' "$GH_FORKS"
152
+ printf '| unique cloners, 14d | **%s** | %s clones — GitHub keeps only 14 days |\n\n' "$GH_UNIQ" "$GH_CLONES"
153
+ printf '## Packages\n\n| package | version | all time | last week | since |\n|---|---|---:|---:|---|\n'
154
+ printf '%s' "$rows" | jq -r 'sort_by(-.all_time)[] | "| `\(.package)` | \(.version) | \(.all_time) | \(.last_week) | \(.since) |"'
155
+ printf '\n## Using these for social proof\n\n'
156
+ printf 'npm counts mirrors, CI and crawlers alongside people. **%s downloads** is defensible; ' "$NPM_ALL"
157
+ printf '"%s developers" is not — say downloads, or cite unique cloners (**%s in 14 days**), which are closer to humans.\n\n' "$NPM_ALL" "$GH_UNIQ"
158
+ printf 'The all-time total spans retired lines as well as the current one. For claims about the product as it stands today, '
159
+ printf 'the honest subtotal is the **`@%s` scope: %s**.\n\n' "$MAINTAINER" "$NPM_SCOPED"
160
+ printf '## What cannot be recovered\n\n'
161
+ printf 'GitHub keeps **14 days** of clone and view traffic and offers no backfill, so all-time clones do not exist for any repo. '
162
+ printf '`.claude/scripts/gh-traffic-capture.sh` runs daily to stop further days being lost; everything before its first run is gone.\n'
163
+ }
164
+
165
+ if [[ "$MODE" == write ]]; then
166
+ mkdir -p "$(dirname "$OUT")"
167
+ emit > "$OUT"
168
+ echo " ✓ recorded → text/download-stats.md ($(wc -l < "$OUT" | tr -d ' ') lines)"
169
+ echo " npm all-time=$NPM_ALL scope=$NPM_SCOPED packages=$NPM_COUNT stars=$GH_STARS uniq-cloners-14d=$GH_UNIQ"
170
+ else
171
+ emit
172
+ fi
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env bash
2
+ # factory-brief-check.sh — pre-flight a factory brief for DEFERRED DECISIONS.
3
+ #
4
+ # manifest: portable
5
+ # Reads no credential and names no monorepo path — every rule is a literal
6
+ # in this file and the brief arrives as an argument or on stdin. The red
7
+ # half of --self-test reads fixtures/factory-brief-real.md, which ships
8
+ # beside it; absent, the self-test goes RED rather than skipping.
9
+ #
10
+ # WHY THIS EXISTS. A brief that hands the doer a decision instead of a decision
11
+ # already made costs a whole run. The first factory run spent ~46 minutes and
12
+ # ended in a refutation because one parenthesis said "verify which is right" —
13
+ # the launching session had two candidate reads of the workspace and shipped
14
+ # both. The doer is the wrong place to settle that: it has no board, no
15
+ # neighbours, and no cheap way to be wrong. This gate reads the brief BEFORE a
16
+ # worktree, a port, or a model call is spent, and refuses.
17
+ #
18
+ # Usage:
19
+ # factory-brief-check.sh <brief-file> check a brief file
20
+ # factory-brief-check.sh - check a brief on stdin
21
+ # cat brief.md | factory-brief-check.sh same
22
+ # factory-brief-check.sh --self-test prove the gate goes RED and GREEN
23
+ #
24
+ # Exit: 0 = brief is launchable · 1 = RED (or self-test failed) · 2 = usage.
25
+ #
26
+ # Env:
27
+ # FACTORY_BRIEF_FIXTURE override the --self-test red fixture (default:
28
+ # fixtures/factory-brief-real.md beside this script)
29
+
30
+ set -uo pipefail
31
+
32
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # The rules. Each is a NAMED pattern, matched case-insensitively, one per line
36
+ # of the brief. A rule fires when the brief leaves a decision for the doer.
37
+ #
38
+ # Third field is the input the pattern is matched against: `line` is the raw
39
+ # line, `stripped` is the line with every file-path token removed first.
40
+ #
41
+ # Deliberate deviation, documented: `tbd` and `todo` match against `stripped`,
42
+ # so `text/template-todo.md` and `<slug>-todo.md` — which nearly every real
43
+ # brief in this repo cites — are NOT hits, while a bare `TBD.` at the end of a
44
+ # sentence still is. A gate that goes red on the name of a file it expects you
45
+ # to name is decoration, and gets disabled; a gate that misses `TBD.` because
46
+ # of the full stop is worse.
47
+ # ---------------------------------------------------------------------------
48
+ RULES=(
49
+ 'verify-which|verify[[:space:]]+which|line'
50
+ 'check-whether|check[[:space:]]+whether|line'
51
+ 'tbd|(^|[^-_/[:alnum:]])tbd([^-_[:alnum:]]|$)|stripped'
52
+ 'todo|(^|[^-_/[:alnum:]])todo([^-_[:alnum:]]|$)|stripped'
53
+ 'decide-later|decide[[:space:]]+later|line'
54
+ 'or-alternatively|or[[:space:]]+alternatively|line'
55
+ 'either-or|(^|[^[:alnum:]])either[^[:alnum:]].*[[:space:]]or[[:space:]]|line'
56
+ 'trailing-question|\?[[:space:]]*$|line'
57
+ 'if-possible|if[[:space:]]+possible|line'
58
+ 'ideally|(^|[^[:alnum:]])ideally([^[:alnum:]]|$)|line'
59
+ )
60
+
61
+ # Rule 'code-span-options': two options joined by ` ?? ` or ` or ` INSIDE a code
62
+ # span. `a ?? b` in prose is a decision the doer has to make; the same string in
63
+ # shipped code is not, so only backtick spans count. Returns 0 when the line has
64
+ # one.
65
+ code_span_options() {
66
+ awk '
67
+ {
68
+ n = split($0, part, "`")
69
+ # odd-indexed parts (2,4,…) are inside a span
70
+ for (i = 2; i <= n; i += 2) {
71
+ s = part[i]
72
+ if (s ~ /[^[:space:]][[:space:]]+\?\?[[:space:]]+[^[:space:]]/) { found = 1 }
73
+ if (s ~ /[^[:space:]][[:space:]]+or[[:space:]]+[^[:space:]]/) { found = 1 }
74
+ }
75
+ }
76
+ END { exit !found }
77
+ ' <<< "$1"
78
+ }
79
+
80
+ # A file path token. The spec class is lowercase; this adds A-Z so the count and
81
+ # any listing read correctly (`ComponentBrowser.tsx`, not `omponentBrowser.tsx`).
82
+ # Presence is identical either way.
83
+ PATH_RE='[A-Za-z0-9_./-]+\.(tsx|astro|ts|sh|md|mjs|js)([^A-Za-z0-9]|$)'
84
+
85
+ # A 'Done' section: a line that STARTS with the word done, optionally behind a
86
+ # markdown heading or bold marker. "DONE LOOKS LIKE", "## Done", "**Done**".
87
+ DONE_RE='^[[:space:]]*(#+[[:space:]]*)?(\*\*)?[[:space:]]*done([^[:alnum:]]|$)'
88
+
89
+ check_brief() {
90
+ local text="$1" label="${2:-brief}"
91
+ local hits=0 line rule name pat
92
+
93
+ if [ -z "${text//[[:space:]]/}" ]; then
94
+ printf 'BRIEF RED: empty-brief — "<%s is empty — a brief that says nothing defers everything>"\n' "$label"
95
+ return 1
96
+ fi
97
+
98
+ local rest src stripped
99
+ while IFS= read -r line; do
100
+ [ -z "${line//[[:space:]]/}" ] && continue
101
+ stripped="$(sed -E "s#$PATH_RE# #g" <<< "$line")"
102
+ for rule in ${RULES[@]+"${RULES[@]}"}; do
103
+ name="${rule%%|*}"; rest="${rule#*|}"
104
+ pat="${rest%|*}"; src="${rest##*|}"
105
+ if grep -qiE "$pat" <<< "$([ "$src" = stripped ] && printf '%s' "$stripped" || printf '%s' "$line")"; then
106
+ printf 'BRIEF RED: %s — "%s"\n' "$name" "$line"
107
+ hits=$((hits + 1))
108
+ fi
109
+ done
110
+ if code_span_options "$line"; then
111
+ printf 'BRIEF RED: code-span-options — "%s"\n' "$line"
112
+ hits=$((hits + 1))
113
+ fi
114
+ done <<< "$text"
115
+
116
+ local files
117
+ files="$(grep -oE "$PATH_RE" <<< "$text" | sed -E 's/[^A-Za-z0-9]$//' | sort -u)"
118
+ local nfiles=0
119
+ [ -n "$files" ] && nfiles="$(wc -l <<< "$files" | tr -d ' ')"
120
+
121
+ if [ "$nfiles" -eq 0 ]; then
122
+ printf 'BRIEF RED: no-file-path — "<%s names no file the doer can open>"\n' "$label"
123
+ hits=$((hits + 1))
124
+ fi
125
+
126
+ if ! grep -qiE "$DONE_RE" <<< "$text"; then
127
+ printf 'BRIEF RED: no-done-section — "<%s has no Done section — nothing says when to stop>"\n' "$label"
128
+ hits=$((hits + 1))
129
+ fi
130
+
131
+ if [ "$hits" -gt 0 ]; then
132
+ return 1
133
+ fi
134
+ printf 'BRIEF OK: %s files named, 0 deferred decisions\n' "$nfiles"
135
+ return 0
136
+ }
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # --self-test — the red proof. Four cases; the red half runs against the ACTUAL
140
+ # brief that produced the first run's refutation, copied verbatim, not retyped.
141
+ # ---------------------------------------------------------------------------
142
+ self_test() {
143
+ local fixture="${FACTORY_BRIEF_FIXTURE:-$HERE/fixtures/factory-brief-real.md}"
144
+ local tmp out rc bad=0 cases=0
145
+
146
+ if [ ! -f "$fixture" ]; then
147
+ printf 'SELF-TEST RED: the red fixture is missing at %s — the red half cannot be run, so nothing here is proven. Refusing to pass.\n' "$fixture" >&2
148
+ printf 'SELF-TEST RED: 0 cases — fixture absent\n'
149
+ return 1
150
+ fi
151
+
152
+ # (a) the real brief MUST go red, and MUST name verify-which on the real line.
153
+ cases=$((cases + 1))
154
+ out="$(check_brief "$(cat "$fixture")" "$fixture")"; rc=$?
155
+ if [ $rc -eq 0 ]; then
156
+ printf ' FAIL (a) the real brief passed — the gate is blind\n' >&2; bad=1
157
+ elif ! grep -q '^BRIEF RED: verify-which — ' <<< "$out"; then
158
+ printf ' FAIL (a) the real brief went red, but not on verify-which:\n%s\n' "$out" >&2; bad=1
159
+ elif ! grep -qi 'verify which is right' <<< "$(grep '^BRIEF RED: verify-which — ' <<< "$out")"; then
160
+ printf ' FAIL (a) verify-which fired on the wrong line:\n%s\n' "$(grep '^BRIEF RED: verify-which' <<< "$out")" >&2; bad=1
161
+ else
162
+ printf ' ok (a) the real brief is RED on verify-which, quoting "verify which is right"\n'
163
+ fi
164
+
165
+ tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
166
+
167
+ # (b) a clean brief MUST pass.
168
+ cat > "$tmp/clean.md" <<'CLEAN'
169
+ BUILD: the picker gate on the component gallery.
170
+
171
+ WHAT TO BUILD
172
+ Add an "Add to page" control to the preview footer in
173
+ src/components/puck/ComponentBrowser.tsx. It lists the workspace pages, takes
174
+ one, appends the block, then offers two exits.
175
+
176
+ THE DECISION, ALREADY MADE
177
+ Read the workspace slug server-side from Astro.locals.slug in
178
+ src/pages/components.astro and pass it down as an optional prop. There is no
179
+ session, so there is no slug, so the control does not render.
180
+
181
+ TEST
182
+ Extend tests/unit/puck/add-to-page.test.tsx with one case that names the wrong
183
+ result: a missing slug renders the control anyway.
184
+
185
+ DONE LOOKS LIKE
186
+ 1. The control renders signed in and is absent signed out.
187
+ 2. The block lands at the end of the picked page.
188
+ 3. bash .claude/scripts/gate-run.sh doer -- bunx vitest related --run is green.
189
+ 4. node .claude/scripts/wf-check.mjs is green.
190
+ CLEAN
191
+ cases=$((cases + 1))
192
+ out="$(check_brief "$(cat "$tmp/clean.md")" "$tmp/clean.md")"; rc=$?
193
+ if [ $rc -ne 0 ]; then
194
+ printf ' FAIL (b) the clean brief went red — the gate cries wolf:\n%s\n' "$out" >&2; bad=1
195
+ elif ! grep -q '^BRIEF OK: ' <<< "$out"; then
196
+ printf ' FAIL (b) the clean brief exited 0 without printing BRIEF OK\n' >&2; bad=1
197
+ else
198
+ printf ' ok (b) the clean brief is GREEN — %s\n' "$out"
199
+ fi
200
+
201
+ # (c) a brief with no file path MUST go red, even with nothing deferred.
202
+ cat > "$tmp/nopath.md" <<'NOPATH'
203
+ BUILD: make the gallery let you put a block on a page.
204
+
205
+ DONE LOOKS LIKE
206
+ 1. It works signed in and is absent signed out.
207
+ NOPATH
208
+ cases=$((cases + 1))
209
+ out="$(check_brief "$(cat "$tmp/nopath.md")" "$tmp/nopath.md")"; rc=$?
210
+ if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-file-path' <<< "$out"; then
211
+ printf ' FAIL (c) a brief naming no file passed:\n%s\n' "$out" >&2; bad=1
212
+ else
213
+ printf ' ok (c) a brief naming no file is RED on no-file-path\n'
214
+ fi
215
+
216
+ # (d) a brief with no Done section MUST go red.
217
+ cat > "$tmp/nodone.md" <<'NODONE'
218
+ BUILD: the picker gate.
219
+ Edit src/components/puck/ComponentBrowser.tsx and read the slug server-side in
220
+ src/pages/components.astro. Extend tests/unit/puck/add-to-page.test.tsx.
221
+ NODONE
222
+ cases=$((cases + 1))
223
+ out="$(check_brief "$(cat "$tmp/nodone.md")" "$tmp/nodone.md")"; rc=$?
224
+ if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-done-section' <<< "$out"; then
225
+ printf ' FAIL (d) a brief with no Done section passed:\n%s\n' "$out" >&2; bad=1
226
+ else
227
+ printf ' ok (d) a brief with no Done section is RED on no-done-section\n'
228
+ fi
229
+
230
+ # (e) EVERY named rule must fire. A declared rule that no case exercises is a
231
+ # rule that can ship broken and pass a brief it exists to refuse — one line
232
+ # per rule, all eleven, and the count asserted so a line tripping two rules
233
+ # (or none) is caught rather than averaged away.
234
+ cat > "$tmp/allrules.md" <<'ALLRULES'
235
+ BUILD: one line per deferred decision, in src/lib/one.ts.
236
+ Verify which read is right.
237
+ Check whether the snapshot is warm.
238
+ The signal name is TBD.
239
+ Leave a TODO where the gate goes.
240
+ Name the signal now and decide later on the payload.
241
+ Append the block, or alternatively insert it at the top.
242
+ Use either the snapshot or a live read.
243
+ Should the picker show archived pages?
244
+ Reuse the existing helper if possible.
245
+ Ideally the control renders in the footer.
246
+ Set the base to `4321 or 4325` first.
247
+ DONE LOOKS LIKE
248
+ 1. It renders.
249
+ ALLRULES
250
+ cases=$((cases + 1))
251
+ out="$(check_brief "$(cat "$tmp/allrules.md")" "$tmp/allrules.md")"; rc=$?
252
+ local missing="" r n
253
+ for r in verify-which check-whether tbd todo decide-later or-alternatively \
254
+ either-or trailing-question if-possible ideally code-span-options; do
255
+ grep -q "^BRIEF RED: $r — " <<< "$out" || missing="$missing $r"
256
+ done
257
+ n="$(grep -c '^BRIEF RED: ' <<< "$out" | tr -d ' ')"
258
+ if [ -n "$missing" ]; then
259
+ printf ' FAIL (e) rules declared but never fired:%s\n%s\n' "$missing" "$out" >&2; bad=1
260
+ elif [ "$n" != 11 ]; then
261
+ printf ' FAIL (e) expected 11 hits, one per rule, got %s:\n%s\n' "$n" "$out" >&2; bad=1
262
+ else
263
+ printf ' ok (e) all 11 named rules fire — 11 hits, one per rule, no structural hit\n'
264
+ fi
265
+
266
+ # (f) THE RED PROOF ITSELF. Gut this script — empty the RULES array, keep
267
+ # everything else — and case (a) MUST flip: the real brief that cost ~46
268
+ # minutes now passes. A checker that stays green against its own gutted copy
269
+ # proves nothing, which is the failure this whole script exists to refuse.
270
+ #
271
+ # Gut by STRUCTURE, never by line number, so an edit above cannot quietly turn
272
+ # this into a no-op. Two things were measured before this assertion was
273
+ # written, and both are why it reads the way it does:
274
+ # - `"${RULES[@]}"` on an EMPTY array is an unbound variable under `set -u`
275
+ # in bash 3.2, so the gutted copy used to die with a bash error and exit 1.
276
+ # "exit non-zero" would have looked like proof and measured the crash. The
277
+ # expansion in check_brief is empty-safe for exactly this reason.
278
+ # - code_span_options lives OUTSIDE Rules and still runs in the gutted copy;
279
+ # the real fixture happens to trip none of its spans, so the gutted copy
280
+ # reaches BRIEF OK. Both halves are asserted separately so a future change
281
+ # to either one names itself instead of averaging away.
282
+ cases=$((cases + 1))
283
+ awk '/^RULES=\(/{print "RULES=()"; s=1; next} s&&/^\)/{s=0; next} !s' \
284
+ "${BASH_SOURCE[0]}" > "$tmp/gutted.sh"
285
+ if ! grep -q '^RULES=()$' "$tmp/gutted.sh"; then
286
+ printf ' FAIL (f) the gutting did not take — no empty RULES in the copy\n' >&2; bad=1
287
+ elif ! bash -n "$tmp/gutted.sh" 2>/dev/null; then
288
+ printf ' FAIL (f) the gutted copy does not parse — the proof measures a syntax error, not the rules\n' >&2; bad=1
289
+ else
290
+ out="$(bash "$tmp/gutted.sh" "$fixture" 2>&1)"; rc=$?
291
+ if grep -q '^BRIEF RED: verify-which' <<< "$out"; then
292
+ printf ' FAIL (f) the gutted copy STILL refused the brief on verify-which — the RULES array is not load-bearing:\n%s\n' "$out" >&2; bad=1
293
+ elif [ $rc -ne 0 ]; then
294
+ printf ' FAIL (f) the gutted copy stopped the brief for some OTHER reason (rc=%s) — the red half of (a) is not attributable to the rules:\n%s\n' "$rc" "$out" >&2; bad=1
295
+ else
296
+ printf ' ok (f) rules emptied ⇒ the real brief PASSES (%s) — case (a) is attributable to the rules, not to the structural checks\n' "$out"
297
+ fi
298
+ fi
299
+
300
+ if [ $bad -ne 0 ]; then
301
+ printf 'SELF-TEST RED: %s cases — the gate does not hold\n' "$cases"
302
+ return 1
303
+ fi
304
+ printf 'SELF-TEST OK: %s cases — red half red, green half green\n' "$cases"
305
+ return 0
306
+ }
307
+
308
+ main() {
309
+ case "${1:-}" in
310
+ --self-test) self_test; exit $? ;;
311
+ -h|--help)
312
+ awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
313
+ exit 0 ;;
314
+ "")
315
+ if [ -t 0 ]; then
316
+ printf 'usage: factory-brief-check.sh <brief-file> | - | --self-test\n' >&2
317
+ exit 2
318
+ fi
319
+ check_brief "$(cat)" "stdin"; exit $? ;;
320
+ -) check_brief "$(cat)" "stdin"; exit $? ;;
321
+ *)
322
+ if [ ! -f "$1" ]; then
323
+ printf 'factory-brief-check.sh: no such brief file: %s\n' "$1" >&2
324
+ exit 2
325
+ fi
326
+ check_brief "$(cat "$1")" "$1"; exit $? ;;
327
+ esac
328
+ }
329
+
330
+ main "$@"