@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.
- package/.claude/settings.json +1 -1
- package/.claude/skills/writing-tests/SKILL.md +18 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +19 -6
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +18 -3
- package/biome.json +7 -1
- package/docs/architecture.md +43 -1
- package/docs/superpowers/plans/2026-08-21-schema-emission-harness.md +1115 -0
- package/docs/superpowers/specs/2026-08-21-schema-emission-harness-design.md +255 -0
- package/package.json +6 -4
- package/scripts/hooks/prompt-rule-injector.sh +10 -2
- package/scripts/lib/onlooker-event.mjs +38 -1
- package/scripts/lib/prompt-rules.sh +43 -12
- package/scripts/lint/check-bus-coverage.mjs +153 -0
- package/scripts/lint/check-managed-blocks.mjs +206 -0
- package/test/bats/assayer-events.bats +1 -1
- package/test/bats/bursar-events.bats +1 -1
- package/test/bats/cartographer-events.bats +2 -2
- package/test/bats/echo-events.bats +1 -1
- package/test/bats/emission-report-optout.bats +41 -0
- package/test/bats/governor-events.bats +1 -1
- package/test/bats/inspector-events.bats +1 -1
- package/test/bats/librarian-session-end.bats +1 -1
- package/test/bats/lineage-events.bats +2 -2
- package/test/bats/prompt-rules.bats +61 -24
- package/test/bats/tribunal-events.bats +1 -1
- package/test/bats/warden-events.bats +1 -1
- package/test/bus-coverage.json +131 -0
- package/test/helpers/setup.bash +22 -0
- package/test/node/check-bus-coverage.test.mjs +160 -0
- package/test/node/check-managed-blocks.test.mjs +179 -0
- package/test/node/emission-report.test.mjs +97 -0
package/.claude/settings.json
CHANGED
|
@@ -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.
|
|
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",
|
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.
|
|
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.
|
|
226
|
-
|
|
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
|
|
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.
|
|
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
|
-
<!--
|
|
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
|
|
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
package/docs/architecture.md
CHANGED
|
@@ -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:**
|
|
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
|
|