stream_weaver 0.3.1 → 0.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71c5865a49a8746b8592689941d1b1bd4b0d813add49bb60ee8fab85182c1f64
4
- data.tar.gz: 3c08461ec552647cb288bef820d35eaf05dd7cb79284710f9d830d9cbb25d493
3
+ metadata.gz: 0fc050e6a464c6ebf20bcbf71ebfca7ffd93c42adf2791a1dfa40adcffca6856
4
+ data.tar.gz: fa28fa299d91dbd1176182fc5f23240b8d3533772e0c5317354d3c3c68cd1e39
5
5
  SHA512:
6
- metadata.gz: 70eefa999d7ab6b8569e15f4713e60ff29693855ca789c2be276fb3d1211ede33abb819d8af3c096098ec7ac1eb1304ddc5c60da02de5ca4e2df7bd1d71dc417
7
- data.tar.gz: 9452bac8a36c5769132e0eddac60289128b311fd515e1185f3e428c64123e661721db0d2e3eaf644ca1c080df6b63d5c1d730c035211f86e884d33b7cf2c47ef
6
+ metadata.gz: e38ed59b8ebe99f1d40b802774f518a41f3e630ef4bc7513a1695762618a935e53a6226c4c352c7c9ee6d46c94f9b1b650f6b943c35d39d2c9db99dc7ee0c08f
7
+ data.tar.gz: ada2a295a50ea02c29f271bd9001565cb0e5740af5d87d197ac08a66b774d77144e4e39683feada0dc2c753f822862da295ba419ccad75ac62fb5394872c903f
data/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.2] - 2026-09-11
9
+
10
+ ### Fixed
11
+ - **`Org::Reader` code samples containing real Ruby interpolation crashed rendering** - a `code_block`/`md`/`mermaid` heredoc opened with an unquoted tag (`<<~TXT`), so a code sample legitimately showing Ruby string interpolation (e.g. `"key:#{symbol}"`) got that `#{symbol}` actually evaluated against the rendering `App` instance instead of displayed as text -- `NameError: undefined method 'symbol'`, doc fails to render entirely, both server-side (`canvas-read`, the canvas bridge) and in the Chrome extension (whose JS heredoc rewriter mirrors the same interpolation semantics for the Opal-compiled path). Heredoc tags are now single-quoted (`<<~'TXT'`), so code/prose content is always treated as literal text, matching `emit_table`'s existing `markdown: false` philosophy. Reported via a real Gist RCA doc that wouldn't render.
12
+
8
13
  ## [0.3.1] - 2026-09-11
9
14
 
10
15
  ### Added
@@ -0,0 +1,438 @@
1
+ # Audit Report Doc Genre: design
2
+
3
+ Date: 2026-09-13
4
+ Status: design only, no code. Revised 2026-09-13 against the prior-org-decisions brief; Forrest ruled on section 8 the same day (all decided, see there).
5
+ Home repo: stream_weaver (see section 1 for why).
6
+
7
+ ## 0. What this is
8
+
9
+ A reusable "audit report" document genre for StreamWeaver: one data contract, three small
10
+ components, one genre-level DSL macro, and one org-mode file schema. Any tool that
11
+ audits something (a guidance file, a skill library, a cabinet API surface) emits the
12
+ contract once and gets the same styled report: score hero, pass/warn/fail finding
13
+ cards, before/after stat panels, numbered owner to-do list.
14
+
15
+ The trigger was a competitor's LLM-generated "vault audit" page. The point here is not
16
+ to copy the page. It is that cultiv-ai already computes the audit data (wwfd-eval,
17
+ skill-ab, the drift detector, idea-ledger) and only the presentation is missing. This
18
+ genre is deterministic code from data to pixels. No LLM in the render path, which is the
19
+ whole cost argument versus routing through Artifact or a chrome mockup loop.
20
+
21
+ ## 1. Where it lives and why
22
+
23
+ - **stream_weaver** owns the genre: components, the `audit_report` macro, the org
24
+ reader/writer, the CLI dispatch. Rationale: all four render surfaces (standalone,
25
+ canvas-push, canvas-read, export) are stream_weaver's, and the org round-trip code
26
+ already lives here (`lib/stream_weaver/org/`). cultiv-ai depends on the gem by path
27
+ (Gemfile, locked at 0.3.2), so it picks the genre up with no release step.
28
+ - **cultiv-ai / wwfd** own the producers: each tool grows a thin "emit the contract"
29
+ adapter (a `--audit` flag or a `to_audit_report` method). No styling code lives there.
30
+
31
+ ## 2. What already exists (verified, do not rebuild)
32
+
33
+ | Need | Existing primitive | Where | Verdict |
34
+ |---|---|---|---|
35
+ | Document shell | `doc_header`, `doc_section_header`, `sidebar_toc`, `:doc` theme | `lib/stream_weaver/components/doc_header.rb:24-53`, `sidebar_toc.rb`, `theme.rb:199-213` | Reuse as-is |
36
+ | Big number + label | `stat_display(value:, label:, color:, size:)` | `lib/stream_weaver/components.rb:2251-2272` | Reuse inside comparison panels; too small and too bare for the hero |
37
+ | Graded colouring convention | `score_table` thresholds (green >= 70, yellow 40-69, red < 40) | `components.rb:910-921` | Reuse the thresholds, not the component |
38
+ | Two-column before/after | `comparison(before_label:, after_label:) { before {}; after {} }` | `lib/stream_weaver/components/comparison.rb` | Reuse as the layout wrapper |
39
+ | Cards, N-up grid | `card`/`card_header`/`card_body`, `grid columns: [1,2,3]` | `components.rb`, `docs/components_reference.md:182-260` | Reuse |
40
+ | Coloured chip | `badge(text, variant: :success/:warning/:danger)` | `components.rb:2227` | Reuse for the status chip |
41
+ | Severity left-border | `priority_item` CSS pattern | `components.rb:2336` | Copy the CSS idea into `finding_card` |
42
+ | Pass/warn/fail badge | `status_badge(:strong/:maybe/:skip)` | `components.rb:1378` | Do not reuse: labels are domain-hardcoded |
43
+ | Checklist | `md` task lists: kramdown GFM renders `- [ ]`/`- [X]` as static checkboxes | `adapter/alpinejs.rb:2023-2029`, verified by running kramdown | Reuse; needs CSS only (no `.task-list` rules exist in `views.rb`) |
44
+ | Org round-trip | `Org::Reader` / `Org::Writer`, `streamweaver org-export` / `org-render` | `lib/stream_weaver/org/reader.rb`, `cli.rb:2006-2035` | Narrow dialect, see section 6 |
45
+
46
+ Prior-art report shells: `docs/streamweaver_canvas/mailroom-incident-20260813-1123.rb`
47
+ and `pm-discount-feasibility-20260819-1844.rb` (toc + header + numbered sections +
48
+ callouts). None combine the doc shell with the metric components; this genre is the
49
+ first to do so.
50
+
51
+ ## 3. Data contract: AuditReport v1
52
+
53
+ Wire format is a plain Hash (YAML or JSON on disk). Every tool already emits JSON or
54
+ YAML, so this is the cheapest thing for a producer to hit. Org is the persisted,
55
+ human-editable form of the same data (section 6); the DSL is the render.
56
+
57
+ ```yaml
58
+ audit_report: 1 # schema version, required
59
+ title: "WWFD guidance eval" # required
60
+ subject: "patterns/wwfd-claude-guidance.yaml" # what was audited, optional
61
+ tool: "wwfd-eval" # producer id, optional, shown as a header pill
62
+ generated_at: "2026-09-13T08:40:00-04:00"
63
+
64
+ score: # required: the genre's identity is the hero number
65
+ value: 87 # numeric
66
+ max: 100 # optional; absent means "display value as-is" (a KPI, not a ratio)
67
+ unit: "%" # optional display unit
68
+ grade: "B" # optional; producer-supplied, never derived by the renderer
69
+ label: "Guidance coverage" # required; the small caption under the number
70
+ headline: "7 of 8 patterns pass" # optional; the one-line stat beside the number
71
+ delta: -13 # optional; signed change vs baseline, rendered as a chip
72
+ baseline_label: "baseline 2026-09-01"
73
+
74
+ summary: | # optional markdown, rendered under the hero
75
+ One paragraph of what this audit is and what changed.
76
+
77
+ findings: # optional list; section omitted when empty
78
+ - id: f1 # required, stable, referenced by todos
79
+ status: fail # required: pass | warn | fail | skip
80
+ category: "delegation" # optional short chip (the "framework tag")
81
+ title: "Delegation ladder is not a trigger" # required
82
+ body: | # optional markdown
83
+ The guidance names the failure mode but never fires on it.
84
+ owner: forrest # optional
85
+ fix: "Add lean-coordinator as a pattern trigger" # optional recommended action
86
+ fixed: false # optional; true means "fix applied", rendered as a struck/green state
87
+
88
+ before_after: # optional; section omitted when absent
89
+ before_label: "Baseline 2026-09-01"
90
+ after_label: "This run"
91
+ metrics:
92
+ - label: "Score"
93
+ before: 100
94
+ after: 87
95
+ unit: "%"
96
+ good: up # up | down | none: which direction is an improvement
97
+
98
+ fixes_applied: # optional list of {title, body}; rendered as success callouts
99
+ - title: "Renamed trigger X"
100
+ body: "…"
101
+
102
+ todos: # optional; section omitted when empty
103
+ - title: "Add delegation-ladder trigger"
104
+ owner: forrest
105
+ due: 2026-09-20
106
+ done: false
107
+ ref: f1 # optional finding id; renders as a link to the card
108
+ ```
109
+
110
+ Rules the renderer enforces:
111
+
112
+ - `skip` is a real fourth status ("could not evaluate"), not a warn. The drift detector's
113
+ `:error` state and skill-ab's `not_yet_tested` both need it.
114
+ - Findings render fail, then warn, then pass, then skip. Counts per status go in the
115
+ section header as chips, so the "what the audit found" summary is free.
116
+ - The hero colour follows `score_table`'s thresholds on `value / max` when `max` is
117
+ present, and is neutral when it is absent. Grade is display-only.
118
+ - Everything except `audit_report`, `title`, and `score` is optional. A report with
119
+ only a hero and a before/after block (delegation-roi) is valid.
120
+
121
+ ### 3.1 How each cultiv-ai tool maps onto it
122
+
123
+ | Tool | score | findings.status | before/after | todos | Fit |
124
+ |---|---|---|---|---|---|
125
+ | wwfd-eval | percent, real `delta` vs `evals/baseline.json` | 1/0 per pattern -> pass/fail | baseline vs current score, natively | failed patterns | Strong |
126
+ | skill-ab report | share of skills with `earns_its_place` | earns_its_place->pass, inconclusive/not_yet_tested->warn/skip, deletion_candidate->fail | bare vs skill arm counts | deletion candidates, low-agreement skills | Good, lossy remap |
127
+ | Drift detector | `(total - failures) / total` | clean->pass, violations->fail, error->skip | none | `ScanProposal.proposed_action` | Good |
128
+ | idea-ledger report | derived from counts (shipped share) | shipped->pass, advancing->warn, declined->fail, filed->skip | none | its "Follow-ups" section | Usable, semantics stretched |
129
+ | delegation-roi | `minutes_saved`, no max | none (empty list) | window vs prior window, natively | none | Hero + before/after only |
130
+ | meeting-signals | none | none | none | owner + item rows | Poor; to-do only, do not force |
131
+
132
+ `ScanProposal` (`lib/cultiv_cabinet/secretary/scan_proposal.rb`) is already the shared
133
+ per-finding record across dozens of scans. A one-method mapping (`trigger_condition` ->
134
+ title, `proposed_action` -> fix, `urgency` P0/P1/P2 -> fail/warn/pass, `route_to` ->
135
+ owner, `proposal_type` -> category) turns every existing scanner into a producer without
136
+ touching the scanners.
137
+
138
+ ## 4. Component set
139
+
140
+ Four additions to stream_weaver: three components and one macro. All static, canvas-safe by construction (no
141
+ `sendEvent`, no state), so they work in live canvas, canvas-read, and export. Each
142
+ needs the Alpine adapter render, the static adapter render, CSS in the `views.rb`
143
+ stylesheet heredoc (`sw-` BEM), and a `spec/components_spec.rb` block, following the
144
+ `stat_display` pattern (`components.rb:2251`, `display_dsl.rb:161`,
145
+ `adapter/alpinejs.rb:7129-7136`, `views.rb:2862+`).
146
+
147
+ 1. **`score_hero(value:, label:, max: nil, unit: nil, grade: nil, headline: nil, delta: nil, baseline_label: nil)`**
148
+ New. A hero band: the number at display-font scale with the grade beside it, the
149
+ label under, the headline stat and delta chip to the right. Reuses the `.sw-stat`
150
+ colour vocabulary. Why not `stat_display size: :xl`: the hero needs three extra
151
+ slots (grade, headline, delta), not a bigger font.
152
+ 2. **`finding_card(status:, title:, category: nil, owner: nil, fixed: false) { body }`**
153
+ New, thin. `card` with a status-coloured left border (the `priority_item` idea), a
154
+ `badge` chip for the status, a muted chip for the category, and an optional owner
155
+ line in the footer. `fixed: true` swaps the chip to "fixed" and dims the card.
156
+ 3. **`stat_comparison(before_label:, after_label:, metrics: [...])`**
157
+ New, thin. `comparison` layout with one `stat_display` per metric in each panel, plus
158
+ a delta chip coloured by `good:`. Why a component and not a macro: the org reader and
159
+ writer need one construct to map a table onto, and export parity needs one render
160
+ method.
161
+ 4. **`audit_report(data)`** the genre macro, the single chokepoint.
162
+ Takes the contract Hash and emits: `sidebar_toc` (only for sections present),
163
+ `doc_header` (eyebrow = tool, title, pills = date, subject, grade), `score_hero`,
164
+ summary `md`, section 1 "What the audit found" as `grid columns: [1,2,3]` of
165
+ `finding_card`, section 2 `stat_comparison`, section 3 fixes as `callout(:success)`
166
+ rows, section 4 the to-do list as one `md` block of GFM checkbox lines in the fixed
167
+ convention `- [ ] title · owner · due · ref:f1`, preceded by an `[n/N]` cookie the
168
+ macro computes from `done` at render time (Forrest's ruling, decision 5). No
169
+ checklist component: kramdown already renders task lists as static checkboxes, so
170
+ the only work is `.task-list` CSS in `views.rb`. Validates the contract and raises with a path on the
171
+ first bad field, so a producer bug is a loud error and not a half-rendered page.
172
+ Lives in a new `lib/stream_weaver/genres/audit_report.rb` and registers one DSL
173
+ method; the doc-builder skill gains a short "audit report" reference section.
174
+
175
+ Standalone use is the doc-builder body pattern unchanged:
176
+
177
+ ```ruby
178
+ # streamweaver-doc: v1
179
+ audit_report YAML.safe_load_file("wwfd-eval-2026-09-13.yaml", symbolize_names: true)
180
+ ```
181
+
182
+ and `streamweaver canvas-push audit < body.rb` pushes it.
183
+
184
+ ## 5. Render pipeline
185
+
186
+ ```
187
+ producer (wwfd-eval, skill-ab, ...) --emits--> contract YAML/JSON (wire format, not persisted)
188
+ |
189
+ audit_report(hash) DSL body
190
+ |
191
+ streamweaver org-export (existing Org::Writer, extended)
192
+ v
193
+ report.org <-- greppable, diffable, versioned source of truth
194
+ |
195
+ Org::Reader (existing, extended) <-- canvas-read, org-render, extension preview
196
+ v
197
+ DSL --> standalone | canvas-push | canvas-read | export
198
+ ```
199
+
200
+ Nothing new at the CLI. The `.org` file is a plain instance of the shipped StreamWeaver
201
+ org dialect (`docs/superpowers/specs/2026-08-13-org-doc-format-design.md`), so every
202
+ surface that already understands that dialect delivers the genre for free: canvas-read
203
+ native `.org` (S1 in `docs/plans/org-doc-preview-surfaces.md`, shipped), the browser
204
+ extension's local-file drag-drop and Gist preview (S2, S3, shipped), and `org-render |
205
+ canvas-push`. Two surfaces do not take `.org` yet and are out of scope here:
206
+ `canvas-push` pipes stdin verbatim (hence the `org-render` step), and `streamweaver
207
+ export file.org` fails today (open mark disc-175); static HTML of an audit comes from
208
+ canvas-read's export or from `org-render file.org > body.rb` then `export body.rb`. The first line stays the literal `#+STREAMWEAVER_DSL: 1` so
209
+ every detection surface (content.js, sandbox.js, canvas-read, `doc_store.rb:167`) keeps
210
+ working.
211
+
212
+ There is no hand-rolled org emitter and no second parser. Producers never write org
213
+ directly; they hand a Hash to `audit_report`, and the existing writer serialises the
214
+ expanded component tree. orgkit is not a dependency of the genre. It becomes relevant
215
+ only if a tool later needs to mutate an existing report file in place (for example
216
+ ticking a to-do box), which is orgkit's byte-span mutation contract and
217
+ out of scope here.
218
+
219
+ No LLM is called anywhere in this pipeline. The token cost of showing an audit is the
220
+ cost of one `canvas-push` of a one-line body, or zero if the reader opens the `.org`
221
+ in canvas-read.
222
+
223
+ ## 6. Org schema for the source file
224
+
225
+ The peer session's prior-decisions brief (2026-09-13) rules that the audit `.org` must
226
+ be an instance of the shipped dialect, not a parallel one: callouts and cards ride
227
+ inside `#+begin_quote` because org-ruby drops unknown block types entirely, the
228
+ round-trip bar is rendered-equivalent not byte-identical, and no hand-rolled org
229
+ emitters. This section is designed to that ruling. An earlier draft of this section
230
+ used tags, free property drawers and TODO headlines; it was dropped because the
231
+ dialect's reader has none of those (`reader.rb:29` numbered headlines only,
232
+ `reader.rb:215-223` two property keys and a mandatory `CUSTOM_ID`).
233
+
234
+ ### 6.1 Principle: the components self-identify, the genre needs no reader
235
+
236
+ The four new components each get one org construct that (a) is a real org element
237
+ every generic renderer already shows, (b) is discriminated by the dialect's existing
238
+ mechanisms (quote-block marker line, `#+ATTR_STREAMWEAVER:` on a table), and (c)
239
+ degrades to the nearest plain construct in a viewer that does not know StreamWeaver.
240
+ Because the components self-identify, `#+STREAMWEAVER_GENRE: audit_report` is
241
+ advisory metadata only (tolerated by `PREAMBLE_RE`, `reader.rb:47`); the file is an
242
+ ordinary dialect doc that happens to use these constructs. No genre dispatch, no
243
+ reserved section names.
244
+
245
+ | Component | Org construct | Discriminator (Phase 3 addition to the dialect) | Degrades to |
246
+ |---|---|---|---|
247
+ | `score_hero` | quote block | marker line starts with `*🎯 ` (new reserved emoji, rule 3 table) | a blockquote showing the score |
248
+ | `finding_card` | quote block, card syntax | card whose `[badge]` is exactly `pass`, `warn`, `fail` or `skip` (rule 4 refinement); `/(meta)/` is the category | a plain card |
249
+ | `stat_comparison` | org table | `#+ATTR_STREAMWEAVER: :stat_comparison t :before "…" :after "…"` on the line before | a plain table |
250
+ | to-do list | plain org checkbox list inside prose | none needed; it is ordinary `md` text to the reader (`reader.rb:327-330`) and round-trips byte-identical | a native org checkbox list |
251
+ | fixes applied | existing ✅ callout | none needed | already in dialect |
252
+ | sections | existing numbered headlines with `:CUSTOM_ID:` | none needed | already in dialect |
253
+
254
+ Finding scalar fields that have no slot in the card syntax (`id`, `owner`, `fix`,
255
+ `fixed`) ride as git-style trailer lines at the end of the card body: `Id: f1`,
256
+ `Owner: forrest`, `Fix: …`, `Fixed: yes`. The reader strips recognised trailers into
257
+ the component; anything else stays body text.
258
+
259
+ ### 6.2 The file
260
+
261
+ ```org
262
+ #+STREAMWEAVER_DSL: 1
263
+ #+TITLE: WWFD guidance eval
264
+ #+STREAMWEAVER_GENRE: audit_report
265
+ #+DATE: 2026-09-13
266
+
267
+ #+begin_quote
268
+ wwfd-eval
269
+ WWFD guidance eval
270
+ 2026-09-13 · patterns/wwfd-claude-guidance.yaml · [B](good)
271
+ #+end_quote
272
+
273
+ #+begin_quote
274
+ *🎯 87/100% B — Guidance coverage*
275
+ 7 of 8 patterns pass
276
+ Δ -13 vs baseline 2026-09-01
277
+ #+end_quote
278
+
279
+ One paragraph of summary. Plain org prose, rendered as markdown.
280
+
281
+ * 1 What the audit found
282
+ :PROPERTIES:
283
+ :CUSTOM_ID: findings
284
+ :END:
285
+
286
+ #+begin_quote
287
+ *[fail] Delegation ladder is not a trigger* /(delegation)/
288
+ The guidance names the failure mode but never fires on it.
289
+
290
+ Id: f1
291
+ Owner: forrest
292
+ Fix: Add lean-coordinator as a pattern trigger
293
+ Fixed: no
294
+ #+end_quote
295
+
296
+ #+begin_quote
297
+ *[pass] Complexity skepticism* /(complexity_skepticism)/
298
+ The guidance explicitly names the failure mode and gives the concrete instruction.
299
+
300
+ Id: f2
301
+ #+end_quote
302
+
303
+ * 2 Before and after
304
+ :PROPERTIES:
305
+ :CUSTOM_ID: before-after
306
+ :END:
307
+
308
+ #+ATTR_STREAMWEAVER: :stat_comparison t :before "Baseline 2026-09-01" :after "This run"
309
+ | Metric | Before | After | Unit | Good |
310
+ |---|---|---|---|---|
311
+ | Score | 100 | 87 | % | up |
312
+ | Patterns passing | 8 | 7 | | up |
313
+
314
+ * 3 Fixes applied
315
+ :PROPERTIES:
316
+ :CUSTOM_ID: fixes
317
+ :END:
318
+
319
+ #+begin_quote
320
+ *✅ Renamed trigger X*
321
+ Body of what was changed.
322
+ #+end_quote
323
+
324
+ * 4 Owner to-do
325
+ :PROPERTIES:
326
+ :CUSTOM_ID: todo
327
+ :END:
328
+
329
+ Owner to-do [1/2]
330
+
331
+ - [ ] Add delegation-ladder trigger · forrest · 2026-09-20 · ref:f1
332
+ - [X] Re-run wwfd-eval after the rename · forrest
333
+ ```
334
+
335
+ Greppability in practice: `grep -l '^\*\[fail\]' reports/*.org` lists every audit
336
+ with an open failure; `grep -h '^\*🎯' reports/*.org` is the score history; `git diff`
337
+ on a re-run shows exactly which cards flipped badge and which boxes got ticked;
338
+ `grep -h '^- \[ \]' reports/*.org` is every open to-do across all audits. Editing by
339
+ hand is changing `[fail]` to `[pass]` or `[ ]` to `[X]`. Owner, due and ref are
340
+ text-by-convention, so promotion to UTF reads them with one regex; the report keeps
341
+ the checked snapshot with the ref, which is the org-native shape for that.
342
+
343
+ ### 6.3 What the Phase 3 dialect extension touches
344
+
345
+ Small, and all inside the existing reader and writer:
346
+
347
+ - `Org::Reader`: add 🎯 as a score marker (a separate table from `VARIANT_EMOJI` so
348
+ callouts are untouched); in `emit_card` (`reader.rb:427`) route badge ∈
349
+ {pass, warn, fail, skip} to `finding_card` and parse trailers; in `emit_table`
350
+ (`reader.rb:332`) honour `:stat_comparison` in `pending_attr` alongside `:markdown`.
351
+ - `Org::Writer`: three new `when` branches in `render_component` (`writer.rb:137`) so
352
+ the components stop falling into the `:streamweaver-raw` escape hatch, which is what
353
+ would happen today and is neither greppable nor editable.
354
+ - The spec gets a dated Phase 3 section listing the two quote-block discriminators and the one
355
+ table attribute, with the rule-3 emoji table extended and the rule-4 badge
356
+ refinement stated as an accepted edge case (a plain card badged literally "pass" is
357
+ a finding card; do not badge ordinary cards with those four words).
358
+ - Round-trip spec: `spec/org/round_trip_spec.rb` gains the file above and asserts
359
+ rendered-equivalence.
360
+ - Body emission rule for the macro: `finding_card` and callout bodies must be emitted
361
+ with `md`, never `text`, and must not nest other components. `Org::Writer` has no
362
+ case arm for `text` or nested components inside a callout and drops them silently on
363
+ export (open mark disc-170). The contract already makes `body` markdown, so this is a
364
+ constraint on the macro, not on producers.
365
+
366
+ Accepted trade-off (decided): a checkbox list is invisible to org-agenda. `TODO`/`DONE`
367
+ headlines with `DEADLINE:` would need the dialect to learn TODO keywords and unnumbered
368
+ headlines, a bigger change than the genre justifies; revisit only if agenda visibility
369
+ turns out to matter.
370
+
371
+ ## 7. First consumer and build sequence
372
+
373
+ ### 7.1 First consumer: wwfd-eval
374
+
375
+ Chosen over the two flagged candidates:
376
+
377
+ - **skill-ab report** has zero recorded runs in this environment. There is no real data
378
+ to prototype against, and a report designed against a synthetic fixture is the kind of
379
+ demo-first work the AI Theater test exists to catch. It is the right second consumer:
380
+ its verdict remap and deletion candidates exercise the warn/skip statuses and the
381
+ to-do section that wwfd-eval does not.
382
+ - **coherence-check** is really two things. The CLI is unstructured RSpec prose and its
383
+ own header documents why that was abandoned. The structured half, the drift detector,
384
+ is quiet-on-success, has no before/after, and does not itemise findings past four fixed
385
+ category labels.
386
+ - **wwfd-eval** has real data on disk today (`evals/baseline.json`, 8 per-pattern results
387
+ with reasoning), the only genuine persisted baseline-versus-current score delta in the
388
+ whole set, one finding per pattern with a category and a body, and a to-do list that
389
+ falls out of the failures. It exercises the hero, the finding grid, and the before/after
390
+ block with real values on day one. It is also run by protocol before every WWFD change
391
+ (the global CLAUDE.md change protocol), so the report changes a real decision, commit or
392
+ not, on a recurring basis rather than being looked at once.
393
+
394
+ Its one gap, binary pass/fail with no warn, is fine: warn is optional in the contract and
395
+ skill-ab covers it next.
396
+
397
+ ### 7.2 Sequence (pareto rigor: lanes, rigor per story)
398
+
399
+ | Step | Lane | Rigor | What | Done when |
400
+ |---|---|---|---|---|
401
+ | 1 | components | strict | `score_hero`, `finding_card`, `stat_comparison`: class, DSL, Alpine + static adapters, CSS, specs; `.task-list` CSS for md checkbox lists | `bundle exec rspec` green; `examples/canvas-safe-showcase.rb` gains all three plus a checkbox list; export of the showcase renders them |
402
+ | 2 | components | strict | `audit_report(hash)` macro with contract validation, `examples/audit_report/` body + wrapper, doc-builder skill section | A hand-written YAML matching section 3 renders standalone and via canvas-push identically |
403
+ | 3 | consumer | loose | A 30-line script in wwfd turning `evals/baseline.json` plus a fresh run into contract YAML; push to canvas; Forrest reviews the real thing | Forrest has seen a real wwfd audit on canvas. This is the SDRD checkpoint: the visual reveals what v2 should be before the org work is built |
404
+ | 4 | org | strict | Phase 3 dialect extension: writer branches, reader discriminators and the table attribute, spec amendment, round-trip spec | `org-export` of the step-3 body produces the section 6.2 shape with no `:streamweaver-raw` blocks; `org-render` of it back renders equivalent; canvas-read opens the `.org` directly |
405
+ | 5 | consumer | loose | `bin/wwfd-eval --audit PATH.yaml` in wwfd; `bin/skill-ab report --audit` in cultiv-ai once it has runs; a `ScanProposal` -> finding mapper | Two producers, zero styling code in either repo |
406
+
407
+ Steps 1 and 2 are one warmed builder lane (same files). Step 3 comes before step 4 on
408
+ purpose: seeing the rendered report with real data is cheaper than guessing. Steps 4
409
+ and 5 can run in parallel once 3 is reviewed. Verification item for step 4: confirm
410
+ `Org::Writer.from_dsl` sees the components `audit_report` expands to, not the macro
411
+ call itself (it records the component tree via the recording context, so it should).
412
+
413
+ ## 8. Decisions (ruled by Forrest, 2026-09-13)
414
+
415
+ 1. **Home repo: stream_weaver** owns the genre; cultiv-ai and wwfd own producer
416
+ adapters only. (Alternative rejected: cultiv-ai `lib/stream_weaver_ext/`.)
417
+ 2. **Phase 3 dialect extension: yes.** Two quote-block discriminators (🎯 score marker,
418
+ pass/warn/fail/skip card badge) and one table attribute (`:stat_comparison`) amend
419
+ the shipped org-doc-format spec, so the three components never fall into the raw
420
+ escape hatch on export.
421
+ 3. **Static checklist: moot** under decision 5; a checkbox list is inherently static.
422
+ 4. **First consumer: wwfd-eval**, skill-ab second once it has recorded runs.
423
+ 5. **To-dos: option C, a plain org checkbox list** in the convention
424
+ `- [ ] title · owner · due · ref:f1`, with the macro writing the `[n/N]` cookie.
425
+ The `checklist` component and the `:checklist t` table attribute are removed from
426
+ the plan (three new components, not four). Rationale: live items get promoted to
427
+ UTF and the report keeps a checked snapshot with the ref; checkboxes are the
428
+ org-native shape for that, and kramdown already renders them (section 2).
429
+ (Alternatives rejected: a `:checklist` table; `TODO`/`DONE` headlines.)
430
+
431
+ ## 9. Sources
432
+
433
+ Survey reports from this session (scratchpad, not in repo): stream_weaver primitives and
434
+ org reader scope; cultiv-ai and wwfd tool output shapes; orgkit and cultiv-ai org
435
+ conventions. Key repo references: `docs/superpowers/specs/2026-08-13-org-doc-format-design.md`
436
+ (existing org dialect), `lib/stream_weaver/skills/streamweaver-doc-builder/SKILL.md`,
437
+ `lib/stream_weaver/skills/streamweaver-canvas-safe/SKILL.md`, Tyrion marks disc-170,
438
+ disc-175, disc-182 (org gaps).
@@ -532,13 +532,26 @@ module StreamWeaver
532
532
  # closing tag -- get this backwards and the emitted Ruby won't parse.
533
533
  # Also guards against a body line that happens to equal the tag itself
534
534
  # (which would terminate the heredoc early) by picking a fresh tag.
535
+ #
536
+ # The opening tag is single-quoted (`<<~'TAG'`), NOT bare -- md/mermaid/
537
+ # code_block content is verbatim prose or source code being displayed,
538
+ # never something that should be re-interpreted as Ruby. A bare `<<~TAG`
539
+ # heredoc still performs string interpolation and backslash escapes, so
540
+ # a code sample showing real Ruby string interpolation (e.g.
541
+ # `"key:#{symbol}"`) got that `#{symbol}` actually evaluated against the
542
+ # rendering App instance at instance_eval time, raising a NameError
543
+ # instead of displaying the snippet (reported: a real-world RCA gist
544
+ # with `"corporate-actions-catch-up:#{symbol}"` in a code_block
545
+ # wouldn't render -- "undefined method `symbol'"). Quoting the tag
546
+ # disables both, matching the "preserve literal text" behavior emit_table
547
+ # already relies on for markdown: false tables.
535
548
  def heredoc(prefix, text, base_tag, suffix: "")
536
549
  tag = unique_heredoc_tag(base_tag, text)
537
550
  content = text.split("\n", -1)
538
551
  content.pop if content.last == ""
539
552
  content = [""] if content.empty?
540
553
 
541
- "#{prefix}<<~#{tag}#{suffix}\n#{content.map { |line| line.empty? ? line : " #{line}" }.join("\n")}\n#{tag}"
554
+ "#{prefix}<<~'#{tag}'#{suffix}\n#{content.map { |line| line.empty? ? line : " #{line}" }.join("\n")}\n#{tag}"
542
555
  end
543
556
 
544
557
  def unique_heredoc_tag(base_tag, text)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StreamWeaver
4
- VERSION = "0.3.1".freeze
4
+ VERSION = "0.3.2".freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stream_weaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Forrest Chang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-11 00:00:00.000000000 Z
11
+ date: 2026-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -240,6 +240,7 @@ files:
240
240
  - docs/plans/2026-01-10-canvas-ipc-design.md
241
241
  - docs/plans/2026-01-19-cabinet-control-components.md
242
242
  - docs/plans/2026-01-26-gem-release-and-panel.md
243
+ - docs/plans/2026-09-13-audit-report-doc-genre-design.md
243
244
  - docs/plans/canvas-claude-project-design.md
244
245
  - docs/plans/canvas-doc-location-and-discovery.md
245
246
  - docs/plans/org-doc-preview-surfaces.md