@onlooker-community/ecosystem 0.25.0 → 0.25.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.
- package/.claude-plugin/marketplace.json +27 -14
- package/.claude-plugin/plugin.json +2 -2
- package/.release-please-manifest.json +3 -3
- package/CHANGELOG.md +7 -0
- package/package.json +2 -2
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +7 -0
- package/plugins/cartographer/scripts/lib/cartographer-lock.sh +17 -7
- package/plugins/cartographer/scripts/lib/portable-lock.sh +57 -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 +6 -2
- package/plugins/governor/scripts/hooks/governor-pre-tool-use.sh +6 -2
- package/plugins/governor/scripts/hooks/governor-session-start.sh +6 -2
- package/plugins/governor/scripts/hooks/governor-stop.sh +6 -2
- package/plugins/governor/scripts/lib/portable-lock.sh +59 -0
- package/scripts/lib/portable-lock.sh +1 -1
- package/test/bats/cartographer-lock.bats +19 -0
|
@@ -5,26 +5,26 @@
|
|
|
5
5
|
"email": "community@onlooker.dev"
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
|
-
"description": "
|
|
8
|
+
"description": "Composable observability, memory, and quality-gate plugins for Claude Code — all built on the Onlooker ecosystem event substrate."
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "ecosystem",
|
|
13
13
|
"source": "./",
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "Observability substrate for Claude Code. Provides the shared $ONLOOKER_DIR storage root (default $HOME/.onlooker), canonical schema-validated event emission, session and tool tracking hooks, and prompt rules. Required by all other Onlooker plugins.",
|
|
15
15
|
"author": {
|
|
16
16
|
"name": "Onlooker Community"
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://onlooker.dev",
|
|
19
19
|
"repository": "https://github.com/onlooker-community/ecosystem",
|
|
20
20
|
"license": "MIT",
|
|
21
|
-
"keywords": [],
|
|
22
|
-
"tags": []
|
|
21
|
+
"keywords": ["observability", "substrate", "events", "hooks", "telemetry"],
|
|
22
|
+
"tags": ["observability", "substrate"]
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"name": "archivist",
|
|
26
26
|
"source": "./plugins/archivist",
|
|
27
|
-
"description": "Structured session memory across context truncation
|
|
27
|
+
"description": "Structured session memory across context truncation: extracts decisions, dead ends, and open questions on PreCompact and reinjects the most important items at SessionStart. Requires the ecosystem plugin.",
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "Onlooker Community"
|
|
30
30
|
},
|
|
@@ -34,10 +34,23 @@
|
|
|
34
34
|
"keywords": ["memory", "compaction", "context", "session"],
|
|
35
35
|
"tags": ["memory", "context-engineering"]
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
"name": "cartographer",
|
|
39
|
+
"source": "./plugins/cartographer",
|
|
40
|
+
"description": "Proactive periodic auditor of the persistent instruction layer (CLAUDE.md, AGENTS.md, .claude/rules/). Discovers all instruction files in the repo, extracts semantic maps, and surfaces contradictions, shadowing, gaps, and drift before they cause expensive agent misbehavior. Requires the ecosystem plugin.",
|
|
41
|
+
"author": {
|
|
42
|
+
"name": "Onlooker Community"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://onlooker.dev",
|
|
45
|
+
"repository": "https://github.com/onlooker-community/ecosystem",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"keywords": ["instructions", "audit", "claude-md", "agents-md", "drift", "consistency"],
|
|
48
|
+
"tags": ["instructions", "context-engineering"]
|
|
49
|
+
},
|
|
37
50
|
{
|
|
38
51
|
"name": "tribunal",
|
|
39
52
|
"source": "./plugins/tribunal",
|
|
40
|
-
"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. Requires the ecosystem plugin.",
|
|
53
|
+
"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). Requires the ecosystem plugin.",
|
|
41
54
|
"author": {
|
|
42
55
|
"name": "Onlooker Community"
|
|
43
56
|
},
|
|
@@ -76,7 +89,7 @@
|
|
|
76
89
|
{
|
|
77
90
|
"name": "compass",
|
|
78
91
|
"source": "./plugins/compass",
|
|
79
|
-
"description": "Pre-write intent clarity gate. Intercepts write-class tool calls and
|
|
92
|
+
"description": "Pre-write intent clarity gate. Intercepts write-class tool calls and requires a confidence threshold before allowing them to proceed. Evaluates the pending write against the prior assistant turn as context to avoid false positives on question-answer turns. Requires the ecosystem plugin.",
|
|
80
93
|
"author": {
|
|
81
94
|
"name": "Onlooker Community"
|
|
82
95
|
},
|
|
@@ -89,7 +102,7 @@
|
|
|
89
102
|
{
|
|
90
103
|
"name": "scribe",
|
|
91
104
|
"source": "./plugins/scribe",
|
|
92
|
-
"description": "Intent documentation from agent activity. Captures why changes were made — problem context, decisions, tradeoffs
|
|
105
|
+
"description": "Intent documentation from agent activity. Captures why changes were made — problem context, decisions, tradeoffs — and distills them into readable artifacts at session end. Requires the ecosystem plugin.",
|
|
93
106
|
"author": {
|
|
94
107
|
"name": "Onlooker Community"
|
|
95
108
|
},
|
|
@@ -102,7 +115,7 @@
|
|
|
102
115
|
{
|
|
103
116
|
"name": "counsel",
|
|
104
117
|
"source": "./plugins/counsel",
|
|
105
|
-
"description": "Weekly synthesis and recommendations from
|
|
118
|
+
"description": "Weekly synthesis and recommendations from the full observability stack. Reads all plugin event logs, produces a structured improvement brief, and injects it at session start when the last brief is stale. Requires the ecosystem plugin.",
|
|
106
119
|
"author": {
|
|
107
120
|
"name": "Onlooker Community"
|
|
108
121
|
},
|
|
@@ -115,7 +128,7 @@
|
|
|
115
128
|
{
|
|
116
129
|
"name": "warden",
|
|
117
130
|
"source": "./plugins/warden",
|
|
118
|
-
"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 — warden removes the
|
|
131
|
+
"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. Requires the ecosystem plugin.",
|
|
119
132
|
"author": {
|
|
120
133
|
"name": "Onlooker Community"
|
|
121
134
|
},
|
|
@@ -128,7 +141,7 @@
|
|
|
128
141
|
{
|
|
129
142
|
"name": "librarian",
|
|
130
143
|
"source": "./plugins/librarian",
|
|
131
|
-
"description": "Consolidation layer between archivist's per-session artifacts and the user's durable typed memory store.
|
|
144
|
+
"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. Requires the ecosystem plugin.",
|
|
132
145
|
"author": {
|
|
133
146
|
"name": "Onlooker Community"
|
|
134
147
|
},
|
|
@@ -141,7 +154,7 @@
|
|
|
141
154
|
{
|
|
142
155
|
"name": "curator",
|
|
143
156
|
"source": "./plugins/curator",
|
|
144
|
-
"description": "Maintenance layer for the typed auto-memory store. At every SessionStart, runs four cheap heuristic checks against the memories at ~/.claude/projects/<encoded>/memory/
|
|
157
|
+
"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. Requires the ecosystem plugin.",
|
|
145
158
|
"author": {
|
|
146
159
|
"name": "Onlooker Community"
|
|
147
160
|
},
|
|
@@ -154,7 +167,7 @@
|
|
|
154
167
|
{
|
|
155
168
|
"name": "historian",
|
|
156
169
|
"source": "./plugins/historian",
|
|
157
|
-
"description": "Episodic memory layer
|
|
170
|
+
"description": "Episodic memory layer. At SessionEnd, chunks and sanitizes the session transcript and stores chunks under $ONLOOKER_DIR/historian/<project-key>/sessions/ (default $HOME/.onlooker). On UserPromptSubmit, embeds the prompt and performs similarity retrieval over stored chunks to surface relevant past context. Requires the ecosystem plugin.",
|
|
158
171
|
"author": {
|
|
159
172
|
"name": "Onlooker Community"
|
|
160
173
|
},
|
|
@@ -167,7 +180,7 @@
|
|
|
167
180
|
{
|
|
168
181
|
"name": "assayer",
|
|
169
182
|
"source": "./plugins/assayer",
|
|
170
|
-
"description": "Claim verification. At session end, parses the agent's final message for testable
|
|
183
|
+
"description": "Claim verification. At session end, parses the agent's final message for testable claims (\"I ran the tests, they pass\", \"the build is green\") and checks each against the actual command results in the session transcript, classifying it corroborated, contradicted, or unverifiable. Catches lying-without-malice. Advisory by default. Requires the ecosystem plugin.",
|
|
171
184
|
"author": {
|
|
172
185
|
"name": "Onlooker Community"
|
|
173
186
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecosystem",
|
|
3
|
-
"version": "0.25.
|
|
4
|
-
"description": "Observability substrate for Claude Code. Provides the shared
|
|
3
|
+
"version": "0.25.1",
|
|
4
|
+
"description": "Observability substrate for Claude Code. Provides the shared $ONLOOKER_DIR storage root (default $HOME/.onlooker), canonical schema-validated event emission, session and tool tracking hooks, and prompt rules. Required by all other Onlooker plugins.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
7
7
|
"url": "https://onlooker.dev"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
".": "0.25.
|
|
2
|
+
".": "0.25.1",
|
|
3
3
|
"plugins/archivist": "0.1.0",
|
|
4
4
|
"plugins/tribunal": "1.0.1",
|
|
5
5
|
"plugins/echo": "0.2.0",
|
|
6
|
-
"plugins/cartographer": "0.2.
|
|
7
|
-
"plugins/governor": "0.2.
|
|
6
|
+
"plugins/cartographer": "0.2.1",
|
|
7
|
+
"plugins/governor": "0.2.1",
|
|
8
8
|
"plugins/compass": "0.2.0",
|
|
9
9
|
"plugins/scribe": "0.2.1",
|
|
10
10
|
"plugins/counsel": "0.2.0",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.25.1](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.25.0...ecosystem-v0.25.1) (2026-06-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* vendor portable-lock.sh into cartographer and governor ([#73](https://github.com/onlooker-community/ecosystem/issues/73)) ([ab2c354](https://github.com/onlooker-community/ecosystem/commit/ab2c354b131c26cc642ebb51e84a043dc43cbaa1))
|
|
9
|
+
|
|
3
10
|
## [0.25.0](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.24.0...ecosystem-v0.25.0) (2026-06-04)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlooker-community/ecosystem",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"test": "npm run test:bats && npm run test:schema",
|
|
27
27
|
"test:bats": "bats test/bats",
|
|
28
28
|
"test:schema": "node --test test/node/*.test.mjs",
|
|
29
|
-
"test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh plugins/archivist/scripts/hooks/*.sh plugins/archivist/scripts/lib/*.sh plugins/tribunal/scripts/hooks/*.sh plugins/tribunal/scripts/lib/*.sh plugins/echo/scripts/hooks/*.sh plugins/echo/scripts/lib/*.sh plugins/governor/scripts/hooks/*.sh plugins/governor/scripts/lib/*.sh plugins/compass/scripts/hooks/*.sh plugins/compass/scripts/lib/*.sh plugins/scribe/scripts/hooks/*.sh plugins/scribe/scripts/lib/*.sh plugins/counsel/scripts/hooks/*.sh plugins/counsel/scripts/lib/*.sh plugins/warden/scripts/hooks/*.sh plugins/warden/scripts/lib/*.sh plugins/librarian/scripts/hooks/*.sh plugins/librarian/scripts/lib/*.sh plugins/curator/scripts/hooks/*.sh plugins/curator/scripts/lib/*.sh plugins/historian/scripts/hooks/*.sh plugins/historian/scripts/lib/*.sh plugins/assayer/scripts/hooks/*.sh plugins/assayer/scripts/lib/*.sh",
|
|
29
|
+
"test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh plugins/archivist/scripts/hooks/*.sh plugins/archivist/scripts/lib/*.sh plugins/tribunal/scripts/hooks/*.sh plugins/tribunal/scripts/lib/*.sh plugins/echo/scripts/hooks/*.sh plugins/echo/scripts/lib/*.sh plugins/governor/scripts/hooks/*.sh plugins/governor/scripts/lib/*.sh plugins/compass/scripts/hooks/*.sh plugins/compass/scripts/lib/*.sh plugins/scribe/scripts/hooks/*.sh plugins/scribe/scripts/lib/*.sh plugins/counsel/scripts/hooks/*.sh plugins/counsel/scripts/lib/*.sh plugins/warden/scripts/hooks/*.sh plugins/warden/scripts/lib/*.sh plugins/librarian/scripts/hooks/*.sh plugins/librarian/scripts/lib/*.sh plugins/curator/scripts/hooks/*.sh plugins/curator/scripts/lib/*.sh plugins/historian/scripts/hooks/*.sh plugins/historian/scripts/lib/*.sh plugins/assayer/scripts/hooks/*.sh plugins/assayer/scripts/lib/*.sh plugins/cartographer/scripts/hooks/*.sh plugins/cartographer/scripts/lib/*.sh",
|
|
30
30
|
"lint:references": "node scripts/lint/check-references.mjs",
|
|
31
31
|
"lint:manifests": "node scripts/lint/check-manifests.mjs",
|
|
32
32
|
"coverage:node": "node scripts/coverage/run-coverage.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cartographer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Proactive periodic auditor of the persistent instruction layer (CLAUDE.md, AGENTS.md, .claude/rules/). Discovers all instruction files in the repo, extracts semantic maps, and surfaces contradictions, shadowing, gaps, and drift before they cause expensive agent misbehavior. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Cartographer plugin are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.2.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.2.0...cartographer-v0.2.1) (2026-06-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* vendor portable-lock.sh into cartographer and governor ([#73](https://github.com/onlooker-community/ecosystem/issues/73)) ([ab2c354](https://github.com/onlooker-community/ecosystem/commit/ab2c354b131c26cc642ebb51e84a043dc43cbaa1))
|
|
11
|
+
|
|
5
12
|
## [0.2.0](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.1.0...cartographer-v0.2.0) (2026-05-25)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -9,17 +9,27 @@
|
|
|
9
9
|
# cartographer_lock_acquire <lock_file> # returns 0=acquired, 1=timeout
|
|
10
10
|
# cartographer_lock_release <lock_file>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# portable-lock.sh is vendored into this plugin's lib dir (a sibling of this
|
|
13
|
+
# file) so cartographer stays self-contained when installed standalone from
|
|
14
|
+
# the marketplace, where the ecosystem repo's top-level scripts/lib/ is absent.
|
|
15
|
+
_CARTOGRAPHER_LOCK_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/portable-lock.sh"
|
|
13
16
|
|
|
14
|
-
if [[
|
|
15
|
-
|
|
17
|
+
if [[ -f "$_CARTOGRAPHER_LOCK_LIB" ]]; then
|
|
18
|
+
# shellcheck source=./portable-lock.sh
|
|
19
|
+
source "$_CARTOGRAPHER_LOCK_LIB"
|
|
20
|
+
else
|
|
21
|
+
# The vendored lock should always be present, but if an unexpected
|
|
22
|
+
# packaging or path issue removes it we must degrade gracefully: the
|
|
23
|
+
# cartographer hooks are fail-soft and contractually exit 0, so a hard
|
|
24
|
+
# exit here would crash a session this plugin was only meant to observe.
|
|
25
|
+
# Define a primitive that always fails to acquire, so the hooks'
|
|
26
|
+
# `cartographer_lock_acquire ... || exit 0` skips the audit instead.
|
|
27
|
+
printf '[cartographer-lock] WARN: portable-lock.sh not found at %s; locking disabled, skipping audit\n' \
|
|
16
28
|
"$_CARTOGRAPHER_LOCK_LIB" >&2
|
|
17
|
-
|
|
29
|
+
lock_acquire() { return 1; }
|
|
30
|
+
lock_release() { return 0; }
|
|
18
31
|
fi
|
|
19
32
|
|
|
20
|
-
# shellcheck source=../../../../scripts/lib/portable-lock.sh
|
|
21
|
-
source "$_CARTOGRAPHER_LOCK_LIB"
|
|
22
|
-
|
|
23
33
|
cartographer_lock_acquire() {
|
|
24
34
|
local lock_file="${1:?lock_file required}"
|
|
25
35
|
mkdir -p "$(dirname "$lock_file")" 2>/dev/null || true
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# portable-lock.sh — vendored copy of the ecosystem substrate's portable lock.
|
|
3
|
+
#
|
|
4
|
+
# Vendored into the cartographer plugin so the plugin is self-contained when
|
|
5
|
+
# installed standalone from the marketplace: the cache layout
|
|
6
|
+
# (~/.claude/plugins/cache/<owner>/cartographer/<version>/) does not include
|
|
7
|
+
# the ecosystem repo's top-level scripts/lib/, so reaching up to it breaks.
|
|
8
|
+
# This mirrors the per-plugin vendoring of cartographer-ulid.sh and friends.
|
|
9
|
+
# Keep in sync with scripts/lib/portable-lock.sh at the repo root.
|
|
10
|
+
#
|
|
11
|
+
# Portable advisory file locking via mkdir() atomicity.
|
|
12
|
+
#
|
|
13
|
+
# Replaces flock(1), which ships with util-linux on Linux but is not present
|
|
14
|
+
# in stock macOS. This matters because the Onlooker hooks run on user
|
|
15
|
+
# machines, not just in CI: a macOS user without util-linux would otherwise
|
|
16
|
+
# see concurrent writes to $ONLOOKER_DIR silently clobber each other.
|
|
17
|
+
#
|
|
18
|
+
# mkdir() is atomic on POSIX local filesystems, which is the only place
|
|
19
|
+
# $ONLOOKER_DIR ever lives. Network filesystems (NFS) do not guarantee
|
|
20
|
+
# atomicity, but Claude Code state is local-only.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# lock_acquire "/path/to/file.lock" [timeout_seconds=5]
|
|
24
|
+
# # ... critical section ...
|
|
25
|
+
# lock_release "/path/to/file.lock"
|
|
26
|
+
#
|
|
27
|
+
# Avoid associative arrays so bash 3.2 (macOS default) keeps working.
|
|
28
|
+
|
|
29
|
+
# Acquire an exclusive lock at LOCKPATH. Returns 0 on success, 1 on timeout.
|
|
30
|
+
lock_acquire() {
|
|
31
|
+
local lockpath="${1:-}"
|
|
32
|
+
local timeout="${2:-5}"
|
|
33
|
+
[[ -z "$lockpath" ]] && return 1
|
|
34
|
+
|
|
35
|
+
local lockdir="${lockpath}.d"
|
|
36
|
+
local waited=0
|
|
37
|
+
# Poll at 10 Hz so a 5s timeout = 50 attempts.
|
|
38
|
+
local max_iter=$((timeout * 10))
|
|
39
|
+
while ! mkdir "$lockdir" 2>/dev/null; do
|
|
40
|
+
if ((waited >= max_iter)); then
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
# `sleep 0.1` works on Linux + macOS; the `|| sleep 1` is a paranoid
|
|
44
|
+
# fallback for embedded shells that only accept integer seconds.
|
|
45
|
+
sleep 0.1 2>/dev/null || sleep 1
|
|
46
|
+
waited=$((waited + 1))
|
|
47
|
+
done
|
|
48
|
+
return 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Release the lock previously acquired for LOCKPATH. Safe to call when the
|
|
52
|
+
# lock is not held (no-op in that case).
|
|
53
|
+
lock_release() {
|
|
54
|
+
local lockpath="${1:-}"
|
|
55
|
+
[[ -z "$lockpath" ]] && return 0
|
|
56
|
+
rmdir "${lockpath}.d" 2>/dev/null || true
|
|
57
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "governor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Resource governance and budget enforcement for the Onlooker ecosystem. Tracks per-session token and cost spend, gates Task spawns before they exceed a configurable budget ceiling, and emits governor.* events for audit. Named for the steam-engine governor — a device that regulates output. 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/governor-v0.2.0...governor-v0.2.1) (2026-06-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* vendor portable-lock.sh into cartographer and governor ([#73](https://github.com/onlooker-community/ecosystem/issues/73)) ([ab2c354](https://github.com/onlooker-community/ecosystem/commit/ab2c354b131c26cc642ebb51e84a043dc43cbaa1))
|
|
9
|
+
|
|
3
10
|
## [0.2.0](https://github.com/onlooker-community/ecosystem/compare/governor-v0.1.0...governor-v0.2.0) (2026-05-26)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -24,12 +24,16 @@ fi
|
|
|
24
24
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
25
25
|
# shellcheck disable=SC1091
|
|
26
26
|
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
27
|
-
# shellcheck disable=SC1091
|
|
28
|
-
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
29
27
|
fi
|
|
30
28
|
|
|
31
29
|
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
32
30
|
|
|
31
|
+
# portable-lock.sh is vendored into this plugin's lib dir so the ledger's
|
|
32
|
+
# atomic appends keep working when governor is installed standalone, where the
|
|
33
|
+
# ecosystem repo's top-level scripts/lib/ is absent from the plugin cache.
|
|
34
|
+
# shellcheck source=../lib/portable-lock.sh
|
|
35
|
+
source "${PLUGIN_ROOT}/scripts/lib/portable-lock.sh"
|
|
36
|
+
|
|
33
37
|
# shellcheck source=../lib/governor-config.sh
|
|
34
38
|
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
35
39
|
# shellcheck source=../lib/governor-events.sh
|
|
@@ -36,12 +36,16 @@ fi
|
|
|
36
36
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
37
37
|
# shellcheck disable=SC1091
|
|
38
38
|
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
39
|
-
# shellcheck disable=SC1091
|
|
40
|
-
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
41
39
|
fi
|
|
42
40
|
|
|
43
41
|
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
44
42
|
|
|
43
|
+
# portable-lock.sh is vendored into this plugin's lib dir so the ledger's
|
|
44
|
+
# atomic appends keep working when governor is installed standalone, where the
|
|
45
|
+
# ecosystem repo's top-level scripts/lib/ is absent from the plugin cache.
|
|
46
|
+
# shellcheck source=../lib/portable-lock.sh
|
|
47
|
+
source "${PLUGIN_ROOT}/scripts/lib/portable-lock.sh"
|
|
48
|
+
|
|
45
49
|
# shellcheck source=../lib/governor-config.sh
|
|
46
50
|
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
47
51
|
# shellcheck source=../lib/governor-events.sh
|
|
@@ -28,12 +28,16 @@ fi
|
|
|
28
28
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
29
29
|
# shellcheck disable=SC1091
|
|
30
30
|
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
31
|
-
# shellcheck disable=SC1091
|
|
32
|
-
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
33
31
|
fi
|
|
34
32
|
|
|
35
33
|
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
36
34
|
|
|
35
|
+
# portable-lock.sh is vendored into this plugin's lib dir so the ledger's
|
|
36
|
+
# atomic appends keep working when governor is installed standalone, where the
|
|
37
|
+
# ecosystem repo's top-level scripts/lib/ is absent from the plugin cache.
|
|
38
|
+
# shellcheck source=../lib/portable-lock.sh
|
|
39
|
+
source "${PLUGIN_ROOT}/scripts/lib/portable-lock.sh"
|
|
40
|
+
|
|
37
41
|
# shellcheck source=../lib/governor-config.sh
|
|
38
42
|
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
39
43
|
# shellcheck source=../lib/governor-events.sh
|
|
@@ -25,12 +25,16 @@ fi
|
|
|
25
25
|
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
26
26
|
# shellcheck disable=SC1091
|
|
27
27
|
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
28
|
-
# shellcheck disable=SC1091
|
|
29
|
-
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
30
28
|
fi
|
|
31
29
|
|
|
32
30
|
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
33
31
|
|
|
32
|
+
# portable-lock.sh is vendored into this plugin's lib dir so the ledger's
|
|
33
|
+
# atomic appends keep working when governor is installed standalone, where the
|
|
34
|
+
# ecosystem repo's top-level scripts/lib/ is absent from the plugin cache.
|
|
35
|
+
# shellcheck source=../lib/portable-lock.sh
|
|
36
|
+
source "${PLUGIN_ROOT}/scripts/lib/portable-lock.sh"
|
|
37
|
+
|
|
34
38
|
# shellcheck source=../lib/governor-config.sh
|
|
35
39
|
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
36
40
|
# shellcheck source=../lib/governor-events.sh
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# portable-lock.sh — vendored copy of the ecosystem substrate's portable lock.
|
|
3
|
+
#
|
|
4
|
+
# Vendored into the governor plugin so the ledger's atomic appends keep
|
|
5
|
+
# working when governor is installed standalone from the marketplace: the
|
|
6
|
+
# cache layout (~/.claude/plugins/cache/<owner>/governor/<version>/) does not
|
|
7
|
+
# include the ecosystem repo's top-level scripts/lib/. Without a local copy,
|
|
8
|
+
# lock_acquire would be undefined and governor_ledger_append would poison the
|
|
9
|
+
# ledger after exhausting its retries. This mirrors the per-plugin vendoring
|
|
10
|
+
# of governor-ulid.sh and friends.
|
|
11
|
+
# Keep in sync with scripts/lib/portable-lock.sh at the repo root.
|
|
12
|
+
#
|
|
13
|
+
# Portable advisory file locking via mkdir() atomicity.
|
|
14
|
+
#
|
|
15
|
+
# Replaces flock(1), which ships with util-linux on Linux but is not present
|
|
16
|
+
# in stock macOS. This matters because the Onlooker hooks run on user
|
|
17
|
+
# machines, not just in CI: a macOS user without util-linux would otherwise
|
|
18
|
+
# see concurrent writes to $ONLOOKER_DIR silently clobber each other.
|
|
19
|
+
#
|
|
20
|
+
# mkdir() is atomic on POSIX local filesystems, which is the only place
|
|
21
|
+
# $ONLOOKER_DIR ever lives. Network filesystems (NFS) do not guarantee
|
|
22
|
+
# atomicity, but Claude Code state is local-only.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# lock_acquire "/path/to/file.lock" [timeout_seconds=5]
|
|
26
|
+
# # ... critical section ...
|
|
27
|
+
# lock_release "/path/to/file.lock"
|
|
28
|
+
#
|
|
29
|
+
# Avoid associative arrays so bash 3.2 (macOS default) keeps working.
|
|
30
|
+
|
|
31
|
+
# Acquire an exclusive lock at LOCKPATH. Returns 0 on success, 1 on timeout.
|
|
32
|
+
lock_acquire() {
|
|
33
|
+
local lockpath="${1:-}"
|
|
34
|
+
local timeout="${2:-5}"
|
|
35
|
+
[[ -z "$lockpath" ]] && return 1
|
|
36
|
+
|
|
37
|
+
local lockdir="${lockpath}.d"
|
|
38
|
+
local waited=0
|
|
39
|
+
# Poll at 10 Hz so a 5s timeout = 50 attempts.
|
|
40
|
+
local max_iter=$((timeout * 10))
|
|
41
|
+
while ! mkdir "$lockdir" 2>/dev/null; do
|
|
42
|
+
if ((waited >= max_iter)); then
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
# `sleep 0.1` works on Linux + macOS; the `|| sleep 1` is a paranoid
|
|
46
|
+
# fallback for embedded shells that only accept integer seconds.
|
|
47
|
+
sleep 0.1 2>/dev/null || sleep 1
|
|
48
|
+
waited=$((waited + 1))
|
|
49
|
+
done
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Release the lock previously acquired for LOCKPATH. Safe to call when the
|
|
54
|
+
# lock is not held (no-op in that case).
|
|
55
|
+
lock_release() {
|
|
56
|
+
local lockpath="${1:-}"
|
|
57
|
+
[[ -z "$lockpath" ]] && return 0
|
|
58
|
+
rmdir "${lockpath}.d" 2>/dev/null || true
|
|
59
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Replaces flock(1), which ships with util-linux on Linux but is not present
|
|
5
5
|
# in stock macOS. This matters because the Onlooker hooks run on user
|
|
6
6
|
# machines, not just in CI: a macOS user without util-linux would otherwise
|
|
7
|
-
# see
|
|
7
|
+
# see concurrent writes to $ONLOOKER_DIR silently clobber each other.
|
|
8
8
|
#
|
|
9
9
|
# mkdir() is atomic on POSIX local filesystems, which is the only place
|
|
10
10
|
# $ONLOOKER_DIR ever lives. Network filesystems (NFS) do not guarantee
|
|
@@ -75,3 +75,22 @@ teardown() {
|
|
|
75
75
|
[ "$status" -ne 0 ]
|
|
76
76
|
wait
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
@test "missing vendored portable-lock.sh degrades to a no-op lock, never crashes" {
|
|
80
|
+
# Copy only the wrapper into an isolated dir WITHOUT its sibling
|
|
81
|
+
# portable-lock.sh to simulate a broken packaging/path. The cartographer
|
|
82
|
+
# hooks are fail-soft (exit 0), so sourcing must not abort and acquire must
|
|
83
|
+
# fail so the caller's `... || exit 0` skips the audit instead of crashing.
|
|
84
|
+
cp "${REPO_ROOT}/plugins/cartographer/scripts/lib/cartographer-lock.sh" "${BATS_TEST_TMPDIR}/cartographer-lock.sh"
|
|
85
|
+
run bash -c "
|
|
86
|
+
source '${BATS_TEST_TMPDIR}/cartographer-lock.sh'
|
|
87
|
+
echo SOURCED_OK
|
|
88
|
+
cartographer_lock_acquire '${BATS_TEST_TMPDIR}/x.lock' && echo ACQUIRED || echo ACQUIRE_FAILED
|
|
89
|
+
cartographer_lock_release '${BATS_TEST_TMPDIR}/x.lock' && echo RELEASE_OK
|
|
90
|
+
"
|
|
91
|
+
[ "$status" -eq 0 ]
|
|
92
|
+
[[ "$output" == *"SOURCED_OK"* ]]
|
|
93
|
+
[[ "$output" == *"ACQUIRE_FAILED"* ]]
|
|
94
|
+
[[ "$output" == *"RELEASE_OK"* ]]
|
|
95
|
+
[[ "$output" == *"locking disabled"* ]]
|
|
96
|
+
}
|