@onlooker-community/ecosystem 0.0.2 → 0.2.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.
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bats
2
+
3
+ setup() {
4
+ # shellcheck source=../helpers/setup.bash
5
+ source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
6
+ load_validate_path
7
+ # shellcheck source=../../scripts/lib/onlooker-schema.sh
8
+ source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
9
+ export _HOOK_SESSION_ID="emit-direct-session"
10
+ export ONLOOKER_PLUGIN_NAME="onlooker"
11
+ : >"$ONLOOKER_EVENTS_LOG"
12
+ }
13
+
14
+ @test "onlooker-emit writes canonical tool.agent.spawn to events log" {
15
+ local payload='{"subagent_id":"agent-99","agent_name":"explore","task_summary":"search"}'
16
+ "${REPO_ROOT}/scripts/lib/onlooker-emit.sh" "tool.agent.spawn" "$payload"
17
+ [ "$?" -eq 0 ]
18
+ tail -n 1 "$ONLOOKER_EVENTS_LOG" | jq -e \
19
+ '.schema_version == "1.0"
20
+ and .event_type == "tool.agent.spawn"
21
+ and .session_id == "emit-direct-session"
22
+ and .plugin == "onlooker"
23
+ and .payload.subagent_id == "agent-99"' \
24
+ >/dev/null
25
+ tail -n 1 "$ONLOOKER_EVENTS_LOG" | onlooker_validate_event
26
+ }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bats
2
+
3
+ setup() {
4
+ # shellcheck source=../helpers/setup.bash
5
+ source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
6
+ load_validate_path
7
+ # shellcheck source=../../scripts/lib/onlooker-schema.sh
8
+ source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
9
+ # shellcheck source=../../scripts/lib/tool-history.sh
10
+ source "${REPO_ROOT}/scripts/lib/tool-history.sh"
11
+ export CLAUDE_PLUGIN_ROOT="${REPO_ROOT}"
12
+ }
13
+
14
+ @test "tool_history_build_record returns canonical tool.file.read event" {
15
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-read.json"
16
+ local record
17
+ record=$(tool_history_build_record "$(cat "$fixture")")
18
+ echo "$record" | jq -e \
19
+ '.schema_version == "1.0"
20
+ and .event_type == "tool.file.read"
21
+ and .payload.path == "/project/src/main.ts"
22
+ and .session_id == "history-session-001"' \
23
+ >/dev/null
24
+ echo "$record" | onlooker_validate_event
25
+ }
26
+
27
+ @test "tool_history_build_record returns canonical tool.shell.exec on failure" {
28
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-failure-bash.json"
29
+ local record
30
+ record=$(tool_history_build_record "$(cat "$fixture")")
31
+ echo "$record" | jq -e \
32
+ '.event_type == "tool.shell.exec"
33
+ and .payload.command == "npm test"
34
+ and .payload.blocked == true' \
35
+ >/dev/null
36
+ echo "$record" | onlooker_validate_event
37
+ }
38
+
39
+ @test "tool-history-tracker appends canonical PostToolUse event to session JSONL" {
40
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-read.json"
41
+ local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/history-session-001.jsonl"
42
+ rm -f "$history_file" "${history_file}.lock"
43
+
44
+ run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/tool-history-tracker.sh' 2>/dev/null"
45
+ [ "$status" -eq 0 ]
46
+ [ -f "$history_file" ]
47
+ tail -n 1 "$history_file" | jq -e '.event_type == "tool.file.read"' >/dev/null
48
+ tail -n 1 "$history_file" | onlooker_validate_event
49
+ }
50
+
51
+ @test "tool-history-tracker mirrors event to global onlooker-events log" {
52
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-read.json"
53
+ : >"$ONLOOKER_EVENTS_LOG"
54
+
55
+ cat "$fixture" | "${REPO_ROOT}/scripts/hooks/tool-history-tracker.sh" >/dev/null 2>&1
56
+
57
+ tail -n 1 "$ONLOOKER_EVENTS_LOG" | jq -e '.event_type == "tool.file.read"' >/dev/null
58
+ tail -n 1 "$ONLOOKER_EVENTS_LOG" | onlooker_validate_event
59
+ }
60
+
61
+ @test "tool-history-tracker appends multiple canonical records for same session" {
62
+ local read_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-read.json"
63
+ local fail_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-tool-use-failure-bash.json"
64
+ local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/history-session-001.jsonl"
65
+ rm -f "$history_file" "${history_file}.lock"
66
+
67
+ cat "$read_fixture" | "${REPO_ROOT}/scripts/hooks/tool-history-tracker.sh" >/dev/null 2>&1
68
+ cat "$fail_fixture" | "${REPO_ROOT}/scripts/hooks/tool-history-tracker.sh" >/dev/null 2>&1
69
+
70
+ run wc -l <"$history_file"
71
+ [ "$output" -eq 2 ]
72
+ }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bats
2
+
3
+ setup() {
4
+ # shellcheck source=../helpers/setup.bash
5
+ source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
6
+ load_validate_path
7
+ export CLAUDE_PLUGIN_ROOT="${REPO_ROOT}"
8
+ }
9
+
10
+ @test "tool-sequence-tracker approves and increments turn_tool_seq" {
11
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/non-agent-tool.json"
12
+ local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/test-session-001"
13
+ rm -f "$tracker"
14
+
15
+ run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/tool-sequence-tracker.sh' 2>/dev/null"
16
+ [ "$status" -eq 0 ]
17
+ echo "$output" | jq -e '.decision == "approve"' >/dev/null
18
+
19
+ jq -e '.turn_number == 1 and .turn_tool_seq == 1' "$tracker" >/dev/null
20
+ }
21
+
22
+ @test "tool-sequence-tracker increments on successive tool calls" {
23
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/non-agent-tool.json"
24
+ local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/test-session-001"
25
+ rm -f "$tracker"
26
+
27
+ cat "$fixture" | "${REPO_ROOT}/scripts/hooks/tool-sequence-tracker.sh" >/dev/null 2>&1
28
+ cat "$fixture" | "${REPO_ROOT}/scripts/hooks/tool-sequence-tracker.sh" >/dev/null 2>&1
29
+
30
+ jq -e '.turn_tool_seq == 2' "$tracker" >/dev/null
31
+ }
32
+
33
+ @test "turn_state_ensure_session creates tracker with defaults" {
34
+ local session_id="ensure-session"
35
+ local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/${session_id}"
36
+ rm -f "$tracker"
37
+
38
+ turn_state_ensure_session "$session_id"
39
+ [ "$?" -eq 0 ]
40
+ jq -e '.turn_number == 1 and .turn_tool_seq == 0' "$tracker" >/dev/null
41
+ }
42
+
43
+ @test "turn_state_next_tool increments existing tracker" {
44
+ local session_id="next-tool-session"
45
+ local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/${session_id}"
46
+ rm -f "$tracker"
47
+
48
+ turn_state_ensure_session "$session_id"
49
+ turn_state_next_tool "$session_id"
50
+ turn_state_next_tool "$session_id"
51
+
52
+ jq -e '.turn_tool_seq == 2' "$tracker" >/dev/null
53
+ }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bats
2
+
3
+ setup() {
4
+ # shellcheck source=../helpers/setup.bash
5
+ source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
6
+ load_validate_path
7
+ # shellcheck source=../../scripts/lib/onlooker-schema.sh
8
+ source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
9
+ }
10
+
11
+ @test "validate_file_exists succeeds for existing file" {
12
+ local f="${BATS_TEST_TMPDIR}/exists.txt"
13
+ touch "$f"
14
+ validate_file_exists "$f"
15
+ [ "$?" -eq 0 ]
16
+ }
17
+
18
+ @test "validate_file_exists fails for missing file" {
19
+ ! validate_file_exists "${BATS_TEST_TMPDIR}/missing.txt"
20
+ }
21
+
22
+ @test "validate_dir_exists succeeds for existing directory" {
23
+ validate_dir_exists "${BATS_TEST_TMPDIR}"
24
+ [ "$?" -eq 0 ]
25
+ }
26
+
27
+ @test "ensure_dir_exists creates missing directory" {
28
+ local dir="${BATS_TEST_TMPDIR}/nested/new-dir"
29
+ ensure_dir_exists "$dir"
30
+ [ "$?" -eq 0 ]
31
+ [ -d "$dir" ]
32
+ }
33
+
34
+ @test "ensure_file_exists creates file and parent directories" {
35
+ local f="${BATS_TEST_TMPDIR}/deep/path/file.txt"
36
+ ensure_file_exists "$f"
37
+ [ "$?" -eq 0 ]
38
+ [ -f "$f" ]
39
+ }
40
+
41
+ @test "safe_append writes content to file" {
42
+ local f="${BATS_TEST_TMPDIR}/append.txt"
43
+ safe_append "$f" "line-one"
44
+ safe_append "$f" "line-two"
45
+ grep -q "line-one" "$f"
46
+ grep -q "line-two" "$f"
47
+ }
48
+
49
+ @test "safe_tail returns last N lines" {
50
+ local f="${BATS_TEST_TMPDIR}/tail.txt"
51
+ printf '%s\n' one two three four >"$f"
52
+ local result
53
+ result=$(safe_tail "$f" 2)
54
+ [ "$result" = $'three\nfour' ]
55
+ }
56
+
57
+ @test "hook_set_context exports session and tool from JSON" {
58
+ local input='{"session_id":"sess-42","tool_name":"Agent"}'
59
+ hook_set_context "$input" "PreToolUse"
60
+ [ "${ONLOOKER_HOOK_TYPE}" = "PreToolUse" ]
61
+ [ "${ONLOOKER_TOOL_NAME}" = "Agent" ]
62
+ [ "${_HOOK_SESSION_ID}" = "sess-42" ]
63
+ }
64
+
65
+ @test "hook_bus put/get round-trip" {
66
+ export _HOOK_SESSION_ID="bus-session"
67
+ export _HOOK_TOOL_NAME="Agent"
68
+ hook_bus_init '{"tool_input":{"agent_id":"1"}}'
69
+ hook_bus_put "scanner" '{"found":true}'
70
+ local result
71
+ result=$(hook_bus_get "scanner")
72
+ echo "$result" | jq -e '.found == true' >/dev/null
73
+ }
74
+
75
+ @test "hook_bus_has detects existing finding" {
76
+ export _HOOK_SESSION_ID="bus-session-2"
77
+ export _HOOK_TOOL_NAME="Agent"
78
+ hook_bus_init '{"tool_input":{"agent_id":"2"}}'
79
+ hook_bus_put "flag" '{"ok":true}'
80
+ hook_bus_has "flag"
81
+ [ "$?" -eq 0 ]
82
+ ! hook_bus_has "missing"
83
+ }
84
+
85
+ @test "turn_state_export reads turn numbers from tracker file" {
86
+ local session_id="turn-test-session"
87
+ local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/${session_id}"
88
+ mkdir -p "$(dirname "$tracker")"
89
+ echo '{"turn_number":3,"turn_tool_seq":2}' >"$tracker"
90
+ turn_state_export "$session_id"
91
+ [ "${ONLOOKER_TURN_NUMBER}" = "3" ]
92
+ [ "${ONLOOKER_TURN_TOOL_SEQ}" = "2" ]
93
+ }
94
+
95
+ @test "safe_emit appends canonical event to onlooker events log" {
96
+ export _HOOK_SESSION_ID="emit-session"
97
+ export ONLOOKER_HOOK_TYPE="PreToolUse"
98
+ export ONLOOKER_TOOL_NAME="Read"
99
+ local payload='{"path":"/tmp/example.txt"}'
100
+ safe_emit "tool.file.read" "$payload"
101
+ [ "$?" -eq 0 ]
102
+ [ -f "$ONLOOKER_EVENTS_LOG" ]
103
+ tail -n 1 "$ONLOOKER_EVENTS_LOG" | jq -e \
104
+ '.event_type == "tool.file.read"
105
+ and .session_id == "emit-session"
106
+ and .payload.path == "/tmp/example.txt"
107
+ and .schema_version == "1.0"' \
108
+ >/dev/null
109
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "session_id": "test-session-001",
3
+ "tool_name": "Agent",
4
+ "tool_input": {
5
+ "subagent_type": "explore",
6
+ "description": "Search the codebase",
7
+ "run_in_background": false,
8
+ "isolation": "worktree",
9
+ "model": "sonnet"
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "session_id": "test-session-001",
3
+ "tool_name": "Read",
4
+ "tool_input": {
5
+ "file_path": "/tmp/example.txt"
6
+ }
7
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "session_id": "history-session-001",
3
+ "hook_event_name": "PostToolUseFailure",
4
+ "tool_name": "Bash",
5
+ "tool_use_id": "toolu_bash_001",
6
+ "duration_ms": 4187,
7
+ "tool_input": {
8
+ "command": "npm test",
9
+ "description": "Run test suite"
10
+ },
11
+ "error": "Command exited with non-zero status code 1"
12
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "session_id": "history-session-001",
3
+ "hook_event_name": "PostToolUse",
4
+ "tool_name": "Read",
5
+ "tool_use_id": "toolu_read_001",
6
+ "duration_ms": 42,
7
+ "tool_input": {
8
+ "file_path": "/project/src/main.ts"
9
+ },
10
+ "tool_response": {
11
+ "content": "export function main() {\n return 1;\n}\n"
12
+ }
13
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # Shared setup for Onlooker ecosystem bats tests.
3
+
4
+ # Repo root: test/helpers -> test -> repo
5
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
6
+ export REPO_ROOT
7
+
8
+ # BATS_TEST_TMPDIR may be unset during setup_file on some runners; ensure a temp base.
9
+ if [[ -z "${BATS_TEST_TMPDIR:-}" ]]; then
10
+ export BATS_TEST_TMPDIR="${TMPDIR:-/tmp}/onlooker-bats-${BATS_SUITE_TEST_NUMBER:-$$}"
11
+ mkdir -p "$BATS_TEST_TMPDIR"
12
+ fi
13
+
14
+ # Isolate all filesystem side effects under BATS_TEST_TMPDIR.
15
+ setup_test_env() {
16
+ export TEST_HOME="${BATS_TEST_TMPDIR}/home"
17
+ mkdir -p "$TEST_HOME"
18
+
19
+ export HOME="$TEST_HOME"
20
+ export ONLOOKER_DIR="${TEST_HOME}/.onlooker"
21
+ export CLAUDE_HOME="${TEST_HOME}/.claude"
22
+ export CLAUDE_PLUGIN_ROOT="${REPO_ROOT}"
23
+ }
24
+
25
+ # Source validate-path.sh with test env vars already set.
26
+ load_validate_path() {
27
+ setup_test_env
28
+ # shellcheck disable=SC1091
29
+ source "${REPO_ROOT}/scripts/lib/validate-path.sh"
30
+
31
+ mkdir -p \
32
+ "$(dirname "$ONLOOKER_EVENTS_LOG")" \
33
+ "$ONLOOKER_SESSION_TRACKERS_DIR" \
34
+ "$ONLOOKER_SESSION_HISTORY_DIR" \
35
+ "$ONLOOKER_SESSION_SUMMARIES_DIR" \
36
+ "$ONLOOKER_COMPACT_TRACKERS_DIR" \
37
+ "$ONLOOKER_METRICS_DIR"
38
+ }
@@ -0,0 +1,67 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { test } from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { validate } from '@onlooker-community/schema';
8
+ import { buildCanonicalEvent, mapHookInputToCanonical } from '../../scripts/lib/onlooker-event.mjs';
9
+
10
+ const REPO_ROOT = join(fileURLToPath(new URL('../..', import.meta.url)));
11
+ const FIXTURES = join(REPO_ROOT, 'test/fixtures/hook-inputs');
12
+
13
+ function loadFixture(name) {
14
+ return JSON.parse(readFileSync(join(FIXTURES, name), 'utf8'));
15
+ }
16
+
17
+ test('mapHookInputToCanonical maps PostToolUse Read to tool.file.read', () => {
18
+ const hookInput = loadFixture('post-tool-use-read.json');
19
+ const tmpDir = join(REPO_ROOT, 'test/tmp-schema-events');
20
+ const mapped = mapHookInputToCanonical(hookInput, {
21
+ onlookerDir: tmpDir,
22
+ plugin: 'onlooker',
23
+ });
24
+
25
+ assert.equal(mapped.valid, true);
26
+ assert.equal(mapped.event.event_type, 'tool.file.read');
27
+ assert.equal(mapped.event.schema_version, '1.0');
28
+ assert.equal(mapped.event.payload.path, '/project/src/main.ts');
29
+ assert.equal(validate(mapped.event).valid, true);
30
+ });
31
+
32
+ test('mapHookInputToCanonical maps PostToolUseFailure Bash to tool.shell.exec', () => {
33
+ const hookInput = loadFixture('post-tool-use-failure-bash.json');
34
+ const tmpDir = join(REPO_ROOT, 'test/tmp-schema-events');
35
+ const mapped = mapHookInputToCanonical(hookInput, {
36
+ onlookerDir: tmpDir,
37
+ plugin: 'onlooker',
38
+ });
39
+
40
+ assert.equal(mapped.valid, true);
41
+ assert.equal(mapped.event.event_type, 'tool.shell.exec');
42
+ assert.equal(mapped.event.payload.command, 'npm test');
43
+ assert.equal(mapped.event.payload.blocked, true);
44
+ assert.equal(validate(mapped.event).valid, true);
45
+ });
46
+
47
+ test('buildCanonicalEvent assigns monotonic file-backed sequence', () => {
48
+ const tmpDir = mkdtempSync(join(tmpdir(), 'onlooker-seq-'));
49
+ const a = buildCanonicalEvent({
50
+ onlookerDir: tmpDir,
51
+ plugin: 'onlooker',
52
+ session_id: 'seq-test',
53
+ event_type: 'tool.file.read',
54
+ payload: { path: '/a' },
55
+ });
56
+ const b = buildCanonicalEvent({
57
+ onlookerDir: tmpDir,
58
+ plugin: 'onlooker',
59
+ session_id: 'seq-test',
60
+ event_type: 'tool.file.read',
61
+ payload: { path: '/b' },
62
+ });
63
+
64
+ assert.equal(a.sequence, 0);
65
+ assert.equal(b.sequence, 1);
66
+ rmSync(tmpDir, { recursive: true, force: true });
67
+ });