@onlooker-community/ecosystem 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/beads/SKILL.md +80 -0
- package/.agents/skills/beads/agents/openai.yaml +4 -0
- package/.claude/settings.json +13 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.codex/config.toml +2 -0
- package/.codex/hooks.json +51 -0
- package/.markdownlint.json +3 -0
- package/.release-please-manifest.json +8 -8
- package/AGENTS.md +135 -0
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +56 -1
- package/docs/lesson-promotion-pipeline.md +210 -0
- package/docs/superpowers/plans/2026-08-09-lesson-transform.md +1537 -0
- package/docs/superpowers/specs/2026-08-09-lesson-transform-design.md +261 -0
- package/package.json +3 -2
- package/plugins/archivist/scripts/lib/archivist-config.sh +10 -34
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +39 -62
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +11 -29
- package/plugins/compass/scripts/lib/compass-config.sh +16 -46
- package/plugins/counsel/scripts/lib/counsel-config.sh +15 -46
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-config.sh +19 -44
- package/plugins/echo/scripts/lib/echo-config.sh +30 -59
- package/plugins/governor/scripts/lib/governor-config.sh +11 -41
- package/plugins/historian/scripts/lib/historian-config.sh +9 -33
- package/plugins/inspector/.claude-plugin/plugin.json +1 -1
- package/plugins/inspector/CHANGELOG.md +7 -0
- package/plugins/inspector/scripts/lib/inspector-config.sh +39 -63
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +7 -0
- package/plugins/librarian/config.json +4 -0
- package/plugins/librarian/schema/PROVENANCE.json +7 -0
- package/plugins/librarian/schema/lesson-applies-to.subschema.json +74 -0
- package/plugins/librarian/schema/lesson-evidence.subschema.json +36 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +26 -0
- package/plugins/librarian/scripts/lib/librarian-config.sh +10 -34
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +135 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +311 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +140 -0
- package/plugins/lineage/.claude-plugin/plugin.json +1 -1
- package/plugins/lineage/CHANGELOG.md +7 -0
- package/plugins/lineage/scripts/lib/lineage-config.sh +17 -53
- package/plugins/scribe/.claude-plugin/plugin.json +1 -1
- package/plugins/scribe/CHANGELOG.md +7 -0
- package/plugins/scribe/scripts/lib/scribe-config.sh +17 -47
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +7 -0
- package/plugins/tribunal/scripts/lib/tribunal-config.sh +25 -63
- package/plugins/warden/scripts/lib/warden-config.sh +17 -54
- package/scripts/lib/config-loader.sh +8 -1
- package/scripts/lint/check-lesson-schema-drift.mjs +36 -0
- package/test/bats/librarian-lesson-transform.bats +609 -0
- package/test/node/lesson-schema-drift.test.mjs +28 -0
- package/test/node/lesson-validate-agreement.test.mjs +154 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pure validation rules for lesson candidates. No I/O, no network, no CLI.
|
|
3
|
+
#
|
|
4
|
+
# These rules mirror the vendored sub-schemas in plugins/librarian/schema/.
|
|
5
|
+
# ajv cannot run at runtime (installed plugins ship no node_modules, ADR-005),
|
|
6
|
+
# so enforcement here is jq. The two mechanisms have been proven able to
|
|
7
|
+
# disagree, so tests assert them separately.
|
|
8
|
+
|
|
9
|
+
# Version-shaped token check. Returns 0 when the artifact could plausibly
|
|
10
|
+
# yield a versioned scope, 1 when it definitionally cannot.
|
|
11
|
+
#
|
|
12
|
+
# This rejects only what is impossible, never what is merely low quality:
|
|
13
|
+
# the transform can emit `versioned` scope alone, so an artifact with no
|
|
14
|
+
# version token anywhere cannot produce a valid scope.versions.
|
|
15
|
+
#
|
|
16
|
+
# Usage: librarian_lesson_pregate <artifact_json>
|
|
17
|
+
librarian_lesson_pregate() {
|
|
18
|
+
local artifact="${1:-}"
|
|
19
|
+
[[ -z "$artifact" ]] && return 1
|
|
20
|
+
|
|
21
|
+
local text
|
|
22
|
+
text=$(printf '%s' "$artifact" | jq -r '((.summary // "") + " " + (.detail // ""))' 2>/dev/null) || return 1
|
|
23
|
+
[[ -z "$text" ]] && return 1
|
|
24
|
+
|
|
25
|
+
# Dotted (5.4.21), v-prefixed (v5), or x-range (5.x).
|
|
26
|
+
printf '%s' "$text" | grep -qE '([0-9]+\.[0-9]+)|(\bv[0-9]+)|([0-9]+\.x\b)'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Version range check, mirroring the vendored pattern.
|
|
30
|
+
#
|
|
31
|
+
# Accepts: <6 <=6 =6 >4 >=4 ">=4 <6"
|
|
32
|
+
# Rejects: ^5.4.21 ~5 5.x 5.4.21 >=0 >=0.0.0
|
|
33
|
+
#
|
|
34
|
+
# The >= and > forms require a non-zero lower bound. An unbounded lower bound
|
|
35
|
+
# matches every session and would never expire — version independence in
|
|
36
|
+
# disguise, which this stage is not allowed to mint.
|
|
37
|
+
#
|
|
38
|
+
# Usage: librarian_lesson_valid_range <string>
|
|
39
|
+
librarian_lesson_valid_range() {
|
|
40
|
+
local r="${1:-}"
|
|
41
|
+
[[ -z "$r" ]] && return 1
|
|
42
|
+
|
|
43
|
+
# The integer-part alternative is [0-9]*[1-9][0-9]*, not [1-9][0-9]*: the
|
|
44
|
+
# vendored pattern's equivalent is \d*[1-9]\d*, which allows a leading
|
|
45
|
+
# zero digit (05, 007) as long as some digit is nonzero. [1-9][0-9]* only
|
|
46
|
+
# permits a nonzero *leading* digit, so it rejects >=05 while the vendored
|
|
47
|
+
# schema accepts it — a real jq/schema disagreement, not a style choice.
|
|
48
|
+
local nonzero='([0-9]*[1-9][0-9]*(\.[0-9]+)?(\.[0-9]+)?|0+\.[0-9]*[1-9][0-9]*(\.[0-9]+)?|0+\.0+\.[0-9]*[1-9][0-9]*)'
|
|
49
|
+
local any='[0-9]+(\.[0-9]+)?(\.[0-9]+)?'
|
|
50
|
+
local pattern="^((<|<=|=)${any}|(>|>=)${nonzero}|(>|>=)${any} (<|<=)${any})$"
|
|
51
|
+
|
|
52
|
+
# Use bash's own regex engine rather than grep: grep's ^/$ anchor to line
|
|
53
|
+
# boundaries, not string boundaries, so a value with an embedded newline
|
|
54
|
+
# could smuggle a valid line past an otherwise-rejected string. [[ =~ ]]
|
|
55
|
+
# anchors to the whole string. The pattern must stay unquoted here —
|
|
56
|
+
# quoting the right-hand side of =~ forces literal string matching.
|
|
57
|
+
[[ "$r" =~ $pattern ]]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Validate a full candidate. Prints nothing on success; prints a reason slug
|
|
61
|
+
# to stderr on failure.
|
|
62
|
+
#
|
|
63
|
+
# Usage: librarian_lesson_validate_candidate <candidate_json>
|
|
64
|
+
librarian_lesson_validate_candidate() {
|
|
65
|
+
local candidate="${1:-}"
|
|
66
|
+
[[ -z "$candidate" ]] && { printf 'schema_invalid\n' >&2; return 1; }
|
|
67
|
+
|
|
68
|
+
# Structural shape, including the versioned-only rule and a non-empty
|
|
69
|
+
# resolution. `versions` must be a non-empty object. artifact_ids,
|
|
70
|
+
# session_ids, and observed_at are checked against the same patterns as
|
|
71
|
+
# the vendored lesson-evidence.subschema.json (ULID, non-empty string,
|
|
72
|
+
# RFC3339 date-time) — a provenance-less artifact (session_id/created_at
|
|
73
|
+
# stitched in as "") must fail here, not pass through and get buried
|
|
74
|
+
# permanently once librarian_lesson_seen marks it handled.
|
|
75
|
+
#
|
|
76
|
+
# The `keys - [...] | length == 0` checks mirror `additionalProperties:
|
|
77
|
+
# false` on the vendored `evidence` and `applies_to` sub-schemas
|
|
78
|
+
# (including the "versioned" scope branch), and the `all(type ==
|
|
79
|
+
# "string" and length > 0)` checks mirror their array items' `minLength:
|
|
80
|
+
# 1`. Neither is decorative: without them a model that "helpfully" adds
|
|
81
|
+
# an extra field, or emits an empty-string array entry, produces a
|
|
82
|
+
# proposal that passes here but fails ajv against the contract it claims
|
|
83
|
+
# to satisfy — and lessons are meant to be shared with other people. Each
|
|
84
|
+
# `keys` call is guarded by a preceding `type == "object"` check in the
|
|
85
|
+
# same `and` chain: jq's `and` short-circuits left to right, so `keys` on
|
|
86
|
+
# a missing/non-object value is never reached.
|
|
87
|
+
if ! printf '%s' "$candidate" | jq -e '
|
|
88
|
+
(.claim | type) == "string" and (.claim | length) > 0
|
|
89
|
+
and (.rationale | type) == "string" and (.rationale | length) > 0
|
|
90
|
+
and (.evidence | type) == "object"
|
|
91
|
+
and ((.evidence | keys) - ["artifact_ids", "session_ids", "project_key", "observed_at", "resolution"] | length) == 0
|
|
92
|
+
and (.evidence.artifact_ids | type) == "array" and (.evidence.artifact_ids | length) > 0
|
|
93
|
+
and (.evidence.artifact_ids | all(type == "string" and test("^[0-9A-HJKMNP-TV-Z]{26}$")))
|
|
94
|
+
and (.evidence.session_ids | type) == "array" and (.evidence.session_ids | length) > 0
|
|
95
|
+
and (.evidence.session_ids | all(type == "string" and length > 0))
|
|
96
|
+
and (.evidence.project_key | type) == "string"
|
|
97
|
+
and (.evidence.project_key | test("^[0-9a-f]{12}$"))
|
|
98
|
+
and (.evidence.observed_at | type) == "string"
|
|
99
|
+
and (.evidence.observed_at | test("^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"))
|
|
100
|
+
and (.evidence.resolution | type) == "string" and (.evidence.resolution | length) > 0
|
|
101
|
+
and (.applies_to | type) == "object"
|
|
102
|
+
and ((.applies_to | keys) - ["stack", "scope", "file_patterns", "task_kinds"] | length) == 0
|
|
103
|
+
and (.applies_to.stack | type) == "array" and (.applies_to.stack | length) > 0
|
|
104
|
+
and (.applies_to.stack | all(type == "string" and length > 0))
|
|
105
|
+
and (.applies_to.file_patterns | type) == "array"
|
|
106
|
+
and (.applies_to.file_patterns | all(type == "string" and length > 0))
|
|
107
|
+
and (.applies_to.task_kinds | type) == "array"
|
|
108
|
+
and (.applies_to.task_kinds | all(type == "string" and length > 0))
|
|
109
|
+
and .applies_to.scope.kind == "versioned"
|
|
110
|
+
and ((.applies_to.scope | keys) - ["kind", "versions"] | length) == 0
|
|
111
|
+
and (.applies_to.scope.versions | type) == "object"
|
|
112
|
+
and (.applies_to.scope.versions | length) > 0
|
|
113
|
+
' >/dev/null 2>&1; then
|
|
114
|
+
printf 'schema_invalid\n' >&2
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Cross-field rule JSON Schema cannot express: every versions key must
|
|
119
|
+
# name an entry in stack.
|
|
120
|
+
if ! printf '%s' "$candidate" | jq -e '
|
|
121
|
+
(.applies_to.scope.versions | keys) - .applies_to.stack | length == 0
|
|
122
|
+
' >/dev/null 2>&1; then
|
|
123
|
+
printf 'schema_invalid\n' >&2
|
|
124
|
+
return 1
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Every range must satisfy the vendored pattern. NUL-delimited, not
|
|
128
|
+
# newline-delimited: a range value with an embedded newline would
|
|
129
|
+
# otherwise split into two lines that can each pass individually even
|
|
130
|
+
# though the single value they came from is not a valid range. Do not
|
|
131
|
+
# skip empty reads either — jq never emits one for a non-empty object
|
|
132
|
+
# of strings, so an empty read means the range itself is empty, and
|
|
133
|
+
# librarian_lesson_valid_range already rejects that.
|
|
134
|
+
local range
|
|
135
|
+
while IFS= read -r -d '' range; do
|
|
136
|
+
librarian_lesson_valid_range "$range" || { printf 'schema_invalid\n' >&2; return 1; }
|
|
137
|
+
done < <(printf '%s' "$candidate" | jq --raw-output0 '.applies_to.scope.versions[]' 2>/dev/null)
|
|
138
|
+
|
|
139
|
+
return 0
|
|
140
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lineage",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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.2](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.2.1...lineage-v0.2.2) (2026-08-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* restore config convenience functions & refactor execution plugins to shared loader ([#128](https://github.com/onlooker-community/ecosystem/issues/128)) ([4b3660c](https://github.com/onlooker-community/ecosystem/commit/4b3660c5a8b234187ec1e71c37b63e6a3d305c98))
|
|
9
|
+
|
|
3
10
|
## [0.2.1](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.2.0...lineage-v0.2.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,71 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Config resolution for lineage.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/lineage/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
|
-
# lineage_config_load <repo_root>
|
|
11
|
-
# lineage_config_get <jq-path>
|
|
12
|
-
# lineage_config_get_json <jq-path>
|
|
13
|
-
# lineage_config_max_snippet_chars # echoes the snippet cap (default 4000)
|
|
14
|
-
# lineage_config_redact_enabled # 0 unless redact_secrets is false
|
|
15
|
-
# lineage_config_prompt_source # echoes the prompt-source strategy
|
|
16
|
-
# lineage_config_ignore_globs # echoes ignore globs, one per line
|
|
12
|
+
# lineage_config_load <repo_root> # populates _lineage_CONFIG (JSON)
|
|
13
|
+
# lineage_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# lineage_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
18
|
+
|
|
19
|
+
_lineage_CONFIG="{}"
|
|
19
20
|
|
|
20
21
|
lineage_config_load() {
|
|
21
22
|
local repo_root="${1:-}"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
local merged="{}"
|
|
26
|
-
local file
|
|
27
|
-
|
|
28
|
-
file="${plugin_root}/config.json"
|
|
29
|
-
if [[ -f "$file" ]]; then
|
|
30
|
-
local defaults
|
|
31
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
32
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
33
|
-
|| merged="$defaults"
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
local repo_settings=""
|
|
37
|
-
[[ -n "$repo_root" ]] && repo_settings="${repo_root}/.claude/settings.json"
|
|
38
|
-
|
|
39
|
-
for file in "${home_dir}/.claude/settings.json" "$repo_settings"; do
|
|
40
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
41
|
-
local overlay
|
|
42
|
-
overlay=$(jq '{ lineage: (.lineage // {}) }' "$file" 2>/dev/null) || continue
|
|
43
|
-
[[ -z "$overlay" ]] && continue
|
|
44
|
-
local attempt
|
|
45
|
-
if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
46
|
-
def deepmerge($a; $b):
|
|
47
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
48
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
49
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
50
|
-
elif $b == null then $a
|
|
51
|
-
else $b end;
|
|
52
|
-
deepmerge($a; $b)
|
|
53
|
-
' 2>/dev/null) && [[ -n "$attempt" ]]; then
|
|
54
|
-
merged="$attempt"
|
|
55
|
-
fi
|
|
56
|
-
done
|
|
57
|
-
|
|
58
|
-
_LINEAGE_CONFIG="$merged"
|
|
23
|
+
config_load_plugin "lineage" "$repo_root" "_lineage_CONFIG"
|
|
24
|
+
return 0
|
|
59
25
|
}
|
|
60
26
|
|
|
61
27
|
lineage_config_get() {
|
|
62
28
|
local path="$1"
|
|
63
|
-
|
|
29
|
+
config_get "_lineage_CONFIG" "${path}"
|
|
64
30
|
}
|
|
65
31
|
|
|
66
32
|
lineage_config_get_json() {
|
|
67
33
|
local path="$1"
|
|
68
|
-
|
|
34
|
+
config_get_json "_lineage_CONFIG" "${path}"
|
|
69
35
|
}
|
|
70
36
|
|
|
71
37
|
lineage_config_max_snippet_chars() {
|
|
@@ -75,10 +41,8 @@ lineage_config_max_snippet_chars() {
|
|
|
75
41
|
}
|
|
76
42
|
|
|
77
43
|
lineage_config_redact_enabled() {
|
|
78
|
-
# Default on. jq's `//` treats a literal `false` as empty, so read the raw
|
|
79
|
-
# JSON value and only disable on an explicit false.
|
|
80
44
|
local v
|
|
81
|
-
v=$(
|
|
45
|
+
v=$(lineage_config_get '.lineage.redact_secrets')
|
|
82
46
|
[[ "$v" != "false" ]]
|
|
83
47
|
}
|
|
84
48
|
|
|
@@ -89,5 +53,5 @@ lineage_config_prompt_source() {
|
|
|
89
53
|
}
|
|
90
54
|
|
|
91
55
|
lineage_config_ignore_globs() {
|
|
92
|
-
|
|
56
|
+
lineage_config_get_json '.lineage.ignore_globs // ["**/.git/**","**/node_modules/**","**/dist/**","**/*.lock"]' | jq -r '.[]'
|
|
93
57
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribe",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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.2](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.4.1...scribe-v0.4.2) (2026-08-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* restore config convenience functions & refactor execution plugins to shared loader ([#128](https://github.com/onlooker-community/ecosystem/issues/128)) ([4b3660c](https://github.com/onlooker-community/ecosystem/commit/4b3660c5a8b234187ec1e71c37b63e6a3d305c98))
|
|
9
|
+
|
|
3
10
|
## [0.4.1](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.4.0...scribe-v0.4.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,65 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Config resolution for
|
|
2
|
+
# Config resolution for scribe.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/scribe/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
|
-
# scribe_config_load <repo_root>
|
|
11
|
-
# scribe_config_get <jq-path>
|
|
12
|
-
# scribe_config_get_json <jq-path>
|
|
12
|
+
# scribe_config_load <repo_root> # populates _scribe_CONFIG (JSON)
|
|
13
|
+
# scribe_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# scribe_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
18
|
+
|
|
19
|
+
_scribe_CONFIG="{}"
|
|
15
20
|
|
|
16
21
|
scribe_config_load() {
|
|
17
22
|
local repo_root="${1:-}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
local merged="{}"
|
|
22
|
-
local file
|
|
23
|
-
|
|
24
|
-
file="${plugin_root}/config.json"
|
|
25
|
-
if [[ -f "$file" ]]; then
|
|
26
|
-
local defaults
|
|
27
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
28
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
29
|
-
|| merged="$defaults"
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
local repo_settings=""
|
|
33
|
-
[[ -n "$repo_root" ]] && repo_settings="${repo_root}/.claude/settings.json"
|
|
34
|
-
|
|
35
|
-
for file in "${home_dir}/.claude/settings.json" "$repo_settings"; do
|
|
36
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
37
|
-
local overlay
|
|
38
|
-
overlay=$(jq '{ scribe: (.scribe // {}) }' "$file" 2>/dev/null) || continue
|
|
39
|
-
[[ -z "$overlay" ]] && continue
|
|
40
|
-
local attempt
|
|
41
|
-
if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
42
|
-
def deepmerge($a; $b):
|
|
43
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
44
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
45
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
46
|
-
elif $b == null then $a
|
|
47
|
-
else $b end;
|
|
48
|
-
deepmerge($a; $b)
|
|
49
|
-
' 2>/dev/null) && [[ -n "$attempt" ]]; then
|
|
50
|
-
merged="$attempt"
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
53
|
-
|
|
54
|
-
_SCRIBE_CONFIG="$merged"
|
|
23
|
+
config_load_plugin "scribe" "$repo_root" "_scribe_CONFIG"
|
|
24
|
+
return 0
|
|
55
25
|
}
|
|
56
26
|
|
|
57
27
|
scribe_config_get() {
|
|
58
28
|
local path="$1"
|
|
59
|
-
|
|
29
|
+
config_get "_scribe_CONFIG" "${path}"
|
|
60
30
|
}
|
|
61
31
|
|
|
62
32
|
scribe_config_get_json() {
|
|
63
33
|
local path="$1"
|
|
64
|
-
|
|
65
|
-
}
|
|
34
|
+
config_get_json "_scribe_CONFIG" "${path}"
|
|
35
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tribunal",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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.1.2](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.1...tribunal-v1.1.2) (2026-08-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* restore config convenience functions & refactor execution plugins to shared loader ([#128](https://github.com/onlooker-community/ecosystem/issues/128)) ([4b3660c](https://github.com/onlooker-community/ecosystem/commit/4b3660c5a8b234187ec1e71c37b63e6a3d305c98))
|
|
9
|
+
|
|
3
10
|
## [1.1.1](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.0...tribunal-v1.1.1) (2026-08-01)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,93 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Config resolution for
|
|
2
|
+
# Config resolution for tribunal.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/tribunal/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
|
-
# tribunal_config_load <repo_root>
|
|
11
|
-
# tribunal_config_get <jq-path>
|
|
12
|
-
# tribunal_config_get_json <jq-path>
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
# # falling back to tribunal.judges.model
|
|
17
|
-
#
|
|
18
|
-
# Settings overlay only touches the `tribunal.*` subtree so this plugin coexists
|
|
19
|
-
# with other plugins' configuration.
|
|
12
|
+
# tribunal_config_load <repo_root> # populates _tribunal_CONFIG (JSON)
|
|
13
|
+
# tribunal_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# tribunal_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
15
|
+
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
20
18
|
|
|
21
19
|
_TRIBUNAL_CONFIG="{}"
|
|
22
20
|
|
|
23
21
|
tribunal_config_load() {
|
|
24
22
|
local repo_root="${1:-}"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
local merged="{}"
|
|
29
|
-
local file
|
|
30
|
-
|
|
31
|
-
file="${plugin_root}/config.json"
|
|
32
|
-
if [[ -f "$file" ]]; then
|
|
33
|
-
local defaults
|
|
34
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
35
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
36
|
-
|| merged="$defaults"
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
for file in "${home_dir}/.claude/settings.json" "${repo_root}/.claude/settings.json"; do
|
|
40
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
41
|
-
local overlay
|
|
42
|
-
overlay=$(jq '{ tribunal: (.tribunal // {}) }' "$file" 2>/dev/null) || continue
|
|
43
|
-
[[ -z "$overlay" ]] && continue
|
|
44
|
-
local attempt
|
|
45
|
-
if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
46
|
-
def deepmerge($a; $b):
|
|
47
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
48
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
49
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
50
|
-
elif $b == null then $a
|
|
51
|
-
else $b end;
|
|
52
|
-
deepmerge($a; $b)
|
|
53
|
-
' 2>/dev/null) && [[ -n "$attempt" ]]; then
|
|
54
|
-
merged="$attempt"
|
|
55
|
-
fi
|
|
56
|
-
done
|
|
57
|
-
|
|
58
|
-
_TRIBUNAL_CONFIG="$merged"
|
|
23
|
+
config_load_plugin "tribunal" "$repo_root" "_TRIBUNAL_CONFIG"
|
|
24
|
+
return 0
|
|
59
25
|
}
|
|
60
26
|
|
|
61
|
-
# Read a string value from the loaded config.
|
|
62
|
-
# Usage: tribunal_config_get '.tribunal.session.gate_policy'
|
|
63
27
|
tribunal_config_get() {
|
|
64
28
|
local path="$1"
|
|
65
|
-
|
|
29
|
+
config_get "_TRIBUNAL_CONFIG" "${path}"
|
|
66
30
|
}
|
|
67
31
|
|
|
68
|
-
# Read a JSON value (arrays, objects, numbers) from the loaded config.
|
|
69
|
-
# Usage: tribunal_config_get_json '.tribunal.session.judge_types'
|
|
70
32
|
tribunal_config_get_json() {
|
|
71
33
|
local path="$1"
|
|
72
|
-
|
|
34
|
+
config_get_json "_TRIBUNAL_CONFIG" "${path}"
|
|
73
35
|
}
|
|
74
36
|
|
|
75
|
-
# Returns 0 if tribunal.stop_hook.enabled is true. Default is false.
|
|
76
37
|
tribunal_config_stop_hook_enabled() {
|
|
77
38
|
local v
|
|
78
39
|
v=$(tribunal_config_get '.tribunal.stop_hook.enabled')
|
|
79
40
|
[[ "$v" == "true" ]]
|
|
80
41
|
}
|
|
81
42
|
|
|
82
|
-
# Resolve the model id for a given judge_type.
|
|
83
|
-
# Precedence: tribunal.judges.<type>.model > tribunal.judges.model
|
|
84
43
|
tribunal_config_judge_model() {
|
|
85
|
-
local judge_type="$1"
|
|
86
|
-
local
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
44
|
+
local judge_type="${1:-}"
|
|
45
|
+
local v
|
|
46
|
+
if [[ -n "$judge_type" ]]; then
|
|
47
|
+
v=$(tribunal_config_get ".tribunal.judges.\"$judge_type\".model")
|
|
48
|
+
if [[ -n "$v" ]]; then
|
|
49
|
+
printf '%s' "$v"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
91
52
|
fi
|
|
92
|
-
tribunal_config_get '.tribunal.judges.model'
|
|
53
|
+
v=$(tribunal_config_get '.tribunal.judges.model')
|
|
54
|
+
printf '%s' "${v:-claude-opus-4-7}"
|
|
93
55
|
}
|
|
@@ -1,72 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Config resolution for
|
|
2
|
+
# Config resolution for warden.
|
|
3
3
|
#
|
|
4
|
-
# Reads
|
|
4
|
+
# Uses the shared config loader from ecosystem. Reads five layers, latest wins:
|
|
5
5
|
# 1. plugins/warden/config.json (defaults shipped with the plugin)
|
|
6
6
|
# 2. ~/.claude/settings.json
|
|
7
|
-
# 3.
|
|
7
|
+
# 3. ~/.claude/settings.local.json (local overrides user)
|
|
8
|
+
# 4. <repo>/.claude/settings.json
|
|
9
|
+
# 5. <repo>/.claude/settings.local.json (local overrides project)
|
|
8
10
|
#
|
|
9
11
|
# Exposes:
|
|
10
|
-
# warden_config_load <repo_root>
|
|
11
|
-
# warden_config_get <jq-path>
|
|
12
|
-
# warden_config_get_json <jq-path>
|
|
12
|
+
# warden_config_load <repo_root> # populates _warden_CONFIG (JSON)
|
|
13
|
+
# warden_config_get <jq-path> # echoes string value (empty if unset)
|
|
14
|
+
# warden_config_get_json <jq-path> # echoes JSON value (null if unset)
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
# shellcheck source=../../../scripts/lib/config-loader.sh
|
|
17
|
+
source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
|
|
18
|
+
|
|
19
|
+
_warden_CONFIG="{}"
|
|
15
20
|
|
|
16
21
|
warden_config_load() {
|
|
17
22
|
local repo_root="${1:-}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
local merged="{}"
|
|
22
|
-
local file
|
|
23
|
-
|
|
24
|
-
file="${plugin_root}/config.json"
|
|
25
|
-
if [[ -f "$file" ]]; then
|
|
26
|
-
local defaults
|
|
27
|
-
defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
|
|
28
|
-
merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
|
|
29
|
-
|| merged="$defaults"
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
local repo_settings=""
|
|
33
|
-
[[ -n "$repo_root" ]] && repo_settings="${repo_root}/.claude/settings.json"
|
|
34
|
-
|
|
35
|
-
for file in "${home_dir}/.claude/settings.json" "$repo_settings"; do
|
|
36
|
-
[[ -n "$file" && -f "$file" ]] || continue
|
|
37
|
-
local overlay
|
|
38
|
-
overlay=$(jq '{ warden: (.warden // {}) }' "$file" 2>/dev/null) || continue
|
|
39
|
-
[[ -z "$overlay" ]] && continue
|
|
40
|
-
local attempt
|
|
41
|
-
if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
|
|
42
|
-
def deepmerge($a; $b):
|
|
43
|
-
if ($a|type) == "object" and ($b|type) == "object" then
|
|
44
|
-
reduce (($a|keys) + ($b|keys) | unique)[] as $k
|
|
45
|
-
({}; .[$k] = deepmerge($a[$k]; $b[$k]))
|
|
46
|
-
elif $b == null then $a
|
|
47
|
-
else $b end;
|
|
48
|
-
deepmerge($a; $b)
|
|
49
|
-
' 2>/dev/null) && [[ -n "$attempt" ]]; then
|
|
50
|
-
merged="$attempt"
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
53
|
-
|
|
54
|
-
_WARDEN_CONFIG="$merged"
|
|
23
|
+
config_load_plugin "warden" "$repo_root" "_warden_CONFIG"
|
|
24
|
+
return 0
|
|
55
25
|
}
|
|
56
26
|
|
|
57
27
|
warden_config_get() {
|
|
58
28
|
local path="$1"
|
|
59
|
-
|
|
60
|
-
# empty, so a `false` boolean would read back as "" and a `${v:-true}`
|
|
61
|
-
# default would silently flip it to true. Emit the raw value and map only a
|
|
62
|
-
# literal JSON null to the empty string.
|
|
63
|
-
local v
|
|
64
|
-
v=$(printf '%s' "$_WARDEN_CONFIG" | jq -r "${path}" 2>/dev/null) || return 1
|
|
65
|
-
[[ "$v" == "null" ]] && v=""
|
|
66
|
-
printf '%s' "$v"
|
|
29
|
+
config_get "_warden_CONFIG" "${path}"
|
|
67
30
|
}
|
|
68
31
|
|
|
69
32
|
warden_config_get_json() {
|
|
70
33
|
local path="$1"
|
|
71
|
-
|
|
72
|
-
}
|
|
34
|
+
config_get_json "_warden_CONFIG" "${path}"
|
|
35
|
+
}
|
|
@@ -110,7 +110,14 @@ config_get() {
|
|
|
110
110
|
|
|
111
111
|
# Use indirect expansion to read the variable's value.
|
|
112
112
|
local config_json="${!config_var}"
|
|
113
|
-
|
|
113
|
+
# NB: do NOT use `${path} // empty` — jq's `//` treats `false` and `0` as
|
|
114
|
+
# empty, so a false boolean would read back as "" and a true default would
|
|
115
|
+
# silently flip it. Emit the raw value and map only a literal JSON null to
|
|
116
|
+
# the empty string.
|
|
117
|
+
local v
|
|
118
|
+
v=$(printf '%s' "$config_json" | jq -r "${path}" 2>/dev/null) || return 1
|
|
119
|
+
[[ "$v" == "null" ]] && v=""
|
|
120
|
+
printf '%s' "$v"
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
# Get a JSON value from loaded config.
|