@onlooker-community/ecosystem 0.43.4 → 0.43.5
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-plugin/plugin.json +1 -1
- package/.release-please-manifest.json +17 -17
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +5 -0
- package/docs/lesson-promotion-pipeline.md +9 -1
- package/package.json +1 -1
- package/plugins/archivist/.claude-plugin/plugin.json +1 -1
- package/plugins/archivist/CHANGELOG.md +7 -0
- package/plugins/archivist/scripts/lib/archivist-config.sh +6 -2
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +6 -2
- package/plugins/bursar/.claude-plugin/plugin.json +1 -1
- package/plugins/bursar/CHANGELOG.md +7 -0
- package/plugins/bursar/scripts/lib/bursar-config.sh +6 -2
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +7 -0
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +6 -2
- package/plugins/compass/.claude-plugin/plugin.json +1 -1
- package/plugins/compass/CHANGELOG.md +7 -0
- package/plugins/compass/scripts/lib/compass-config.sh +6 -2
- package/plugins/counsel/.claude-plugin/plugin.json +1 -1
- package/plugins/counsel/CHANGELOG.md +7 -0
- package/plugins/counsel/scripts/lib/counsel-config.sh +6 -2
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-config.sh +6 -2
- package/plugins/echo/.claude-plugin/plugin.json +1 -1
- package/plugins/echo/CHANGELOG.md +7 -0
- package/plugins/echo/scripts/lib/echo-config.sh +6 -2
- package/plugins/governor/.claude-plugin/plugin.json +1 -1
- package/plugins/governor/CHANGELOG.md +7 -0
- package/plugins/governor/scripts/lib/governor-config.sh +6 -2
- package/plugins/historian/.claude-plugin/plugin.json +1 -1
- package/plugins/historian/CHANGELOG.md +7 -0
- package/plugins/historian/scripts/lib/historian-config.sh +6 -2
- package/plugins/inspector/.claude-plugin/plugin.json +1 -1
- package/plugins/inspector/CHANGELOG.md +7 -0
- package/plugins/inspector/scripts/lib/inspector-config.sh +6 -2
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +8 -0
- package/plugins/librarian/scripts/lib/librarian-config.sh +6 -2
- package/plugins/librarian/scripts/lib/librarian-lesson-promote.sh +22 -2
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +19 -1
- package/plugins/lineage/.claude-plugin/plugin.json +1 -1
- package/plugins/lineage/CHANGELOG.md +7 -0
- package/plugins/lineage/scripts/lib/lineage-config.sh +6 -2
- package/plugins/scribe/.claude-plugin/plugin.json +1 -1
- package/plugins/scribe/CHANGELOG.md +7 -0
- package/plugins/scribe/scripts/lib/scribe-config.sh +6 -2
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +7 -0
- package/plugins/tribunal/scripts/lib/tribunal-config.sh +6 -2
- package/plugins/warden/.claude-plugin/plugin.json +1 -1
- package/plugins/warden/CHANGELOG.md +7 -0
- package/plugins/warden/scripts/lib/warden-config.sh +6 -2
- package/scripts/lib/config-loader.sh +15 -2
- package/test/bats/config-lib-self-locating.bats +124 -0
- package/test/bats/librarian-lesson-promote.bats +89 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecosystem",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.5",
|
|
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,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
".": "0.43.
|
|
3
|
-
"plugins/archivist": "0.3.
|
|
4
|
-
"plugins/tribunal": "1.2.
|
|
5
|
-
"plugins/echo": "0.3.
|
|
6
|
-
"plugins/cartographer": "0.6.
|
|
7
|
-
"plugins/governor": "0.3.
|
|
8
|
-
"plugins/compass": "0.4.
|
|
9
|
-
"plugins/scribe": "0.4.
|
|
10
|
-
"plugins/counsel": "0.5.
|
|
11
|
-
"plugins/warden": "0.3.
|
|
12
|
-
"plugins/librarian": "0.13.
|
|
13
|
-
"plugins/curator": "0.2.
|
|
14
|
-
"plugins/historian": "0.3.
|
|
15
|
-
"plugins/assayer": "1.1.
|
|
16
|
-
"plugins/bursar": "0.3.
|
|
17
|
-
"plugins/lineage": "0.2.
|
|
18
|
-
"plugins/inspector": "0.3.
|
|
2
|
+
".": "0.43.5",
|
|
3
|
+
"plugins/archivist": "0.3.2",
|
|
4
|
+
"plugins/tribunal": "1.2.8",
|
|
5
|
+
"plugins/echo": "0.3.2",
|
|
6
|
+
"plugins/cartographer": "0.6.1",
|
|
7
|
+
"plugins/governor": "0.3.3",
|
|
8
|
+
"plugins/compass": "0.4.2",
|
|
9
|
+
"plugins/scribe": "0.4.3",
|
|
10
|
+
"plugins/counsel": "0.5.2",
|
|
11
|
+
"plugins/warden": "0.3.2",
|
|
12
|
+
"plugins/librarian": "0.13.3",
|
|
13
|
+
"plugins/curator": "0.2.4",
|
|
14
|
+
"plugins/historian": "0.3.4",
|
|
15
|
+
"plugins/assayer": "1.1.4",
|
|
16
|
+
"plugins/bursar": "0.3.1",
|
|
17
|
+
"plugins/lineage": "0.2.3",
|
|
18
|
+
"plugins/inspector": "0.3.3"
|
|
19
19
|
}
|
package/AGENTS.md
CHANGED
|
@@ -136,6 +136,11 @@ See `plugins/compass/docs/adr/001-evaluate-prompts-in-context.md` for the full d
|
|
|
136
136
|
a test drives the branch that emits it, `excluded` with a reason when not.
|
|
137
137
|
`npm run test:bus` fails on any registered type that appears in neither list.
|
|
138
138
|
7. Fail-soft when `~/.onlooker/` is absent — plugins must not block a session they were not invited to.
|
|
139
|
+
8. In `plugins/<name>/scripts/lib/<name>-config.sh`, locate `config-loader.sh` from the file's own
|
|
140
|
+
`${BASH_SOURCE[0]}`, never from a caller-supplied `$PLUGIN_ROOT`. That variable is read at source
|
|
141
|
+
time from whatever scope did the sourcing, so a sub-shell that inherits `CLAUDE_PLUGIN_ROOT` but not
|
|
142
|
+
`PLUGIN_ROOT` loses every accessor while still exiting 0 — config silently falls back to defaults.
|
|
143
|
+
`test/bats/config-lib-self-locating.bats` enforces this across every plugin.
|
|
139
144
|
|
|
140
145
|
## Development
|
|
141
146
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.43.5](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.4...ecosystem-v0.43.5) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
* **librarian:** key the declined-ledger guard on the proposal, not the artifact :bug: ([#199](https://github.com/onlooker-community/ecosystem/issues/199)) ([47a195d](https://github.com/onlooker-community/ecosystem/commit/47a195da98a3a565018c35e9e5c6dde2405520de))
|
|
10
|
+
|
|
3
11
|
## [0.43.4](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.3...ecosystem-v0.43.4) (2026-08-22)
|
|
4
12
|
|
|
5
13
|
|
package/CLAUDE.md
CHANGED
|
@@ -83,6 +83,11 @@ See `plugins/compass/docs/adr/001-evaluate-prompts-in-context.md` for the full d
|
|
|
83
83
|
a test drives the branch that emits it, `excluded` with a reason when not.
|
|
84
84
|
`npm run test:bus` fails on any registered type that appears in neither list.
|
|
85
85
|
7. Fail-soft when `~/.onlooker/` is absent — plugins must not block a session they were not invited to.
|
|
86
|
+
8. In `plugins/<name>/scripts/lib/<name>-config.sh`, locate `config-loader.sh` from the file's own
|
|
87
|
+
`${BASH_SOURCE[0]}`, never from a caller-supplied `$PLUGIN_ROOT`. That variable is read at source
|
|
88
|
+
time from whatever scope did the sourcing, so a sub-shell that inherits `CLAUDE_PLUGIN_ROOT` but not
|
|
89
|
+
`PLUGIN_ROOT` loses every accessor while still exiting 0 — config silently falls back to defaults.
|
|
90
|
+
`test/bats/config-lib-self-locating.bats` enforces this across every plugin.
|
|
86
91
|
|
|
87
92
|
## Development
|
|
88
93
|
|
|
@@ -153,7 +153,7 @@ a floor a strong weighted mean cannot average away.
|
|
|
153
153
|
```
|
|
154
154
|
~/.onlooker/librarian/<project-key>/
|
|
155
155
|
lessons/approved/<ulid>.json jury passed; awaiting sync
|
|
156
|
-
lessons/declined.jsonl artifact_id + verdict + reason
|
|
156
|
+
lessons/declined.jsonl artifact_id + lesson_id + verdict + reason
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
The declined ledger matters more than it looks. The watermark advances past a
|
|
@@ -161,6 +161,14 @@ rejected artifact, so without a record a drop is either silently permanent or
|
|
|
161
161
|
on a rescan — re-pays Opus tokens to re-judge the same failures every session.
|
|
162
162
|
Append-only, never re-judged automatically.
|
|
163
163
|
|
|
164
|
+
`lesson_id` names the proposal a verdict came from, and is null when there is
|
|
165
|
+
none — the transform declines an artifact before any proposal exists. Promote's
|
|
166
|
+
double-write guard keys on it so two distinct proposals that share an
|
|
167
|
+
artifact_id each get their own row; keyed on artifact_id it dropped the second
|
|
168
|
+
verdict silently. `librarian_lesson_seen` deliberately stays keyed on
|
|
169
|
+
artifact_id, because it asks a different question: was this *artifact* already
|
|
170
|
+
handled?
|
|
171
|
+
|
|
164
172
|
**"Judged and failed" is not "could not judge."** Only real verdicts go in
|
|
165
173
|
`declined.jsonl`. A tribunal API error, or a jury below quorum, leaves the
|
|
166
174
|
candidate in proposals untouched. Conflating them lets one transient outage
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archivist",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Structured session memory across context truncation: extracts decisions, dead ends, and open questions on PreCompact and reinjects the most important items at SessionStart. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/onlooker-community/ecosystem/compare/archivist-v0.3.1...archivist-v0.3.2) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/archivist-v0.3.0...archivist-v0.3.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -15,8 +15,12 @@
|
|
|
15
15
|
# Settings overlay only touches the `archivist.*` subtree of settings.json so it
|
|
16
16
|
# coexists with other plugins' configuration.
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
|
|
18
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
19
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
20
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
21
|
+
_ARCHIVIST_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
23
|
+
source "${_ARCHIVIST_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
20
24
|
|
|
21
25
|
_ARCHIVIST_CONFIG="{}"
|
|
22
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assayer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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.4](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.3...assayer-v1.1.4) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [1.1.3](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.2...assayer-v1.1.3) (2026-08-10)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# assayer_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# assayer_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_ASSAYER_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_ASSAYER_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_assayer_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bursar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Multi-session, per-project budget accounting for the Onlooker ecosystem. Rolls each session's spend into a per-project ledger on SessionEnd and surfaces \"this project burned $X this week\" at SessionStart. Where governor regulates a single session, bursar is the cross-session rollup: it reads governor.session.complete off the shared event bus and emits bursar.* events for audit. Named for the officer who keeps the accounts. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/bursar-v0.3.0...bursar-v0.3.1) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.0](https://github.com/onlooker-community/ecosystem/compare/bursar-v0.2.2...bursar-v0.3.0) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
# bursar_config_surface_enabled # 0 if bursar.surface_at_session_start is true
|
|
17
17
|
# bursar_config_week_start # echoes "monday" or "sunday"
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
|
|
19
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
20
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
21
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
22
|
+
_BURSAR_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
24
|
+
source "${_BURSAR_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
21
25
|
|
|
22
26
|
_BURSAR_CONFIG="{}"
|
|
23
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cartographer",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Proactive periodic auditor of the persistent instruction layer (CLAUDE.md, AGENTS.md, .claude/rules/). Discovers all instruction files in the repo, extracts semantic maps, and surfaces contradictions, shadowing, gaps, and drift before they cause expensive agent misbehavior. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Cartographer plugin are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.6.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.6.0...cartographer-v0.6.1) (2026-08-22)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
11
|
+
|
|
5
12
|
## [0.6.0](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.5.1...cartographer-v0.6.0) (2026-08-19)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
# cartographer_config_load <repo_root>
|
|
13
13
|
# cartographer_config_get_json ".cartographer.exclude_paths"
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
15
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
16
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
17
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
18
|
+
_CARTOGRAPHER_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
20
|
+
source "${_CARTOGRAPHER_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
17
21
|
|
|
18
22
|
_CARTOGRAPHER_CONFIG=""
|
|
19
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compass",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Pre-write intent clarity gate. Intercepts write-class tool calls and requires a confidence threshold before allowing them to proceed. Evaluates the pending write against the prior assistant turn as context to avoid false positives on question-answer turns. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.2](https://github.com/onlooker-community/ecosystem/compare/compass-v0.4.1...compass-v0.4.2) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.4.1](https://github.com/onlooker-community/ecosystem/compare/compass-v0.4.0...compass-v0.4.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# compass_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# compass_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_COMPASS_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_COMPASS_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_compass_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "counsel",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Weekly synthesis and recommendations from the full observability stack. Reads all plugin event logs, produces a structured improvement brief, and injects it at session start when the last brief is stale.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.2](https://github.com/onlooker-community/ecosystem/compare/counsel-v0.5.1...counsel-v0.5.2) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.5.1](https://github.com/onlooker-community/ecosystem/compare/counsel-v0.5.0...counsel-v0.5.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# counsel_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# counsel_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_COUNSEL_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_COUNSEL_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_counsel_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "curator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Maintenance layer for the user's typed auto-memory store. At every SessionStart, runs four cheap heuristic checks (date_decayed, path_broken, broken_index, orphaned_memory) against the memories at ~/.claude/projects/<encoded>/memory/ inside a wall-clock budget. Surfaces findings as a one-line pointer to /curator review; never edits the memory store directly. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.4](https://github.com/onlooker-community/ecosystem/compare/curator-v0.2.3...curator-v0.2.4) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.2.3](https://github.com/onlooker-community/ecosystem/compare/curator-v0.2.2...curator-v0.2.3) (2026-08-10)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# curator_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# curator_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_CURATOR_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_CURATOR_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_curator_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echo",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Prompt-change regression detection. When a watched agent file is modified, Echo runs a single-judge quality pass via claude -p and compares the score against a stored baseline to report improved, degraded, or neutral. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/onlooker-community/ecosystem/compare/echo-v0.3.1...echo-v0.3.2) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/echo-v0.3.0...echo-v0.3.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# echo_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# echo_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_ECHO_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_ECHO_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_echo_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "governor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Resource governance and budget enforcement for the Onlooker ecosystem. Tracks per-session token and cost spend, gates Task spawns before they exceed a configurable budget ceiling, and emits governor.* events for audit. Named for the steam-engine governor — a device that regulates output. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.3](https://github.com/onlooker-community/ecosystem/compare/governor-v0.3.2...governor-v0.3.3) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.2](https://github.com/onlooker-community/ecosystem/compare/governor-v0.3.1...governor-v0.3.2) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
# governor_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
# governor_config_enforcement # echoes "soft" or "hard"
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
18
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
19
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
20
|
+
_GOVERNOR_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
22
|
+
source "${_GOVERNOR_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
19
23
|
|
|
20
24
|
_GOVERNOR_CONFIG="{}"
|
|
21
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "historian",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Episodic memory layer. At SessionEnd, chunks and sanitizes the session transcript and stores chunks under $ONLOOKER_DIR/historian/<project-key>/sessions/ (default $HOME/.onlooker). On UserPromptSubmit, embeds the prompt and performs similarity retrieval over stored chunks to surface relevant past context. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.4](https://github.com/onlooker-community/ecosystem/compare/historian-v0.3.3...historian-v0.3.4) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.3](https://github.com/onlooker-community/ecosystem/compare/historian-v0.3.2...historian-v0.3.3) (2026-08-10)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Settings overlay only touches the `historian.*` subtree of settings.json.
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
18
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
19
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
20
|
+
_HISTORIAN_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
22
|
+
source "${_HISTORIAN_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
19
23
|
|
|
20
24
|
_HISTORIAN_CONFIG="{}"
|
|
21
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inspector",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Per-edit lint and typecheck gate. Runs the project's configured checks on just the touched file after every Write / Edit / MultiEdit, so the agent sees its own type errors before claiming success. Cheaper than proctor (which runs project-wide verification at Stop); complements assayer (which catches the agent lying about claims). Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.3](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.3.2...inspector-v0.3.3) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.2](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.3.1...inspector-v0.3.2) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# inspector_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# inspector_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_INSPECTOR_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_INSPECTOR_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_inspector_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "librarian",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Consolidation layer between archivist's per-session artifacts and the user's durable typed memory store. Detects which session decisions, dead-ends, and open questions deserve to live across sessions, classifies them into the user/feedback/project/reference types, and queues them as proposals for explicit confirmation. Auto-promotion is opt-in. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.3](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.13.2...librarian-v0.13.3) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
* **librarian:** key the declined-ledger guard on the proposal, not the artifact :bug: ([#199](https://github.com/onlooker-community/ecosystem/issues/199)) ([47a195d](https://github.com/onlooker-community/ecosystem/commit/47a195da98a3a565018c35e9e5c6dde2405520de))
|
|
10
|
+
|
|
3
11
|
## [0.13.2](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.13.1...librarian-v0.13.2) (2026-08-18)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
# Settings overlay only touches the `librarian.*` subtree of settings.json so
|
|
17
17
|
# it coexists with other plugins' configuration.
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
|
|
19
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
20
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
21
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
22
|
+
_LIBRARIAN_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
24
|
+
source "${_LIBRARIAN_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
21
25
|
|
|
22
26
|
_LIBRARIAN_CONFIG="{}"
|
|
23
27
|
|
|
@@ -183,19 +183,39 @@ librarian_lesson_promote() {
|
|
|
183
183
|
# declined.jsonl, double-counting a rejected artifact in the
|
|
184
184
|
# rubric-tuning signal the ledger exists to carry.
|
|
185
185
|
#
|
|
186
|
+
# Keyed on lesson_id, not artifact_id. The approved branch's guard is
|
|
187
|
+
# approved/<lesson_id>.json — per-proposal by construction — and this
|
|
188
|
+
# one has to match it. Keyed on artifact_id it could not tell "this
|
|
189
|
+
# artifact was already declined" from "a DIFFERENT proposal for the
|
|
190
|
+
# same artifact was already declined", so the second of two colliding
|
|
191
|
+
# proposals skipped its append and returned 0: both stamped, one
|
|
192
|
+
# verdict on disk. That is the inverse of the double-write this guard
|
|
193
|
+
# exists to stop, and still loses the per-judge detail (ecosystem-bkj).
|
|
194
|
+
#
|
|
195
|
+
# The artifact_id clause is the fallback for rows that legitimately
|
|
196
|
+
# carry no lesson_id: those written before this change, and those the
|
|
197
|
+
# transform writes for an artifact rejected before any proposal
|
|
198
|
+
# existed. Dropping it would leave an unstamped pre-change proposal
|
|
199
|
+
# double-writing on re-run — trading this bug for the older one.
|
|
200
|
+
#
|
|
186
201
|
# Same scan shape as librarian_lesson_seen: -R/fromjson? survives a
|
|
187
202
|
# truncated trailing line from a process killed mid-append, and
|
|
188
203
|
# `objects` stops a valid-but-non-object line from erroring the whole
|
|
189
204
|
# invocation.
|
|
190
205
|
already_declined=false
|
|
191
206
|
if [[ -f "$declined_path" ]] \
|
|
192
|
-
&& jq -Re --arg a "$artifact_id"
|
|
207
|
+
&& jq -Re --arg a "$artifact_id" --arg l "$lesson_id" \
|
|
208
|
+
'fromjson? | objects
|
|
209
|
+
| select(
|
|
210
|
+
($l != "" and .lesson_id == $l)
|
|
211
|
+
or ((.lesson_id // null) == null and .artifact_id == $a)
|
|
212
|
+
)' \
|
|
193
213
|
"$declined_path" >/dev/null 2>&1; then
|
|
194
214
|
already_declined=true
|
|
195
215
|
fi
|
|
196
216
|
|
|
197
217
|
if [[ "$already_declined" == false ]]; then
|
|
198
|
-
librarian_lesson_append_declined "$key" "$artifact_id" "$reason" "" "$verdict" || {
|
|
218
|
+
librarian_lesson_append_declined "$key" "$artifact_id" "$reason" "" "$verdict" "$lesson_id" || {
|
|
199
219
|
printf 'Lesson %s: cannot append to the declined ledger.\n' "$lesson_id" >&2
|
|
200
220
|
return 1
|
|
201
221
|
}
|
|
@@ -83,13 +83,27 @@ librarian_lesson_write_proposal() {
|
|
|
83
83
|
# written by the transform have no verdict and simply lack the key — a format
|
|
84
84
|
# failure has no jury.
|
|
85
85
|
#
|
|
86
|
-
#
|
|
86
|
+
# `lesson_id` names the PROPOSAL this verdict came from, and is null when there
|
|
87
|
+
# is no proposal to name. The transform declines an artifact before any proposal
|
|
88
|
+
# exists (transform_invalid, schema_invalid, and friends), so its rows carry
|
|
89
|
+
# null here and are identified by artifact_id alone.
|
|
90
|
+
#
|
|
91
|
+
# It exists so promote's double-write guard can key on the proposal rather than
|
|
92
|
+
# the artifact. Keyed on artifact_id, that guard could not tell "this artifact
|
|
93
|
+
# was already declined" from "a DIFFERENT proposal for the same artifact was
|
|
94
|
+
# already declined", and silently dropped the second verdict — see
|
|
95
|
+
# ecosystem-bkj. Note this is the opposite of what librarian_lesson_seen wants:
|
|
96
|
+
# that function asks "was this ARTIFACT already handled?" and correctly stays
|
|
97
|
+
# keyed on artifact_id.
|
|
98
|
+
#
|
|
99
|
+
# Usage: librarian_lesson_append_declined <key> <artifact_id> <reason> [detail] [verdict_json] [lesson_id]
|
|
87
100
|
librarian_lesson_append_declined() {
|
|
88
101
|
local key="$1"
|
|
89
102
|
local artifact_id="$2"
|
|
90
103
|
local reason="$3"
|
|
91
104
|
local detail="${4:-}"
|
|
92
105
|
local verdict="${5:-}"
|
|
106
|
+
local lesson_id="${6:-}"
|
|
93
107
|
[[ -z "$key" || -z "$artifact_id" || -z "$reason" ]] && return 1
|
|
94
108
|
|
|
95
109
|
librarian_lesson_storage_init "$key" || return 1
|
|
@@ -99,12 +113,14 @@ librarian_lesson_append_declined() {
|
|
|
99
113
|
if [[ -n "$verdict" ]]; then
|
|
100
114
|
line=$(jq -cn \
|
|
101
115
|
--arg artifact_id "$artifact_id" \
|
|
116
|
+
--arg lesson_id "$lesson_id" \
|
|
102
117
|
--arg reason "$reason" \
|
|
103
118
|
--arg detail "$detail" \
|
|
104
119
|
--arg at "$now" \
|
|
105
120
|
--argjson verdict "$verdict" \
|
|
106
121
|
'{
|
|
107
122
|
artifact_id: $artifact_id,
|
|
123
|
+
lesson_id: (if $lesson_id == "" then null else $lesson_id end),
|
|
108
124
|
reason: $reason,
|
|
109
125
|
detail: (if $detail == "" then null else $detail end),
|
|
110
126
|
declined_at: $at,
|
|
@@ -113,11 +129,13 @@ librarian_lesson_append_declined() {
|
|
|
113
129
|
else
|
|
114
130
|
line=$(jq -cn \
|
|
115
131
|
--arg artifact_id "$artifact_id" \
|
|
132
|
+
--arg lesson_id "$lesson_id" \
|
|
116
133
|
--arg reason "$reason" \
|
|
117
134
|
--arg detail "$detail" \
|
|
118
135
|
--arg at "$now" \
|
|
119
136
|
'{
|
|
120
137
|
artifact_id: $artifact_id,
|
|
138
|
+
lesson_id: (if $lesson_id == "" then null else $lesson_id end),
|
|
121
139
|
reason: $reason,
|
|
122
140
|
detail: (if $detail == "" then null else $detail end),
|
|
123
141
|
declined_at: $at
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lineage",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Per-change provenance for the Onlooker ecosystem. Records session/turn metadata plus a secret-redacted, size-capped snippet for every Edit/Write/MultiEdit at PostToolUse, then answers \"why does this line exist?\" via /lineage by joining change records to historian-preserved transcripts. Emits lineage.* events for audit. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.3](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.2.2...lineage-v0.2.3) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.2.2](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.2.1...lineage-v0.2.2) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# lineage_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# lineage_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_LINEAGE_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_LINEAGE_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_lineage_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribe",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Intent documentation from agent activity. Captures why changes were made — problem context, decisions, tradeoffs — and distills them into readable artifacts at session end.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.3](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.4.2...scribe-v0.4.3) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.4.2](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.4.1...scribe-v0.4.2) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# scribe_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# scribe_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_SCRIBE_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_SCRIBE_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_scribe_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tribunal",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Multi-agent execution with LLM-as-a-Judge quality gates. An Actor performs work; a jury of typed Judges scores it against a project-overridable rubric; a Meta-Judge reviews the jury for bias; the gate decides accept, retry, or exhaust. Grounded in LLM-as-a-Judge (Zheng et al. 2023) and LLM-as-a-Meta-Judge (Wu et al. 2024). 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.2.8](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.7...tribunal-v1.2.8) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [1.2.7](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.6...tribunal-v1.2.7) (2026-08-16)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# tribunal_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# tribunal_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_TRIBUNAL_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_TRIBUNAL_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_TRIBUNAL_CONFIG="{}"
|
|
20
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warden",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Untrusted-content gate. Scans content flowing in through WebFetch and Read for prompt-injection patterns, and when a threat is detected closes a session-scoped gate that blocks Write, Edit, and Bash until the user explicitly clears it. Grounded in Meta's Agents Rule of Two: an agent should hold no more than two of {private data, external actions, untrusted content} at once — warden removes the external-actions property while untrusted content is in play. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/onlooker-community/ecosystem/compare/warden-v0.3.1...warden-v0.3.2) (2026-08-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **config:** let every config lib find the loader from its own path :broom: ([#198](https://github.com/onlooker-community/ecosystem/issues/198)) ([249fe41](https://github.com/onlooker-community/ecosystem/commit/249fe41bf191db48239c2028ba77a10b3dcb03af))
|
|
9
|
+
|
|
3
10
|
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/warden-v0.3.0...warden-v0.3.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
# warden_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
14
|
# warden_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
|
|
16
|
+
# Resolve the shared loader from this file's own location. $PLUGIN_ROOT is
|
|
17
|
+
# whatever the sourcing scope happened to set, so a sub-shell that inherits
|
|
18
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT would lose every accessor silently.
|
|
19
|
+
_WARDEN_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck source=scripts/lib/config-loader.sh
|
|
21
|
+
source "${_WARDEN_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
18
22
|
|
|
19
23
|
_warden_CONFIG="{}"
|
|
20
24
|
|
|
@@ -6,8 +6,21 @@
|
|
|
6
6
|
# across the ecosystem.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
|
-
# # In your plugin's config loader (e.g., plugins/bursar/scripts/lib/bursar-config.sh)
|
|
10
|
-
#
|
|
9
|
+
# # In your plugin's config loader (e.g., plugins/bursar/scripts/lib/bursar-config.sh).
|
|
10
|
+
# # Locate this file from the sourcing file's OWN path, never from a
|
|
11
|
+
# # caller-supplied $PLUGIN_ROOT: that variable is read at source time from
|
|
12
|
+
# # whatever scope did the sourcing, so a sub-shell that inherits
|
|
13
|
+
# # CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT loses every accessor below while
|
|
14
|
+
# # still exiting 0 (see ecosystem-88v, ecosystem-7bj). Four levels up from
|
|
15
|
+
# # plugins/<name>/scripts/lib is the repo root.
|
|
16
|
+
# _BURSAR_CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
# # shellcheck source=scripts/lib/config-loader.sh
|
|
18
|
+
# source "${_BURSAR_CONFIG_LIB_DIR}/../../../../scripts/lib/config-loader.sh"
|
|
19
|
+
#
|
|
20
|
+
# The shellcheck directive is repo-root-relative, not file-relative: shellcheck
|
|
21
|
+
# resolves source= against its own working directory, and `npm run
|
|
22
|
+
# test:shellcheck` runs it from the repo root. A file-relative path there
|
|
23
|
+
# silently degrades to SC1091 "not following", which `-S error` hides.
|
|
11
24
|
# config_load_plugin "bursar" "$repo_root" "_BURSAR_CONFIG"
|
|
12
25
|
# config_get "_BURSAR_CONFIG" '.bursar.window' # returns value or empty string
|
|
13
26
|
#
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
#
|
|
3
|
+
# Guards that every plugin config lib locates config-loader.sh from its OWN
|
|
4
|
+
# path, not from a caller-supplied $PLUGIN_ROOT.
|
|
5
|
+
#
|
|
6
|
+
# These libs used to open with:
|
|
7
|
+
#
|
|
8
|
+
# source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
9
|
+
#
|
|
10
|
+
# PLUGIN_ROOT is read at source time from whatever scope did the sourcing. From
|
|
11
|
+
# a scope that never set it, the path collapses to
|
|
12
|
+
# /../../scripts/lib/config-loader.sh, the source fails, and config_load_plugin
|
|
13
|
+
# and config_get never get defined. The caller then gets empty strings from
|
|
14
|
+
# every accessor while the script keeps going and exits 0 — the config silently
|
|
15
|
+
# falls back to defaults instead of failing loudly.
|
|
16
|
+
#
|
|
17
|
+
# Cartographer hit exactly this (ecosystem-88v): run-audit.sh and both hooks
|
|
18
|
+
# sourced the config lib inside `bash -c` sub-shells that inherited
|
|
19
|
+
# CLAUDE_PLUGIN_ROOT but not PLUGIN_ROOT. It went unnoticed for months because
|
|
20
|
+
# the analyzers take their settings as positional parameters and kept working.
|
|
21
|
+
#
|
|
22
|
+
# The sweep in ecosystem-7bj made every lib self-locating via BASH_SOURCE, which
|
|
23
|
+
# is correct regardless of caller scope, cwd, or sub-shell nesting. These tests
|
|
24
|
+
# cover all 16 at once so plugin 17 cannot quietly reintroduce the pattern.
|
|
25
|
+
|
|
26
|
+
setup() {
|
|
27
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
28
|
+
setup_test_env
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Every plugins/<name>/scripts/lib/<name>-config.sh in the repo.
|
|
32
|
+
_config_libs() {
|
|
33
|
+
find "${REPO_ROOT}/plugins" -path '*/scripts/lib/*-config.sh' -type f | sort
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Without this, a typo in _config_libs would make every test below pass over an
|
|
37
|
+
# empty list and report coverage that does not exist. Not pinned to 16, so
|
|
38
|
+
# adding or removing a plugin does not fail it.
|
|
39
|
+
@test "the config-lib glob matches at least one file" {
|
|
40
|
+
local count
|
|
41
|
+
count=$(_config_libs | wc -l | tr -d ' ')
|
|
42
|
+
[ "$count" -gt 0 ]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "no config lib resolves config-loader.sh through \$PLUGIN_ROOT" {
|
|
46
|
+
local offenders=""
|
|
47
|
+
local lib
|
|
48
|
+
while IFS= read -r lib; do
|
|
49
|
+
if grep -q 'source "\${PLUGIN_ROOT}' "$lib"; then
|
|
50
|
+
offenders+="${lib#"${REPO_ROOT}/"}"$'\n'
|
|
51
|
+
fi
|
|
52
|
+
done < <(_config_libs)
|
|
53
|
+
|
|
54
|
+
[ -z "$offenders" ] || {
|
|
55
|
+
printf 'config libs still sourcing through $PLUGIN_ROOT:\n%s' "$offenders" >&2
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@test "every config lib derives its own directory from BASH_SOURCE" {
|
|
61
|
+
local missing=""
|
|
62
|
+
local lib
|
|
63
|
+
while IFS= read -r lib; do
|
|
64
|
+
grep -q 'BASH_SOURCE\[0\]' "$lib" || missing+="${lib#"${REPO_ROOT}/"}"$'\n'
|
|
65
|
+
done < <(_config_libs)
|
|
66
|
+
|
|
67
|
+
[ -z "$missing" ] || {
|
|
68
|
+
printf 'config libs not self-locating via BASH_SOURCE:\n%s' "$missing" >&2
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# The behavioral test the source-text guards above stand in for. Sources each
|
|
74
|
+
# lib in a sub-shell with PLUGIN_ROOT unset and asserts the shared accessors it
|
|
75
|
+
# depends on actually got defined.
|
|
76
|
+
@test "every config lib yields working accessors with PLUGIN_ROOT unset" {
|
|
77
|
+
local failures=""
|
|
78
|
+
local lib
|
|
79
|
+
while IFS= read -r lib; do
|
|
80
|
+
local plugin_dir plugin_name out
|
|
81
|
+
plugin_dir="${lib%/scripts/lib/*}"
|
|
82
|
+
plugin_name="$(basename "$plugin_dir")"
|
|
83
|
+
|
|
84
|
+
# CLAUDE_PLUGIN_ROOT is set because config_load_plugin genuinely needs it to
|
|
85
|
+
# find the plugin's own config.json; PLUGIN_ROOT is deliberately absent,
|
|
86
|
+
# which is the exact shape of the sub-shell that broke cartographer.
|
|
87
|
+
out=$(env -u PLUGIN_ROOT CLAUDE_PLUGIN_ROOT="$plugin_dir" HOME="$HOME" \
|
|
88
|
+
bash -c '
|
|
89
|
+
set -u
|
|
90
|
+
source "$1" 2>&1 || exit 1
|
|
91
|
+
type -t config_load_plugin >/dev/null 2>&1 || { echo "config_load_plugin undefined"; exit 1; }
|
|
92
|
+
type -t config_get >/dev/null 2>&1 || { echo "config_get undefined"; exit 1; }
|
|
93
|
+
config_load_plugin "$2" "" _PROBE || { echo "config_load_plugin failed"; exit 1; }
|
|
94
|
+
# A plugin ships a config.json, so the merged config must be a non-empty
|
|
95
|
+
# JSON object. "{}" here would mean the load degraded to defaults.
|
|
96
|
+
printf "%s" "$_PROBE" | jq -e "type == \"object\" and (length > 0)" >/dev/null \
|
|
97
|
+
|| { echo "merged config empty: $_PROBE"; exit 1; }
|
|
98
|
+
' _ "$lib" "$plugin_name" 2>&1) \
|
|
99
|
+
|| failures+="${plugin_name}: ${out}"$'\n'
|
|
100
|
+
done < <(_config_libs)
|
|
101
|
+
|
|
102
|
+
[ -z "$failures" ] || {
|
|
103
|
+
printf 'config libs broken when PLUGIN_ROOT is unset:\n%s' "$failures" >&2
|
|
104
|
+
return 1
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Regression guard for the specific reported failure, kept separate so a break
|
|
109
|
+
# names cartographer directly rather than appearing in a list of 16.
|
|
110
|
+
@test "cartographer config survives the sub-shell shape that broke it" {
|
|
111
|
+
local plugin_dir="${REPO_ROOT}/plugins/cartographer"
|
|
112
|
+
run env -u PLUGIN_ROOT CLAUDE_PLUGIN_ROOT="$plugin_dir" bash -c '
|
|
113
|
+
source "'"${plugin_dir}"'/scripts/lib/cartographer-config.sh"
|
|
114
|
+
cartographer_config_load ""
|
|
115
|
+
cartographer_config_audit_interval_hours
|
|
116
|
+
'
|
|
117
|
+
[ "$status" -eq 0 ] || return 1
|
|
118
|
+
# The shipped default. Under the old pattern this came back empty, because
|
|
119
|
+
# the accessor was never defined and the failure was swallowed.
|
|
120
|
+
[[ -n "$output" ]] || return 1
|
|
121
|
+
[[ "$output" != *"No such file or directory"* ]] || return 1
|
|
122
|
+
[[ "$output" != *"command not found"* ]] || return 1
|
|
123
|
+
[ "$output" -eq "$output" ] 2>/dev/null
|
|
124
|
+
}
|
|
@@ -47,14 +47,20 @@ STUB
|
|
|
47
47
|
_dir() { printf '%s' "$(librarian_lessons_dir "$PROJECT_KEY")"; }
|
|
48
48
|
|
|
49
49
|
# A judged proposal. $1 = id, $2 = visibility, $3 = status,
|
|
50
|
-
# $4 = verdict judges JSON array.
|
|
50
|
+
# $4 = verdict judges JSON array, $5 = artifact_id (defaults to "art-<id>").
|
|
51
|
+
#
|
|
52
|
+
# $5 exists so a test can seed two DISTINCT proposals that share one
|
|
53
|
+
# artifact_id. The default derives the artifact from the id, which makes that
|
|
54
|
+
# collision unreachable — and the collision is the whole subject of the
|
|
55
|
+
# declined-ledger guard tests below (ecosystem-bkj).
|
|
51
56
|
_seed_judged() {
|
|
52
57
|
local id="$1" visibility="$2" status="$3" judges="$4"
|
|
58
|
+
local artifact_id="${5:-art-$1}"
|
|
53
59
|
local now
|
|
54
60
|
now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
55
61
|
jq -n --arg id "$id" --arg v "$visibility" --arg s "$status" \
|
|
56
|
-
--arg t "$now" --argjson j "$judges" \
|
|
57
|
-
'{ id: $id, artifact_id:
|
|
62
|
+
--arg t "$now" --argjson j "$judges" --arg aid "$artifact_id" \
|
|
63
|
+
'{ id: $id, artifact_id: $aid, status: $s, visibility: $v,
|
|
58
64
|
confirmed_at: $t, judged_at: $t,
|
|
59
65
|
candidate: { claim: "Prefer jq -c for compact output",
|
|
60
66
|
rationale: "Readable diffs",
|
|
@@ -550,3 +556,83 @@ _promote_refuses_shape() {
|
|
|
550
556
|
[ "$status" -eq 0 ]
|
|
551
557
|
[ -f "$(_dir)/approved/skill01.json" ]
|
|
552
558
|
}
|
|
559
|
+
|
|
560
|
+
# ── declined-ledger guard: per-proposal, not per-artifact (ecosystem-bkj) ─────
|
|
561
|
+
#
|
|
562
|
+
# The guard that stops a double-write into declined.jsonl used to key on
|
|
563
|
+
# artifact_id. That cannot tell "this artifact was already declined" apart from
|
|
564
|
+
# "a DIFFERENT proposal for the same artifact was already declined", so the
|
|
565
|
+
# second of two colliding proposals found a matching row, skipped its append,
|
|
566
|
+
# and returned 0 — both proposals stamped, one verdict on disk. The inverse of
|
|
567
|
+
# the double-write the guard exists to prevent, and still data loss in the
|
|
568
|
+
# per-judge detail declined.jsonl carries for rubric tuning.
|
|
569
|
+
#
|
|
570
|
+
# The approved branch never had this: its guard is approved/<lesson_id>.json,
|
|
571
|
+
# which is per-proposal by construction. These tests hold the declined branch
|
|
572
|
+
# to the same standard.
|
|
573
|
+
|
|
574
|
+
@test "two rejected proposals sharing an artifact_id both reach the ledger" {
|
|
575
|
+
_seed_judged "dupa" "public" "rejected" "$(_split)" "art-shared"
|
|
576
|
+
_seed_judged "dupb" "public" "rejected" "$(_split)" "art-shared"
|
|
577
|
+
|
|
578
|
+
run librarian_lesson_promote "$PROJECT_KEY" "dupa"
|
|
579
|
+
[ "$status" -eq 0 ] || return 1
|
|
580
|
+
run librarian_lesson_promote "$PROJECT_KEY" "dupb"
|
|
581
|
+
[ "$status" -eq 0 ] || return 1
|
|
582
|
+
|
|
583
|
+
# Two distinct proposals were judged and rejected, so two verdicts are
|
|
584
|
+
# owed. Under the artifact_id-keyed guard this was 1.
|
|
585
|
+
[ "$(grep -c 'art-shared' "$(_dir)/declined.jsonl")" -eq 2 ] || return 1
|
|
586
|
+
|
|
587
|
+
# And they are distinguishable — a row per proposal, not two identical
|
|
588
|
+
# rows, which is what makes the ledger usable for rubric tuning.
|
|
589
|
+
local ids
|
|
590
|
+
ids=$(jq -r '.lesson_id' "$(_dir)/declined.jsonl" | sort | tr '\n' ' ')
|
|
591
|
+
[ "$ids" = "dupa dupb " ]
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
@test "the declined guard still blocks a true double-write for one proposal" {
|
|
595
|
+
# The behavior the guard was added for, which the re-key must not lose:
|
|
596
|
+
# a stamp that fails AFTER the declined row lands, then a standalone
|
|
597
|
+
# re-run. Same chmod shape as the artifact_id-keyed test above.
|
|
598
|
+
_seed_judged "rekey01" "public" "rejected" "$(_split)"
|
|
599
|
+
|
|
600
|
+
chmod 0500 "$(_dir)/proposals"
|
|
601
|
+
run --separate-stderr librarian_lesson_promote "$PROJECT_KEY" "rekey01"
|
|
602
|
+
chmod 0700 "$(_dir)/proposals"
|
|
603
|
+
[ "$status" -ne 0 ] || return 1
|
|
604
|
+
[ "$(grep -c 'art-rekey01' "$(_dir)/declined.jsonl")" -eq 1 ] || return 1
|
|
605
|
+
|
|
606
|
+
run librarian_lesson_promote "$PROJECT_KEY" "rekey01"
|
|
607
|
+
[ "$status" -eq 0 ] || return 1
|
|
608
|
+
[ "$(grep -c 'art-rekey01' "$(_dir)/declined.jsonl")" -eq 1 ]
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
@test "a legacy declined row without lesson_id still dedups by artifact_id" {
|
|
612
|
+
# Rows written before this change carry no lesson_id. Keying purely on
|
|
613
|
+
# lesson_id would make them match nothing, so an unstamped proposal left
|
|
614
|
+
# over from a historical stamp failure would double-write on re-run —
|
|
615
|
+
# trading this bug for the one the guard was built to stop. The fallback
|
|
616
|
+
# keeps the old artifact_id behavior for exactly those rows.
|
|
617
|
+
_seed_judged "legacy01" "public" "rejected" "$(_split)"
|
|
618
|
+
printf '%s\n' '{"artifact_id":"art-legacy01","reason":"rejected","detail":null,"declined_at":"2020-01-01T00:00:00Z"}' \
|
|
619
|
+
>> "$(_dir)/declined.jsonl"
|
|
620
|
+
|
|
621
|
+
run librarian_lesson_promote "$PROJECT_KEY" "legacy01"
|
|
622
|
+
[ "$status" -eq 0 ] || return 1
|
|
623
|
+
[ "$(grep -c 'art-legacy01' "$(_dir)/declined.jsonl")" -eq 1 ]
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
@test "librarian_lesson_seen still matches on artifact_id, not lesson_id" {
|
|
627
|
+
# seen() answers "was this ARTIFACT already handled?", which is correctly
|
|
628
|
+
# artifact-scoped — it is what stops transform from re-proposing. Adding
|
|
629
|
+
# lesson_id to the row must not perturb it.
|
|
630
|
+
_seed_judged "seen01" "public" "rejected" "$(_split)" "art-seen-shared"
|
|
631
|
+
run librarian_lesson_promote "$PROJECT_KEY" "seen01"
|
|
632
|
+
[ "$status" -eq 0 ] || return 1
|
|
633
|
+
|
|
634
|
+
run librarian_lesson_seen "$PROJECT_KEY" "art-seen-shared"
|
|
635
|
+
[ "$status" -eq 0 ] || return 1
|
|
636
|
+
run librarian_lesson_seen "$PROJECT_KEY" "art-never-declined"
|
|
637
|
+
[ "$status" -ne 0 ]
|
|
638
|
+
}
|