@onlooker-community/ecosystem 0.33.0 → 0.34.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.
- package/.agents/skills/beads/SKILL.md +80 -0
- package/.agents/skills/beads/agents/openai.yaml +4 -0
- package/.claude/settings.json +13 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.codex/config.toml +2 -0
- package/.codex/hooks.json +51 -0
- package/.markdownlint.json +3 -0
- package/.release-please-manifest.json +8 -8
- package/AGENTS.md +135 -0
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +56 -1
- package/docs/lesson-promotion-pipeline.md +210 -0
- package/docs/superpowers/plans/2026-08-09-lesson-transform.md +1537 -0
- package/docs/superpowers/specs/2026-08-09-lesson-transform-design.md +261 -0
- package/package.json +3 -2
- package/plugins/archivist/scripts/lib/archivist-config.sh +10 -34
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +39 -62
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +11 -29
- package/plugins/compass/scripts/lib/compass-config.sh +16 -46
- package/plugins/counsel/scripts/lib/counsel-config.sh +15 -46
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-config.sh +19 -44
- package/plugins/echo/scripts/lib/echo-config.sh +30 -59
- package/plugins/governor/scripts/lib/governor-config.sh +11 -41
- package/plugins/historian/scripts/lib/historian-config.sh +9 -33
- package/plugins/inspector/.claude-plugin/plugin.json +1 -1
- package/plugins/inspector/CHANGELOG.md +7 -0
- package/plugins/inspector/scripts/lib/inspector-config.sh +39 -63
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +7 -0
- package/plugins/librarian/config.json +4 -0
- package/plugins/librarian/schema/PROVENANCE.json +7 -0
- package/plugins/librarian/schema/lesson-applies-to.subschema.json +74 -0
- package/plugins/librarian/schema/lesson-evidence.subschema.json +36 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +26 -0
- package/plugins/librarian/scripts/lib/librarian-config.sh +10 -34
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +135 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +311 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +140 -0
- package/plugins/lineage/.claude-plugin/plugin.json +1 -1
- package/plugins/lineage/CHANGELOG.md +7 -0
- package/plugins/lineage/scripts/lib/lineage-config.sh +17 -53
- package/plugins/scribe/.claude-plugin/plugin.json +1 -1
- package/plugins/scribe/CHANGELOG.md +7 -0
- package/plugins/scribe/scripts/lib/scribe-config.sh +17 -47
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +7 -0
- package/plugins/tribunal/scripts/lib/tribunal-config.sh +25 -63
- package/plugins/warden/scripts/lib/warden-config.sh +17 -54
- package/scripts/lib/config-loader.sh +8 -1
- package/scripts/lint/check-lesson-schema-drift.mjs +36 -0
- package/test/bats/librarian-lesson-transform.bats +609 -0
- package/test/node/lesson-schema-drift.test.mjs +28 -0
- package/test/node/lesson-validate-agreement.test.mjs +154 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Lesson Transform — Design
|
|
2
|
+
|
|
3
|
+
**Status:** Approved, not started.
|
|
4
|
+
**Tracked by:** `ecosystem-4z8.1`, under epic `ecosystem-4z8`.
|
|
5
|
+
**Parent design:** `docs/superpowers/specs/2026-08-08-promotion-pipeline-design.md` in the
|
|
6
|
+
[onlooker](https://github.com/onlooker-community/onlooker) repo, Sections 2–4.
|
|
7
|
+
That document governs the pipeline. This one covers stage five only, and where
|
|
8
|
+
the two disagree, the disagreements are called out explicitly below.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What this stage is
|
|
13
|
+
|
|
14
|
+
Librarian's fifth stage. It reads one durable, classified, deduped archivist
|
|
15
|
+
artifact and emits a **lesson candidate** — the subset of a Lesson that can be
|
|
16
|
+
inferred from an artifact.
|
|
17
|
+
|
|
18
|
+
It is not a Lesson. `lesson.schema.json` v2 requires thirteen fields; this stage
|
|
19
|
+
owns four:
|
|
20
|
+
|
|
21
|
+
| Field | Owner |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `claim`, `rationale`, `evidence`, `applies_to` | **this stage** |
|
|
24
|
+
| `visibility` | the human, at confirmation (`4z8.2`) |
|
|
25
|
+
| `consensus` | the jury (`4z8.3`) |
|
|
26
|
+
| `id`, `schema_version`, `source`, `status`, `superseded_by`, `author_key`, `promoted_at` | the pool write (`4z8.4`) |
|
|
27
|
+
|
|
28
|
+
Keeping that split explicit is what stops a stage inventing a field it does not
|
|
29
|
+
own. It also answers the open question the epic carried: the transform *cannot*
|
|
30
|
+
validate against `lesson.schema.json`, because the object is deliberately
|
|
31
|
+
incomplete at this point.
|
|
32
|
+
|
|
33
|
+
## Decisions
|
|
34
|
+
|
|
35
|
+
Four decisions were settled during design. The first and third depart from the
|
|
36
|
+
parent spec; both departures are argued below rather than assumed.
|
|
37
|
+
|
|
38
|
+
**1. No recoverable resolution means declined.** `evidence.resolution` is
|
|
39
|
+
required with `minLength: 1`, and the archivist artifact shape has no resolution
|
|
40
|
+
field — it must be inferred from prose. When it is not there, the artifact is
|
|
41
|
+
declined. This preserves the contract's stance that "this breaks" without "and
|
|
42
|
+
this fixed it" is a warning, not a lesson.
|
|
43
|
+
|
|
44
|
+
**2. Validation is against vendored sub-schemas**, not the full lesson schema,
|
|
45
|
+
and with no network access at runtime. See *Validation* below.
|
|
46
|
+
|
|
47
|
+
**3. The transform emits `versioned` only.** It can never emit
|
|
48
|
+
`version_independent`. If it cannot infer versions, the artifact is declined.
|
|
49
|
+
|
|
50
|
+
The parent spec allows either branch and relies on the jury's `scope_accuracy`
|
|
51
|
+
criterion to catch a lazy justification — "the schema stops the accident, the
|
|
52
|
+
jury stops the lazy excuse." That defense has a hole: **private lessons run no
|
|
53
|
+
jury at all.** A weakly-justified `version_independent` candidate marked private
|
|
54
|
+
reaches the pool with nothing checking it, and a lesson with no version bound
|
|
55
|
+
never expires.
|
|
56
|
+
|
|
57
|
+
That is precisely the motivating failure. The stale vitest artifact was a
|
|
58
|
+
private, local memory that misled its own author across sessions. Closing the
|
|
59
|
+
branch at the transform makes the guarantee structural at every visibility tier
|
|
60
|
+
instead of relying on a gate the private tier skips. `version_independent`
|
|
61
|
+
becomes an explicit human choice at `4z8.2`, made by someone with the context to
|
|
62
|
+
write a justification that is actually true.
|
|
63
|
+
|
|
64
|
+
**4. A cheap pre-gate runs before the model.** Mirrors librarian's existing
|
|
65
|
+
pre-LLM durability filter.
|
|
66
|
+
|
|
67
|
+
## Architecture
|
|
68
|
+
|
|
69
|
+
New file: `plugins/librarian/scripts/lib/librarian-lesson-transform.sh`, sourced
|
|
70
|
+
by the existing `librarian-session-end.sh` chain and running after conflict/dup
|
|
71
|
+
detection. No new hook. No new plugin — librarian already owns the watermark,
|
|
72
|
+
and a second copy of that state would be free to drift.
|
|
73
|
+
|
|
74
|
+
### Storage
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
~/.onlooker/librarian/<project-key>/lessons/
|
|
78
|
+
proposals/<ulid>.json this stage
|
|
79
|
+
approved/<ulid>.json 4z8.4
|
|
80
|
+
declined.jsonl 4z8.4 (but see Boundary changes)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Lesson state lives in its own subtree rather than reusing librarian's existing
|
|
84
|
+
`proposals/` directory. The two carry different consequences: a memory promotion
|
|
85
|
+
writes to your machine, a lesson proposal is a step toward publishing beyond it.
|
|
86
|
+
`4z8.2`'s open question is specifically the risk of users confusing the two, and
|
|
87
|
+
separate trees mean a confirmation surface cannot merge them by accident.
|
|
88
|
+
|
|
89
|
+
Each candidate carries its source `artifact_id` at the top level, which is
|
|
90
|
+
load-bearing for idempotency.
|
|
91
|
+
|
|
92
|
+
Use `$ONLOOKER_DIR`, never a hardcoded `~/.onlooker`, so the test suite's
|
|
93
|
+
isolated temp home is respected.
|
|
94
|
+
|
|
95
|
+
## The three steps
|
|
96
|
+
|
|
97
|
+
### 1. Pre-gate (bash, free)
|
|
98
|
+
|
|
99
|
+
`librarian_lesson_pregate <artifact_json>` scans `summary` and `detail` for a
|
|
100
|
+
version-shaped token (`\d+\.\d+`, `v5`, `5.x`).
|
|
101
|
+
|
|
102
|
+
Because `versioned` is the only branch this stage can emit, an artifact with no
|
|
103
|
+
version token anywhere cannot produce a valid `scope.versions`. Rejecting it is
|
|
104
|
+
a fact about the output shape, not a quality heuristic. The pre-gate rejects
|
|
105
|
+
nothing else — "does this have a resolution" is semantic and stays with the
|
|
106
|
+
model, because a keyword regex would silently drop good candidates.
|
|
107
|
+
|
|
108
|
+
Pre-gate rejections are silent: no event, no ledger entry. `declined.jsonl`
|
|
109
|
+
exists to make expensive drops auditable and to keep re-runs cheap. Re-running a
|
|
110
|
+
regex is free, so recording it would dilute the signal `4z8.4` wants for rubric
|
|
111
|
+
tuning.
|
|
112
|
+
|
|
113
|
+
### 2. Model call
|
|
114
|
+
|
|
115
|
+
Mirrors `librarian-classifier.sh`: `claude -p --max-turns 1`, model, temperature
|
|
116
|
+
and max-tokens from config, a hard wall-clock timeout so SessionEnd cannot hang,
|
|
117
|
+
fail-soft to empty string, markdown fences stripped before parsing. The timeout
|
|
118
|
+
follows the classifier's ceiling of 20 seconds per call, and like the classifier
|
|
119
|
+
it falls back through `timeout`, `gtimeout`, then no timeout when neither exists.
|
|
120
|
+
|
|
121
|
+
The prompt supplies the artifact in a delimited block and requires one of two
|
|
122
|
+
responses: a candidate object, or an explicit refusal carrying a reason
|
|
123
|
+
(`no_resolution` or `no_versions`). A refusal is a real answer and must be
|
|
124
|
+
distinguishable from a failed call.
|
|
125
|
+
|
|
126
|
+
Config lives in the plugin's `config.json` under
|
|
127
|
+
`librarian.lesson_transform.{model, timeout_seconds}`, read via
|
|
128
|
+
`librarian_config_get` from the caller, with user overrides under the plugin
|
|
129
|
+
namespace key per ADR-004. There is no `enabled` flag — that option was removed
|
|
130
|
+
repo-wide in #108.
|
|
131
|
+
|
|
132
|
+
Two keys only. `claude --help` exposes `--model` and no sampling flags, so
|
|
133
|
+
`temperature` and `max_output_tokens` cannot reach the model through this CLI.
|
|
134
|
+
`librarian-classifier.sh` currently reads both and passes them to a function
|
|
135
|
+
that ignores them — dead config that reads as though it works. Sampling control
|
|
136
|
+
would require moving off `claude -p` to the API, which is a separate decision.
|
|
137
|
+
|
|
138
|
+
### 3. Validation
|
|
139
|
+
|
|
140
|
+
Two layers, because **ajv is not available at runtime**. It resolves in dev and
|
|
141
|
+
CI only; installed marketplace plugins ship no `node_modules` (ADR-005).
|
|
142
|
+
|
|
143
|
+
- **Runtime:** dependency-free `jq`, which works everywhere.
|
|
144
|
+
- **Source of truth:** sub-schemas vendored at
|
|
145
|
+
`plugins/librarian/schema/lesson-evidence.subschema.json` and
|
|
146
|
+
`lesson-applies-to.subschema.json`, extracted from the published
|
|
147
|
+
`lesson.schema.json`.
|
|
148
|
+
- **Tests:** assert the `jq` validator and the vendored sub-schema agree over a
|
|
149
|
+
corpus. The parent spec sets this precedent directly — the two mechanisms were
|
|
150
|
+
"proven able to disagree," so they are asserted separately.
|
|
151
|
+
- **CI:** a drift guard asserts the vendored copies carry recorded provenance
|
|
152
|
+
(source path and `schema_version: 2`) and have not been edited by hand.
|
|
153
|
+
|
|
154
|
+
Note that this is weaker than the guard used for *event* schemas, which
|
|
155
|
+
compares against `schema.onlooker.dev`. That endpoint currently serves no
|
|
156
|
+
lesson schema — `lesson.schema.json`, `/`, and `lesson/v2.json` all return
|
|
157
|
+
404 — so a fetch-and-compare guard is not available yet. When lesson schemas
|
|
158
|
+
are published, the guard should be upgraded to fetch-and-compare; until then
|
|
159
|
+
a cross-repo drift can only be caught by a human. Tracked separately.
|
|
160
|
+
|
|
161
|
+
The version-range pattern is where this earns its keep. The schema accepts `<6`,
|
|
162
|
+
`<=6`, `=6`, `>4`, `>=4`, and two-sided `>=4 <6`. It **rejects** npm-style
|
|
163
|
+
`^5.4.21`, `~5`, `5.x`, and bare `5.4.21` — which are exactly what a model
|
|
164
|
+
reaches for by default.
|
|
165
|
+
|
|
166
|
+
It also rejects `>=0`, `>=0.0`, and `>=0.0.0` by construction. An unbounded
|
|
167
|
+
lower bound is version-independence wearing a costume: it matches every session
|
|
168
|
+
and never expires. That closes the same hole decision 3 closes, one layer down.
|
|
169
|
+
|
|
170
|
+
Plus the cross-field rule JSON Schema cannot express: every key of
|
|
171
|
+
`scope.versions` must name an entry in `applies_to.stack`.
|
|
172
|
+
|
|
173
|
+
## Failure taxonomy
|
|
174
|
+
|
|
175
|
+
Two categories look identical from the call site — both produce no candidate —
|
|
176
|
+
and conflating them is the sharpest risk in this stage.
|
|
177
|
+
|
|
178
|
+
| Condition | Outcome |
|
|
179
|
+
|---|---|
|
|
180
|
+
| pre-gate: no version token | silent skip, nothing written |
|
|
181
|
+
| model returns unparseable JSON | declined, `transform_invalid` |
|
|
182
|
+
| model refuses: no resolution | declined, `no_resolution` |
|
|
183
|
+
| model refuses: cannot infer versions | declined, `no_versions` |
|
|
184
|
+
| candidate fails sub-schema validation | declined, `schema_invalid` |
|
|
185
|
+
| `versions` key not named in `stack` | declined, `schema_invalid` |
|
|
186
|
+
| `claude` CLI absent | **untouched**, no ledger entry |
|
|
187
|
+
| timeout, or empty response | **untouched**, no ledger entry |
|
|
188
|
+
|
|
189
|
+
A verdict says "this artifact cannot become a lesson." An outage says "we could
|
|
190
|
+
not tell." Recording an outage as a verdict buries a good artifact permanently,
|
|
191
|
+
because the watermark has moved past it and declined entries are never re-read.
|
|
192
|
+
|
|
193
|
+
## Idempotency
|
|
194
|
+
|
|
195
|
+
**The watermark cannot provide it.** `last_scan.json` is `{scanned_at: <ISO>}` —
|
|
196
|
+
a wall-clock timestamp stamped at scan time, not an artifact-id set. The parent
|
|
197
|
+
spec's failure table states that the watermark, ledger and pool are "all keyed
|
|
198
|
+
by `artifact_id`"; that is true of the ledger and pool but not the watermark.
|
|
199
|
+
|
|
200
|
+
Before spending a model call, skip when the `artifact_id` already appears in
|
|
201
|
+
`lessons/proposals/`, `lessons/declined.jsonl`, or `lessons/approved/`. That
|
|
202
|
+
check is what actually delivers "the same artifact cannot be promoted twice" and
|
|
203
|
+
"re-runs are cheap."
|
|
204
|
+
|
|
205
|
+
Librarian's existing tombstones do not help here: they are keyed by body hash
|
|
206
|
+
with a 180-day TTL, built for "the user rejected this memory, stop offering it."
|
|
207
|
+
Lesson idempotency must be artifact-keyed and permanent.
|
|
208
|
+
|
|
209
|
+
## Testing
|
|
210
|
+
|
|
211
|
+
bats, using the repo's isolated-temp-home helpers.
|
|
212
|
+
|
|
213
|
+
**The parent spec's acceptance test inverts.** It designates artifact
|
|
214
|
+
`01KZ45MKAM734ZS7JK24D2DK0R` — the real vitest/vite mismatch — and expects it to
|
|
215
|
+
transform to `kind: versioned` with `{vite: "<6", vitest: ">=4"}`. Under decision
|
|
216
|
+
1 it cannot: the artifact records only that the mismatch is real, and its session
|
|
217
|
+
ended with an open question titled "What is the correct fix for the vitest/vite
|
|
218
|
+
version mismatch?" No resolution was ever found. Neither does its later
|
|
219
|
+
refutation artifact (`01KZEAF9EY4C6TTR0V7YFN9VYJ`), which says the claim was
|
|
220
|
+
disproven without stating what is true.
|
|
221
|
+
|
|
222
|
+
So it becomes the **negative** test: declined, `no_resolution`. The artifact that
|
|
223
|
+
motivated the whole design proves the resolution rule fires.
|
|
224
|
+
|
|
225
|
+
The positive test uses a synthetic fixture, since no artifact on disk carries
|
|
226
|
+
both a resolution and versions. Shape it like the vitest case so it still
|
|
227
|
+
asserts `{vite: "<6", vitest: ">=4"}`.
|
|
228
|
+
|
|
229
|
+
Also:
|
|
230
|
+
|
|
231
|
+
- validator agreement over a range corpus — `^5.4.21`, `~5`, `5.x`, `5.4.21`,
|
|
232
|
+
`>=0`, `>=0.0.0` must all fail; `<6`, `>=4`, `>=4 <6`, `=6` must pass
|
|
233
|
+
- infrastructure is not a verdict: with `claude` absent, nothing is written to
|
|
234
|
+
`declined.jsonl` or anywhere else. This is the test most likely to regress
|
|
235
|
+
silently
|
|
236
|
+
- idempotency: an `artifact_id` already in `declined.jsonl` triggers no model call
|
|
237
|
+
- cross-field: a `versions` key absent from `stack` is declined
|
|
238
|
+
- pre-gate: an artifact with no version token produces no ledger entry
|
|
239
|
+
|
|
240
|
+
## Events
|
|
241
|
+
|
|
242
|
+
`librarian.lesson.proposed` and `librarian.lesson.declined`, emitted through
|
|
243
|
+
`scripts/lib/onlooker-event.mjs`. Both must be registered in
|
|
244
|
+
`@onlooker-community/schema` before they are emitted.
|
|
245
|
+
|
|
246
|
+
## Boundary changes to sibling issues
|
|
247
|
+
|
|
248
|
+
Building this stage first has two consequences for the issues around it:
|
|
249
|
+
|
|
250
|
+
- **`4z8.4`** currently owns `declined.jsonl` and its append function. The
|
|
251
|
+
transform produces declines, so the storage primitive moves here and `4z8.4`
|
|
252
|
+
reuses it.
|
|
253
|
+
- **`4z8.2`** gains the `version_independent` path from decision 3, including
|
|
254
|
+
where a human writes and edits the justification.
|
|
255
|
+
|
|
256
|
+
## Out of scope
|
|
257
|
+
|
|
258
|
+
Version inference from anything but artifact prose. Reading the project's
|
|
259
|
+
current `package.json` or lockfile would report today's versions, not the
|
|
260
|
+
versions in effect when the problem was observed — and the gap between those two
|
|
261
|
+
is the entire staleness problem this pipeline exists to solve.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlooker-community/ecosystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh plugins/archivist/scripts/hooks/*.sh plugins/archivist/scripts/lib/*.sh plugins/tribunal/scripts/hooks/*.sh plugins/tribunal/scripts/lib/*.sh plugins/echo/scripts/hooks/*.sh plugins/echo/scripts/lib/*.sh plugins/governor/scripts/hooks/*.sh plugins/governor/scripts/lib/*.sh plugins/compass/scripts/hooks/*.sh plugins/compass/scripts/lib/*.sh plugins/scribe/scripts/hooks/*.sh plugins/scribe/scripts/lib/*.sh plugins/counsel/scripts/hooks/*.sh plugins/counsel/scripts/lib/*.sh plugins/warden/scripts/hooks/*.sh plugins/warden/scripts/lib/*.sh plugins/librarian/scripts/hooks/*.sh plugins/librarian/scripts/lib/*.sh plugins/curator/scripts/hooks/*.sh plugins/curator/scripts/lib/*.sh plugins/historian/scripts/hooks/*.sh plugins/historian/scripts/lib/*.sh plugins/assayer/scripts/hooks/*.sh plugins/assayer/scripts/lib/*.sh plugins/cartographer/scripts/hooks/*.sh plugins/cartographer/scripts/lib/*.sh plugins/bursar/scripts/hooks/*.sh plugins/bursar/scripts/lib/*.sh plugins/lineage/scripts/hooks/*.sh plugins/lineage/scripts/lib/*.sh plugins/inspector/scripts/hooks/*.sh plugins/inspector/scripts/lib/*.sh",
|
|
26
26
|
"lint:references": "node scripts/lint/check-references.mjs",
|
|
27
27
|
"lint:manifests": "node scripts/lint/check-manifests.mjs",
|
|
28
|
+
"lint:lesson-schema": "node scripts/lint/check-lesson-schema-drift.mjs",
|
|
28
29
|
"coverage:node": "node scripts/coverage/run-coverage.mjs",
|
|
29
30
|
"coverage:bash": "node scripts/coverage/bash-coverage.mjs",
|
|
30
31
|
"coverage": "npm run coverage:node && npm run coverage:bash",
|
|
31
|
-
"test:ci": "npm run test:shellcheck && npm run test:bats && npm run test:schema && npm run lint:check && npm run lint:manifests && npm run lint:references",
|
|
32
|
+
"test:ci": "npm run test:shellcheck && npm run test:bats && npm run test:schema && npm run lint:check && npm run lint:manifests && npm run lint:references && npm run lint:lesson-schema",
|
|
32
33
|
"lint:check": "biome check . && markdownlint '**/*.md'",
|
|
33
34
|
"lint": "biome lint --write && markdownlint --fix '**/*.md'",
|
|
34
35
|
"format": "biome format --write && markdownlint --fix '**/*.md'",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Config resolution for Archivist.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/archivist/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
12
|
# archivist_config_load <repo_root> # populates _ARCHIVIST_CONFIG (JSON)
|
|
@@ -13,46 +15,20 @@
|
|
|
13
15
|
# Settings overlay only touches the `archivist.*` subtree of settings.json so it
|
|
14
16
|
# coexists with other plugins' configuration.
|
|
15
17
|
|
|
18
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
19
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
20
|
+
|
|
16
21
|
_ARCHIVIST_CONFIG="{}"
|
|
17
22
|
|
|
18
23
|
archivist_config_load() {
|
|
19
24
|
local repo_root="${1:-}"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
local merged="{}"
|
|
24
|
-
local file
|
|
25
|
-
|
|
26
|
-
file="${plugin_root}/config.json"
|
|
27
|
-
if [[ -f "$file" ]]; then
|
|
28
|
-
local defaults
|
|
29
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
30
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
31
|
-
|| merged="$defaults"
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
for file in "${home_dir}/.claude/settings.json" "${repo_root}/.claude/settings.json"; do
|
|
35
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
36
|
-
local overlay
|
|
37
|
-
overlay=$(jq '{ archivist: (.archivist // {}) }' "$file" 2>/dev/null) || continue
|
|
38
|
-
[[ -z "$overlay" ]] && continue
|
|
39
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
40
|
-
def deepmerge($a; $b):
|
|
41
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
42
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
43
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
44
|
-
elif $b == null then $a
|
|
45
|
-
else $b end;
|
|
46
|
-
deepmerge($a; $b)
|
|
47
|
-
' 2>/dev/null) || true
|
|
48
|
-
done
|
|
49
|
-
|
|
50
|
-
_ARCHIVIST_CONFIG="$merged"
|
|
25
|
+
config_load_plugin "archivist" "$repo_root" "_ARCHIVIST_CONFIG"
|
|
26
|
+
return 0
|
|
51
27
|
}
|
|
52
28
|
|
|
53
29
|
# Read a value from the loaded config. Usage:
|
|
54
30
|
# archivist_config_get '.archivist.injection.max_items'
|
|
55
31
|
archivist_config_get() {
|
|
56
32
|
local path="$1"
|
|
57
|
-
|
|
33
|
+
config_get "_ARCHIVIST_CONFIG" "${path}"
|
|
58
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assayer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Claim verification. At session end, parses the agent's final message for testable claims (\"I ran the tests, they pass\", \"the build is green\") and checks each against the actual command results in the session transcript, classifying it corroborated, contradicted, or unverifiable. Catches lying-without-malice. Advisory by default. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.2](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.1...assayer-v1.1.2) (2026-08-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* restore config convenience functions & refactor execution plugins to shared loader ([#128](https://github.com/onlooker-community/ecosystem/issues/128)) ([4b3660c](https://github.com/onlooker-community/ecosystem/commit/4b3660c5a8b234187ec1e71c37b63e6a3d305c98))
|
|
9
|
+
|
|
3
10
|
## [1.1.1](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.0...assayer-v1.1.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,82 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Config
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
# Config resolution for assayer.
|
|
3
|
+
#
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
|
+
# 1. plugins/assayer/config.json (defaults shipped with the plugin)
|
|
6
|
+
# 2. ~/.claude/settings.json
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
10
|
+
#
|
|
11
|
+
# Exposes:
|
|
12
|
+
# assayer_config_load <repo_root> # populates _assayer_CONFIG (JSON)
|
|
13
|
+
# assayer_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# assayer_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
|
+
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
18
|
+
|
|
19
|
+
_assayer_CONFIG="{}"
|
|
8
20
|
|
|
9
21
|
assayer_config_load() {
|
|
10
22
|
local repo_root="${1:-}"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
local plugin_config="${CLAUDE_PLUGIN_ROOT:-}/config.json"
|
|
14
|
-
if [[ -f "$plugin_config" ]]; then
|
|
15
|
-
_ASSAYER_PLUGIN_CONFIG_JSON=$(cat "$plugin_config" 2>/dev/null) || _ASSAYER_PLUGIN_CONFIG_JSON=""
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
_ASSAYER_CONFIG_JSON=""
|
|
19
|
-
if [[ -n "$repo_root" ]]; then
|
|
20
|
-
local settings_file="${repo_root}/.claude/settings.json"
|
|
21
|
-
if [[ -f "$settings_file" ]]; then
|
|
22
|
-
local settings
|
|
23
|
-
settings=$(cat "$settings_file" 2>/dev/null) || settings=""
|
|
24
|
-
local block
|
|
25
|
-
block=$(printf '%s' "$settings" | jq -c '.assayer // empty' 2>/dev/null) || block=""
|
|
26
|
-
[[ -n "$block" ]] && _ASSAYER_CONFIG_JSON="$block"
|
|
27
|
-
fi
|
|
28
|
-
fi
|
|
23
|
+
config_load_plugin "assayer" "$repo_root" "_assayer_CONFIG"
|
|
24
|
+
return 0
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
# Get a single scalar value. Checks settings.json first, then plugin config.json.
|
|
32
27
|
assayer_config_get() {
|
|
33
|
-
local
|
|
34
|
-
|
|
35
|
-
if [[ -n "$_ASSAYER_CONFIG_JSON" ]]; then
|
|
36
|
-
local val
|
|
37
|
-
val=$(printf '%s' "$_ASSAYER_CONFIG_JSON" | jq -r "${key} // empty" 2>/dev/null) || val=""
|
|
38
|
-
[[ -n "$val" && "$val" != "null" ]] && {
|
|
39
|
-
printf '%s' "$val"
|
|
40
|
-
return 0
|
|
41
|
-
}
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
if [[ -n "$_ASSAYER_PLUGIN_CONFIG_JSON" ]]; then
|
|
45
|
-
local val
|
|
46
|
-
val=$(printf '%s' "$_ASSAYER_PLUGIN_CONFIG_JSON" | jq -r ".assayer${key} // empty" 2>/dev/null) || val=""
|
|
47
|
-
[[ -n "$val" && "$val" != "null" ]] && {
|
|
48
|
-
printf '%s' "$val"
|
|
49
|
-
return 0
|
|
50
|
-
}
|
|
51
|
-
fi
|
|
28
|
+
local path="$1"
|
|
29
|
+
config_get "_assayer_CONFIG" "${path}"
|
|
52
30
|
}
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
local
|
|
56
|
-
|
|
57
|
-
printf '%s' "${val:-claude-haiku-4-5-20251001}"
|
|
32
|
+
assayer_config_get_json() {
|
|
33
|
+
local path="$1"
|
|
34
|
+
config_get_json "_assayer_CONFIG" "${path}"
|
|
58
35
|
}
|
|
59
36
|
|
|
60
|
-
|
|
61
|
-
local
|
|
62
|
-
|
|
63
|
-
printf '%s' "${
|
|
37
|
+
assayer_config_model() {
|
|
38
|
+
local v
|
|
39
|
+
v=$(assayer_config_get '.assayer.evaluation.model')
|
|
40
|
+
printf '%s' "${v:-claude-haiku-4-5-20251001}"
|
|
64
41
|
}
|
|
65
42
|
|
|
66
43
|
assayer_config_max_claims() {
|
|
67
|
-
local
|
|
68
|
-
|
|
69
|
-
printf '%s' "${
|
|
44
|
+
local v
|
|
45
|
+
v=$(assayer_config_get '.assayer.max_claims')
|
|
46
|
+
printf '%s' "${v:-12}"
|
|
70
47
|
}
|
|
71
48
|
|
|
72
49
|
assayer_config_min_confidence() {
|
|
73
|
-
local
|
|
74
|
-
|
|
75
|
-
printf '%s' "${
|
|
50
|
+
local v
|
|
51
|
+
v=$(assayer_config_get '.assayer.min_confidence')
|
|
52
|
+
printf '%s' "${v:-0.5}"
|
|
76
53
|
}
|
|
77
54
|
|
|
78
|
-
|
|
79
|
-
local
|
|
80
|
-
|
|
81
|
-
printf '%s' "${
|
|
55
|
+
assayer_config_timeout() {
|
|
56
|
+
local v
|
|
57
|
+
v=$(assayer_config_get '.assayer.evaluation.timeout_seconds')
|
|
58
|
+
printf '%s' "${v:-60}"
|
|
82
59
|
}
|
|
@@ -1,54 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# cartographer-config.sh — load and query Cartographer configuration.
|
|
3
3
|
#
|
|
4
|
-
# Merges
|
|
4
|
+
# Uses the shared config loader from ecosystem. Merges five layers in precedence order:
|
|
5
5
|
# 1. plugins/cartographer/config.json (plugin defaults)
|
|
6
6
|
# 2. ~/.claude/settings.json (.cartographer subtree)
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (.cartographer subtree, local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json (.cartographer subtree)
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (.cartographer subtree, local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Usage:
|
|
10
12
|
# cartographer_config_load <repo_root>
|
|
11
13
|
# cartographer_config_get_json ".cartographer.exclude_paths"
|
|
12
14
|
|
|
15
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
16
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
17
|
+
|
|
13
18
|
_CARTOGRAPHER_CONFIG=""
|
|
14
|
-
_CARTOGRAPHER_PLUGIN_CONFIG=""
|
|
15
19
|
|
|
16
20
|
cartographer_config_load() {
|
|
17
21
|
local repo_root="${1:-}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
local plugin_config="$plugin_dir/config.json"
|
|
21
|
-
|
|
22
|
-
_CARTOGRAPHER_PLUGIN_CONFIG="{}"
|
|
23
|
-
if [[ -f "$plugin_config" ]]; then
|
|
24
|
-
_CARTOGRAPHER_PLUGIN_CONFIG=$(cat "$plugin_config")
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
local home_settings="{}"
|
|
28
|
-
if [[ -f "$HOME/.claude/settings.json" ]]; then
|
|
29
|
-
home_settings=$(cat "$HOME/.claude/settings.json")
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
local repo_settings="{}"
|
|
33
|
-
if [[ -n "$repo_root" && -f "$repo_root/.claude/settings.json" ]]; then
|
|
34
|
-
repo_settings=$(cat "$repo_root/.claude/settings.json")
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
_CARTOGRAPHER_CONFIG=$(jq -n \
|
|
38
|
-
--argjson plugin "$_CARTOGRAPHER_PLUGIN_CONFIG" \
|
|
39
|
-
--argjson home "$home_settings" \
|
|
40
|
-
--argjson repo "$repo_settings" \
|
|
41
|
-
'$plugin * {"cartographer": (($plugin.cartographer // {}) * ($home.cartographer // {}) * ($repo.cartographer // {}))}')
|
|
22
|
+
config_load_plugin "cartographer" "$repo_root" "_CARTOGRAPHER_CONFIG"
|
|
23
|
+
return 0
|
|
42
24
|
}
|
|
43
25
|
|
|
44
26
|
cartographer_config_get() {
|
|
45
27
|
local path="${1:-}"
|
|
46
|
-
|
|
28
|
+
config_get "_CARTOGRAPHER_CONFIG" "${path}"
|
|
47
29
|
}
|
|
48
30
|
|
|
49
31
|
cartographer_config_get_json() {
|
|
50
32
|
local path="${1:-}"
|
|
51
|
-
|
|
33
|
+
config_get_json "_CARTOGRAPHER_CONFIG" "${path}"
|
|
52
34
|
}
|
|
53
35
|
|
|
54
36
|
cartographer_config_model_extraction() {
|
|
@@ -1,65 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Config resolution for
|
|
2
|
+
# Config resolution for compass.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/compass/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
|
-
# compass_config_load <repo_root>
|
|
11
|
-
# compass_config_get <jq-path>
|
|
12
|
-
# compass_config_get_json <jq-path>
|
|
12
|
+
# compass_config_load <repo_root> # populates _compass_CONFIG (JSON)
|
|
13
|
+
# compass_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# compass_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
18
|
+
|
|
19
|
+
_compass_CONFIG="{}"
|
|
15
20
|
|
|
16
21
|
compass_config_load() {
|
|
17
22
|
local repo_root="${1:-}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
local merged="{}"
|
|
22
|
-
local file
|
|
23
|
-
|
|
24
|
-
file="${plugin_root}/config.json"
|
|
25
|
-
if [[ -f "$file" ]]; then
|
|
26
|
-
local defaults
|
|
27
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
28
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
29
|
-
|| merged="$defaults"
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
local repo_settings=""
|
|
33
|
-
[[ -n "$repo_root" ]] && repo_settings="${repo_root}/.claude/settings.json"
|
|
34
|
-
|
|
35
|
-
for file in "${home_dir}/.claude/settings.json" "$repo_settings"; do
|
|
36
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
37
|
-
local overlay
|
|
38
|
-
overlay=$(jq '{ compass: (.compass // {}) }' "$file" 2>/dev/null) || continue
|
|
39
|
-
[[ -z "$overlay" ]] && continue
|
|
40
|
-
local attempt
|
|
41
|
-
if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
42
|
-
def deepmerge($a; $b):
|
|
43
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
44
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
45
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
46
|
-
elif $b == null then $a
|
|
47
|
-
else $b end;
|
|
48
|
-
deepmerge($a; $b)
|
|
49
|
-
' 2>/dev/null) && [[ -n "$attempt" ]]; then
|
|
50
|
-
merged="$attempt"
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
53
|
-
|
|
54
|
-
_COMPASS_CONFIG="$merged"
|
|
23
|
+
config_load_plugin "compass" "$repo_root" "_compass_CONFIG"
|
|
24
|
+
return 0
|
|
55
25
|
}
|
|
56
26
|
|
|
57
27
|
compass_config_get() {
|
|
58
28
|
local path="$1"
|
|
59
|
-
|
|
29
|
+
config_get "_compass_CONFIG" "${path}"
|
|
60
30
|
}
|
|
61
31
|
|
|
62
32
|
compass_config_get_json() {
|
|
63
33
|
local path="$1"
|
|
64
|
-
|
|
34
|
+
config_get_json "_compass_CONFIG" "${path}"
|
|
65
35
|
}
|