@onlooker-community/ecosystem 0.43.6 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.release-please-manifest.json +3 -3
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +7 -0
- package/plugins/cartographer/scripts/run-audit.sh +23 -1
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +7 -0
- package/plugins/librarian/scripts/lib/librarian-cli.sh +34 -3
- package/plugins/librarian/skills/librarian/SKILL.md +15 -4
- package/test/bats/cartographer-run-audit.bats +50 -4
- package/test/bats/librarian-lesson-review.bats +89 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecosystem",
|
|
3
|
-
"version": "0.
|
|
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,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
".": "0.
|
|
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",
|
|
6
|
-
"plugins/cartographer": "0.6.
|
|
6
|
+
"plugins/cartographer": "0.6.2",
|
|
7
7
|
"plugins/governor": "0.3.3",
|
|
8
8
|
"plugins/compass": "0.4.2",
|
|
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.
|
|
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,19 @@
|
|
|
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
|
+
|
|
10
|
+
## [0.43.7](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.6...ecosystem-v0.43.7) (2026-08-23)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **cartographer:** drop a typeless finding instead of hashing it as "unknown" :broom: ([#203](https://github.com/onlooker-community/ecosystem/issues/203)) ([a949d5d](https://github.com/onlooker-community/ecosystem/commit/a949d5d43f3ae3515ac6278b35d1be5cc468cb0e))
|
|
16
|
+
|
|
3
17
|
## [0.43.6](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.43.5...ecosystem-v0.43.6) (2026-08-22)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cartographer",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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.2](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.6.1...cartographer-v0.6.2) (2026-08-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **cartographer:** drop a typeless finding instead of hashing it as "unknown" :broom: ([#203](https://github.com/onlooker-community/ecosystem/issues/203)) ([a949d5d](https://github.com/onlooker-community/ecosystem/commit/a949d5d43f3ae3515ac6278b35d1be5cc468cb0e))
|
|
11
|
+
|
|
5
12
|
## [0.6.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.6.0...cartographer-v0.6.1) (2026-08-22)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -247,7 +247,29 @@ run_synthesize() {
|
|
|
247
247
|
while IFS= read -r finding; do
|
|
248
248
|
[[ -z "$finding" ]] && continue
|
|
249
249
|
local ftype ffile_a fexcerpt_a ffile_b fexcerpt_b
|
|
250
|
-
|
|
250
|
+
# `// ""` collapses absent, null and empty into one check, the same way
|
|
251
|
+
# cartographer_issue_found_payload does — jq's `//` treats "" as
|
|
252
|
+
# present, so an empty type would otherwise slip past.
|
|
253
|
+
ftype=$(printf '%s' "$finding" | jq -r '.type // ""')
|
|
254
|
+
|
|
255
|
+
# A finding with no type is malformed: every analyzer writes a literal
|
|
256
|
+
# type, so this means the phase that produced it has a bug. Drop it here
|
|
257
|
+
# rather than hashing it.
|
|
258
|
+
#
|
|
259
|
+
# This used to default to "unknown". The emit path already refused to
|
|
260
|
+
# announce such a finding (ecosystem-ci0), but the orchestrator stored
|
|
261
|
+
# it anyway under a hash derived from a type it does not have, and then
|
|
262
|
+
# touched its dedup sentinel. That sentinel made the silence permanent:
|
|
263
|
+
# every later audit saw "known", took the refresh path, and emitted
|
|
264
|
+
# nothing — so the finding sat on disk forever, counted in
|
|
265
|
+
# new_finding_count, and could never be announced. Dropping it keeps the
|
|
266
|
+
# stored corpus honest and leaves the hash of every well-formed finding
|
|
267
|
+
# untouched, so no existing dedup sentinel is orphaned (ecosystem-0ty).
|
|
268
|
+
if [[ -z "$ftype" ]]; then
|
|
269
|
+
log "synthesize: dropped a finding that carries no type — the analyzer that produced it has a bug"
|
|
270
|
+
continue
|
|
271
|
+
fi
|
|
272
|
+
|
|
251
273
|
ffile_a=$(printf '%s' "$finding" | jq -r '.file_a // ""')
|
|
252
274
|
fexcerpt_a=$(printf '%s' "$finding" | jq -r '.excerpt_a // ""')
|
|
253
275
|
ffile_b=$(printf '%s' "$finding" | jq -r '.file_b // ""')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "librarian",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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
|
-
|
|
650
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
@@ -268,10 +268,12 @@ _run_audit() {
|
|
|
268
268
|
[ "$output" = "session_start_first_run" ]
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
# A typeless finding must not vanish.
|
|
272
|
-
#
|
|
273
|
-
# the
|
|
274
|
-
#
|
|
271
|
+
# A typeless finding must not vanish. Since ecosystem-0ty the synthesize phase
|
|
272
|
+
# drops it before it is hashed, so this line now comes from there rather than
|
|
273
|
+
# from the emit-phase builder refusal that ecosystem-ci0 added. Both write
|
|
274
|
+
# "carries no type" on purpose: what matters to an operator is that the audit
|
|
275
|
+
# log says a finding was discarded and why, not which layer discarded it. The
|
|
276
|
+
# builder's own refusal is still covered directly in cartographer-events.bats.
|
|
275
277
|
@test "a finding with no type is reported in the audit log" {
|
|
276
278
|
cat > "${STUB_BIN}/claude" <<'STUB'
|
|
277
279
|
#!/usr/bin/env bash
|
|
@@ -286,6 +288,50 @@ STUB
|
|
|
286
288
|
grep -q 'carries no type' "$AUDIT_LOG"
|
|
287
289
|
}
|
|
288
290
|
|
|
291
|
+
# Refusing to announce a typeless finding was only half of ecosystem-ci0. The
|
|
292
|
+
# orchestrator still stored it under a hash computed from a type it does not
|
|
293
|
+
# have, then touched its dedup sentinel — so on every later audit the sentinel
|
|
294
|
+
# said "known", the refresh path ran, and no bus event ever fired again. The
|
|
295
|
+
# finding sat on disk forever, counted in new_finding_count, and was
|
|
296
|
+
# permanently unannounceable. These three assert it is dropped instead
|
|
297
|
+
# (ecosystem-0ty).
|
|
298
|
+
|
|
299
|
+
_typeless_stub() {
|
|
300
|
+
cat > "${STUB_BIN}/claude" <<'STUB'
|
|
301
|
+
#!/usr/bin/env bash
|
|
302
|
+
cat >/dev/null
|
|
303
|
+
echo call >> "$CLAUDE_CALL_LOG"
|
|
304
|
+
printf '[{"severity":"warning","file_a":"CLAUDE.md","excerpt_a":"Always prefer tabs.","description":"typeless"}]'
|
|
305
|
+
STUB
|
|
306
|
+
chmod +x "${STUB_BIN}/claude"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@test "a finding with no type is never written to findings/" {
|
|
310
|
+
_typeless_stub
|
|
311
|
+
run _run_audit
|
|
312
|
+
[ "$status" -eq 0 ] || return 1
|
|
313
|
+
[ "$(find "${CARTOGRAPHER_DIR}/findings" -name '*.json' 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@test "a finding with no type leaves no dedup sentinel" {
|
|
317
|
+
# The sentinel is what made this permanent: with one on disk, a corrected
|
|
318
|
+
# finding that later hashes the same way would be seen as already known and
|
|
319
|
+
# silently refreshed instead of announced.
|
|
320
|
+
_typeless_stub
|
|
321
|
+
run _run_audit
|
|
322
|
+
[ "$status" -eq 0 ] || return 1
|
|
323
|
+
[ "$(find "${CARTOGRAPHER_DIR}/dedup" -type f 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@test "a finding with no type is not counted as a new finding" {
|
|
327
|
+
# audit.complete used to report a finding that produced no issue.found,
|
|
328
|
+
# so a consumer reading only the bus saw counts that its own event stream
|
|
329
|
+
# could not account for.
|
|
330
|
+
_typeless_stub
|
|
331
|
+
_run_audit
|
|
332
|
+
_last_audit_complete | jq -e '.payload.new_finding_count == 0' >/dev/null
|
|
333
|
+
}
|
|
334
|
+
|
|
289
335
|
|
|
290
336
|
# ── Resolution reaching the bus (ecosystem-w2i) ──────────────────────────────
|
|
291
337
|
#
|
|
@@ -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)
|