@onlooker-community/ecosystem 0.34.0 → 0.43.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.
Files changed (112) hide show
  1. package/.claude/skills/writing-tests/SKILL.md +27 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/release.yml +14 -14
  4. package/.release-please-manifest.json +7 -7
  5. package/AGENTS.md +116 -0
  6. package/CHANGELOG.md +175 -0
  7. package/CLAUDE.md +5 -0
  8. package/docs/lesson-promotion-pipeline.md +6 -6
  9. package/docs/superpowers/plans/2026-08-10-lesson-confirmation.md +1273 -0
  10. package/docs/superpowers/plans/2026-08-11-lesson-judging.md +1232 -0
  11. package/docs/superpowers/plans/2026-08-11-lesson-unconfirm.md +458 -0
  12. package/docs/superpowers/plans/2026-08-12-author-key.md +567 -0
  13. package/docs/superpowers/plans/2026-08-13-approved-pool.md +747 -0
  14. package/docs/superpowers/plans/2026-08-13-librarian-cluster.md +392 -0
  15. package/docs/superpowers/plans/2026-08-14-criterion-scores-consumers.md +1374 -0
  16. package/docs/superpowers/plans/2026-08-14-criterion-scores-schema.md +277 -0
  17. package/docs/superpowers/plans/2026-08-16-cartographer-undocumented-entity.md +741 -0
  18. package/docs/superpowers/specs/2026-08-10-lesson-confirmation-design.md +213 -0
  19. package/docs/superpowers/specs/2026-08-11-lesson-judging-design.md +267 -0
  20. package/docs/superpowers/specs/2026-08-11-lesson-unconfirm-design.md +152 -0
  21. package/docs/superpowers/specs/2026-08-12-author-key-design.md +317 -0
  22. package/docs/superpowers/specs/2026-08-13-approved-pool-design.md +232 -0
  23. package/docs/superpowers/specs/2026-08-14-criterion-scores-design.md +254 -0
  24. package/docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md +249 -0
  25. package/package.json +2 -2
  26. package/plugins/assayer/.claude-plugin/plugin.json +1 -1
  27. package/plugins/assayer/CHANGELOG.md +7 -0
  28. package/plugins/assayer/scripts/lib/assayer-config.sh +6 -0
  29. package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
  30. package/plugins/cartographer/CHANGELOG.md +54 -0
  31. package/plugins/cartographer/README.md +56 -1
  32. package/plugins/cartographer/config.json +7 -1
  33. package/plugins/cartographer/scripts/hooks/cartographer-post-write.sh +4 -4
  34. package/plugins/cartographer/scripts/hooks/cartographer-session-start.sh +7 -4
  35. package/plugins/cartographer/scripts/lib/cartographer-collect.sh +8 -1
  36. package/plugins/cartographer/scripts/lib/cartographer-config.sh +26 -0
  37. package/plugins/cartographer/scripts/lib/cartographer-events.sh +90 -0
  38. package/plugins/cartographer/scripts/lib/cartographer-filter.sh +105 -0
  39. package/plugins/cartographer/scripts/lib/cartographer-omission.sh +130 -0
  40. package/plugins/cartographer/scripts/lib/cartographer-resolve.sh +132 -0
  41. package/plugins/cartographer/scripts/run-audit.sh +136 -53
  42. package/plugins/cartographer/skills/cartographer/SKILL.md +33 -9
  43. package/plugins/curator/.claude-plugin/plugin.json +1 -1
  44. package/plugins/curator/CHANGELOG.md +7 -0
  45. package/plugins/curator/scripts/lib/curator-emit.sh +2 -1
  46. package/plugins/historian/.claude-plugin/plugin.json +1 -1
  47. package/plugins/historian/CHANGELOG.md +7 -0
  48. package/plugins/historian/scripts/lib/historian-emit.sh +2 -1
  49. package/plugins/librarian/.claude-plugin/plugin.json +1 -1
  50. package/plugins/librarian/CHANGELOG.md +71 -0
  51. package/plugins/librarian/config.json +30 -1
  52. package/plugins/librarian/docs/adr/002-agent-definitions-are-shared-assets.md +82 -0
  53. package/plugins/librarian/scripts/hooks/librarian-session-end.sh +28 -0
  54. package/plugins/librarian/scripts/hooks/librarian-session-start.sh +42 -12
  55. package/plugins/librarian/scripts/lib/librarian-author-key.sh +279 -0
  56. package/plugins/librarian/scripts/lib/librarian-cli.sh +346 -2
  57. package/plugins/librarian/scripts/lib/librarian-emit.sh +2 -1
  58. package/plugins/librarian/scripts/lib/librarian-lesson-judge.sh +349 -0
  59. package/plugins/librarian/scripts/lib/librarian-lesson-promote.sh +225 -0
  60. package/plugins/librarian/scripts/lib/librarian-lesson-review.sh +312 -0
  61. package/plugins/librarian/scripts/lib/librarian-lesson-rubric.sh +56 -0
  62. package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +82 -13
  63. package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +6 -1
  64. package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +131 -62
  65. package/plugins/librarian/skills/librarian/SKILL.md +98 -1
  66. package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
  67. package/plugins/tribunal/CHANGELOG.md +63 -0
  68. package/plugins/tribunal/agents/tribunal-judge-adversarial.md +34 -0
  69. package/plugins/tribunal/agents/tribunal-judge-security.md +33 -0
  70. package/plugins/tribunal/agents/tribunal-judge-standard.md +12 -0
  71. package/plugins/tribunal/config.json +1 -0
  72. package/plugins/tribunal/scripts/lib/tribunal-aggregate.sh +154 -9
  73. package/plugins/tribunal/scripts/lib/tribunal-gate.sh +195 -16
  74. package/plugins/tribunal/skills/tribunal/SKILL.md +5 -3
  75. package/scripts/lib/prompt-rules.sh +6 -1
  76. package/test/bats/archivist-inject.bats +1 -1
  77. package/test/bats/assayer-extract.bats +2 -2
  78. package/test/bats/bursar-session-start.bats +3 -3
  79. package/test/bats/cartographer-config.bats +43 -0
  80. package/test/bats/cartographer-events.bats +267 -0
  81. package/test/bats/cartographer-filter.bats +169 -0
  82. package/test/bats/cartographer-lock.bats +3 -3
  83. package/test/bats/cartographer-omission.bats +202 -0
  84. package/test/bats/cartographer-resolve.bats +287 -0
  85. package/test/bats/cartographer-run-audit.bats +325 -0
  86. package/test/bats/compass-sanitizer.bats +11 -11
  87. package/test/bats/compass-transcript.bats +2 -2
  88. package/test/bats/config.bats +15 -15
  89. package/test/bats/curator-session-start.bats +10 -3
  90. package/test/bats/emit-payload-default.bats +52 -0
  91. package/test/bats/governor-ledger.bats +1 -1
  92. package/test/bats/historian-prompt-submit.bats +1 -1
  93. package/test/bats/inspector-post-write-hook.bats +4 -4
  94. package/test/bats/librarian-author-key.bats +477 -0
  95. package/test/bats/librarian-cli.bats +16 -16
  96. package/test/bats/librarian-lesson-judge.bats +884 -0
  97. package/test/bats/librarian-lesson-promote.bats +552 -0
  98. package/test/bats/librarian-lesson-review.bats +900 -0
  99. package/test/bats/librarian-lesson-transform.bats +17 -3
  100. package/test/bats/librarian-session-end.bats +161 -0
  101. package/test/bats/librarian-session-start.bats +36 -2
  102. package/test/bats/lineage-config.bats +1 -1
  103. package/test/bats/lineage-redact.bats +5 -5
  104. package/test/bats/session-tracker.bats +4 -4
  105. package/test/bats/tribunal-aggregate.bats +354 -1
  106. package/test/bats/tribunal-gate.bats +631 -0
  107. package/test/bats/tribunal-judge-agents.bats +119 -0
  108. package/test/bats/tribunal-jury.bats +1 -1
  109. package/test/bats/turn-tracker.bats +1 -1
  110. package/test/bats/warden-sanitizer.bats +3 -3
  111. package/test/bats/worktree-tracker.bats +2 -2
  112. package/test/node/lesson-validate-agreement.test.mjs +35 -6
@@ -3,6 +3,7 @@
3
3
  #
4
4
  # Exposes:
5
5
  # librarian_cli list # one-line summary + table of pending proposals
6
+ # librarian_cli lessons list [--confirmed] # pending lesson queue, or the confirmed ones
6
7
  # librarian_cli show <proposal_id> # full proposal body + provenance + conflict state
7
8
  # librarian_cli accept <proposal_id> # write to typed memory store, mark accepted
8
9
  # librarian_cli reject <proposal_id> [reason] # tombstone + mark rejected
@@ -18,7 +19,11 @@
18
19
  # bullet line, and the file is created if it doesn't exist.
19
20
  #
20
21
  # Depends on (sourced by the caller): librarian-config.sh,
21
- # librarian-project-key.sh, librarian-storage.sh, librarian-emit.sh
22
+ # librarian-project-key.sh, librarian-storage.sh, librarian-emit.sh,
23
+ # librarian-lesson-storage.sh, librarian-lesson-validate.sh,
24
+ # librarian-lesson-review.sh, librarian-lesson-judge.sh,
25
+ # librarian-lesson-rubric.sh, librarian-author-key.sh,
26
+ # librarian-lesson-promote.sh
22
27
 
23
28
  # ----------------------------------------------------------------------------
24
29
  # Project key + memory store path resolution
@@ -117,7 +122,8 @@ _librarian_cli_set_proposal_status() {
117
122
  local key="$1"
118
123
  local proposal_id="$2"
119
124
  local new_status="$3"
120
- local extra_json="${4:-{\}}"
125
+ local extra_json="${4:-}"
126
+ [ -z "$extra_json" ] && extra_json='{}'
121
127
  [[ -z "$key" || -z "$proposal_id" || -z "$new_status" ]] && return 1
122
128
 
123
129
  local path
@@ -324,6 +330,343 @@ librarian_cli_status() {
324
330
  printf 'pending: %s, accepted: %s, rejected: %s\n' "$pending" "$accepted" "$rejected"
325
331
  }
326
332
 
333
+ # ----------------------------------------------------------------------------
334
+ # Lesson confirmation surface
335
+ #
336
+ # Namespaced under `lessons` and kept apart from the memory verbs on purpose.
337
+ # Accepting a memory writes a file on this machine; confirming a lesson commits
338
+ # it toward leaving this machine, irreversibly once synced. Those two decisions
339
+ # should not sit one keystroke apart.
340
+ # ----------------------------------------------------------------------------
341
+
342
+ # Usage: librarian_cli_lessons_list [--confirmed] [cwd]
343
+ #
344
+ # Bare `list` shows the pending queue the review walk drives. `--confirmed`
345
+ # shows lessons already confirmed and not yet judged, which is otherwise
346
+ # undiscoverable: unconfirm operates only on a `confirmed` lesson, and the
347
+ # pending list by definition never contains one. Without this view a human who
348
+ # confirmed at the wrong visibility last session has no way back to the id.
349
+ librarian_cli_lessons_list() {
350
+ local status="pending" cwd="" json=0
351
+ while [[ $# -gt 0 ]]; do
352
+ case "$1" in
353
+ --confirmed) status="confirmed"; shift ;;
354
+ --json) json=1; shift ;;
355
+ --*)
356
+ # Same contract as the sibling verbs: an unrecognized flag is
357
+ # refused rather than falling through to the cwd bucket, where
358
+ # it would resolve no project key and report a path problem the
359
+ # caller does not have.
360
+ printf 'unknown option: %s\n' "$1" >&2
361
+ return 1
362
+ ;;
363
+ *) cwd="$1"; shift ;;
364
+ esac
365
+ done
366
+
367
+ local key rows
368
+ key=$(_librarian_cli_project_key "$cwd")
369
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
370
+ rows=$(librarian_lesson_list_by_status "$key" "$status")
371
+
372
+ # Checked before the empty-set branch below: the skill parses this output,
373
+ # and an empty result must still emit `[]`, not the prose empty-state
374
+ # message that follows.
375
+ if [[ "$json" -eq 1 ]]; then
376
+ printf '%s' "$rows"
377
+ return 0
378
+ fi
379
+
380
+ if [[ "$(printf '%s' "$rows" | jq 'length')" -eq 0 ]]; then
381
+ printf 'No %s lessons.\n' "$status"
382
+ return 0
383
+ fi
384
+ printf '%s' "$rows" | jq -r '.[] | "\(.id) \(.candidate.claim)"'
385
+ }
386
+
387
+ librarian_cli_lessons_show() {
388
+ local lesson_id="${1:-}"
389
+ local cwd="${2:-}"
390
+ [[ -z "$lesson_id" ]] && { printf 'usage: librarian_cli lessons show <lesson_id>\n'; return 1; }
391
+
392
+ local key path
393
+ key=$(_librarian_cli_project_key "$cwd")
394
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
395
+ path="$(librarian_lessons_dir "$key")/proposals/${lesson_id}.json"
396
+ [[ -f "$path" ]] || { printf 'Lesson %s not found.\n' "$lesson_id"; return 1; }
397
+
398
+ # visibility sits next to status because the two are read together: it is
399
+ # the field a human needs to check before deciding whether to unconfirm,
400
+ # and it is only set on a confirmed lesson. A pending one renders `—`
401
+ # rather than a jq `null`, so the absence reads as "not decided yet".
402
+ jq -r '
403
+ "id: \(.id)",
404
+ "status: \(.status)",
405
+ "visibility: \(.visibility // "—")",
406
+ "artifact: \(.artifact_id)",
407
+ "claim: \(.candidate.claim)",
408
+ "rationale: \(.candidate.rationale)",
409
+ "resolution: \(.candidate.evidence.resolution)",
410
+ "stack: \(.candidate.applies_to.stack | join(", "))",
411
+ "scope: \(.candidate.applies_to.scope | tojson)"
412
+ ' "$path"
413
+ }
414
+
415
+ librarian_cli_lessons_confirm() {
416
+ local lesson_id="${1:-}"
417
+ local visibility="${2:-}"
418
+ [[ -z "$lesson_id" || -z "$visibility" ]] && {
419
+ printf 'usage: librarian_cli lessons confirm <lesson_id> <private|org|public> [--justification TEXT] [cwd]\n'
420
+ return 1
421
+ }
422
+ shift 2
423
+
424
+ # --justification is a named flag, not a position. This is the verb that
425
+ # commits a lesson toward leaving this machine, so a caller who supplies
426
+ # cwd but omits justification must never have cwd silently misread as the
427
+ # justification that flips scope to version_independent.
428
+ local justification="" justification_given=0 cwd=""
429
+ while [[ $# -gt 0 ]]; do
430
+ case "$1" in
431
+ --justification)
432
+ justification="${2:-}"
433
+ justification_given=1
434
+ if [[ $# -ge 2 ]]; then
435
+ shift 2
436
+ else
437
+ shift 1
438
+ fi
439
+ ;;
440
+ --*)
441
+ # An unrecognized flag must never fall through to the cwd
442
+ # bucket below: a typo'd --justifcation would otherwise be
443
+ # swallowed here, its value would land as a plain positional
444
+ # (misread as cwd, then overwritten by the real trailing
445
+ # path), and the lesson would confirm with an empty
446
+ # justification and no diagnostic — a persisted wrong state
447
+ # this CLI has no verb to undo.
448
+ printf 'unknown option: %s\n' "$1" >&2
449
+ return 1
450
+ ;;
451
+ *)
452
+ cwd="$1"
453
+ shift
454
+ ;;
455
+ esac
456
+ done
457
+
458
+ # A justification that is blank after trimming whitespace is refused
459
+ # outright rather than silently treated as "no justification supplied" —
460
+ # the validator's minLength: 1 counts whitespace, so passing it through
461
+ # would flip scope to version_independent on content nobody actually
462
+ # wrote.
463
+ if [[ "$justification_given" -eq 1 ]] && [[ "$justification" =~ ^[[:space:]]*$ ]]; then
464
+ printf 'justification is blank after trimming whitespace; omit --justification instead of passing an empty value.\n' >&2
465
+ return 1
466
+ fi
467
+
468
+ local key
469
+ key=$(_librarian_cli_project_key "$cwd")
470
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
471
+
472
+ librarian_lesson_confirm "$key" "$lesson_id" "$visibility" "$justification" || return 1
473
+ printf 'Confirmed %s at %s visibility.\n' "$lesson_id" "$visibility"
474
+ }
475
+
476
+ librarian_cli_lessons_pass() {
477
+ local lesson_id="${1:-}"
478
+ local reason="${2:-}"
479
+ local cwd="${3:-}"
480
+ [[ -z "$lesson_id" ]] && { printf 'usage: librarian_cli lessons pass <lesson_id> [reason]\n'; return 1; }
481
+
482
+ # reason and cwd are positional, not flags — but a flag-shaped token here
483
+ # is almost always a typo for confirm's `--justification` (the likeliest
484
+ # guess is `--reason`). Reject it rather than silently accepting it as
485
+ # the literal reason: librarian_lesson_pass writes to the append-only
486
+ # passed.jsonl ledger with no CLI verb to correct a bad line afterward.
487
+ case "$reason" in
488
+ --*) printf 'unknown option: %s\n' "$reason" >&2; return 1 ;;
489
+ esac
490
+ case "$cwd" in
491
+ --*) printf 'unknown option: %s\n' "$cwd" >&2; return 1 ;;
492
+ esac
493
+
494
+ local key
495
+ key=$(_librarian_cli_project_key "$cwd")
496
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
497
+
498
+ librarian_lesson_pass "$key" "$lesson_id" "$reason" || return 1
499
+ printf 'Passed on %s.\n' "$lesson_id"
500
+ }
501
+
502
+ librarian_cli_lessons_unconfirm() {
503
+ local lesson_id="${1:-}"
504
+ local cwd="${2:-}"
505
+ [[ -z "$lesson_id" ]] && { printf 'usage: librarian_cli lessons unconfirm <lesson_id>\n'; return 1; }
506
+
507
+ # Reject a flag-shaped token rather than treating it as cwd. The sibling
508
+ # verbs already do this; a stray flag absorbed as a path resolves to the
509
+ # wrong project key and the verb then reports success against a lesson it
510
+ # never touched. Here that write-safety case can't actually arise — cwd
511
+ # is the last positional, so an invalid one (e.g. "--force") already
512
+ # fails project-key resolution below and returns 1 with no write. This
513
+ # guard is a diagnostic, not a write guard: it swaps that generic "No
514
+ # project key resolvable" for "unknown option: --force" so the caller
515
+ # isn't sent hunting a path problem they don't have. Keep it for that
516
+ # reason even though it looks redundant next to confirm's and pass's.
517
+ case "$cwd" in
518
+ --*) printf 'unknown option: %s\n' "$cwd" >&2; return 1 ;;
519
+ esac
520
+
521
+ local key
522
+ key=$(_librarian_cli_project_key "$cwd")
523
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
524
+
525
+ # Read the status before the call so the message can tell "took back a
526
+ # confirmation" apart from "it was already pending". librarian_lesson_unconfirm
527
+ # returns 0 for both by design — 0 means "the lesson is pending now" — and
528
+ # keeping that contract single-valued is what lets callers stay simple, so
529
+ # the distinction is made here rather than by widening the return.
530
+ #
531
+ # A status changing between this read and the call can only misword the
532
+ # message; the write itself is guarded inside librarian_lesson_unconfirm,
533
+ # which re-reads the file and refuses anything that is not `confirmed`.
534
+ local prior_status
535
+ prior_status=$(jq -r '.status // ""' \
536
+ "$(librarian_lessons_dir "$key")/proposals/${lesson_id}.json" 2>/dev/null)
537
+
538
+ librarian_lesson_unconfirm "$key" "$lesson_id" || return 1
539
+ if [[ "$prior_status" == "pending" ]]; then
540
+ printf 'Lesson %s was already pending; nothing to take back.\n' "$lesson_id"
541
+ else
542
+ printf 'Unconfirmed %s; it is pending again.\n' "$lesson_id"
543
+ fi
544
+ }
545
+
546
+ # Usage: librarian_cli_lessons_judge <lesson_id> <verdicts-json> [cwd]
547
+ #
548
+ # Verdicts come from the judge subagents the skill dispatched. Exit 2 means the
549
+ # candidate could not be judged and stays confirmed — distinct from a rejection,
550
+ # which is a real verdict and exits 0.
551
+ librarian_cli_lessons_judge() {
552
+ local lesson_id="" verdicts="" cwd=""
553
+ local positional=0
554
+ while [[ $# -gt 0 ]]; do
555
+ case "$1" in
556
+ --*)
557
+ printf 'unknown option: %s\n' "$1" >&2
558
+ return 1
559
+ ;;
560
+ *)
561
+ case "$positional" in
562
+ 0) lesson_id="$1" ;;
563
+ 1) verdicts="$1" ;;
564
+ *) cwd="$1" ;;
565
+ esac
566
+ positional=$((positional + 1))
567
+ shift
568
+ ;;
569
+ esac
570
+ done
571
+
572
+ if [[ -z "$lesson_id" || -z "$verdicts" ]]; then
573
+ printf 'usage: librarian_cli lessons judge <lesson_id> <verdicts-json> [cwd]\n'
574
+ return 1
575
+ fi
576
+
577
+ local key
578
+ key=$(_librarian_cli_project_key "$cwd")
579
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
580
+
581
+ librarian_lesson_judge "$key" "$lesson_id" "$verdicts"
582
+ local rc=$?
583
+ case "$rc" in
584
+ 0)
585
+ printf 'Lesson %s is now %s.\n' "$lesson_id" \
586
+ "$(jq -r '.status' "$(librarian_lessons_dir "$key")/proposals/${lesson_id}.json")"
587
+ # Promotion failing does not undo a correct verdict: report it and
588
+ # leave the lesson promotable by a standalone `lessons promote`.
589
+ librarian_lesson_promote "$key" "$lesson_id" \
590
+ || printf 'Lesson %s was judged but not promoted; run `lessons promote %s` to retry.\n' \
591
+ "$lesson_id" "$lesson_id"
592
+ ;;
593
+ 2)
594
+ printf 'Lesson %s could not be judged; it stays confirmed. Re-run to retry.\n' "$lesson_id"
595
+ ;;
596
+ *)
597
+ printf 'Lesson %s could not be judged (exit %d); see above.\n' "$lesson_id" "$rc"
598
+ ;;
599
+ esac
600
+ return $rc
601
+ }
602
+
603
+ # Usage: librarian_cli_lessons_promote <lesson_id> [cwd]
604
+ #
605
+ # Runnable standalone on purpose: promotion fails for reasons judging does not
606
+ # — a malformed author secret, absent node, a full disk — and a standalone run
607
+ # is how a correctly-judged, not-yet-promoted lesson gets reconciled.
608
+ librarian_cli_lessons_promote() {
609
+ local lesson_id="" cwd=""
610
+ local positional=0
611
+ while [[ $# -gt 0 ]]; do
612
+ case "$1" in
613
+ --*)
614
+ printf 'unknown option: %s\n' "$1" >&2
615
+ return 1
616
+ ;;
617
+ *)
618
+ case "$positional" in
619
+ 0) lesson_id="$1" ;;
620
+ *) cwd="$1" ;;
621
+ esac
622
+ positional=$((positional + 1))
623
+ shift
624
+ ;;
625
+ esac
626
+ done
627
+
628
+ [[ -z "$lesson_id" ]] && { printf 'usage: librarian_cli lessons promote <lesson_id> [cwd]\n'; return 1; }
629
+
630
+ local key
631
+ key=$(_librarian_cli_project_key "$cwd")
632
+ [[ -z "$key" ]] && { printf 'No project key resolvable from this directory.\n'; return 1; }
633
+
634
+ librarian_lesson_promote "$key" "$lesson_id" || return 1
635
+ printf 'Lesson %s promoted.\n' "$lesson_id"
636
+ }
637
+
638
+ librarian_cli_lessons_defer() {
639
+ local lesson_id="${1:-}"
640
+ [[ -z "$lesson_id" ]] && { printf 'usage: librarian_cli lessons defer <lesson_id>\n'; return 1; }
641
+ printf 'Deferred %s; it stays in the queue.\n' "$lesson_id"
642
+ }
643
+
644
+ librarian_cli_lessons_status() {
645
+ local cwd="${1:-}"
646
+ local key pending
647
+ key=$(_librarian_cli_project_key "$cwd")
648
+ [[ -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')"
651
+ }
652
+
653
+ librarian_cli_lessons() {
654
+ local verb="${1:-list}"
655
+ shift || true
656
+ case "$verb" in
657
+ list) librarian_cli_lessons_list "$@" ;;
658
+ show) librarian_cli_lessons_show "$@" ;;
659
+ confirm) librarian_cli_lessons_confirm "$@" ;;
660
+ pass) librarian_cli_lessons_pass "$@" ;;
661
+ unconfirm) librarian_cli_lessons_unconfirm "$@" ;;
662
+ judge) librarian_cli_lessons_judge "$@" ;;
663
+ promote) librarian_cli_lessons_promote "$@" ;;
664
+ defer) librarian_cli_lessons_defer "$@" ;;
665
+ status) librarian_cli_lessons_status "$@" ;;
666
+ *) printf 'unknown lessons action: %s\n' "$verb"; return 2 ;;
667
+ esac
668
+ }
669
+
327
670
  librarian_cli() {
328
671
  local action="${1:-list}"
329
672
  shift || true
@@ -334,6 +677,7 @@ librarian_cli() {
334
677
  reject) librarian_cli_reject "$@" ;;
335
678
  defer) librarian_cli_defer "$@" ;;
336
679
  status) librarian_cli_status "$@" ;;
680
+ lessons) librarian_cli_lessons "$@" ;;
337
681
  *) printf 'unknown action: %s\n' "$action"; return 2 ;;
338
682
  esac
339
683
  }
@@ -50,7 +50,8 @@ _LIBRARIAN_EVENT_JS="${_LIBRARIAN_EVENT_JS:-$(_librarian_resolve_event_js)}"
50
50
  librarian_emit() {
51
51
  local event_type="${1:-}"
52
52
  local session_id="${2:-}"
53
- local payload="${3:-{\}}"
53
+ local payload="${3:-}"
54
+ [ -z "$payload" ] && payload='{}'
54
55
 
55
56
  [[ -z "$event_type" || -z "$session_id" ]] && return 0
56
57
  [[ -z "$_LIBRARIAN_EVENT_JS" || ! -f "$_LIBRARIAN_EVENT_JS" ]] && return 0