@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,353 @@
1
+ #!/usr/bin/env node
2
+ // classification: portable
3
+ // Red proof for factory-executor.js — the trace, the slug and the close.
4
+ //
5
+ // WHY THIS IS DIFFERENT FROM factory-review-check.mjs: that checker defines OLD
6
+ // and NEW as two inline string copies and never opens the executor, so it stays
7
+ // green whether or not the file is fixed. It proves a shape. THIS one READS THE
8
+ // FILE for every assertion, and lifts the two behavioural tables (slugFor, the
9
+ // close status re-parse) OUT of the source with new Function so they cannot
10
+ // drift from what ships. Revert any one of the three fixes and this goes red.
11
+ //
12
+ // Usage: node .claude/scripts/factory-executor-check.mjs [path]
13
+ // Exit 0 = all green. Exit 1 = at least one assertion failed.
14
+
15
+ import { readFileSync } from 'node:fs'
16
+
17
+ const F = process.argv[2] || '.claude/workflows/factory-executor.js'
18
+ const src = readFileSync(F, 'utf8')
19
+
20
+ const fails = []
21
+ function check(name, pass, detail) {
22
+ if (!pass) fails.push(name)
23
+ console.log(` ${pass ? ' ok ' : 'RED '} ${name}${detail ? ` — ${detail}` : ''}`)
24
+ }
25
+
26
+ // Lift a `const <name> = ...` block out of the source by balanced braces, so the
27
+ // behavioural tables below exercise THE FILE and not a copy of it.
28
+ function lift(startPat) {
29
+ const i = src.search(startPat)
30
+ if (i < 0) return null
31
+ const open = src.indexOf('{', i)
32
+ if (open < 0) return null
33
+ let depth = 0
34
+ for (let j = open; j < src.length; j++) {
35
+ if (src[j] === '{') depth++
36
+ else if (src[j] === '}') { depth--; if (depth === 0) return src.slice(i, j + 1) }
37
+ }
38
+ return null
39
+ }
40
+
41
+ console.log(`factory-executor-check: ${F}\n`)
42
+
43
+ // ── 1. THE TRACE — the emit is issued at both boundaries of all six stages ──
44
+ //
45
+ // ON THE NUMBER: the brief asked for "12 factory-emit.sh occurrences". That is
46
+ // the plan's RUNTIME boundary count per job (6 stages × start/end, minus Ready's
47
+ // start, minus Prove's when the review refutes, plus the repair's build+review
48
+ // pair) mis-stated as a static grep. Statically there is exactly ONE
49
+ // `factory-emit.sh` literal — inside the emitStage helper — and N emitStage CALL
50
+ // SITES. Inlining the curl twelve times to satisfy a grep would reintroduce the
51
+ // very divergence the slugFor fix removes: one command, many renderings. So the
52
+ // count is reported, and COVERAGE is what is asserted.
53
+ console.log('1. the trace — stage emit coverage')
54
+
55
+ const EMIT_CALL = /emitStage\(\s*[^,]+,\s*'(\w+)'\s*,\s*([\s\S]*?),\s*\{/g
56
+
57
+ // COVERAGE IS PER CODE PATH, NOT PER STAGE NAME. A union over the stage name
58
+ // scores `build` as covered when the MAIN path has lost its fail emit but the
59
+ // REPAIR path still carries one — absent evidence defaulting to benign, in the
60
+ // checker written to stop that. So each emitting path gets its own lane, keyed
61
+ // off the options object: `repair:` (the repair build), `pass: N` (review pass 1
62
+ // vs 2), else `main`. Every lane must carry both boundaries.
63
+ function optionsAfter(idx) {
64
+ let depth = 0
65
+ for (let j = idx; j < src.length; j++) {
66
+ if (src[j] === '{') depth++
67
+ else if (src[j] === '}') { depth--; if (depth === 0) return src.slice(idx, j + 1) }
68
+ }
69
+ return ''
70
+ }
71
+ const lanes = new Map()
72
+ let m
73
+ while ((m = EMIT_CALL.exec(src))) {
74
+ const [, stage, statusExpr] = m
75
+ const opts = optionsAfter(src.lastIndexOf('{', EMIT_CALL.lastIndex))
76
+ const lane = /\brepair:/.test(opts) ? 'repair' : (opts.match(/\bpass:\s*(\d+)/) || [, null])[1] ? `pass${opts.match(/\bpass:\s*(\d+)/)[1]}` : 'main'
77
+ const key = `${stage}:${lane}`
78
+ if (!lanes.has(key)) lanes.set(key, new Set())
79
+ for (const st of (statusExpr.match(/'(start|ok|fail)'/g) || []).map((q) => q.slice(1, -1))) lanes.get(key).add(st)
80
+ }
81
+
82
+ // FAIL CLOSED on a partial parse. If the regex misses a call, coverage would be
83
+ // computed from a subset and a missing boundary could read as present-elsewhere.
84
+ const rawCalls = (src.match(/emitStage\(/g) || []).length - 1 // minus the definition
85
+ const parsedCalls = (src.match(EMIT_CALL) || []).length
86
+ check(
87
+ 'every emitStage call site was parsed (a partial parse cannot score coverage)',
88
+ rawCalls > 0 && parsedCalls === rawCalls,
89
+ `${parsedCalls} parsed of ${rawCalls} call site(s)`,
90
+ )
91
+
92
+ // The invariant is one EXECUTED rendering, not one mention: the name also
93
+ // appears in a comment, a schema description and the operator hint in EMITS
94
+ // DARK. What must never be duplicated is the line that BUILDS AND RUNS the
95
+ // command — two of those is the slugFor disease with a different subject.
96
+ const runSites = (src.match(/factory-emit\.sh \$\{parts\.join/g) || []).length
97
+ check('exactly one EXECUTED factory-emit.sh rendering — one command, one builder', runSites === 1,
98
+ `${runSites} executed of ${(src.match(/factory-emit\.sh/g) || []).length} total mention(s) (the rest are comments, a schema description and the DARK hint)`)
99
+
100
+ // Every lane the pipeline actually has. Named explicitly so a lane that
101
+ // DISAPPEARS is red too — a missing key cannot be scored by iterating what is
102
+ // present.
103
+ const REQUIRED_LANES = [
104
+ ['claim:main', 'the claim'],
105
+ ['build:main', 'the first build'],
106
+ ['build:repair', 'the repair build'],
107
+ ['review:pass1', 'review pass 1'],
108
+ ['review:pass2', 'review pass 2 (after the repair)'],
109
+ ['prove:main', 'the walk'],
110
+ ['close:main', 'the close'],
111
+ ]
112
+ for (const [key, what] of REQUIRED_LANES) {
113
+ const s = lanes.get(key)
114
+ check(`${key}: start + ok + fail (${what})`, !!s && s.has('start') && s.has('ok') && s.has('fail'), s ? `[${[...s].sort().join(' ')}]` : 'LANE ABSENT')
115
+ }
116
+ // Ready is the deliberate asymmetry: the read happens BEFORE any tid is known,
117
+ // so a per-job `start` would be invented rather than observed.
118
+ const ready = lanes.get('ready:main') || new Set()
119
+ check('ready: ok, and NO start (a start before any tid is known is invented, not observed)', ready.has('ok') && !ready.has('start'), `[${[...ready].sort().join(' ')}]`)
120
+
121
+ // A stage that did not run must be ABSENT — there is no `skip` in the vocabulary.
122
+ const refutedBranch = lift(/if \(review\.refuted\) \{/)
123
+ check('the refuted-review branch emits NO prove frame (absent, never "skipped")', refutedBranch !== null && !refutedBranch.includes('emitStage'))
124
+
125
+ check('EMITS DARK is keyed on attempts, not only on failures', /emitStats\.pushed === 0/.test(src) && /EMITS DARK/.test(src),
126
+ 'zero pushed and zero failed must not read as a clean turn')
127
+
128
+ // ── 2. THE CLOSE — status is a copy out of a script line, never a belief ────
129
+ console.log('\n2. the close — status is read back, not asserted')
130
+
131
+ const closeSchema = lift(/const CLOSE_SCHEMA = /)
132
+ check('CLOSE_SCHEMA exists', closeSchema !== null)
133
+ check("CLOSE_SCHEMA requires 'checkLine'", !!closeSchema && /required:\s*\[[^\]]*'checkLine'[^\]]*\]/.test(closeSchema))
134
+ check('CLOSE_SCHEMA still requires tagged + status (no field lost)',
135
+ !!closeSchema && /'tagged'/.test(closeSchema) && /'status'/.test(closeSchema))
136
+ check('closePrompt tells the agent to run factory-close-check.sh', /factory-close-check\.sh/.test(src))
137
+
138
+ // A stage agent that THROWS propagates out of the stage; the item is then dropped
139
+ // by `out.filter(Boolean)` and lands in NEITHER results NOR skipped — a job that
140
+ // built, reviewed and walked disappears from the turn's own summary. Both agents
141
+ // that can throw after work has been done must be caught.
142
+ check('the Close agent is .catch()-guarded (a thrown Close must not vanish the job)',
143
+ /schema: CLOSE_SCHEMA \}\)\s*\n\s*\.catch\(\(\) => null\)/.test(src))
144
+ check('the repair agent is .catch()-guarded (a thrown repair must not leave the row picked with no close)',
145
+ /repairPrompt\(t, b, r1\.findings\)[\s\S]{0,220}?\.catch\(\(\) => null\)/.test(src))
146
+
147
+ // Lift the executor's OWN four-line derivation and drive it. The first factory
148
+ // run is the fixture: task:01a07591e5c350a306c93933 is still `picked` while the
149
+ // Close agent returned {"status":"done"}.
150
+ const deriveSrc = src.match(/ +const line = c &&[\s\S]*?const status = [^\n]*\n/)
151
+ check('the status derivation is present in the file and liftable', deriveSrc !== null)
152
+ if (deriveSrc) {
153
+ const NEW = new Function('c', `${deriveSrc[0]}\nreturn status`)
154
+ const OLD = (c) => (c && c.status) || 'unknown' // the pre-fix line
155
+ const cases = [
156
+ ['THE BUG: agent said done, board said picked', { status: 'done', checkLine: 'CLOSE tid=task:01a07591e5c350a306c93933 lane=none verdict=red status=picked' }, 'unverified'],
157
+ ['an honest close: both say done', { status: 'done', checkLine: 'CLOSE tid=x lane=fast verdict=ok status=done' }, 'done'],
158
+ ['the script failed, no CLOSE line', { status: 'unverified', checkLine: 'factory-close-check.sh: row not found' }, 'unverified'],
159
+ ['checkLine absent entirely', { status: 'done' }, 'unverified'],
160
+ ['the close agent was lost', null, 'unverified'],
161
+ ]
162
+ let oldFails = 0, newFails = 0
163
+ for (const [name, input, want] of cases) {
164
+ const o = OLD(input), n = NEW(input)
165
+ if (o !== want) oldFails++
166
+ if (n !== want) newFails++
167
+ console.log(` ${name.padEnd(46)} want=${String(want).padEnd(11)} OLD=${String(o).padEnd(11)} NEW=${n}${n === want ? '' : ' <-- NEW WRONG'}`)
168
+ }
169
+ check('the pre-fix line fails the cases this fix exists for', oldFails > 0, `OLD: ${oldFails} failure(s) — must be > 0 or the proof is worthless`)
170
+ check("the file's own derivation passes every case", newFails === 0, `NEW: ${newFails} failure(s)`)
171
+ }
172
+
173
+ // ── 3. THE SLUG — one function, zero re-derivations ────────────────────────
174
+ console.log('\n3. the slug — one function, no second sanitiser')
175
+
176
+ check('exactly one slugFor definition', (src.match(/const slugFor\s*=/g) || []).length === 1)
177
+ // NOTE THE SCOPING: `${t.tid}` legitimately appears in labels, receiver payloads
178
+ // and prompt prose. What must be zero is the RENDERED SLUG — the git/branch/
179
+ // worktree/receipt-path shape the first run's builder had to hand-patch.
180
+ const rawSlugSites = (src.match(/task-\$\{t\.tid\}/g) || []).length
181
+ check('zero raw `task-${t.tid}` renderings in the git/branch/worktree lines', rawSlugSites === 0, `${rawSlugSites} found (was 8 across 6 lines)`)
182
+ check('the prompts render the slug through slugFor', (src.match(/\$\{slugFor\(t\.tid\)\}/g) || []).length >= 8)
183
+
184
+ const slugSrc = lift(/const slugFor = /)
185
+ check('slugFor is liftable from the file', slugSrc !== null)
186
+ if (slugSrc) {
187
+ const slugFor = new Function(`${slugSrc}\nreturn slugFor`)()
188
+ const cases = [
189
+ // The directory the first run ACTUALLY built in.
190
+ ['task:01a07591e5c350a306c93933', 'task-01a07591e5c350a306c93933', 'the first run’s own tid'],
191
+ ['01a07591e5c350a306c93933', 'task-01a07591e5c350a306c93933', 'a bare id gets the prefix once'],
192
+ ['task/abc', 'task-abc', 'a slash separator'],
193
+ ['task:a b:c', 'task-a-b-c', 'spaces and colons collapse'],
194
+ ]
195
+ let bad = 0
196
+ for (const [tid, want, why] of cases) {
197
+ const got = slugFor(tid)
198
+ if (got !== want) bad++
199
+ console.log(` slugFor(${JSON.stringify(tid).padEnd(30)}) = ${String(got).padEnd(30)} want ${want}${got === want ? '' : ' <-- WRONG'} (${why})`)
200
+ }
201
+ check('slugFor maps every case to the directory the walk will be told to check', bad === 0)
202
+ // Empty is a stop, not a default: `task-` shared by N jobs is one worktree,
203
+ // one branch and one receipt for all of them, silently.
204
+ let threw = false
205
+ try { slugFor(':::') } catch { threw = true }
206
+ check('a tid with no slug-safe characters THROWS (it must not collapse to `task-`)', threw)
207
+ }
208
+
209
+ // ── 4. THE ARITHMETIC — one site, and this workstream did not move it ──────
210
+ // The hoist is the point: pass 1 and pass 2 must not be able to disagree.
211
+ console.log('\n4. the review jury — one arithmetic site, so two passes cannot drift')
212
+ const arith = src.match(/const refuted = votes[^\n]*/g) || []
213
+ check('exactly one vote-arithmetic site', arith.length === 1, arith.length === 1 ? arith[0].trim() : `${arith.length} sites`)
214
+
215
+ // ── 5. THE CLOSE STATUS TABLE — the board status the close ASKED FOR ───────
216
+ // `closeOk = w.verdict === 'ok' && status !== 'unverified'` accepted ANY board
217
+ // status the read-back reported honestly. The shape that got through: a NON-UI
218
+ // task, verdict ok, `tasks:status` refused, factory-close-check.sh prints
219
+ // `status=picked`, the agent copies `picked`, both words agree → closeOk true →
220
+ // close emits `ok` → event.ts writes **workflow:done** on a row the board reads
221
+ // `picked`. The expected status is a function of (verdict, t.ui) and is known at
222
+ // the call site, so this drives THE FILE's own derivation against that table.
223
+ console.log('\n5. the close — expected status is derived from (verdict, ui), and tagged:false is not a close')
224
+
225
+ // Lifted through `const closeOk`, and it returns ONLY closeOk on purpose: a body
226
+ // that returns {status, expectedStatus, tagged} would THROW on the pre-fix file
227
+ // (`expectedStatus is not defined`) and the RED rows would arrive as a crash
228
+ // instead of as a wrong answer. A crash is not a red proof; a wrong answer is.
229
+ const closeSrc = src.match(/ +const line = c &&[\s\S]*?\n +const closeOk = [^\n]*\n/)
230
+ check('the closeOk derivation is liftable from the file (a lift miss must be RED, never skipped)', closeSrc !== null)
231
+ if (closeSrc) {
232
+ const NEW = new Function('c', 'w', 't', `${closeSrc[0]}\nreturn closeOk`)
233
+ // The pre-fix line, verbatim from a60084d09.
234
+ const OLD = (c, w) => {
235
+ const line = c && typeof c.checkLine === 'string' ? c.checkLine : ''
236
+ const m = /\bstatus=([a-z]+)\b/.exec(line)
237
+ const claimed = (c && typeof c.status === 'string' && c.status) || ''
238
+ const status = m && m[1] === claimed ? m[1] : 'unverified'
239
+ return w.verdict === 'ok' && status !== 'unverified'
240
+ }
241
+ const cl = (st) => ({ status: st, checkLine: `CLOSE tid=x lane=fast verdict=ok status=${st}`, tagged: true })
242
+ const cases = [
243
+ // THE FINDING, as reported: non-UI, walk ok, board still picked.
244
+ ['non-UI ok, board says picked — tasks:status was refused', cl('picked'), { verdict: 'ok' }, { ui: false }, false],
245
+ // A UI task is SUPPOSED to stay picked: a person has not looked yet.
246
+ ['UI ok, board says picked — that is the asked-for status', cl('picked'), { verdict: 'ok' }, { ui: true }, true],
247
+ // tagged:false is tasks:notes refusing — the row carries no lane/verdict.
248
+ ['non-UI ok, board says done, but tagged:false', { status: 'done', checkLine: 'CLOSE tid=x lane=fast verdict=ok status=done', tagged: false }, { verdict: 'ok' }, { ui: false }, false],
249
+ // The honest close, which must stay green or the fix has broken the loop.
250
+ ['non-UI ok, board says done, tagged true', cl('done'), { verdict: 'ok' }, { ui: false }, true],
251
+ // A UI task must never be counted closed on `done`: nobody looked.
252
+ ['UI ok, board says done — nobody looked, so that is wrong too', cl('done'), { verdict: 'ok' }, { ui: true }, false],
253
+ // A red verdict closes `fail` whatever the row says (event.ts: a close ok is
254
+ // workflow:done, so a red that emits ok makes the trace contradict the board).
255
+ ['red verdict, board says picked', cl('picked'), { verdict: 'red' }, { ui: false }, false],
256
+ // `tagged` unsaid is not `tagged:false` — it must not read as a refusal.
257
+ ['non-UI ok, done, tagged unsaid (agent omitted it)', { status: 'done', checkLine: 'CLOSE tid=x lane=fast verdict=ok status=done' }, { verdict: 'ok' }, { ui: false }, true],
258
+ // The original divergence fixture: the words disagree → status unverified.
259
+ ['agent said done, board said picked', { status: 'done', checkLine: 'CLOSE tid=task:01a07591e5c350a306c93933 lane=none verdict=red status=picked', tagged: true }, { verdict: 'ok' }, { ui: false }, false],
260
+ // A lost Close agent: no line, nothing known.
261
+ ['the close agent was lost', null, { verdict: 'ok' }, { ui: false }, false],
262
+ ]
263
+ let oldFails = 0, newFails = 0
264
+ for (const [name, c, w, t, want] of cases) {
265
+ let o, n
266
+ try { o = OLD(c, w) } catch (e) { o = `threw:${e.message}` }
267
+ try { n = NEW(c, w, t) } catch (e) { n = `threw:${e.message}` }
268
+ if (o !== want) oldFails++
269
+ if (n !== want) newFails++
270
+ console.log(` ${name.padEnd(58)} want=${String(want).padEnd(6)} OLD=${String(o).padEnd(6)} NEW=${n}${n === want ? '' : ' <-- NEW WRONG'}`)
271
+ }
272
+ check('the pre-fix closeOk line fails the rows this fix exists for', oldFails > 0,
273
+ `OLD: ${oldFails} failure(s) — must be > 0 or the proof is worthless`)
274
+ check("the file's own closeOk derivation passes every row", newFails === 0, `NEW: ${newFails} failure(s)`)
275
+ }
276
+
277
+ // The reason line has to NAME the two words, or a `fail` frame is unactionable.
278
+ check('the close fail reason names expected vs actual board status',
279
+ /expected \$\{expectedStatus\}/.test(src), 'the frame must say which status it wanted')
280
+ check('status, expectedStatus, tagged and closeOk are carried into the result',
281
+ /status, expectedStatus, tagged, closeOk, checkLine: line,/.test(src))
282
+ check('the turn line counts closeOk, not the walk verdict',
283
+ /const ok = results\.filter\(\(r\) => r\.closeOk\)\.length/.test(src),
284
+ 'a green walk whose close was refused is not an ok job')
285
+ check('a walked-ok-but-unclosed job is counted, not lost between ok and red',
286
+ /const held = results\.filter\(\(r\) => r\.verdict === 'ok' && !r\.closeOk\)\.length/.test(src))
287
+
288
+ // ── 6. THE LOST JURY — zero survivors is not a green review step ───────────
289
+ // `runReview` does `.filter(Boolean)`, so a lens that throws leaves lenses:0 —
290
+ // and the vote line computes `0 * 2 > Math.max(1, 0)` = false, i.e. NOT refuted
291
+ // (measured; a repair-pass finding asserted the opposite). The review emit then
292
+ // recorded `step:done` with reason undefined and detail {lenses:0, attempted:1}.
293
+ // Fixed at the emit, both passes, without touching the arithmetic (§4).
294
+ console.log('\n6. the review emit — a jury with zero survivors emits fail, both passes')
295
+
296
+ const lostSites = src.match(/'review',\s*r(\d)\.refuted \|\| r\1\.lenses === 0 \? 'fail' : 'ok'/g) || []
297
+ check('both review emit sites fail closed on lenses === 0', lostSites.length === 2,
298
+ `${lostSites.length} of 2 site(s) — pass 1 and pass 2 must not disagree about what a lost jury is`)
299
+ const lostReasons = src.match(/no lens survived \(\$\{r\d\.attempted\} attempted\)/g) || []
300
+ check("the reason names the lost jury — 'no lens survived (N attempted)'", lostReasons.length === 2,
301
+ `${lostReasons.length} of 2 reason(s)`)
302
+ // The arithmetic itself FAILS CLOSED: zero survivors ⇒ refuted, `>=` on the tie.
303
+ // The previous version of this check asserted the line was UNTOUCHED — a scope
304
+ // guard that kept `0*2 > 1` fail-open through two merges on 2026-09-06. The
305
+ // behavioural proof lives in factory-review-check.mjs, which lifts this line
306
+ // out of the file; here we only assert the fail-open shape is gone.
307
+ const arith2 = src.match(/const refuted = votes[^\n]*/g) || []
308
+ check('the vote arithmetic fails closed (zero survivors ⇒ refuted; no Math.max(1, …) fail-open shape)',
309
+ arith2.length === 1 && /votes\.length === 0 \? true/.test(arith2[0]) && !/Math\.max\(1, votes\.length\)/.test(arith2[0]),
310
+ arith2[0] || 'no `const refuted = votes…` line found')
311
+
312
+ // ── 7. THE EMIT CONTRACT — factory-emit.sh takes --detail k=v, not JSON ────
313
+ // Measured 2026-09-06 against the merged script: `--detail '{"slug":"x"}'` dies
314
+ // `--detail wants k=v` at EXIT 2, because its parser is
315
+ // `case "$kv" in *=*)` and a JSON blob carries no `=`. Every emit with a detail
316
+ // object — Build, Review, Prove, Close — wrote NO frame and was counted as a
317
+ // receiver rejection. A newline inside a value splits the script's own
318
+ // newline-joined heredoc and kills the whole emit the same way.
319
+ console.log('\n7. the emit contract — read off .claude/scripts/factory-emit.sh')
320
+
321
+ check('no JSON blob is passed to --detail (the script refuses it, exit 2)',
322
+ !/--detail \$\{shq\(JSON\.stringify/.test(src))
323
+ check('--detail is rendered as repeated k=v pairs', /--detail \$\{shq\(`\$\{k\}=\$\{val\}`\)\}/.test(src))
324
+ check('detail values are whitespace-collapsed (a newline splits the script\'s heredoc and kills the emit)',
325
+ src.includes("String(v).replace(/\\s+/g, ' ').trim()"))
326
+ check('an empty detail value is dropped, not posted as `k=`', /if \(!val\) continue/.test(src))
327
+ check('a detail key that would mis-split is dropped BY NAME, not silently',
328
+ /dropped detail key/.test(src))
329
+ check('the header comment states the real flag set, not `--detail <json>`',
330
+ /\[--detail k=v\]\.\.\./.test(src) && !/\[--detail <json>\]/.test(src))
331
+ check('EMIT_SCHEMA names factory-emit.sh\'s own exit codes', /0 emitted · 2 bad arguments · 3 no credential · 4 the receiver refused/.test(src))
332
+
333
+ // ── 8. READY — one frame per turn, and the slug guard runs first ───────────
334
+ // The emits were pushed one per row, before round 1 and before the slugFor
335
+ // guard: `agent()` is eager and these sit outside `parallel`, so a full page
336
+ // (200 rows) spawned 200 haiku agents ahead of the barrier that prices memory —
337
+ // and a turn about to throw on a malformed tid had already written N `ready ok`
338
+ // frames for jobs that never started.
339
+ console.log('\n8. ready — one frame per turn, after the slug guard')
340
+
341
+ const readyCalls = src.match(/emitStage\([^\n]*'ready'[^\n]*/g) || []
342
+ check('exactly one ready emit site', readyCalls.length === 1, `${readyCalls.length} site(s)`)
343
+ check('the ready emit is NOT inside a per-row loop', readyCalls.length === 1 && !/^for \(/.test(readyCalls[0].trim()),
344
+ readyCalls.length === 1 ? readyCalls[0].trim().slice(0, 80) : 'unparsed')
345
+ check('the ready frame carries the row count', readyCalls.length === 1 && /count: tasks\.length/.test(readyCalls[0]))
346
+ const guardIdx = src.indexOf('for (const t of tasks) slugFor(t.tid)')
347
+ const readyIdx = readyCalls.length === 1 ? src.indexOf(readyCalls[0]) : -1
348
+ check('the slugFor guard runs BEFORE the ready emit (a turn that throws must not have emitted first)',
349
+ guardIdx > 0 && readyIdx > guardIdx, `guard@${guardIdx} ready@${readyIdx}`)
350
+
351
+ console.log(`\nfactory-executor-check: ${fails.length ? `${fails.length} RED` : 'all green'}`)
352
+ if (fails.length) for (const f of fails) console.log(` RED ${f}`)
353
+ process.exit(fails.length ? 1 : 0)
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env bash
2
+ # factory-peak.sh — the resident price of a running tree, measured.
3
+ #
4
+ # manifest: portable
5
+ #
6
+ # WHAT IT IS. Width is an assumption until something reads the bill.
7
+ # factory-width.sh DIVIDES the free memory by an assumed price per cycle; this
8
+ # script MEASURES that price. Point it at the root pid of anything that forks —
9
+ # a parked preview server, a gate, a fleet worktree — and it samples the
10
+ # resident set of the whole process TREE until the job ends or the clock runs
11
+ # out, then prints the peak.
12
+ #
13
+ # bash .claude/scripts/factory-peak.sh --pid 12163 --seconds 30
14
+ # bash .claude/scripts/factory-peak.sh --pid "$!" --until /tmp/run.done --interval 5
15
+ # bash .claude/scripts/factory-peak.sh --self-test
16
+ #
17
+ # Output — one line per sample, then the verdict:
18
+ #
19
+ # t=0 procs=4 rss_mb=91
20
+ # t=2 procs=4 rss_mb=104
21
+ # PEAK rss_mb=104 at t=2 procs=4
22
+ #
23
+ # WHAT THE NUMBER IS. The SUM of every process's RSS in the tree. RSS
24
+ # double-counts pages that processes share — a node parent and its workerd child
25
+ # share libraries and mapped files — so the figure is an UPPER BOUND on what the
26
+ # tree costs the box. That is the conservative direction for a budget, and it is
27
+ # the same rough arithmetic gate_headroom already prices a cycle with.
28
+ #
29
+ # RSS IS A LIVE QUANTITY, NOT A CONSTANT. The OS reclaims and compresses idle
30
+ # anonymous pages, so an idle tree's resident set DECAYS while you watch it: the
31
+ # same parked preview server, untouched, read PEAK 138 MB in one 30s window and
32
+ # 87 MB in another. Neither is wrong — a peak is what the tree held while it was
33
+ # watched. Price a cycle from a window in which the tree is DOING the work being
34
+ # priced, and read an idle tree's number as its floor, not its cost under load.
35
+ #
36
+ # THE TREE. Descendants via `pgrep -P`, walked breadth-first with a seen-set and
37
+ # a depth cap (a live tree reshapes between levels; a pid must be counted once).
38
+ # If the root pid is its own process-group leader, the group's other members are
39
+ # added too (mode=children+pgid). When it is NOT — a server forked by a wrapper
40
+ # shell inherits the WRAPPER's group — that group belongs to somebody else and is
41
+ # left alone (mode=children). The mode is printed, so the number is interpretable.
42
+ #
43
+ # FAILS CLOSED. A tree that is not there is never a cheap tree:
44
+ #
45
+ # exit 2 the root pid is not alive at start nothing was measured
46
+ # exit 3 the root exited mid-run TRUNCATED, peak still printed
47
+ # exit 4 every sample read 0 MB the sampler is broken, not the tree free
48
+ # exit 5 --self-test failed
49
+ #
50
+ # `PEAK rss_mb=0` is never printed as a pass — that is the benign default this
51
+ # instrument exists to refuse.
52
+ set -uo pipefail
53
+
54
+ PID=""
55
+ INTERVAL=2
56
+ UNTIL_FILE=""
57
+ SECONDS_CAP=""
58
+ DO_SELFTEST=0
59
+ MAX_DEPTH=6
60
+ HARD_CAP="${FACTORY_PEAK_HARD_CAP:-3600}"
61
+
62
+ die() { printf '%s\n' "$*" >&2; }
63
+
64
+ usage() {
65
+ die "usage: factory-peak.sh --pid <root pid> [--interval 2] [--until <file>|--seconds N]"
66
+ die " factory-peak.sh --self-test"
67
+ }
68
+
69
+ # RED-PROOF SEAM — --self-test's red half neuters exactly this line and asserts
70
+ # the green half then fails. Keep it a one-liner.
71
+ _children() { pgrep -P "$1" 2>/dev/null; }
72
+
73
+ _alive() { kill -0 "$1" 2>/dev/null; }
74
+
75
+ # Every descendant of $1, breadth-first, each pid once, depth-capped.
76
+ _tree_pids() {
77
+ local root="$1" seen=" $1 " d=0 p c
78
+ local -a frontier=("$root") next=()
79
+ printf '%s\n' "$root"
80
+ while [ "${#frontier[@]}" -gt 0 ] && [ "$d" -lt "$MAX_DEPTH" ]; do
81
+ next=()
82
+ for p in "${frontier[@]}"; do
83
+ while read -r c; do
84
+ [ -n "$c" ] || continue
85
+ case "$seen" in *" $c "*) continue ;; esac
86
+ seen="$seen$c "
87
+ printf '%s\n' "$c"
88
+ next+=("$c")
89
+ done < <(_children "$p")
90
+ done
91
+ frontier=(${next[@]+"${next[@]}"})
92
+ d=$((d + 1))
93
+ done
94
+ }
95
+
96
+ # echoes "<rss_mb> <procs>"; 0 0 when nothing in the tree answered.
97
+ _sample() {
98
+ local pids list
99
+ pids="$(_tree_pids "$PID" | sort -un)"
100
+ if [ "$MODE" = "children+pgid" ]; then
101
+ pids="$(printf '%s\n%s\n' "$pids" \
102
+ "$(ps -axo pid=,pgid= | awk -v g="$PID" '$2 == g { print $1 }')" | sort -un)"
103
+ fi
104
+ list="$(printf '%s' "$pids" | tr '\n' ',' | sed 's/,$//')"
105
+ [ -n "$list" ] && [ "$list" != "," ] || { printf '0 0\n'; return; }
106
+ ps -o rss= -p "$list" 2>/dev/null \
107
+ | awk '{ s += $1; n++ } END { printf "%d %d\n", s / 1024, n }'
108
+ }
109
+
110
+ measure() {
111
+ local start now t mb n samples=0 truncated=0
112
+ local peak=0 peak_t=0 peak_n=0
113
+
114
+ _alive "$PID" || { die "root pid $PID is not alive — nothing to measure"; return 2; }
115
+
116
+ MODE=children
117
+ local rpgid
118
+ rpgid="$(ps -o pgid= -p "$PID" 2>/dev/null | tr -d ' ')"
119
+ [ -n "$rpgid" ] && [ "$rpgid" = "$PID" ] && MODE="children+pgid"
120
+ printf '# pid=%s mode=%s interval=%s%s%s\n' "$PID" "$MODE" "$INTERVAL" \
121
+ "${SECONDS_CAP:+ seconds=$SECONDS_CAP}" "${UNTIL_FILE:+ until=$UNTIL_FILE}"
122
+
123
+ start="$(date +%s)"
124
+ while :; do
125
+ now="$(date +%s)"; t=$((now - start))
126
+ if ! _alive "$PID"; then
127
+ truncated=1
128
+ die "# root pid $PID exited at t=${t}s — the tree is gone"
129
+ break
130
+ fi
131
+ read -r mb n <<EOS
132
+ $(_sample)
133
+ EOS
134
+ printf 't=%s procs=%s rss_mb=%s\n' "$t" "$n" "$mb"
135
+ samples=$((samples + 1))
136
+ if [ "$mb" -gt "$peak" ]; then peak="$mb"; peak_t="$t"; peak_n="$n"; fi
137
+
138
+ if [ -n "$UNTIL_FILE" ] && [ -e "$UNTIL_FILE" ]; then break; fi
139
+ if [ -n "$SECONDS_CAP" ] && [ "$t" -ge "$SECONDS_CAP" ]; then break; fi
140
+ if [ "$t" -ge "$HARD_CAP" ]; then break; fi
141
+ sleep "$INTERVAL"
142
+ done
143
+
144
+ if [ "$samples" -eq 0 ]; then
145
+ die "no sample was taken — root pid $PID died before the first read"
146
+ return 3
147
+ fi
148
+ if [ "$peak" -le 0 ]; then
149
+ die "every one of $samples sample(s) read 0 MB — the sampler could not see the tree,"
150
+ die "which is not the same as the tree being free. Refusing to print a peak."
151
+ return 4
152
+ fi
153
+ printf 'PEAK rss_mb=%s at t=%s procs=%s\n' "$peak" "$peak_t" "$peak_n"
154
+ if [ "$truncated" -eq 1 ]; then
155
+ printf 'TRUNCATED — the root exited before the window closed; the peak is a lower bound\n'
156
+ return 3
157
+ fi
158
+ return 0
159
+ }
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # --self-test: a tree whose allocation is KNOWN, then the red half.
163
+ # ---------------------------------------------------------------------------
164
+ self_test() {
165
+ local rt alloc_mb=50 base_mb expected measured delta rc
166
+ local tmpdir child root out
167
+
168
+ command -v python3 >/dev/null 2>&1 || {
169
+ die "SELF-TEST unrun — no python3 on this box to build a known tree"
170
+ return 5
171
+ }
172
+ rt=python3
173
+
174
+ tmpdir="$(mktemp -d -t factory-peak)" || { die "SELF-TEST unrun — mktemp failed"; return 5; }
175
+ child="$tmpdir/hold.py"
176
+ # The child must KEEP its pages hot, not merely allocate them. macOS compresses
177
+ # idle anonymous pages: a bytearray touched once and then slept on falls out of
178
+ # the resident set within seconds (measured — a 133MB tree read 20MB at t=3s),
179
+ # so a self-test that allocates and sleeps asserts against a number the OS has
180
+ # already taken back. Re-touching every page is what makes the allocation a
181
+ # thing the sampler can be held to.
182
+ cat > "$child" <<PY
183
+ import time
184
+ b = bytearray(${alloc_mb} * 1024 * 1024)
185
+ end = time.time() + 25
186
+ while time.time() < end:
187
+ for i in range(0, len(b), 4096):
188
+ b[i] = 1 # keep every page RESIDENT, not just allocated
189
+ time.sleep(0.05)
190
+ PY
191
+
192
+ # Baseline: what one interpreter costs holding nothing. Measured, never guessed —
193
+ # it is what makes the 30% tolerance mean something.
194
+ "$rt" -c 'import time; time.sleep(6)' &
195
+ local bpid=$!
196
+ sleep 2
197
+ base_mb="$(ps -o rss= -p "$bpid" 2>/dev/null | awk '{ printf "%d", $1 / 1024 }')"
198
+ kill "$bpid" 2>/dev/null; wait "$bpid" 2>/dev/null
199
+ case "${base_mb:-}" in ''|*[!0-9]*) base_mb=0 ;; esac
200
+ if [ "$base_mb" -le 0 ]; then
201
+ die "SELF-TEST unrun — could not read the baseline interpreter's RSS"
202
+ rm -rf "$tmpdir"; return 5
203
+ fi
204
+
205
+ # The known tree: one sh, two children each holding alloc_mb resident.
206
+ sh -c "\"$rt\" \"$child\" & \"$rt\" \"$child\" & wait" &
207
+ root=$!
208
+ sleep 2 # let both children reach the plateau
209
+
210
+ out="$(PID="$root" INTERVAL=1 SECONDS_CAP=8 UNTIL_FILE="" measure 2>&1)"
211
+ rc=$?
212
+ kill -- -"$root" 2>/dev/null
213
+ pkill -P "$root" 2>/dev/null
214
+ kill "$root" 2>/dev/null
215
+ wait "$root" 2>/dev/null
216
+ rm -rf "$tmpdir"
217
+
218
+ if [ "$rc" -ne 0 ]; then
219
+ printf '%s\n' "$out"
220
+ die "SELF-TEST FAIL — the sampler exited $rc against a known-live tree"
221
+ return 5
222
+ fi
223
+ measured="$(printf '%s\n' "$out" | awk -F'rss_mb=' '/^PEAK /{ split($2, a, " "); print a[1] }')"
224
+ case "${measured:-}" in ''|*[!0-9]*)
225
+ printf '%s\n' "$out"
226
+ die "SELF-TEST FAIL — no PEAK line to read"
227
+ return 5 ;;
228
+ esac
229
+
230
+ # The KNOWN quantity is the allocation: 2 x alloc_mb, page-touched, resident.
231
+ # The interpreter baseline is measured, but it is the unstable term (it swings
232
+ # with how far the interpreter has loaded when it is sampled), so it is never
233
+ # allowed into the FLOOR — only into the ceiling, as headroom. Asserting the
234
+ # symmetric "expected = 2*alloc + 2*baseline" would put a term that moves 36%
235
+ # between runs inside a 30% budget.
236
+ local known floor ceiling
237
+ known=$(( 2 * alloc_mb ))
238
+ floor=$(( known * 70 / 100 ))
239
+ ceiling=$(( known * 130 / 100 + 2 * base_mb ))
240
+ delta="$(awk -v m="$measured" -v k="$known" 'BEGIN { printf "%+.1f", (m - k) * 100 / k }')"
241
+ printf 'SELF-TEST known=%sMB (2x%s alloc) measured=%sMB delta=%s%% window=[%s,%s] (ceiling carries 2x%s baseline)\n' \
242
+ "$known" "$alloc_mb" "$measured" "$delta" "$floor" "$ceiling" "$base_mb"
243
+ if [ "$measured" -lt "$floor" ] || [ "$measured" -gt "$ceiling" ]; then
244
+ die "SELF-TEST FAIL — measured peak ${measured}MB is outside [${floor},${ceiling}] for a known ${known}MB allocation"
245
+ return 5
246
+ fi
247
+ printf 'SELF-TEST green half ok — tree measured within 30%% of a known allocation\n'
248
+
249
+ # --- red half: gut the recursion, assert the green half then goes RED --------
250
+ if [ "${FACTORY_PEAK_NO_RED:-0}" = "1" ]; then return 0; fi
251
+ local gutted
252
+ gutted="$(mktemp -t factory-peak-red)" || { die "SELF-TEST unrun — mktemp failed"; return 5; }
253
+ sed 's/^_children() { pgrep -P "\$1" 2>\/dev\/null; }$/_children() { :; }/' "$0" > "$gutted"
254
+ if ! grep -q '^_children() { :; }$' "$gutted"; then
255
+ rm -f "$gutted"
256
+ die "SELF-TEST unrun — could not neuter the recursion seam; the red proof did not run"
257
+ return 5
258
+ fi
259
+ FACTORY_PEAK_NO_RED=1 bash "$gutted" --self-test >/dev/null 2>&1
260
+ rc=$?
261
+ rm -f "$gutted"
262
+ if [ "$rc" -eq 0 ]; then
263
+ die "SELF-TEST FAIL — a copy with the child recursion REMOVED still passed."
264
+ die "The green half proves nothing; it is not reading the tree."
265
+ return 5
266
+ fi
267
+ printf 'SELF-TEST red-proof ok — recursion removed => exit %s (root only, tree unseen)\n' "$rc"
268
+ printf 'SELF-TEST PASS\n'
269
+ return 0
270
+ }
271
+
272
+ while [ $# -gt 0 ]; do
273
+ case "$1" in
274
+ --pid) PID="${2:-}"; shift 2 ;;
275
+ --interval) INTERVAL="${2:-}"; shift 2 ;;
276
+ --until) UNTIL_FILE="${2:-}"; shift 2 ;;
277
+ --seconds) SECONDS_CAP="${2:-}"; shift 2 ;;
278
+ --self-test) DO_SELFTEST=1; shift ;;
279
+ -h|--help) usage; exit 0 ;;
280
+ *) die "unknown argument: $1"; usage; exit 64 ;;
281
+ esac
282
+ done
283
+
284
+ if [ "$DO_SELFTEST" -eq 1 ]; then
285
+ self_test
286
+ exit $?
287
+ fi
288
+
289
+ case "${PID:-}" in
290
+ ''|*[!0-9]*) usage; exit 64 ;;
291
+ esac
292
+ case "$INTERVAL" in ''|*[!0-9]*) die "--interval must be whole seconds"; exit 64 ;; esac
293
+ [ "$INTERVAL" -lt 1 ] && INTERVAL=1
294
+ if [ -n "$SECONDS_CAP" ]; then
295
+ case "$SECONDS_CAP" in *[!0-9]*) die "--seconds must be a whole number"; exit 64 ;; esac
296
+ fi
297
+ # Neither stop condition given: bound the run rather than sample forever.
298
+ [ -z "$SECONDS_CAP" ] && [ -z "$UNTIL_FILE" ] && SECONDS_CAP=30
299
+
300
+ measure
301
+ exit $?