@onlooker-community/ecosystem 0.31.0 → 0.31.2
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/CHANGELOG.md +14 -0
- 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/hooks/archivist-extract.sh +10 -0
- package/plugins/archivist/scripts/hooks/archivist-inject.sh +10 -0
- package/plugins/archivist/scripts/lib/archivist-events.sh +6 -0
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/hooks/assayer-stop.sh +10 -0
- package/plugins/assayer/scripts/lib/assayer-events.sh +6 -0
- package/plugins/bursar/.claude-plugin/plugin.json +1 -1
- package/plugins/bursar/CHANGELOG.md +7 -0
- package/plugins/bursar/scripts/lib/bursar-events.sh +6 -0
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +7 -0
- package/plugins/cartographer/scripts/lib/cartographer-events.sh +6 -0
- package/plugins/compass/.claude-plugin/plugin.json +1 -1
- package/plugins/compass/CHANGELOG.md +7 -0
- package/plugins/compass/scripts/lib/compass-events.sh +6 -0
- package/plugins/counsel/.claude-plugin/plugin.json +1 -1
- package/plugins/counsel/CHANGELOG.md +7 -0
- package/plugins/counsel/scripts/lib/counsel-events.sh +6 -0
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/hooks/curator-session-start.sh +10 -0
- package/plugins/curator/scripts/lib/curator-emit.sh +10 -0
- package/plugins/echo/.claude-plugin/plugin.json +1 -1
- package/plugins/echo/CHANGELOG.md +7 -0
- package/plugins/echo/scripts/hooks/echo-stop-gate.sh +10 -0
- package/plugins/echo/scripts/lib/echo-events.sh +6 -0
- package/plugins/governor/.claude-plugin/plugin.json +1 -1
- package/plugins/governor/CHANGELOG.md +7 -0
- package/plugins/governor/scripts/hooks/governor-post-tool-use.sh +10 -0
- package/plugins/governor/scripts/hooks/governor-pre-tool-use.sh +10 -0
- package/plugins/governor/scripts/hooks/governor-session-start.sh +10 -0
- package/plugins/governor/scripts/hooks/governor-stop.sh +10 -0
- package/plugins/governor/scripts/lib/governor-events.sh +6 -0
- package/plugins/historian/.claude-plugin/plugin.json +1 -1
- package/plugins/historian/CHANGELOG.md +14 -0
- package/plugins/historian/scripts/hooks/historian-prompt-submit.sh +12 -1
- package/plugins/historian/scripts/hooks/historian-session-end.sh +12 -1
- package/plugins/historian/scripts/lib/historian-emit.sh +10 -0
- package/plugins/inspector/.claude-plugin/plugin.json +1 -1
- package/plugins/inspector/CHANGELOG.md +7 -0
- package/plugins/inspector/scripts/lib/inspector-events.sh +6 -0
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +7 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +80 -6
- package/plugins/librarian/scripts/hooks/librarian-session-start.sh +10 -0
- package/plugins/librarian/scripts/lib/librarian-conflict-detector.sh +215 -0
- package/plugins/librarian/scripts/lib/librarian-emit.sh +10 -0
- package/plugins/lineage/.claude-plugin/plugin.json +1 -1
- package/plugins/lineage/CHANGELOG.md +7 -0
- package/plugins/lineage/scripts/lib/lineage-events.sh +6 -0
- package/plugins/scribe/.claude-plugin/plugin.json +1 -1
- package/plugins/scribe/CHANGELOG.md +7 -0
- package/plugins/scribe/scripts/lib/scribe-events.sh +6 -0
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +7 -0
- package/plugins/tribunal/scripts/hooks/tribunal-stop-gate.sh +10 -0
- package/plugins/tribunal/scripts/lib/tribunal-events.sh +6 -0
- package/plugins/warden/.claude-plugin/plugin.json +1 -1
- package/plugins/warden/CHANGELOG.md +7 -0
- package/plugins/warden/scripts/lib/warden-events.sh +6 -0
|
@@ -25,9 +25,20 @@ if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
|
25
25
|
_ECOSYSTEM_ROOT="$_candidate"
|
|
26
26
|
fi
|
|
27
27
|
fi
|
|
28
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
29
|
+
# works regardless of which ecosystem version is installed.
|
|
30
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
31
|
+
for _candidate in "${PLUGIN_ROOT}/../../ecosystem/"*/scripts/lib/validate-path.sh; do
|
|
32
|
+
if [[ -f "$_candidate" ]]; then
|
|
33
|
+
_ECOSYSTEM_ROOT="$(cd "$(dirname "$(dirname "$_candidate")")" && pwd)"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
28
38
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
29
39
|
# shellcheck disable=SC1091
|
|
30
|
-
CLAUDE_PLUGIN_ROOT="$
|
|
40
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
41
|
+
source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
31
42
|
fi
|
|
32
43
|
|
|
33
44
|
# shellcheck source=../lib/historian-config.sh
|
|
@@ -16,6 +16,16 @@ _historian_resolve_event_js() {
|
|
|
16
16
|
ecosystem_root="$candidate"
|
|
17
17
|
fi
|
|
18
18
|
fi
|
|
19
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
20
|
+
# works regardless of which ecosystem version is installed.
|
|
21
|
+
if [[ -z "$ecosystem_root" ]]; then
|
|
22
|
+
for candidate in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
23
|
+
if [[ -f "$candidate" ]]; then
|
|
24
|
+
ecosystem_root="$(cd "$(dirname "$(dirname "$candidate")")" && pwd)"
|
|
25
|
+
break
|
|
26
|
+
fi
|
|
27
|
+
done
|
|
28
|
+
fi
|
|
19
29
|
|
|
20
30
|
if [[ -n "$ecosystem_root" ]]; then
|
|
21
31
|
printf '%s/scripts/lib/onlooker-event.mjs' "$ecosystem_root"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inspector",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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.1](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.3.0...inspector-v0.3.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [0.3.0](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.2.0...inspector-v0.3.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -23,6 +23,12 @@ _inspector_event_js_path() {
|
|
|
23
23
|
for c in "${candidates[@]}"; do
|
|
24
24
|
[[ -f "$c" ]] && { printf '%s' "$c"; return 0; }
|
|
25
25
|
done
|
|
26
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
27
|
+
# works regardless of which ecosystem version is installed.
|
|
28
|
+
local mjs
|
|
29
|
+
for mjs in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
30
|
+
[[ -f "$mjs" ]] && { printf '%s' "$mjs"; return 0; }
|
|
31
|
+
done
|
|
26
32
|
return 1
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "librarian",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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.4.1](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.4.0...librarian-v0.4.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [0.4.0](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.3.0...librarian-v0.4.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -25,6 +25,16 @@ if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
|
25
25
|
_ECOSYSTEM_ROOT="$_candidate"
|
|
26
26
|
fi
|
|
27
27
|
fi
|
|
28
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
29
|
+
# works regardless of which ecosystem version is installed.
|
|
30
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
31
|
+
for _candidate in "${PLUGIN_ROOT}/../../ecosystem/"*/scripts/lib/validate-path.sh; do
|
|
32
|
+
if [[ -f "$_candidate" ]]; then
|
|
33
|
+
_ECOSYSTEM_ROOT="$(cd "$(dirname "$(dirname "$_candidate")")" && pwd)"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
28
38
|
|
|
29
39
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
30
40
|
# shellcheck disable=SC1091
|
|
@@ -47,6 +57,8 @@ source "${PLUGIN_ROOT}/scripts/lib/librarian-archivist-reader.sh"
|
|
|
47
57
|
source "${PLUGIN_ROOT}/scripts/lib/librarian-durability.sh"
|
|
48
58
|
# shellcheck source=../lib/librarian-classifier.sh
|
|
49
59
|
source "${PLUGIN_ROOT}/scripts/lib/librarian-classifier.sh"
|
|
60
|
+
# shellcheck source=../lib/librarian-conflict-detector.sh
|
|
61
|
+
source "${PLUGIN_ROOT}/scripts/lib/librarian-conflict-detector.sh"
|
|
50
62
|
|
|
51
63
|
INPUT=$(cat 2>/dev/null || true)
|
|
52
64
|
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
|
|
@@ -222,14 +234,75 @@ for ((i = 0; i < KEPT_COUNT; i++)); do
|
|
|
222
234
|
continue
|
|
223
235
|
fi
|
|
224
236
|
|
|
225
|
-
# Build and persist the proposal.
|
|
226
|
-
# memory store
|
|
227
|
-
# conflict_state: "none" for now.
|
|
237
|
+
# Build and persist the proposal. Detect conflicts against the user's
|
|
238
|
+
# memory store before writing.
|
|
228
239
|
PROPOSAL_ID=$(librarian_ulid)
|
|
229
240
|
FILENAME=$(librarian_classifier_filename "$MEMORY_TYPE" "$TITLE")
|
|
230
241
|
ARTIFACT_ID=$(printf '%s' "$ARTIFACT" | jq -r '.id // ""')
|
|
231
242
|
ARTIFACT_SESSION=$(printf '%s' "$ARTIFACT" | jq -r '.session_id // ""')
|
|
232
243
|
|
|
244
|
+
# Resolve the typed memory store path for this project.
|
|
245
|
+
MEMORY_STORE_PATH=$(librarian_config_get '.librarian.memory_store_path')
|
|
246
|
+
[[ -z "$MEMORY_STORE_PATH" || "$MEMORY_STORE_PATH" == "null" ]] && \
|
|
247
|
+
MEMORY_STORE_PATH='${HOME}/.claude/projects/${CLAUDE_PROJECT_ENCODED}/memory'
|
|
248
|
+
|
|
249
|
+
# Expand env vars in the path.
|
|
250
|
+
MEMORY_STORE_PATH=$(eval echo "$MEMORY_STORE_PATH")
|
|
251
|
+
|
|
252
|
+
# Initialize conflict state; will scan if memory dir exists.
|
|
253
|
+
CONFLICT_STATE="none"
|
|
254
|
+
CONFLICT_WITH="[]"
|
|
255
|
+
|
|
256
|
+
# Detect conflicts against existing memories only if the directory exists.
|
|
257
|
+
if [[ -d "$MEMORY_STORE_PATH" ]]; then
|
|
258
|
+
# Build a temporary proposal for conflict detection.
|
|
259
|
+
TEMP_PROPOSAL=$(jq -n \
|
|
260
|
+
--arg id "$PROPOSAL_ID" \
|
|
261
|
+
--arg memory_type "$MEMORY_TYPE" \
|
|
262
|
+
--arg filename "$FILENAME" \
|
|
263
|
+
--arg title "$TITLE" \
|
|
264
|
+
--arg body "$BODY" \
|
|
265
|
+
--argjson classifier_confidence "$CONFIDENCE" \
|
|
266
|
+
'{
|
|
267
|
+
id: $id,
|
|
268
|
+
proposed: {
|
|
269
|
+
type: $memory_type,
|
|
270
|
+
filename: $filename,
|
|
271
|
+
title: $title,
|
|
272
|
+
body: $body,
|
|
273
|
+
classifier_confidence: $classifier_confidence
|
|
274
|
+
}
|
|
275
|
+
}')
|
|
276
|
+
|
|
277
|
+
# Detect conflicts against existing memories.
|
|
278
|
+
DUP_THRESHOLD=$(librarian_config_get '.librarian.conflict.duplicate_threshold')
|
|
279
|
+
[[ -z "$DUP_THRESHOLD" || "$DUP_THRESHOLD" == "null" ]] && DUP_THRESHOLD="0.7"
|
|
280
|
+
MERGE_THRESHOLD=$(librarian_config_get '.librarian.conflict.merge_candidate_threshold')
|
|
281
|
+
[[ -z "$MERGE_THRESHOLD" || "$MERGE_THRESHOLD" == "null" ]] && MERGE_THRESHOLD="0.45"
|
|
282
|
+
|
|
283
|
+
CONFLICT_RESULT=$(librarian_conflict_scan "$TEMP_PROPOSAL" "$MEMORY_STORE_PATH" \
|
|
284
|
+
"$DUP_THRESHOLD" "$MERGE_THRESHOLD" 2>/dev/null) || CONFLICT_RESULT=""
|
|
285
|
+
|
|
286
|
+
# Ensure we have valid JSON; fall back to "none" if scan fails.
|
|
287
|
+
if [[ -z "$CONFLICT_RESULT" ]] || ! printf '%s' "$CONFLICT_RESULT" | jq -e '.' >/dev/null 2>&1; then
|
|
288
|
+
CONFLICT_RESULT='{"conflict_state":"none","conflict_with":[]}'
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
CONFLICT_STATE=$(printf '%s' "$CONFLICT_RESULT" | jq -r '.conflict_state // "none"' 2>/dev/null) || CONFLICT_STATE="none"
|
|
292
|
+
CONFLICT_WITH=$(printf '%s' "$CONFLICT_RESULT" | jq -c '.conflict_with // []' 2>/dev/null) || CONFLICT_WITH="[]"
|
|
293
|
+
|
|
294
|
+
# Silently drop duplicates — no proposal written.
|
|
295
|
+
if [[ "$CONFLICT_STATE" == "duplicate" ]]; then
|
|
296
|
+
POST_CLASSIFIER_DROPPED=$((POST_CLASSIFIER_DROPPED + 1))
|
|
297
|
+
librarian_emit "librarian.candidate.dropped" "$SESSION_ID" "$(jq -cn \
|
|
298
|
+
--arg reason "duplicate" \
|
|
299
|
+
--arg src "$ARTIFACT_ID" \
|
|
300
|
+
'{ reason: $reason, source_artifact_id: (if $src == "" then null else $src end) }
|
|
301
|
+
| with_entries(select(.value != null))')"
|
|
302
|
+
continue
|
|
303
|
+
fi
|
|
304
|
+
fi
|
|
305
|
+
|
|
233
306
|
PROPOSAL_JSON=$(jq -n \
|
|
234
307
|
--arg id "$PROPOSAL_ID" \
|
|
235
308
|
--arg created_at "$NOW_TS" \
|
|
@@ -238,7 +311,8 @@ for ((i = 0; i < KEPT_COUNT; i++)); do
|
|
|
238
311
|
--arg title "$TITLE" \
|
|
239
312
|
--arg body "$BODY" \
|
|
240
313
|
--argjson classifier_confidence "$CONFIDENCE" \
|
|
241
|
-
--arg conflict_state "
|
|
314
|
+
--arg conflict_state "$CONFLICT_STATE" \
|
|
315
|
+
--argjson conflict_with "$CONFLICT_WITH" \
|
|
242
316
|
--arg artifact_id "$ARTIFACT_ID" \
|
|
243
317
|
--arg artifact_session "$ARTIFACT_SESSION" \
|
|
244
318
|
'{
|
|
@@ -254,7 +328,7 @@ for ((i = 0; i < KEPT_COUNT; i++)); do
|
|
|
254
328
|
classifier_confidence: $classifier_confidence
|
|
255
329
|
},
|
|
256
330
|
conflict_state: $conflict_state,
|
|
257
|
-
conflict_with:
|
|
331
|
+
conflict_with: $conflict_with,
|
|
258
332
|
status: "pending"
|
|
259
333
|
}')
|
|
260
334
|
|
|
@@ -304,7 +378,7 @@ for ((i = 0; i < KEPT_COUNT; i++)); do
|
|
|
304
378
|
--arg proposal_id "$PROPOSAL_ID" \
|
|
305
379
|
--arg memory_type "$MEMORY_TYPE" \
|
|
306
380
|
--argjson classifier_confidence "$CONFIDENCE" \
|
|
307
|
-
--arg conflict_state "
|
|
381
|
+
--arg conflict_state "$CONFLICT_STATE" \
|
|
308
382
|
--arg src "$ARTIFACT_ID" \
|
|
309
383
|
'{
|
|
310
384
|
proposal_id: $proposal_id,
|
|
@@ -25,6 +25,16 @@ if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
|
25
25
|
_ECOSYSTEM_ROOT="$_candidate"
|
|
26
26
|
fi
|
|
27
27
|
fi
|
|
28
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
29
|
+
# works regardless of which ecosystem version is installed.
|
|
30
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
31
|
+
for _candidate in "${PLUGIN_ROOT}/../../ecosystem/"*/scripts/lib/validate-path.sh; do
|
|
32
|
+
if [[ -f "$_candidate" ]]; then
|
|
33
|
+
_ECOSYSTEM_ROOT="$(cd "$(dirname "$(dirname "$_candidate")")" && pwd)"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
28
38
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
29
39
|
# shellcheck disable=SC1091
|
|
30
40
|
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Conflict and duplicate detection for librarian proposals.
|
|
3
|
+
#
|
|
4
|
+
# Compares proposed memories against existing memories in the user's
|
|
5
|
+
# typed memory store (~/.claude/projects/<encoded>/memory/) and classifies
|
|
6
|
+
# each proposal into one of:
|
|
7
|
+
# - none (no similarity, safe to propose)
|
|
8
|
+
# - duplicate (similarity >= duplicate_threshold; drop silently)
|
|
9
|
+
# - merge_candidate (merge_threshold <= similarity < duplicate_threshold)
|
|
10
|
+
# - conflict_candidate (opposing sentiment on overlapping content)
|
|
11
|
+
#
|
|
12
|
+
# Uses deterministic token-set Jaccard similarity (no LLM call) with
|
|
13
|
+
# configurable thresholds.
|
|
14
|
+
|
|
15
|
+
# Extract and normalize tokens from text for similarity comparison.
|
|
16
|
+
# Lowercases, removes stopwords, splits on non-alphanumeric.
|
|
17
|
+
_librarian_conflict_normalize() {
|
|
18
|
+
local text="$1"
|
|
19
|
+
printf '%s' "$text" \
|
|
20
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
21
|
+
| tr -cs '[:alnum:]' '\n' \
|
|
22
|
+
| grep -v '^$' \
|
|
23
|
+
| sort -u \
|
|
24
|
+
| paste -sd ' ' -
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Compute Jaccard similarity (intersection / union) of two token sets.
|
|
28
|
+
# Input: two space-separated token strings.
|
|
29
|
+
# Output: float 0-1.
|
|
30
|
+
_librarian_conflict_jaccard() {
|
|
31
|
+
local tokens1="$1"
|
|
32
|
+
local tokens2="$2"
|
|
33
|
+
|
|
34
|
+
[[ -z "$tokens1" || -z "$tokens2" ]] && { printf '0'; return; }
|
|
35
|
+
|
|
36
|
+
TOKENS1="$tokens1" TOKENS2="$tokens2" python3 -c '
|
|
37
|
+
import os
|
|
38
|
+
tokens1 = set(os.environ.get("TOKENS1", "").split())
|
|
39
|
+
tokens2 = set(os.environ.get("TOKENS2", "").split())
|
|
40
|
+
if not tokens1 and not tokens2:
|
|
41
|
+
print(0)
|
|
42
|
+
else:
|
|
43
|
+
intersection = len(tokens1 & tokens2)
|
|
44
|
+
union = len(tokens1 | tokens2)
|
|
45
|
+
similarity = intersection / union if union > 0 else 0
|
|
46
|
+
print(f"{similarity:.2f}")
|
|
47
|
+
'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Check for opposing sentiment markers (do/don't, always/never, etc.).
|
|
51
|
+
# Returns 0 if opposing markers found, 1 otherwise.
|
|
52
|
+
_librarian_conflict_opposing_sentiment() {
|
|
53
|
+
local text1="$1"
|
|
54
|
+
local text2="$2"
|
|
55
|
+
|
|
56
|
+
# Normalize to lowercase.
|
|
57
|
+
text1=$(printf '%s' "$text1" | tr '[:upper:]' '[:lower:]')
|
|
58
|
+
text2=$(printf '%s' "$text2" | tr '[:upper:]' '[:lower:]')
|
|
59
|
+
|
|
60
|
+
# Define opposing pairs.
|
|
61
|
+
local -a pairs=("always:never" "never:always" "do:don't" "don't:do" "prefer:avoid"
|
|
62
|
+
"avoid:prefer" "enable:disable" "disable:enable" "keep:remove"
|
|
63
|
+
"remove:keep" "use:avoid" "avoid:use")
|
|
64
|
+
|
|
65
|
+
for pair in "${pairs[@]}"; do
|
|
66
|
+
local a="${pair%%:*}"
|
|
67
|
+
local b="${pair##*:}"
|
|
68
|
+
|
|
69
|
+
# Check if text1 has 'a' and text2 has 'b', or vice versa.
|
|
70
|
+
if [[ "$text1" == *"$a"* && "$text2" == *"$b"* ]]; then
|
|
71
|
+
return 0 # opposing
|
|
72
|
+
fi
|
|
73
|
+
if [[ "$text1" == *"$b"* && "$text2" == *"$a"* ]]; then
|
|
74
|
+
return 0 # opposing
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
return 1 # not opposing
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Detect conflict state for a single proposal against a single existing memory.
|
|
82
|
+
# Returns conflict classification and (optionally) the conflicting memory filename.
|
|
83
|
+
#
|
|
84
|
+
# Usage: librarian_conflict_check <proposal_body> <existing_memory_path> \
|
|
85
|
+
# <duplicate_threshold> <merge_threshold>
|
|
86
|
+
#
|
|
87
|
+
# Output: JSON object { conflict_state, conflicting_file (optional) }
|
|
88
|
+
librarian_conflict_check() {
|
|
89
|
+
local proposal_body="$1"
|
|
90
|
+
local memory_path="$2"
|
|
91
|
+
local dup_threshold="${3:-0.7}"
|
|
92
|
+
local merge_threshold="${4:-0.45}"
|
|
93
|
+
|
|
94
|
+
[[ -z "$proposal_body" || -z "$memory_path" ]] && {
|
|
95
|
+
printf '%s' '{"conflict_state":"none"}'
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
[[ ! -f "$memory_path" ]] && {
|
|
99
|
+
printf '%s' '{"conflict_state":"none"}'
|
|
100
|
+
return 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Extract the body of the existing memory (skip frontmatter).
|
|
104
|
+
local existing_body
|
|
105
|
+
existing_body=$(awk '/^---$/{ if (++count == 2) { in_body = 1; next } }
|
|
106
|
+
in_body { print }' "$memory_path")
|
|
107
|
+
|
|
108
|
+
[[ -z "$existing_body" ]] && {
|
|
109
|
+
printf '%s' '{"conflict_state":"none"}'
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Normalize both bodies to token sets.
|
|
114
|
+
local prop_tokens existing_tokens
|
|
115
|
+
prop_tokens=$(_librarian_conflict_normalize "$proposal_body")
|
|
116
|
+
existing_tokens=$(_librarian_conflict_normalize "$existing_body")
|
|
117
|
+
|
|
118
|
+
# Compute Jaccard similarity.
|
|
119
|
+
local similarity
|
|
120
|
+
similarity=$(_librarian_conflict_jaccard "$prop_tokens" "$existing_tokens")
|
|
121
|
+
|
|
122
|
+
# Classify based on similarity + optional sentiment check.
|
|
123
|
+
if awk "BEGIN { exit !($similarity >= $dup_threshold) }"; then
|
|
124
|
+
# Definitely a duplicate.
|
|
125
|
+
jq -cn --arg file "$(basename "$memory_path")" \
|
|
126
|
+
'{ conflict_state: "duplicate", conflicting_file: $file }'
|
|
127
|
+
elif awk "BEGIN { exit !($similarity >= $merge_threshold) }"; then
|
|
128
|
+
# Merge candidate — significant overlap but not identical.
|
|
129
|
+
jq -cn --arg file "$(basename "$memory_path")" \
|
|
130
|
+
'{ conflict_state: "merge_candidate", conflicting_file: $file }'
|
|
131
|
+
elif awk "BEGIN { exit !($similarity >= $merge_threshold * 0.8) }"; then
|
|
132
|
+
# Check for opposing sentiment in the overlap zone.
|
|
133
|
+
if _librarian_conflict_opposing_sentiment "$proposal_body" "$existing_body"; then
|
|
134
|
+
jq -cn --arg file "$(basename "$memory_path")" \
|
|
135
|
+
'{ conflict_state: "conflict_candidate", conflicting_file: $file }'
|
|
136
|
+
else
|
|
137
|
+
printf '%s' '{"conflict_state":"none"}'
|
|
138
|
+
fi
|
|
139
|
+
else
|
|
140
|
+
# No meaningful overlap.
|
|
141
|
+
printf '%s' '{"conflict_state":"none"}'
|
|
142
|
+
fi
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Scan a proposal against all existing memories in the project.
|
|
146
|
+
# Returns the highest-priority conflict found (duplicate > conflict > merge).
|
|
147
|
+
#
|
|
148
|
+
# Usage: librarian_conflict_scan <proposal_json> <memory_dir> \
|
|
149
|
+
# <duplicate_threshold> <merge_threshold>
|
|
150
|
+
#
|
|
151
|
+
# Output: JSON object { conflict_state, conflict_with: [...] }
|
|
152
|
+
librarian_conflict_scan() {
|
|
153
|
+
local proposal="$1"
|
|
154
|
+
local memory_dir="$2"
|
|
155
|
+
local dup_threshold="${3:-0.7}"
|
|
156
|
+
local merge_threshold="${4:-0.45}"
|
|
157
|
+
|
|
158
|
+
[[ -z "$proposal" || -z "$memory_dir" ]] && {
|
|
159
|
+
printf '%s' '{"conflict_state":"none","conflict_with":[]}'
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
[[ ! -d "$memory_dir" ]] && {
|
|
163
|
+
printf '%s' '{"conflict_state":"none","conflict_with":[]}'
|
|
164
|
+
return 0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
local proposal_body
|
|
168
|
+
proposal_body=$(printf '%s' "$proposal" | jq -r '.proposed.body // ""')
|
|
169
|
+
[[ -z "$proposal_body" ]] && {
|
|
170
|
+
printf '%s' '{"conflict_state":"none","conflict_with":[]}'
|
|
171
|
+
return 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
local worst_state=""
|
|
175
|
+
local conflicts='[]'
|
|
176
|
+
|
|
177
|
+
# Scan all memory files in the project.
|
|
178
|
+
for memory_file in "$memory_dir"/*.md; do
|
|
179
|
+
[[ ! -f "$memory_file" ]] && continue
|
|
180
|
+
|
|
181
|
+
local check
|
|
182
|
+
check=$(librarian_conflict_check "$proposal_body" "$memory_file" \
|
|
183
|
+
"$dup_threshold" "$merge_threshold")
|
|
184
|
+
[[ -z "$check" ]] && continue
|
|
185
|
+
|
|
186
|
+
local state
|
|
187
|
+
state=$(printf '%s' "$check" | jq -r '.conflict_state // "none"')
|
|
188
|
+
local filename
|
|
189
|
+
filename=$(printf '%s' "$check" | jq -r '.conflicting_file // ""')
|
|
190
|
+
|
|
191
|
+
# Track conflicts; prioritize by severity: duplicate > conflict > merge.
|
|
192
|
+
case "$state" in
|
|
193
|
+
duplicate)
|
|
194
|
+
worst_state="duplicate"
|
|
195
|
+
conflicts=$(printf '%s' "$conflicts" | jq --arg f "$filename" '. + [$f]')
|
|
196
|
+
;;
|
|
197
|
+
conflict_candidate)
|
|
198
|
+
if [[ "$worst_state" != "duplicate" ]]; then
|
|
199
|
+
worst_state="conflict_candidate"
|
|
200
|
+
conflicts=$(printf '%s' "$conflicts" | jq --arg f "$filename" '. + [$f]')
|
|
201
|
+
fi
|
|
202
|
+
;;
|
|
203
|
+
merge_candidate)
|
|
204
|
+
if [[ -z "$worst_state" ]]; then
|
|
205
|
+
worst_state="merge_candidate"
|
|
206
|
+
conflicts=$(printf '%s' "$conflicts" | jq --arg f "$filename" '. + [$f]')
|
|
207
|
+
fi
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
[[ -z "$worst_state" ]] && worst_state="none"
|
|
213
|
+
printf '%s' "$(jq -cn --arg state "$worst_state" --argjson files "$conflicts" \
|
|
214
|
+
'{ conflict_state: $state, conflict_with: $files }')"
|
|
215
|
+
}
|
|
@@ -21,6 +21,16 @@ _librarian_resolve_event_js() {
|
|
|
21
21
|
ecosystem_root="$candidate"
|
|
22
22
|
fi
|
|
23
23
|
fi
|
|
24
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
25
|
+
# works regardless of which ecosystem version is installed.
|
|
26
|
+
if [[ -z "$ecosystem_root" ]]; then
|
|
27
|
+
for candidate in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
28
|
+
if [[ -f "$candidate" ]]; then
|
|
29
|
+
ecosystem_root="$(cd "$(dirname "$(dirname "$candidate")")" && pwd)"
|
|
30
|
+
break
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
fi
|
|
24
34
|
|
|
25
35
|
if [[ -n "$ecosystem_root" ]]; then
|
|
26
36
|
printf '%s/scripts/lib/onlooker-event.mjs' "$ecosystem_root"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lineage",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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.1](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.2.0...lineage-v0.2.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [0.2.0](https://github.com/onlooker-community/ecosystem/compare/lineage-v0.1.0...lineage-v0.2.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -24,6 +24,12 @@ _lineage_event_js_path() {
|
|
|
24
24
|
for c in "${candidates[@]}"; do
|
|
25
25
|
[[ -f "$c" ]] && { printf '%s' "$c"; return 0; }
|
|
26
26
|
done
|
|
27
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
28
|
+
# works regardless of which ecosystem version is installed.
|
|
29
|
+
local mjs
|
|
30
|
+
for mjs in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
31
|
+
[[ -f "$mjs" ]] && { printf '%s' "$mjs"; return 0; }
|
|
32
|
+
done
|
|
27
33
|
return 1
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribe",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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.1](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.4.0...scribe-v0.4.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [0.4.0](https://github.com/onlooker-community/ecosystem/compare/scribe-v0.3.0...scribe-v0.4.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -24,6 +24,12 @@ _scribe_event_js_path() {
|
|
|
24
24
|
for c in "${candidates[@]}"; do
|
|
25
25
|
[[ -f "$c" ]] && { printf '%s' "$c"; return 0; }
|
|
26
26
|
done
|
|
27
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
28
|
+
# works regardless of which ecosystem version is installed.
|
|
29
|
+
local mjs
|
|
30
|
+
for mjs in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
31
|
+
[[ -f "$mjs" ]] && { printf '%s' "$mjs"; return 0; }
|
|
32
|
+
done
|
|
27
33
|
return 1
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tribunal",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
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.1](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.0...tribunal-v1.1.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [1.1.0](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.0.1...tribunal-v1.1.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -24,6 +24,16 @@ if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
|
24
24
|
_ECOSYSTEM_ROOT="$_candidate"
|
|
25
25
|
fi
|
|
26
26
|
fi
|
|
27
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
28
|
+
# works regardless of which ecosystem version is installed.
|
|
29
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
30
|
+
for _candidate in "${PLUGIN_ROOT}/../../ecosystem/"*/scripts/lib/validate-path.sh; do
|
|
31
|
+
if [[ -f "$_candidate" ]]; then
|
|
32
|
+
_ECOSYSTEM_ROOT="$(cd "$(dirname "$(dirname "$_candidate")")" && pwd)"
|
|
33
|
+
break
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
fi
|
|
27
37
|
|
|
28
38
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
29
39
|
# shellcheck disable=SC1091
|
|
@@ -37,6 +37,12 @@ _tribunal_event_js_path() {
|
|
|
37
37
|
for c in "${candidates[@]}"; do
|
|
38
38
|
[[ -f "$c" ]] && { printf '%s' "$c"; return 0; }
|
|
39
39
|
done
|
|
40
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
41
|
+
# works regardless of which ecosystem version is installed.
|
|
42
|
+
local mjs
|
|
43
|
+
for mjs in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
44
|
+
[[ -f "$mjs" ]] && { printf '%s' "$mjs"; return 0; }
|
|
45
|
+
done
|
|
40
46
|
return 1
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warden",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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.1](https://github.com/onlooker-community/ecosystem/compare/warden-v0.3.0...warden-v0.3.1) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **plugins:** glob-discover ecosystem root so sub-plugins work from cache :relieved: ([#110](https://github.com/onlooker-community/ecosystem/issues/110)) ([3639240](https://github.com/onlooker-community/ecosystem/commit/3639240383e1b820e5d8ea42639e0b863ef0d90e))
|
|
9
|
+
|
|
3
10
|
## [0.3.0](https://github.com/onlooker-community/ecosystem/compare/warden-v0.2.0...warden-v0.3.0) (2026-07-03)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -28,6 +28,12 @@ _warden_event_js_path() {
|
|
|
28
28
|
for c in "${candidates[@]}"; do
|
|
29
29
|
[[ -f "$c" ]] && { printf '%s' "$c"; return 0; }
|
|
30
30
|
done
|
|
31
|
+
# Glob-discover the ecosystem plugin under the shared plugin cache parent;
|
|
32
|
+
# works regardless of which ecosystem version is installed.
|
|
33
|
+
local mjs
|
|
34
|
+
for mjs in "${plugin_root}/../../ecosystem/"*/scripts/lib/onlooker-event.mjs; do
|
|
35
|
+
[[ -f "$mjs" ]] && { printf '%s' "$mjs"; return 0; }
|
|
36
|
+
done
|
|
31
37
|
return 1
|
|
32
38
|
}
|
|
33
39
|
|