@onlooker-community/ecosystem 0.43.7 → 0.44.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecosystem",
3
- "version": "0.43.7",
3
+ "version": "0.44.0",
4
4
  "description": "Observability substrate for Claude Code. Provides the shared $ONLOOKER_DIR storage root (default $HOME/.onlooker), canonical schema-validated event emission, session and tool tracking hooks, and prompt rules. Required by all other Onlooker plugins.",
5
5
  "author": {
6
6
  "name": "Onlooker Community",
@@ -1,5 +1,5 @@
1
1
  {
2
- ".": "0.43.7",
2
+ ".": "0.44.0",
3
3
  "plugins/archivist": "0.3.2",
4
4
  "plugins/tribunal": "1.2.8",
5
5
  "plugins/echo": "0.3.2",
@@ -9,7 +9,7 @@
9
9
  "plugins/scribe": "0.4.3",
10
10
  "plugins/counsel": "0.5.2",
11
11
  "plugins/warden": "0.3.2",
12
- "plugins/librarian": "0.13.3",
12
+ "plugins/librarian": "0.14.0",
13
13
  "plugins/curator": "0.2.4",
14
14
  "plugins/historian": "0.3.4",
15
15
  "plugins/assayer": "1.1.4",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.44.0](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.7...ecosystem-v0.44.0) (2026-08-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * **librarian:** let lessons status close the judge walk :abacus: ([#205](https://github.com/onlooker-community/ecosystem/issues/205)) ([8da1cd7](https://github.com/onlooker-community/ecosystem/commit/8da1cd72a7e564aae44750efaa1e59f1ecd59641))
9
+
3
10
  ## [0.43.7](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.6...ecosystem-v0.43.7) (2026-08-23)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlooker-community/ecosystem",
3
- "version": "0.43.7",
3
+ "version": "0.44.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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librarian",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.0](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.13.3...librarian-v0.14.0) (2026-08-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * **librarian:** let lessons status close the judge walk :abacus: ([#205](https://github.com/onlooker-community/ecosystem/issues/205)) ([8da1cd7](https://github.com/onlooker-community/ecosystem/commit/8da1cd72a7e564aae44750efaa1e59f1ecd59641))
9
+
3
10
  ## [0.13.3](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.13.2...librarian-v0.13.3) (2026-08-22)
4
11
 
5
12
 
@@ -641,13 +641,44 @@ librarian_cli_lessons_defer() {
641
641
  printf 'Deferred %s; it stays in the queue.\n' "$lesson_id"
642
642
  }
643
643
 
644
+ # One authoritative line for the whole lesson queue.
645
+ #
646
+ # Reports a count per status rather than pending alone, because the judge walk
647
+ # closes on this call. It used to close on counts the model had tallied itself
648
+ # across the loop, with nothing to check them against — every other walk in the
649
+ # skill ends on a CLI call, and this one had none to end on (ecosystem-sv3).
650
+ #
651
+ # `awaiting promotion` is the walk's fourth bucket: a lesson whose verdict is
652
+ # recorded but whose pool entry or declined row has not landed. promote()
653
+ # stamps promoted_at LAST, after the terminal record, so a terminal status with
654
+ # no stamp is exactly that state — and it must not be folded into `approved`,
655
+ # which would claim a pool entry that does not exist.
656
+ #
657
+ # The walk's remaining bucket, could-not-judge, is deliberately absent: such a
658
+ # candidate stays `confirmed`, which is indistinguishable on disk from one that
659
+ # was never judged this run. That count is run-scoped and stays the model's.
644
660
  librarian_cli_lessons_status() {
645
661
  local cwd="${1:-}"
646
- local key pending
662
+ local key
647
663
  key=$(_librarian_cli_project_key "$cwd")
648
664
  [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
649
- pending=$(librarian_lesson_list_pending "$key")
650
- printf 'lessons pending: %s\n' "$(printf '%s' "$pending" | jq 'length')"
665
+
666
+ local s counts=()
667
+ for s in pending confirmed approved rejected; do
668
+ counts+=("$(librarian_lesson_list_by_status "$key" "$s" | jq 'length')")
669
+ done
670
+
671
+ # A terminal verdict whose promote() never stamped. Counted from the same
672
+ # per-status reads rather than a second directory walk.
673
+ local awaiting=0 n
674
+ for s in approved rejected; do
675
+ n=$(librarian_lesson_list_by_status "$key" "$s" \
676
+ | jq '[.[] | select(has("promoted_at") | not)] | length')
677
+ awaiting=$(( awaiting + n ))
678
+ done
679
+
680
+ printf 'lessons pending: %s, confirmed: %s, approved: %s, rejected: %s, awaiting promotion: %s\n' \
681
+ "${counts[0]}" "${counts[1]}" "${counts[2]}" "${counts[3]}" "$awaiting"
651
682
  }
652
683
 
653
684
  librarian_cli_lessons() {
@@ -141,10 +141,21 @@ For `lessons judge`, run the jury over confirmed candidates:
141
141
  those ids and list them at the end so the user knows to re-run. A broken judge
142
142
  must never become a rejection — the artifact's watermark has already moved,
143
143
  so a false rejection buries a good lesson permanently.
144
- 5. Finish by reporting counts: approved, rejected, could-not-judge, and
145
- **judged but not promoted** (verdict recorded, pool write still pending —
146
- list these ids with `librarian_cli lessons promote <id>` as the next step).
147
- Don't fold this bucket into "approved": a lesson here has no pool entry yet.
144
+ 5. **Finish on `librarian_cli lessons status`, not on your own tally.** It
145
+ prints one line pending, confirmed, approved, rejected, and **awaiting
146
+ promotion** read from disk, so it is the same kind of authoritative close
147
+ the review walk ends on. Report those numbers as it gives them; a long batch
148
+ is exactly where a running count kept in your head drifts.
149
+
150
+ Two things the call cannot tell you, which you must still report yourself:
151
+
152
+ - **could-not-judge** — those candidates stay `confirmed`, which on disk is
153
+ indistinguishable from a candidate that was never judged this run. Keep
154
+ the ids from step 4 and list them.
155
+ - **which** lessons are awaiting promotion. The call gives the count; you
156
+ have the ids. List them with `librarian_cli lessons promote <id>` as the
157
+ next step, and don't fold them into "approved" — a lesson here has no pool
158
+ entry yet. If your ids and the count disagree, trust the count and say so.
148
159
 
149
160
  `scope_accuracy` is the criterion that matters most on a `version_independent`
150
161
  candidate. The schema guarantees such a lesson **carries** a justification; this
@@ -387,6 +387,29 @@ _cli_setup() {
387
387
  source "${PLUGIN_ROOT}/scripts/lib/librarian-cli.sh"
388
388
  }
389
389
 
390
+ # ── lessons status: a count per status (ecosystem-sv3) ───────────────────────
391
+ #
392
+ # The judge walk used to close by reporting counts the model tracked itself
393
+ # across the loop, with nothing to check them against — every other walk in the
394
+ # skill ends on an authoritative CLI call. `lessons status` reported only a
395
+ # pending count, so there was nothing to ground the summary on.
396
+
397
+ # Force a proposal to a terminal status on disk. Reaching `approved` through
398
+ # the real path means empaneling a jury, which is what the judge walk exists to
399
+ # avoid doing twice; the status field is what the counter reads.
400
+ _set_status() {
401
+ local id="$1" status="$2" promoted="${3:-}"
402
+ local f
403
+ f="$(librarian_lessons_dir "$PROJECT_KEY")/proposals/${id}.json"
404
+ local tmp="${f}.tmp"
405
+ if [[ -n "$promoted" ]]; then
406
+ jq --arg s "$status" --arg p "$promoted" '.status = $s | .promoted_at = $p' "$f" >"$tmp"
407
+ else
408
+ jq --arg s "$status" '.status = $s | del(.promoted_at)' "$f" >"$tmp"
409
+ fi
410
+ mv -f "$tmp" "$f"
411
+ }
412
+
390
413
  @test "lessons status reports zero on an empty queue" {
391
414
  _cli_setup
392
415
  run librarian_cli lessons status "$PROJECT_REPO"
@@ -394,6 +417,72 @@ _cli_setup() {
394
417
  [[ "$output" == *"0"* ]] || return 1
395
418
  }
396
419
 
420
+ @test "lessons status reports a count for every status, not just pending" {
421
+ _cli_setup
422
+ local a b c d
423
+ a=$(_seed_pending)
424
+ b=$(_seed_pending)
425
+ c=$(_seed_pending)
426
+ d=$(_seed_pending)
427
+ librarian_lesson_confirm "$PROJECT_KEY" "$b" "org"
428
+ _set_status "$c" "approved" "2026-08-01T00:00:00Z"
429
+ _set_status "$d" "rejected" "2026-08-01T00:00:00Z"
430
+
431
+ run librarian_cli lessons status "$PROJECT_REPO"
432
+ [ "$status" -eq 0 ] || return 1
433
+ [[ "$output" == *"pending: 1"* ]] || return 1
434
+ [[ "$output" == *"confirmed: 1"* ]] || return 1
435
+ [[ "$output" == *"approved: 1"* ]] || return 1
436
+ [[ "$output" == *"rejected: 1"* ]]
437
+ }
438
+
439
+ @test "lessons status counts a judged-but-unpromoted lesson separately" {
440
+ # The judge walk's fourth bucket. promote() stamps promoted_at LAST, after
441
+ # the terminal record lands, so a terminal status with no stamp is exactly
442
+ # the state the walk is told not to fold into "approved" — the lesson has
443
+ # no pool entry yet.
444
+ _cli_setup
445
+ local a b
446
+ a=$(_seed_pending)
447
+ b=$(_seed_pending)
448
+ _set_status "$a" "approved" "2026-08-01T00:00:00Z"
449
+ _set_status "$b" "approved"
450
+
451
+ run librarian_cli lessons status "$PROJECT_REPO"
452
+ [ "$status" -eq 0 ] || return 1
453
+ [[ "$output" == *"approved: 2"* ]] || return 1
454
+ [[ "$output" == *"awaiting promotion: 1"* ]]
455
+ }
456
+
457
+ @test "lessons status reports awaiting promotion for a rejected lesson too" {
458
+ # A rejected verdict also lands its terminal record (the declined row)
459
+ # before the stamp, so the same recovery state exists on that side.
460
+ _cli_setup
461
+ local a b
462
+ a=$(_seed_pending)
463
+ b=$(_seed_pending)
464
+ _set_status "$a" "rejected"
465
+ # A promoted sibling, so the count cannot come out right by counting every
466
+ # rejected lesson — without it this passes whether or not the stamp is read.
467
+ _set_status "$b" "rejected" "2026-08-01T00:00:00Z"
468
+
469
+ run librarian_cli lessons status "$PROJECT_REPO"
470
+ [ "$status" -eq 0 ] || return 1
471
+ [[ "$output" == *"rejected: 2"* ]] || return 1
472
+ [[ "$output" == *"awaiting promotion: 1"* ]]
473
+ }
474
+
475
+ @test "lessons status reports zeros across every status on an empty queue" {
476
+ _cli_setup
477
+ run librarian_cli lessons status "$PROJECT_REPO"
478
+ [ "$status" -eq 0 ] || return 1
479
+ [[ "$output" == *"pending: 0"* ]] || return 1
480
+ [[ "$output" == *"confirmed: 0"* ]] || return 1
481
+ [[ "$output" == *"approved: 0"* ]] || return 1
482
+ [[ "$output" == *"rejected: 0"* ]] || return 1
483
+ [[ "$output" == *"awaiting promotion: 0"* ]]
484
+ }
485
+
397
486
  @test "lessons list shows a pending lesson" {
398
487
  _cli_setup
399
488
  id=$(_seed_pending)