- name
- post-publish
- description
- Close the feedback loop. Canonize to Supermemory, update world-data, write ratings to sheets, grade reporters, update criteria files, update newsroom memory. Type-aware — edition, interview, supplemental, dispatch all converge here.
- version
- 1.12
- updated
- 2026-07-11T00:00:00.000Z
- tags
- ["media","active"]
- effort
- high
- disable-model-invocation
- true
- argument-hint
- [--type edition|interview|supplemental|dispatch] [--cycle XX] [--source <path>]
# /post-publish — Feedback Loop Closer
## Purpose
The published artifact is canon-on-disk but not yet canon-in-memory. This skill canonizes it (bay-tribune, world-data), writes feedback (sheets, grades), and updates the files that train future cycles.
Everything this skill does makes the next cycle smarter than the last one.
**This is the convergence point** for editions and non-editions. `/write-edition`, `/interview`, `/dispatch`, `/write-supplemental` all converge here via `--type`. The format contract ([[EDITION_PIPELINE]] §Published `.txt` Format Contract) is the API: every type emits the same `.txt` shape, and post-publish reads from that uniformly.
These steps are base concepts. Each cycle exposes new gaps — a new ingest need, a feedback source we didn't account for, a step that needs splitting or combining. Evaluate after every run and update. Not done — compounding.
This is currently a teamwork skill — Mags and Mike walk through it together. As steps prove reliable, they automate one at a time.
## Usage
`/post-publish --type <type> --cycle <XX> --source <path>`
Flags:
- `--type {edition|interview|supplemental|dispatch}` — defaults to `edition`. Required for non-edition artifacts.
- `--cycle <XX>` — engine cycle number. Required.
- `--source <path>` — path to the published `.txt`. Defaults to `editions/cycle_pulse_<type>_<cycle>_<slug>.txt` for non-edition; `editions/cycle_pulse_edition_<cycle>.txt` for edition.
- `--skip-<name>` — explicit opt-out for any substep that the type matrix marks ✓. Silent omission of a ✓ substep is a skill failure.
### Per-type substep matrix
| Step | edition | interview | supplemental | dispatch |
|------|---------|-----------|--------------|----------|
| 0 staleness gate | — | — | — | — | (RETIRED S225 — see §Step 0 below)
| 1a wiki ingest | ✓ | ✓ | ✓ | ✓ |
| 1b text ingest | ✓ | ✓ (transcript only — see §Step 1) | ✓ | ✓ |
| 1c NotebookLM push | ✓ (with `--audio`) | ✓ (source only) | ✓ (source only) | ✓ (source only) |
| 2a citizen cards | ✓ | ✓ | ✓ | ✓ |
| 2a-cul cultural cards | — | ✓ (when CUL-IDs present) | ✓ (when CUL-IDs present) | ✓ (when CUL-IDs present) |
| 2e voice reflection ingest | — | ✓ (citizen-voice subjects only) | — | — |
| 2b new businesses | ✓ | ✓ | ✓ | ✓ |
| 2d truesource sweep | ✓ | — | ✓ | — |
| 2c world summary | ✓ | — | — | — |
| 3 civic wiki | ✓ | — | — | — |
| 4 coverage ratings | ✓ | — (C93-gated) | — (C93-gated) | — (C93-gated) |
| 5 citizen+business intake | ✓ | ✓ | ✓ | ✓ |
| 5-bis wd-card rebuild (new POPIDs) | ✓ | ✓ | ✓ | ✓ |
| 6 grade | ✓ | — | — | — |
| 7 grade history | ✓ | — | — | — |
| 8 exemplars | ✓ | — | — | — |
| 9 newsroom memory | ✓ | ✓ | ✓ | ✓ |
| 10 criteria files | ✓ | — | — | — |
| 10b archive briefing bundle | ✓ | — | — | — |
| 11 filing + bot | ✓ | ✓ | ✓ | ✓ |
| 12 production log | ✓ | ✓ | ✓ | ✓ |
| 13 checklist | ✓ | ✓ | ✓ | ✓ |
`—` = skipped by default for that type (no flag needed; declared by the matrix).
C93-gated = skipped pending the C93 observation outcome (plan [[plans/2026-04-26-non-edition-publishing-pipeline]] T8). The `--skip-<name>` flag is only required to opt out of substeps the matrix marks ✓ for the chosen type.
## Prerequisites
Verify these exist before starting (path varies by `--type`):
- `<source>` — published `.txt` (the canonical artifact)
- `output/production_log_c{XX}.md` — consolidated per-cycle production log (S195 convention; merges city-hall-prep + city-hall-run + sift + write-edition + post-publish into one log per cycle)
- `output/world_summary_c{XX}.md` — world summary
For `--type interview`: `<source>` IS the transcript at `editions/cycle_pulse_interview-transcript_{XX}_<slug>.txt` (capture-only since /interview v2.0, S233 pipeline.30 — the legacy bare article `cycle_pulse_interview_*.txt` is retired). No companion artifact; single canonical transcript.
## Parallelization Notes (S215, closes G-P5)
Several substeps are independent and can run concurrently to compress wall time. Default to running these in parallel via separate background invocations:
| Group | Substeps | Constraint |
|-------|----------|-----------|
| **Parallel-OK** | 2a citizen cards, 2c world summary, 4 coverage ratings, 1c NotebookLM push | No interdependency. Each writes a different surface (world-data citizens, world-data summary, sheets, NotebookLM). Run concurrently. 1c's audio wait (~2-6 min) hides entirely inside 2a's 30-40 min wall time. |
| **Sequential after parallel group** | Step 5 (citizen+business intake) | Depends on Step 2a completion via the verification gate (NAMES INDEX presence + parsed count). |
| **Sequential after Step 5** | Step 5-bis (wd-card rebuild for newly-appended POPIDs) | Depends on Step 5 output JSON for `appended[].popid` range. Closes G-P32 one-cycle card lag per [[../../../docs/adr/0007-cross-layer-canon-authority-precedence|ADR-0007]]. |
| **Sequential after Step 5-bis** | Steps 6 (grade), 7 (grade history), 8 (exemplars) | Depend on Step 5's intake JSON for entity attribution; Step 5-bis must complete first so newly-appended citizens have wd-cards for grade lookups. |
| **Parallel-OK with the grade group** | Step 9 (newsroom memory), Step 10 (criteria files) | Independent of the grade-chain output; can run alongside Steps 6-8. |
| **Sequential at end** | Step 11 (filing + bot), Step 12 (production log), Step 13 (checklist) | Final state-of-the-skill steps; run after all prior substeps complete. |
Wall-time saving on edition runs: ~60-90s (Step 2a citizen cards is the slowest substep; running 2c + 4 against its wall time recovers their duration).
## Steps
### Step 0: Pre-flight Staleness Gate — RETIRED S225 (pipeline.23, closes G-P28)
**Retired.** The S215 staleness gate modeled the wrong dependency direction. `scripts/checkPostPublishStaleness.js` compared `world_summary` + `engine_audit` mtimes against the `/city-hall` production log, but `world_summary` is INPUT to `/city-hall` (built before it), not output — so the gate fired STALE every cycle by design flaw (G-P28, G-S11). The advisory line "Re-run /build-world-summary before continuing" misled the operator into spurious rebuilds; Mike spent cycles escalating "should we rebuild?" with no real signal underneath.
If real sequencing concerns arise (e.g., engine produced new state but world_summary wasn't rebuilt), compare `output/world_summary_c<XX>.md` mtime against `output/engine_audit_c<XX>.json` manually — that's the correct upstream baseline. Engine-sheet may retire `scripts/checkPostPublishStaleness.js` + `lib/staleness.js` in a future sweep (vestigial after this retirement).
### Step 1: Bay-Tribune Ingest — Canon
**1a. Wiki ingest — per-entity records (PRIMARY)**
```bash
node scripts/ingestEditionWiki.js <source> --type <type> --apply
```
Extracts per-citizen, per-initiative, per-business, per-storyline records from the standardized sections (NAMES INDEX, BUSINESSES NAMED) into bay-tribune. Each entity gets its own searchable record. Log entity count.
**Verification gate:** stdout reports entity count ≥ 1 OR explicit "0 entities — body had no named citizens/businesses" (acceptable for pure-atmosphere dispatches). Fail otherwise.
Review: spot-check 2-3 records against the article body.
**1b. Text ingest (BACKUP) — single canonical home (S215, closes G-P1)**
Step 1b is the **only** home for edition text ingest. As of S215, `/write-edition` Step 6 no longer runs `ingestEdition.js` at close — running it both there and here produced duplicate bay-tribune text records (different doc IDs, same content) that polluted future sift queries. If `/write-edition` close already ingested text for some reason (rare emergency backfill), skip this substep and document in the production log.
```bash
node scripts/ingestEdition.js <source> --type <type> --cycle <XX>
```
**`--type edition` is frame-only (pipeline.65, S429).** A narrated edition (the `THE WEEK'S REPORTING` shape `cron-saturday-run.js` publishes) ingests as ONE doc — masthead + Mags narration, `metadata.scope=frame`. Article bodies are NOT in that doc: the Saturday sweep (`stepSweep`, one `article-c{N}-{stem}` doc per curated article) owns them. The ingest deletes stale body chunks `edition-c{N}-…-2..N` from any earlier monolith ingest first. Editions without the marker (pre-narration format) still ingest full-body — no sweep exists for them. Dispatch/supplemental/interview types are unchanged.
Full text chunked to bay-tribune. Wiki records rank higher for entity searches; text chunks are backup for full-text queries. Log doc IDs.
For `--type interview` (post /interview v2.0, S233 pipeline.30): the `<source>` IS the transcript — no companion re-run. Step 1b runs once against the transcript `.txt`. The legacy two-invocation pattern (article + companion transcript) is retired; /interview v2.0 emits only the transcript.
**Verification gate:** bay-tribune doc ID returned (single ID — for interview, the transcript `.txt` is the canonical artifact, no companion article exists post /interview v2.0). Fail if the API call returns no ID or an error string.
**Note:** Podcast runs AFTER post-publish as a separate creative process. Podcast handles its own bay-tribune ingest.
**1c. NotebookLM push — Mike's ingest surface (S310, research.23)**
```bash
node scripts/notebooklmPush.js --file <source> --cycle <XX> --audio # edition
node scripts/notebooklmPush.js --file <source> --cycle <XX> # interview/supplemental/dispatch (source only — audio quota is scarce, editions get it)
```
Pushes the artifact into the Bay Tribune notebook (Mike's actual reading/listening path). Edition runs additionally generate the audio overview, deliver it to Drive + Discord (both drops, Mike-direct S310), and capture the notebook's own summary to `output/nlm_summary_c<XX>.md`.
**NON-BLOCKING BY CONTRACT:** the script exits 0 on every runtime failure (auth expiry — NotebookLM cookies rot every 2–4 weeks; rate limits; internal-API drift) and prints `NOTEBOOKLM ... (non-blocking):` warnings. A warning here never halts /post-publish — log it in the production log and continue. If auth expired: Mike refreshes the cookie string (DevTools → notebooklm.google.com → copy `cookie:` header value → `/root/.nlm/cookies.txt`), then `nlm login --manual -f /root/.nlm/cookies.txt`.
**Verification gate:** stdout ends with `NotebookLM push complete` OR a `(non-blocking)` warning — both pass. Anything else (non-zero exit) is a real script defect, not a bridge failure.
Parallel-OK: independent of the citizen/grade chain — run alongside Step 2a.
### Step 2: World-Data Updates — City State
**2a. Refresh citizen cards**
```bash
node scripts/buildCitizenCards.js --apply
```
Citizens who appeared get updated profiles in world-data. New citizens get cards built. Verify new citizens have gender from ledger.
**Why `--apply`:** the script defaults to dry-run (writes nothing). Without the flag the substep silently does no real work. (G-P12, S195 — caught on E93 when --apply surfaced the 401 retry storm; previous cycles likely shipped dry-runs masquerading as success.)
**Verification gate (strengthened S230 per canon.3 / ADR-0007):** ALL THREE conditions must pass — (a) stdout reports refreshed/created card count ≥ 1 when NAMES INDEX is non-empty (or "0 citizens to refresh — empty NAMES INDEX" if empty); (b) stdout reports `Errors: 0`; (c) script exit code is 0. Any failing condition aborts /post-publish at Step 2a with a pointer to `output/citizen_card_failures_c<XX>.json` for engine-sheet diagnosis. The failure-list dump + non-zero exit ship via engine-sheet T6 (canon.3 plan); pre-T6 the script still exits 0 on partial failure, so the gate is partially load-bearing until T6 lands. Closes G-P34 lineage (S195 G-P12 → S222 G-P34 → S223 chunking fix → S230 gate strengthening).
**Why this gate strengthening matters (per ADR-0007):** wd-citizens is the derived lookup layer that reporters + Mara + sift Step 4 enrichment hit via MCP `lookup_citizen`. A 19% partial-write failure (S222 C94 empirical) means wd-citizens has a silent gap per cycle that compounds — same shape as the bay-tribune-only-citizens drift the ADR governs. Non-zero exit + failure dump make the gap visible immediately instead of catching it three cycles later when a reporter notices a missing card.
**2a-cul. Refresh cultural cards** (`--type {dispatch|interview|supplemental}` — only when NAMES INDEX contains CUL-IDs)
```bash
# For each CUL-XXXXXXX entry parsed from <source> NAMES INDEX:
node scripts/buildCulturalCards.js --apply --cul <CUL-ID>
```
`buildCitizenCards.js` is Sim_Ledger-only — it cannot refresh cultural-only entities (musicians, artists, public figures registered in `wd-cultural` without a Sim_Ledger row). When the artifact's NAMES INDEX names cultural figures (e.g., S188 KONO Second Song dispatch named Marin Tao POP-00537 + Brody Kale CUL-905CBDE8), this substep refreshes their `wd-cultural` cards so the appearance count increments. Citizens with both POP- and CUL- IDs (Beverly Hayes is both citizen AND cultural figure) get both cards refreshed — Step 2a handles the citizen card via POPID; this substep handles the cultural card via CUL-ID.
Skipped for `--type edition` by default — edition NAMES INDEX entries are typically Sim_Ledger citizens with established cards. If a future edition surfaces CUL-only entries, the substep activates the same way (matrix flip + run).
**Verification gate:** for each CUL-ID in source NAMES INDEX, `lookup_cultural(name)` returns a card with `last_appeared` reflecting this cycle. If no CUL-IDs in NAMES INDEX, substep skipped with stdout "0 cultural-only entries — substep N/A this artifact." Plan reference: [[archive/plans/2026-04-30-dispatch-gap-followups]] Task E6 (closes the Brody Kale unrefreshed gap from S188).
**2e. Voice reflection ingest** (`--type interview` — only when an interview subject is a citizen-voice agent, i.e. their POPID appears in a `.claude/agents/citizen-voice-*/IDENTITY.md`)
engine.43 T5: an interview becomes real lived history for a voiced citizen — their answers drain into the same `Reflection_Intake` pipeline the 24/7 wake loop writes, so the next cycle's drain nudges their dials and their tension register carries across surfaces (a tension opened at a wake can resolve in an interview, and vice versa).
For each citizen-voice subject:
1. Extract that citizen's OWN answers from the transcript (never the reporter's questions, never another subject's words — S298: the other party's words must not color the dial nudge) into a scratch file, answers only, in order.
2. Drain (dry-run first, then live):
```bash
node -e "require('/root/GodWorld/lib/env');require('/root/GodWorld/lib/personaProvider').drainConversationReflection_('<POPID>', require('fs').readFileSync('<answers-file>','utf8'), 'INTERVIEW', {dryRun:true}).then(r=>console.log(JSON.stringify(r)))"
# inspect event/affect/tension, then re-run without {dryRun:true}
```
One `Reflection_Intake` row per citizen interviewed (`daypart='INTERVIEW'`, `applied='no'`, gated — the cycle drain picks it up like any WAKE row). Tension open/resolve mirrors wake step 3 (cap 3, typed page lines). Re-running the same transcript is a no-op (`duplicate: true` — snippet-matched idempotency guard). Skipped with "0 citizen-voice subjects — substep N/A" when no subject is a voiced agent.
**Voice-packet dedup (pipeline.43 Task 5).** Citizens whose published quotes came through the `/write-edition` quote-supply pre-pass already own their intake row — `citizenVoice.js --record` wrote the page doc (`daypart='PRESS'`) + gated `Reflection_Intake` row at speak time. If `output/voices/voices_c{XX}.json` exists and lists the citizen (non-fallback entry), 2e must NOT write a second reflection row for the same quote — log `2e skip: quote-supplied via voice packet` per citizen. Interview-format subjects (premium `/interview` path) keep the 2e backfill unchanged. Citizen usage tracking (NAMES INDEX / usage log / fame) is publication-side and stays untouched — it records printing, not speaking.
**Verification gate:** live run returns `{"drained":true,...}` per citizen-voice subject (or `duplicate:true` on re-run); no page-prose writes beyond typed tension lines (ADR-0014 — conversational prose never lands on the Supermemory citizen page).
**2b. New businesses**
Flagged here for visibility; actual writes happen in Step 5 via `ingestPublishedEntities.js` (which reads BUSINESSES NAMED and appends new entries to Business_Ledger).
**Verification gate:** BUSINESSES NAMED parsed; NEW rows count logged to production log Step 12 — actual append verification lives in Step 5's output JSON.
**2d. Refresh A's player truesource — incremental sweep** (`--type edition` and `--type supplemental`)
```bash
node scripts/ingestPlayerTrueSource.js --apply --skip-subfolder --include-flat --include-prospects
```
Voir sur GitHub