@onlooker-community/ecosystem 0.32.2 → 0.33.1

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 (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.release-please-manifest.json +9 -9
  3. package/CHANGELOG.md +19 -0
  4. package/package.json +1 -1
  5. package/plugins/archivist/scripts/lib/archivist-config.sh +10 -34
  6. package/plugins/assayer/.claude-plugin/plugin.json +1 -1
  7. package/plugins/assayer/CHANGELOG.md +7 -0
  8. package/plugins/assayer/scripts/lib/assayer-config.sh +39 -62
  9. package/plugins/bursar/.claude-plugin/plugin.json +1 -1
  10. package/plugins/bursar/CHANGELOG.md +12 -0
  11. package/plugins/bursar/scripts/hooks/bursar-session-end.sh +6 -5
  12. package/plugins/bursar/scripts/lib/bursar-config.sh +10 -43
  13. package/plugins/cartographer/scripts/lib/cartographer-config.sh +11 -29
  14. package/plugins/compass/scripts/lib/compass-config.sh +16 -46
  15. package/plugins/counsel/scripts/lib/counsel-config.sh +15 -46
  16. package/plugins/curator/.claude-plugin/plugin.json +1 -1
  17. package/plugins/curator/CHANGELOG.md +7 -0
  18. package/plugins/curator/scripts/lib/curator-config.sh +19 -44
  19. package/plugins/echo/scripts/lib/echo-config.sh +30 -59
  20. package/plugins/governor/scripts/lib/governor-config.sh +11 -41
  21. package/plugins/historian/scripts/lib/historian-config.sh +9 -33
  22. package/plugins/inspector/.claude-plugin/plugin.json +1 -1
  23. package/plugins/inspector/CHANGELOG.md +7 -0
  24. package/plugins/inspector/scripts/lib/inspector-config.sh +39 -63
  25. package/plugins/librarian/.claude-plugin/plugin.json +1 -1
  26. package/plugins/librarian/CHANGELOG.md +7 -0
  27. package/plugins/librarian/scripts/hooks/librarian-session-end.sh +38 -3
  28. package/plugins/librarian/scripts/lib/librarian-config.sh +10 -34
  29. package/plugins/lineage/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lineage/CHANGELOG.md +7 -0
  31. package/plugins/lineage/scripts/lib/lineage-config.sh +17 -53
  32. package/plugins/scribe/.claude-plugin/plugin.json +1 -1
  33. package/plugins/scribe/CHANGELOG.md +7 -0
  34. package/plugins/scribe/scripts/lib/scribe-config.sh +17 -47
  35. package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
  36. package/plugins/tribunal/CHANGELOG.md +7 -0
  37. package/plugins/tribunal/scripts/lib/tribunal-config.sh +25 -63
  38. package/plugins/warden/scripts/lib/warden-config.sh +17 -54
  39. package/scripts/lib/config-loader.sh +138 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curator",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Maintenance layer for the user's typed auto-memory store. At every SessionStart, runs four cheap heuristic checks (date_decayed, path_broken, broken_index, orphaned_memory) against the memories at ~/.claude/projects/<encoded>/memory/ inside a wall-clock budget. Surfaces findings as a one-line pointer to /curator review; never edits the memory store directly. 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/curator-v0.2.1...curator-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/curator-v0.2.0...curator-v0.2.1) (2026-08-01)
4
11
 
5
12
 
@@ -1,60 +1,35 @@
1
1
  #!/usr/bin/env bash
2
- # Config resolution for Curator.
2
+ # Config resolution for curator.
3
3
  #
4
- # Reads three layers, latest wins:
4
+ # Uses the shared config loader from ecosystem. Reads five layers, latest wins:
5
5
  # 1. plugins/curator/config.json (defaults shipped with the plugin)
6
6
  # 2. ~/.claude/settings.json
7
- # 3. <repo>/.claude/settings.json
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
- # curator_config_load <repo_root> # populates _CURATOR_CONFIG (JSON)
12
+ # curator_config_load <repo_root> # populates _curator_CONFIG (JSON)
11
13
  # curator_config_get <jq-path> # echoes string value (empty if unset)
12
- #
13
- # Settings overlay only touches the `curator.*` subtree of settings.json.
14
+ # curator_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"
14
18
 
15
- _CURATOR_CONFIG="{}"
19
+ _curator_CONFIG="{}"
16
20
 
17
21
  curator_config_load() {
18
22
  local repo_root="${1:-}"
19
- local plugin_root="${CLAUDE_PLUGIN_ROOT:-}"
20
- local home_dir="${HOME:-}"
21
-
22
- local merged="{}"
23
- local file
24
-
25
- file="${plugin_root}/config.json"
26
- if [[ -f "$file" ]]; then
27
- local defaults
28
- defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
29
- merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
30
- || merged="$defaults"
31
- fi
32
-
33
- for file in "${home_dir}/.claude/settings.json" "${repo_root}/.claude/settings.json"; do
34
- [[ -n "$file" && -f "$file" ]] || continue
35
- local overlay
36
- overlay=$(jq '{ curator: (.curator // {}) }' "$file" 2>/dev/null) || continue
37
- [[ -z "$overlay" ]] && continue
38
- merged=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
39
- def deepmerge($a; $b):
40
- if ($a|type) == "object" and ($b|type) == "object" then
41
- reduce (($a|keys) + ($b|keys) | unique)[] as $k
42
- ({}; .[$k] = deepmerge($a[$k]; $b[$k]))
43
- elif $b == null then $a
44
- else $b end;
45
- deepmerge($a; $b)
46
- ' 2>/dev/null) || true
47
- done
48
-
49
- _CURATOR_CONFIG="$merged"
23
+ config_load_plugin "curator" "$repo_root" "_curator_CONFIG"
24
+ return 0
50
25
  }
51
26
 
52
27
  curator_config_get() {
53
28
  local path="$1"
54
- # The `// empty` operator treats `false` the same as null, so a
55
- # value of `false` would silently disappear and the caller would
56
- # misread "explicitly disabled" as "default to enabled". Use an
57
- # explicit null check so booleans round-trip correctly.
58
- printf '%s' "$_CURATOR_CONFIG" \
59
- | jq -r "${path} | if . == null then empty else . end" 2>/dev/null
29
+ config_get "_curator_CONFIG" "${path}"
30
+ }
31
+
32
+ curator_config_get_json() {
33
+ local path="$1"
34
+ config_get_json "_curator_CONFIG" "${path}"
60
35
  }
@@ -1,100 +1,71 @@
1
1
  #!/usr/bin/env bash
2
- # Config loading for Echo.
3
- # Reads config.json from the repo's .claude/settings.json echo.* keys,
4
- # falling back to the plugin's own config.json defaults.
2
+ # Config resolution for echo.
3
+ #
4
+ # Uses the shared config loader from ecosystem. Reads five layers, latest wins:
5
+ # 1. plugins/echo/config.json (defaults shipped with the plugin)
6
+ # 2. ~/.claude/settings.json
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)
10
+ #
11
+ # Exposes:
12
+ # echo_config_load <repo_root> # populates _echo_CONFIG (JSON)
13
+ # echo_config_get <jq-path> # echoes string value (empty if unset)
14
+ # echo_config_get_json <jq-path> # echoes JSON value (null if unset)
5
15
 
6
- _ECHO_CONFIG_JSON=""
7
- _ECHO_PLUGIN_CONFIG_JSON=""
16
+ # shellcheck source=../../../scripts/lib/config-loader.sh
17
+ source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
18
+
19
+ _echo_CONFIG="{}"
8
20
 
9
21
  echo_config_load() {
10
22
  local repo_root="${1:-}"
11
-
12
- _ECHO_PLUGIN_CONFIG_JSON=""
13
- local plugin_config="${CLAUDE_PLUGIN_ROOT:-}/config.json"
14
- if [[ -f "$plugin_config" ]]; then
15
- _ECHO_PLUGIN_CONFIG_JSON=$(cat "$plugin_config" 2>/dev/null) || _ECHO_PLUGIN_CONFIG_JSON=""
16
- fi
17
-
18
- _ECHO_CONFIG_JSON=""
19
- if [[ -n "$repo_root" ]]; then
20
- local settings_file="${repo_root}/.claude/settings.json"
21
- if [[ -f "$settings_file" ]]; then
22
- local settings
23
- settings=$(cat "$settings_file" 2>/dev/null) || settings=""
24
- local echo_block
25
- echo_block=$(printf '%s' "$settings" | jq -c '.echo // empty' 2>/dev/null) || echo_block=""
26
- [[ -n "$echo_block" ]] && _ECHO_CONFIG_JSON="$echo_block"
27
- fi
28
- fi
23
+ config_load_plugin "echo" "$repo_root" "_echo_CONFIG"
24
+ return 0
29
25
  }
30
26
 
31
- # Get a single scalar value. Checks settings.json first, then plugin config.json.
32
27
  echo_config_get() {
33
- local key="$1"
34
-
35
- if [[ -n "$_ECHO_CONFIG_JSON" ]]; then
36
- local val
37
- val=$(printf '%s' "$_ECHO_CONFIG_JSON" | jq -r "${key} // empty" 2>/dev/null) || val=""
38
- [[ -n "$val" && "$val" != "null" ]] && { printf '%s' "$val"; return 0; }
39
- fi
40
-
41
- if [[ -n "$_ECHO_PLUGIN_CONFIG_JSON" ]]; then
42
- local val
43
- val=$(printf '%s' "$_ECHO_PLUGIN_CONFIG_JSON" | jq -r ".echo${key} // empty" 2>/dev/null) || val=""
44
- [[ -n "$val" && "$val" != "null" ]] && { printf '%s' "$val"; return 0; }
45
- fi
28
+ local path="$1"
29
+ config_get "_echo_CONFIG" "${path}"
46
30
  }
47
31
 
48
32
  echo_config_get_json() {
49
- local key="$1"
50
-
51
- if [[ -n "$_ECHO_CONFIG_JSON" ]]; then
52
- local val
53
- val=$(printf '%s' "$_ECHO_CONFIG_JSON" | jq -c "${key} // empty" 2>/dev/null) || val=""
54
- [[ -n "$val" && "$val" != "null" && "$val" != "empty" ]] && { printf '%s' "$val"; return 0; }
55
- fi
56
-
57
- if [[ -n "$_ECHO_PLUGIN_CONFIG_JSON" ]]; then
58
- local val
59
- val=$(printf '%s' "$_ECHO_PLUGIN_CONFIG_JSON" | jq -c ".echo${key} // empty" 2>/dev/null) || val=""
60
- [[ -n "$val" && "$val" != "null" && "$val" != "empty" ]] && { printf '%s' "$val"; return 0; }
61
- fi
33
+ local path="$1"
34
+ config_get_json "_echo_CONFIG" "${path}"
62
35
  }
63
36
 
64
37
  echo_config_model() {
65
38
  local val
66
- val=$(echo_config_get '.evaluation.model')
39
+ val=$(echo_config_get '.echo.evaluation.model')
67
40
  printf '%s' "${val:-claude-haiku-4-5-20251001}"
68
41
  }
69
42
 
70
43
  echo_config_timeout() {
71
44
  local val
72
- val=$(echo_config_get '.evaluation.timeout_seconds')
45
+ val=$(echo_config_get '.echo.evaluation.timeout_seconds')
73
46
  printf '%s' "${val:-60}"
74
47
  }
75
48
 
76
49
  echo_config_drift_threshold() {
77
50
  local val
78
- val=$(echo_config_get '.drift_threshold')
51
+ val=$(echo_config_get '.echo.drift_threshold')
79
52
  printf '%s' "${val:-0.05}"
80
53
  }
81
54
 
82
- # Prints newline-separated list of watch glob patterns.
83
55
  echo_config_watch_paths() {
84
56
  local raw
85
- raw=$(echo_config_get_json '.watch_paths')
86
- if [[ -n "$raw" ]]; then
57
+ raw=$(echo_config_get_json '.echo.watch_paths')
58
+ if [[ -n "$raw" && "$raw" != "null" ]]; then
87
59
  printf '%s' "$raw" | jq -r '.[]' 2>/dev/null
88
60
  else
89
61
  printf 'plugins/*/agents/*.md\n'
90
62
  fi
91
63
  }
92
64
 
93
- # Prints newline-separated list of exclude glob patterns.
94
65
  echo_config_exclude_paths() {
95
66
  local raw
96
- raw=$(echo_config_get_json '.exclude_paths')
97
- if [[ -n "$raw" ]]; then
67
+ raw=$(echo_config_get_json '.echo.exclude_paths')
68
+ if [[ -n "$raw" && "$raw" != "null" ]]; then
98
69
  printf '%s' "$raw" | jq -r '.[]' 2>/dev/null
99
70
  fi
100
71
  # Always exclude Echo's own tree — hardcoded, not overridable.
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env bash
2
2
  # Config resolution for Governor.
3
3
  #
4
- # Reads three layers, latest wins:
4
+ # Uses the shared config loader from ecosystem. Reads five layers, latest wins:
5
5
  # 1. plugins/governor/config.json (defaults shipped with the plugin)
6
6
  # 2. ~/.claude/settings.json
7
- # 3. <repo>/.claude/settings.json
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
12
  # governor_config_load <repo_root> # populates _GOVERNOR_CONFIG (JSON)
@@ -12,57 +14,25 @@
12
14
  # governor_config_get_json <jq-path> # echoes JSON value (null if unset)
13
15
  # governor_config_enforcement # echoes "soft" or "hard"
14
16
 
17
+ # shellcheck source=../../../scripts/lib/config-loader.sh
18
+ source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
19
+
15
20
  _GOVERNOR_CONFIG="{}"
16
21
 
17
22
  governor_config_load() {
18
23
  local repo_root="${1:-}"
19
- local plugin_root="${CLAUDE_PLUGIN_ROOT:-}"
20
- local home_dir="${HOME:-}"
21
-
22
- local merged="{}"
23
- local file
24
-
25
- file="${plugin_root}/config.json"
26
- if [[ -f "$file" ]]; then
27
- local defaults
28
- defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
29
- merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
30
- || merged="$defaults"
31
- fi
32
-
33
- local repo_settings=""
34
- [[ -n "$repo_root" ]] && repo_settings="${repo_root}/.claude/settings.json"
35
-
36
- for file in "${home_dir}/.claude/settings.json" "$repo_settings"; do
37
- [[ -n "$file" && -f "$file" ]] || continue
38
- local overlay
39
- overlay=$(jq '{ governor: (.governor // {}) }' "$file" 2>/dev/null) || continue
40
- [[ -z "$overlay" ]] && continue
41
- local attempt
42
- if attempt=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
43
- def deepmerge($a; $b):
44
- if ($a|type) == "object" and ($b|type) == "object" then
45
- reduce (($a|keys) + ($b|keys) | unique)[] as $k
46
- ({}; .[$k] = deepmerge($a[$k]; $b[$k]))
47
- elif $b == null then $a
48
- else $b end;
49
- deepmerge($a; $b)
50
- ' 2>/dev/null) && [[ -n "$attempt" ]]; then
51
- merged="$attempt"
52
- fi
53
- done
54
-
55
- _GOVERNOR_CONFIG="$merged"
24
+ config_load_plugin "governor" "$repo_root" "_GOVERNOR_CONFIG"
25
+ return 0
56
26
  }
57
27
 
58
28
  governor_config_get() {
59
29
  local path="$1"
60
- printf '%s' "$_GOVERNOR_CONFIG" | jq -r "${path} // empty" 2>/dev/null
30
+ config_get "_GOVERNOR_CONFIG" "${path}"
61
31
  }
62
32
 
63
33
  governor_config_get_json() {
64
34
  local path="$1"
65
- printf '%s' "$_GOVERNOR_CONFIG" | jq -c "${path}" 2>/dev/null
35
+ config_get_json "_GOVERNOR_CONFIG" "${path}"
66
36
  }
67
37
 
68
38
  governor_config_enforcement() {
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env bash
2
2
  # Config resolution for Historian.
3
3
  #
4
- # Reads three layers, latest wins:
4
+ # Uses the shared config loader from ecosystem. Reads five layers, latest wins:
5
5
  # 1. plugins/historian/config.json (defaults shipped with the plugin)
6
6
  # 2. ~/.claude/settings.json
7
- # 3. <repo>/.claude/settings.json
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
12
  # historian_config_load <repo_root> # populates _HISTORIAN_CONFIG (JSON)
@@ -12,41 +14,15 @@
12
14
  #
13
15
  # Settings overlay only touches the `historian.*` subtree of settings.json.
14
16
 
17
+ # shellcheck source=../../../scripts/lib/config-loader.sh
18
+ source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
19
+
15
20
  _HISTORIAN_CONFIG="{}"
16
21
 
17
22
  historian_config_load() {
18
23
  local repo_root="${1:-}"
19
- local plugin_root="${CLAUDE_PLUGIN_ROOT:-}"
20
- local home_dir="${HOME:-}"
21
-
22
- local merged="{}"
23
- local file
24
-
25
- file="${plugin_root}/config.json"
26
- if [[ -f "$file" ]]; then
27
- local defaults
28
- defaults=$(jq '.' "$file" 2>/dev/null) || defaults="{}"
29
- merged=$(jq -n --argjson a "$merged" --argjson b "$defaults" '$a * $b' 2>/dev/null) \
30
- || merged="$defaults"
31
- fi
32
-
33
- for file in "${home_dir}/.claude/settings.json" "${repo_root}/.claude/settings.json"; do
34
- [[ -n "$file" && -f "$file" ]] || continue
35
- local overlay
36
- overlay=$(jq '{ historian: (.historian // {}) }' "$file" 2>/dev/null) || continue
37
- [[ -z "$overlay" ]] && continue
38
- merged=$(jq -n --argjson a "$merged" --argjson b "$overlay" '
39
- def deepmerge($a; $b):
40
- if ($a|type) == "object" and ($b|type) == "object" then
41
- reduce (($a|keys) + ($b|keys) | unique)[] as $k
42
- ({}; .[$k] = deepmerge($a[$k]; $b[$k]))
43
- elif $b == null then $a
44
- else $b end;
45
- deepmerge($a; $b)
46
- ' 2>/dev/null) || true
47
- done
48
-
49
- _HISTORIAN_CONFIG="$merged"
24
+ config_load_plugin "historian" "$repo_root" "_HISTORIAN_CONFIG"
25
+ return 0
50
26
  }
51
27
 
52
28
  # Read a value from the loaded config. The explicit null check (instead of
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inspector",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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.2](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.3.1...inspector-v0.3.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.3.1](https://github.com/onlooker-community/ecosystem/compare/inspector-v0.3.0...inspector-v0.3.1) (2026-08-01)
4
11
 
5
12
 
@@ -1,62 +1,37 @@
1
1
  #!/usr/bin/env bash
2
- # inspector-config.sh load and query Inspector configuration.
2
+ # Config resolution for inspector.
3
3
  #
4
- # Merges three layers in precedence order (later wins):
5
- # 1. plugins/inspector/config.json (plugin defaults)
6
- # 2. ~/.claude/settings.json (.inspector subtree)
7
- # 3. <repo>/.claude/settings.json (.inspector subtree)
4
+ # Uses the shared config loader from ecosystem. Reads five layers, latest wins:
5
+ # 1. plugins/inspector/config.json (defaults shipped with the plugin)
6
+ # 2. ~/.claude/settings.json
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
- # Usage:
10
- # inspector_config_load <repo_root>
11
- # inspector_config_get ".inspector.timeout_seconds_per_check"
12
- # inspector_config_get_json ".inspector.exclude_paths"
13
- # inspector_config_checks_for_extension ".ts"
11
+ # Exposes:
12
+ # inspector_config_load <repo_root> # populates _inspector_CONFIG (JSON)
13
+ # inspector_config_get <jq-path> # echoes string value (empty if unset)
14
+ # inspector_config_get_json <jq-path> # echoes JSON value (null if unset)
14
15
 
15
- _INSPECTOR_CONFIG=""
16
- _INSPECTOR_PLUGIN_CONFIG=""
16
+ # shellcheck source=../../../scripts/lib/config-loader.sh
17
+ source "${PLUGIN_ROOT}/../../scripts/lib/config-loader.sh"
18
+
19
+ _inspector_CONFIG="{}"
17
20
 
18
21
  inspector_config_load() {
19
22
  local repo_root="${1:-}"
20
- local plugin_dir
21
- plugin_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
22
- local plugin_config="$plugin_dir/config.json"
23
-
24
- _INSPECTOR_PLUGIN_CONFIG="{}"
25
- if [[ -f "$plugin_config" ]]; then
26
- _INSPECTOR_PLUGIN_CONFIG=$(cat "$plugin_config")
27
- fi
28
-
29
- local home_settings="{}"
30
- if [[ -f "$HOME/.claude/settings.json" ]]; then
31
- home_settings=$(cat "$HOME/.claude/settings.json")
32
- fi
33
-
34
- local repo_settings="{}"
35
- if [[ -n "$repo_root" && -f "$repo_root/.claude/settings.json" ]]; then
36
- repo_settings=$(cat "$repo_root/.claude/settings.json")
37
- fi
38
-
39
- _INSPECTOR_CONFIG=$(jq -n \
40
- --argjson plugin "$_INSPECTOR_PLUGIN_CONFIG" \
41
- --argjson home "$home_settings" \
42
- --argjson repo "$repo_settings" \
43
- '$plugin * {"inspector": (($plugin.inspector // {}) * ($home.inspector // {}) * ($repo.inspector // {}))}')
23
+ config_load_plugin "inspector" "$repo_root" "_inspector_CONFIG"
24
+ return 0
44
25
  }
45
26
 
46
27
  inspector_config_get() {
47
- local path="${1:-}"
48
- printf '%s' "$_INSPECTOR_CONFIG" | jq -r "$path // empty" 2>/dev/null
28
+ local path="$1"
29
+ config_get "_inspector_CONFIG" "${path}"
49
30
  }
50
31
 
51
32
  inspector_config_get_json() {
52
- local path="${1:-}"
53
- printf '%s' "$_INSPECTOR_CONFIG" | jq -c "$path // empty" 2>/dev/null
54
- }
55
-
56
- inspector_config_show_clean_runs() {
57
- local v
58
- v=$(inspector_config_get '.inspector.show_clean_runs')
59
- [[ "$v" == "true" ]]
33
+ local path="$1"
34
+ config_get_json "_inspector_CONFIG" "${path}"
60
35
  }
61
36
 
62
37
  inspector_config_timeout_per_check() {
@@ -77,25 +52,26 @@ inspector_config_output_excerpt_max_bytes() {
77
52
  printf '%s' "${v:-4096}"
78
53
  }
79
54
 
55
+ inspector_config_show_clean_runs() {
56
+ local v
57
+ v=$(inspector_config_get '.inspector.show_clean_runs')
58
+ [[ "$v" == "true" ]]
59
+ }
60
+
80
61
  inspector_config_exclude_paths() {
81
- inspector_config_get_json '.inspector.exclude_paths // []'
62
+ inspector_config_get_json '.inspector.exclude_paths // ["node_modules",".git","vendor",".venv","dist",".next",".nuxt","build","__pycache__","target","coverage"]'
82
63
  }
83
64
 
84
- # Emits a JSON array of {name, argv, kind} objects for the given file extension
85
- # (including the leading dot). Returns an empty array when no checks are
86
- # configured for the extension.
87
65
  inspector_config_checks_for_extension() {
88
- local ext="${1:-}"
89
- [[ -z "$ext" ]] && { printf '[]'; return; }
90
- printf '%s' "$_INSPECTOR_CONFIG" | jq -c --arg ext "$ext" '
91
- (.inspector.checks // {}) as $checks
92
- | ($checks[$ext] // [])
93
- | map(
94
- if type == "array" then
95
- { name: (.[0] // "check"), argv: ., kind: "lint" }
96
- else
97
- { name: (.name // "check"), argv: (.argv // []), kind: (.kind // "lint") }
98
- end
99
- )
100
- ' 2>/dev/null || printf '[]'
66
+ local ext="$1"
67
+ [[ -z "$ext" ]] && { printf '%s\n' "[]"; return 0; }
68
+ local raw
69
+ raw=$(inspector_config_get_json ".inspector.checks[\"$ext\"] // []")
70
+ [[ -z "$raw" ]] && { printf '%s\n' "[]"; return 0; }
71
+ # Normalize bare argv arrays into objects with name, kind, argv fields
72
+ printf '%s' "$raw" | jq -c 'map(
73
+ if type == "array" then
74
+ {name: .[0], kind: "lint", argv: .}
75
+ else . end
76
+ )'
101
77
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librarian",
3
- "version": "0.6.0",
3
+ "version": "0.6.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.6.1](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.6.0...librarian-v0.6.1) (2026-08-02)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **hooks:** prevent SessionEnd timeouts causing breadcrumb accumulation ([#121](https://github.com/onlooker-community/ecosystem/issues/121)) ([3c7a409](https://github.com/onlooker-community/ecosystem/commit/3c7a40937aa3ea8c1db85dd0bb3fcc22589bbb49))
9
+
3
10
  ## [0.6.0](https://github.com/onlooker-community/ecosystem/compare/librarian-v0.5.0...librarian-v0.6.0) (2026-08-02)
4
11
 
5
12
 
@@ -60,6 +60,17 @@ source "${PLUGIN_ROOT}/scripts/lib/librarian-classifier.sh"
60
60
  # shellcheck source=../lib/librarian-conflict-detector.sh
61
61
  source "${PLUGIN_ROOT}/scripts/lib/librarian-conflict-detector.sh"
62
62
 
63
+ librarian_now_ms() {
64
+ local now_ms
65
+ now_ms=$(python3 - <<'PY' 2>/dev/null
66
+ import time
67
+ print(int(time.time() * 1000))
68
+ PY
69
+ ) || now_ms=""
70
+ [[ -z "$now_ms" ]] && now_ms=$(( $(date +%s) * 1000 ))
71
+ printf '%s' "$now_ms"
72
+ }
73
+
63
74
  INPUT=$(cat 2>/dev/null || true)
64
75
  CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
65
76
  SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
@@ -98,7 +109,7 @@ fi
98
109
  # Emit scan.started and load candidate window.
99
110
  # ----------------------------------------------------------------------------
100
111
 
101
- SCAN_START_TS_S=$(date +%s)
112
+ SCAN_START_TS_MS=$(librarian_now_ms)
102
113
  ARTIFACTS=$(librarian_archivist_load_since "$PROJECT_KEY" "$WATERMARK")
103
114
  ARTIFACT_COUNT=$(printf '%s' "$ARTIFACTS" | jq 'length' 2>/dev/null) || ARTIFACT_COUNT=0
104
115
 
@@ -114,7 +125,7 @@ librarian_emit "librarian.scan.started" "$SESSION_ID" "$(jq -cn \
114
125
  # don't re-walk the same window.
115
126
  if [[ "$ARTIFACT_COUNT" == "0" ]]; then
116
127
  librarian_storage_write_last_scan "$PROJECT_KEY" || true
117
- DURATION_MS=$(( ($(date +%s) - SCAN_START_TS_S) * 1000 ))
128
+ DURATION_MS=$(( $(librarian_now_ms) - SCAN_START_TS_MS ))
118
129
  librarian_emit "librarian.scan.complete" "$SESSION_ID" "$(jq -cn \
119
130
  --arg outcome "empty" \
120
131
  --argjson duration_ms "$DURATION_MS" \
@@ -155,6 +166,30 @@ for ((i = 0; i < DROPPED_EMIT_COUNT; i++)); do
155
166
  | with_entries(select(.value != null))')"
156
167
  done
157
168
 
169
+ # ----------------------------------------------------------------------------
170
+ # Runtime budget check: SessionEnd has 1.5s total. If we're running low on time,
171
+ # skip classification to ensure scan.complete can be emitted before CLI timeout.
172
+ # Retained artifacts will be re-scanned on the next session when time permits.
173
+ # ----------------------------------------------------------------------------
174
+
175
+ ELAPSED_MS=$(( $(librarian_now_ms) - SCAN_START_TS_MS ))
176
+ BUDGET_THRESHOLD_MS=1000
177
+ if [[ "$ELAPSED_MS" -ge "$BUDGET_THRESHOLD_MS" ]]; then
178
+ librarian_storage_write_last_scan "$PROJECT_KEY" || true
179
+ DURATION_MS=$(( $(librarian_now_ms) - SCAN_START_TS_MS ))
180
+ librarian_emit "librarian.scan.complete" "$SESSION_ID" "$(jq -cn \
181
+ --arg outcome "budget_exceeded" \
182
+ --argjson duration_ms "$DURATION_MS" \
183
+ --argjson candidates_proposed 0 \
184
+ --argjson candidates_dropped "$DROPPED_TOTAL" \
185
+ --argjson artifact_count_in_window "$ARTIFACT_COUNT" \
186
+ '{ outcome: $outcome, duration_ms: $duration_ms,
187
+ candidates_proposed: $candidates_proposed,
188
+ candidates_dropped: $candidates_dropped,
189
+ artifact_count_in_window: $artifact_count_in_window }')"
190
+ exit 0
191
+ fi
192
+
158
193
  # ----------------------------------------------------------------------------
159
194
  # Classifier loop — one Haiku call per surviving candidate.
160
195
  # ----------------------------------------------------------------------------
@@ -398,7 +433,7 @@ librarian_storage_write_last_scan "$PROJECT_KEY" || true
398
433
  TOTAL_DROPPED=$((DROPPED_TOTAL + POST_CLASSIFIER_DROPPED))
399
434
  OUTCOME="ok"
400
435
  [[ "$PROPOSED_COUNT" == "0" ]] && OUTCOME="empty"
401
- DURATION_MS=$(( ($(date +%s) - SCAN_START_TS_S) * 1000 ))
436
+ DURATION_MS=$(( $(librarian_now_ms) - SCAN_START_TS_MS ))
402
437
 
403
438
  librarian_emit "librarian.scan.complete" "$SESSION_ID" "$(jq -cn \
404
439
  --arg outcome "$OUTCOME" \