@onlooker-community/ecosystem 0.43.2 → 0.43.4

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 (34) hide show
  1. package/.claude/settings.json +1 -1
  2. package/.claude/skills/writing-tests/SKILL.md +18 -0
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.release-please-manifest.json +1 -1
  5. package/AGENTS.md +19 -6
  6. package/CHANGELOG.md +14 -0
  7. package/CLAUDE.md +18 -3
  8. package/biome.json +7 -1
  9. package/docs/architecture.md +43 -1
  10. package/docs/superpowers/plans/2026-08-21-schema-emission-harness.md +1115 -0
  11. package/docs/superpowers/specs/2026-08-21-schema-emission-harness-design.md +255 -0
  12. package/package.json +6 -4
  13. package/scripts/hooks/prompt-rule-injector.sh +10 -2
  14. package/scripts/lib/onlooker-event.mjs +38 -1
  15. package/scripts/lib/prompt-rules.sh +43 -12
  16. package/scripts/lint/check-bus-coverage.mjs +153 -0
  17. package/scripts/lint/check-managed-blocks.mjs +206 -0
  18. package/test/bats/assayer-events.bats +1 -1
  19. package/test/bats/bursar-events.bats +1 -1
  20. package/test/bats/cartographer-events.bats +2 -2
  21. package/test/bats/echo-events.bats +1 -1
  22. package/test/bats/emission-report-optout.bats +41 -0
  23. package/test/bats/governor-events.bats +1 -1
  24. package/test/bats/inspector-events.bats +1 -1
  25. package/test/bats/librarian-session-end.bats +1 -1
  26. package/test/bats/lineage-events.bats +2 -2
  27. package/test/bats/prompt-rules.bats +61 -24
  28. package/test/bats/tribunal-events.bats +1 -1
  29. package/test/bats/warden-events.bats +1 -1
  30. package/test/bus-coverage.json +131 -0
  31. package/test/helpers/setup.bash +22 -0
  32. package/test/node/check-bus-coverage.test.mjs +160 -0
  33. package/test/node/check-managed-blocks.test.mjs +179 -0
  34. package/test/node/emission-report.test.mjs +97 -0
@@ -15,4 +15,4 @@
15
15
  }
16
16
  ]
17
17
  }
18
- }
18
+ }
@@ -144,6 +144,24 @@ tail -n 1 "$ONLOOKER_EVENTS_LOG" \
144
144
 
145
145
  Production code must emit only through `scripts/lib/onlooker-event.mjs` (often via a plugin wrapper like `librarian_emit` / `assayer_emit_event`) — never by writing the log directly. New event types must be registered in `@onlooker-community/schema` before they validate.
146
146
 
147
+ You no longer need a bespoke per-plugin test proving a payload validates. The
148
+ suite gates every emission at once: `ONLOOKER_TEST_REPORT_DIR` is set during
149
+ `test:bats` and `test:schema`, and `npm run test:bus` fails on any rejected
150
+ emission. Write the test that drives the branch; the gate does the validating.
151
+
152
+ What still matters is exercising the *rare* branches. A payload is only checked
153
+ when some test makes the code emit it, so an enum bug on an error path stays
154
+ invisible until a test reaches that path.
155
+
156
+ If a test deliberately emits an invalid payload — proving a bad event type or
157
+ malformed payload gets rejected — wrap the call in `expect_emission_rejected`
158
+ (`test/helpers/setup.bash`) instead of calling it directly. It unsets
159
+ `ONLOOKER_TEST_REPORT_DIR` for the duration of the call and restores it
160
+ afterward, so the deliberate rejection never lands in `emissions.jsonl`. Skip
161
+ this and Gate A fails: it counts every rejection in the report as a real
162
+ regression, deliberate or not, and can't tell your negative test from schema
163
+ drift.
164
+
147
165
  ### Seed fixtures and generate IDs
148
166
 
149
167
  Write fixtures with `jq -n` into the plugin's project-keyed directory, and generate IDs with the plugin's `*-ulid.sh` (ULIDs, not UUIDs — a repo-wide convention). A typical artifact seeder:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecosystem",
3
- "version": "0.43.2",
3
+ "version": "0.43.4",
4
4
  "description": "Observability substrate for Claude Code. Provides the shared $ONLOOKER_DIR storage root (default $HOME/.onlooker), canonical schema-validated event emission, session and tool tracking hooks, and prompt rules. Required by all other Onlooker plugins.",
5
5
  "author": {
6
6
  "name": "Onlooker Community",
@@ -1,5 +1,5 @@
1
1
  {
2
- ".": "0.43.2",
2
+ ".": "0.43.4",
3
3
  "plugins/archivist": "0.3.1",
4
4
  "plugins/tribunal": "1.2.7",
5
5
  "plugins/echo": "0.3.1",
package/AGENTS.md CHANGED
@@ -132,7 +132,10 @@ See `plugins/compass/docs/adr/001-evaluate-prompts-in-context.md` for the full d
132
132
  3. Store runtime artifacts under `${ONLOOKER_DIR:-$HOME/.onlooker}/<name>/<project-key>/`. Always use `$ONLOOKER_DIR` — never hardcode `~/.onlooker` — so the test suite's isolated temp home is respected.
133
133
  4. Derive the project key via `tribunal_project_key` (or equivalent) — first 12 hex chars of SHA256(`remote:<origin-url>`), falling back to SHA256(`root:<repo-root>`) for repos without a remote. See `plugins/tribunal/scripts/lib/tribunal-project-key.sh`.
134
134
  5. Register event types in `@onlooker-community/schema` before emitting them. The runtime emitter is dependency-free and **fails open**: it validates against the schema package only when that package is resolvable (dev, CI, tests) and emits unconditionally otherwise, because installed marketplace plugins ship no `node_modules`. Schema drift is caught in CI against the published schemas at `schema.onlooker.dev`. See [ADR-005](docs/adr/005-runtime-emitter-fails-open.md).
135
- 6. Fail-soft when `~/.onlooker/` is absent plugins must not block a session they were not invited to.
135
+ 6. Triage every new event type into `test/bus-coverage.json` `expected` when
136
+ a test drives the branch that emits it, `excluded` with a reason when not.
137
+ `npm run test:bus` fails on any registered type that appears in neither list.
138
+ 7. Fail-soft when `~/.onlooker/` is absent — plugins must not block a session they were not invited to.
136
139
 
137
140
  ## Development
138
141
 
@@ -163,6 +166,16 @@ Workflow:
163
166
  - ULIDs everywhere for IDs (not UUIDs). Each plugin ships its own `*_ulid` helper (e.g. `archivist-ulid.sh`, `tribunal-ulid.sh`); there is no shared ecosystem helper. Copy `plugins/tribunal/scripts/lib/tribunal-ulid.sh` as a starting point and rename the function prefix.
164
167
  - Config defaults live in `config.json`. User overrides go in `~/.claude/settings.json` (global) or `.claude/settings.json` (per-project) under the plugin's namespace key (e.g. `"compass"`, `"tribunal"`). See ADR-004.
165
168
 
169
+ <!-- Every `bd`-generated block is fenced by markdownlint-disable/enable pairs.
170
+ `bd setup` rewrites whatever sits between BEGIN and END verbatim, so any
171
+ fix `npm run lint` applies inside a block is undone the next time anyone
172
+ regenerates it. CI runs `lint:check`, which reports but never rewrites, so
173
+ the churn lands as a red build unrelated to whatever that person was doing.
174
+ The pairs sit *outside* the markers so `bd` does not clobber them.
175
+ MD012 (consecutive blank lines), MD024 (duplicate headings) and MD034
176
+ (bare URLs) are the rules the generated text trips. See ecosystem-55g.
177
+ scripts/lint/check-managed-blocks.mjs enforces that every block is fenced. -->
178
+ <!-- markdownlint-disable MD012 MD024 MD034 -->
166
179
  <!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:970c3bf2 -->
167
180
  ## Beads Issue Tracker
168
181
 
@@ -218,13 +231,13 @@ This protocol applies when ending a Beads implementation workflow. It is subordi
218
231
  - Do not commit or push without clear authority from the active profile or the current user request.
219
232
  - If a required sync or push is blocked, stop and report the exact command and error.
220
233
  <!-- END BEADS INTEGRATION -->
234
+ <!-- markdownlint-enable MD012 MD024 MD034 -->
221
235
 
222
236
  <!-- The Codex block below repeats the "Beads Issue Tracker" heading from the
223
237
  integration block above. Both are generated and re-synced by different `bd`
224
238
  subcommands, so neither heading can be renamed by hand without breaking
225
- idempotent regeneration. These comments sit outside the BEGIN/END markers
226
- so `bd` does not clobber them when it rewrites the block. -->
227
- <!-- markdownlint-disable MD024 -->
239
+ idempotent regeneration. -->
240
+ <!-- markdownlint-disable MD012 MD024 MD034 -->
228
241
  <!-- BEGIN BEADS CODEX SETUP: generated by bd setup codex -->
229
242
  ## Beads Issue Tracker
230
243
 
@@ -246,6 +259,6 @@ bd prime # Refresh Beads context
246
259
  - Run `bd prime` when Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use `/hooks` to inspect or toggle them.
247
260
  - Keep persistent project memory in Beads via `bd remember`; do not create ad hoc memory files.
248
261
 
249
- **Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See <https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md> for details and anti-patterns.
262
+ **Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/core-concepts/sync-concepts.md for details and anti-patterns.
250
263
  <!-- END BEADS CODEX SETUP -->
251
- <!-- markdownlint-enable MD024 -->
264
+ <!-- markdownlint-enable MD012 MD024 MD034 -->
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.43.4](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.3...ecosystem-v0.43.4) (2026-08-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **events:** route prompt_rule emission through the canonical emitter :relieved: ([#196](https://github.com/onlooker-community/ecosystem/issues/196)) ([c233bfa](https://github.com/onlooker-community/ecosystem/commit/c233bfa466cf400e767f4673e0143081618314a7))
9
+
10
+ ## [0.43.3](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.2...ecosystem-v0.43.3) (2026-08-22)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **ci:** fence bd-managed blocks against lint churn :relieved: ([#194](https://github.com/onlooker-community/ecosystem/issues/194)) ([e09fcb6](https://github.com/onlooker-community/ecosystem/commit/e09fcb6836645999feb831a7494236d4a54ed318))
16
+
3
17
  ## [0.43.2](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.1...ecosystem-v0.43.2) (2026-08-21)
4
18
 
5
19
 
package/CLAUDE.md CHANGED
@@ -79,7 +79,10 @@ See `plugins/compass/docs/adr/001-evaluate-prompts-in-context.md` for the full d
79
79
  3. Store runtime artifacts under `${ONLOOKER_DIR:-$HOME/.onlooker}/<name>/<project-key>/`. Always use `$ONLOOKER_DIR` — never hardcode `~/.onlooker` — so the test suite's isolated temp home is respected.
80
80
  4. Derive the project key via `tribunal_project_key` (or equivalent) — first 12 hex chars of SHA256(`remote:<origin-url>`), falling back to SHA256(`root:<repo-root>`) for repos without a remote. See `plugins/tribunal/scripts/lib/tribunal-project-key.sh`.
81
81
  5. Register event types in `@onlooker-community/schema` before emitting them. The runtime emitter is dependency-free and **fails open**: it validates against the schema package only when that package is resolvable (dev, CI, tests) and emits unconditionally otherwise, because installed marketplace plugins ship no `node_modules`. Schema drift is caught in CI against the published schemas at `schema.onlooker.dev`. See [ADR-005](docs/adr/005-runtime-emitter-fails-open.md).
82
- 6. Fail-soft when `~/.onlooker/` is absent plugins must not block a session they were not invited to.
82
+ 6. Triage every new event type into `test/bus-coverage.json` `expected` when
83
+ a test drives the branch that emits it, `excluded` with a reason when not.
84
+ `npm run test:bus` fails on any registered type that appears in neither list.
85
+ 7. Fail-soft when `~/.onlooker/` is absent — plugins must not block a session they were not invited to.
83
86
 
84
87
  ## Development
85
88
 
@@ -110,7 +113,17 @@ Workflow:
110
113
  - ULIDs everywhere for IDs (not UUIDs). Each plugin ships its own `*_ulid` helper (e.g. `archivist-ulid.sh`, `tribunal-ulid.sh`); there is no shared ecosystem helper. Copy `plugins/tribunal/scripts/lib/tribunal-ulid.sh` as a starting point and rename the function prefix.
111
114
  - Config defaults live in `config.json`. User overrides go in `~/.claude/settings.json` (global) or `.claude/settings.json` (per-project) under the plugin's namespace key (e.g. `"compass"`, `"tribunal"`). See ADR-004.
112
115
 
113
- <!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:6cd5cc61 -->
116
+ <!-- Every `bd`-generated block is fenced by markdownlint-disable/enable pairs.
117
+ `bd setup` rewrites whatever sits between BEGIN and END verbatim, so any
118
+ fix `npm run lint` applies inside a block is undone the next time anyone
119
+ regenerates it. CI runs `lint:check`, which reports but never rewrites, so
120
+ the churn lands as a red build unrelated to whatever that person was doing.
121
+ The pairs sit *outside* the markers so `bd` does not clobber them.
122
+ MD012 (consecutive blank lines), MD024 (duplicate headings) and MD034
123
+ (bare URLs) are the rules the generated text trips. See ecosystem-55g.
124
+ scripts/lint/check-managed-blocks.mjs enforces that every block is fenced. -->
125
+ <!-- markdownlint-disable MD012 MD024 MD034 -->
126
+ <!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:46cd31e7 -->
114
127
  ## Beads Issue Tracker
115
128
 
116
129
  This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
@@ -130,7 +143,7 @@ bd close <id> # Complete work
130
143
  - Run `bd prime` for detailed command reference and session close protocol
131
144
  - Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
132
145
 
133
- **Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See <https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md> for details and anti-patterns.
146
+ **Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/core-concepts/sync-concepts.md for details and anti-patterns.
134
147
 
135
148
  ## Agent Context Profiles
136
149
 
@@ -154,6 +167,7 @@ This protocol applies when ending a Beads implementation workflow. It is subordi
154
167
 
155
168
  # Team-maintainer opt-in only, unless current instructions forbid it:
156
169
  git pull --rebase
170
+ bd dolt push
157
171
  git push
158
172
  git status
159
173
  ```
@@ -164,3 +178,4 @@ This protocol applies when ending a Beads implementation workflow. It is subordi
164
178
  - Do not commit or push without clear authority from the active profile or the current user request.
165
179
  - If a required sync or push is blocked, stop and report the exact command and error.
166
180
  <!-- END BEADS INTEGRATION -->
181
+ <!-- markdownlint-enable MD012 MD024 MD034 -->
package/biome.json CHANGED
@@ -42,5 +42,11 @@
42
42
  "organizeImports": "on"
43
43
  }
44
44
  }
45
- }
45
+ },
46
+ "overrides": [
47
+ {
48
+ "includes": [".claude/settings.json"],
49
+ "formatter": { "enabled": false }
50
+ }
51
+ ]
46
52
  }
@@ -93,7 +93,49 @@ Every observable event flows through `onlooker-event.mjs` before being written t
93
93
 
94
94
  The schema is versioned independently and published to npm. Plugin shell scripts invoke `onlooker-event.mjs` at runtime so schema validation always reflects the installed version.
95
95
 
96
- > **Note:** Not all events in the JSONL log are schema-validated. `prompt_rule.*` events are currently emitted outside the canonical schema pipeline (the event types are not yet defined in `@onlooker-community/schema`). Schema-first emission is the goal for all future event types.
96
+ > **Note:** Every emission path that fires in normal operation routes through `onlooker-event.mjs`. `prompt_rule.*` was the last routine exception `prompt_rules_emit` hand-built its envelope with `jq` and wrote straight to `$ONLOOKER_EVENTS_LOG`, so those lines carried none of the required `id`/`schema_version`/`runtime`/`machine_id`/`sequence` fields and added a `turn` field the envelope forbids. It now goes through the emitter like everything else.
97
+ >
98
+ > One hand-built envelope remains, in the `safe_emit` fallback in `scripts/lib/validate-path.sh`. It is reached only when `$ONLOOKER_EMIT` is missing, which does not happen in a normal checkout or install, so it is latent rather than live — but if it ever fires it writes the same unvalidatable shape. Route new event types through the emitter rather than writing to the log directly: an envelope assembled anywhere else will drift from the schema without anything noticing.
99
+
100
+ ### Emission gates
101
+
102
+ Payload drift used to be invisible. The emitter validates against
103
+ `@onlooker-community/schema` wherever it resolves and rejects a bad event with
104
+ a non-zero exit, but hooks fail soft and exit 0, so the rejection was destroyed
105
+ and the event simply never appeared.
106
+
107
+ Two CI gates close that hole. During the test suite `ONLOOKER_TEST_REPORT_DIR`
108
+ is set, and the emitter appends one line per emission to `emissions.jsonl`
109
+ recording whether validation ran (`validated`) and, if so, whether it passed
110
+ (`valid`). `npm run test:bus` (`scripts/lint/check-bus-coverage.mjs`) reads
111
+ that report and runs two gates against it, after `test:bats` and `test:schema`
112
+ have populated the report:
113
+
114
+ - **Gate A** fails if the report is empty, if nothing in it was actually
115
+ validated (the schema package never resolved — usually a missing
116
+ `npm ci`), or if any recorded emission was rejected, naming the type and
117
+ its ajv errors.
118
+ - **Gate B** checks every event type in `@onlooker-community/schema`'s
119
+ `ALL_EVENT_TYPES` against `test/bus-coverage.json`, and fails when:
120
+ 1. an `expected` type never produced a validated emission during the
121
+ suite;
122
+ 2. a registered type is missing from both `expected` and `excluded` (or
123
+ the manifest names a type the schema doesn't register);
124
+ 3. an `excluded` type carries an empty reason; or
125
+ 4. an `excluded` type *did* emit and validate during the suite — without
126
+ this check, moving a genuinely-emitted type into `excluded` with any
127
+ non-empty reason would satisfy the other three, so coverage could be
128
+ silently under-claimed while CI stayed green.
129
+
130
+ Gate B only runs when Gate A found at least one validated emission in the
131
+ report. A merely non-empty report where nothing validated would otherwise
132
+ bury the one real failure under one "expected type never emitted" line per
133
+ expected type — 82 lines of noise for 1 real cause, measured against the
134
+ current manifest.
135
+
136
+ Adding an event type therefore requires triaging it into
137
+ `test/bus-coverage.json` — as `expected`, meaning a test exercises the branch
138
+ that emits it, or as `excluded` with a stated reason.
97
139
 
98
140
  ## Project keying
99
141