@onlooker-community/ecosystem 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@
12
12
  "name": "ecosystem",
13
13
  "source": "./",
14
14
  "description": "Fill this out",
15
- "version": "0.3.3",
15
+ "version": "0.7.0",
16
16
  "author": {
17
17
  "name": "Onlooker Community"
18
18
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecosystem",
3
- "version": "0.3.3",
3
+ "version": "0.7.0",
4
4
  "description": "TODO fill this out",
5
5
  "author": {
6
6
  "name": "Onlooker Community",
@@ -17,13 +17,55 @@ jobs:
17
17
  name: Release Please
18
18
  runs-on: ubuntu-latest
19
19
  steps:
20
- - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
20
+ - name: Create Release PR
21
+ id: release
22
+ uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
21
23
  with:
22
- release-type: node
24
+ # Do not set release-type here — it overrides release-please-config.json
25
+ # and skips extra-files (plugin.json, marketplace.json). Release type
26
+ # is defined per-package in release-please-config.json.
27
+ config-file: release-please-config.json
28
+ manifest-file: .release-please-manifest.json
23
29
  # Use a PAT instead of GITHUB_TOKEN so the release PR triggers
24
30
  # downstream workflows (CI, etc.). Events caused by GITHUB_TOKEN
25
31
  # deliberately do not fan out to other workflows; that policy
26
32
  # leaves release PRs unchecked. Set RELEASE_PLEASE_PAT to a
27
33
  # fine-grained token with Contents:write + Pull requests:write
28
34
  # scoped to this repo.
29
- token: ${{ secrets.RELEASE_PLEASE_PAT }}
35
+ token: ${{ secrets.RELEASE_PLEASE_PAT }}
36
+
37
+ - uses: actions/checkout@v6
38
+ if: ${{ steps.release.outputs.releases_created == 'true'}}
39
+
40
+ - uses: actions/setup-node@v6
41
+ if: ${{ steps.release.outputs.releases_created == 'true'}}
42
+ with:
43
+ node-version: '22'
44
+ cache: npm
45
+
46
+ - name: Publish tools packages to npm
47
+ if: ${{ steps.release.outputs.releases_created == 'true' }}
48
+ env:
49
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
50
+ PATHS_RELEASED: ${{ steps.release.outputs.paths_released }}
51
+ run: |
52
+ set -euo pipefail
53
+ if [[ -z "${NPM_TOKEN}" ]]; then
54
+ echo "NPM_TOKEN secret is required to publish to npm." >&2
55
+ exit 1
56
+ fi
57
+ printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> "${HOME}/.npmrc"
58
+ npm ci
59
+ # paths_released is JSON (e.g. ["."], not bare paths)
60
+ mapfile -t release_paths < <(echo "${PATHS_RELEASED}" | jq -r '.[]')
61
+ if [[ "${#release_paths[@]}" -eq 0 ]]; then
62
+ echo "No paths in paths_released; skipping npm publish." >&2
63
+ exit 0
64
+ fi
65
+ for path in "${release_paths[@]}"; do
66
+ if [[ "$path" == "." ]]; then
67
+ npm publish --access public --provenance
68
+ else
69
+ (cd "$path" && npm publish --access public --provenance)
70
+ fi
71
+ done
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.6.0"
2
+ ".": "0.7.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0](https://github.com/onlooker-community/ecosystem/compare/v0.6.0...v0.7.0) (2026-05-22)
4
+
5
+
6
+ ### Features
7
+
8
+ * **hooks:** add PreCompact and PostCompact context compaction trackers ([#15](https://github.com/onlooker-community/ecosystem/issues/15)) ([1ec5632](https://github.com/onlooker-community/ecosystem/commit/1ec5632404676ed8b35d324b79ad71a2e9093505))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** apply release-please extra-files for Claude plugin manifests ([#17](https://github.com/onlooker-community/ecosystem/issues/17)) ([da9913c](https://github.com/onlooker-community/ecosystem/commit/da9913ca4f7497280edc34f8c64baa903c1e6754))
14
+
15
+
16
+ ### Chores
17
+
18
+ * enhance release workflow for npm packages ([3b37b56](https://github.com/onlooker-community/ecosystem/commit/3b37b56270a13fec95c2cd6ee8816ba5725a680a))
19
+ * remove npm publish workflow ([5f29c33](https://github.com/onlooker-community/ecosystem/commit/5f29c33baca8c10289d48f8126dc6eb4b4fe8153))
20
+ * remove test job from npm publish workflow ([f25bf9d](https://github.com/onlooker-community/ecosystem/commit/f25bf9d65fbe5066fe9963ce8d075fe81dc8e5c9))
21
+
3
22
  ## [0.6.0](https://github.com/onlooker-community/ecosystem/compare/v0.5.0...v0.6.0) (2026-05-22)
4
23
 
5
24
 
package/hooks/hooks.json CHANGED
@@ -97,6 +97,46 @@
97
97
  }
98
98
  ]
99
99
  }
100
+ ],
101
+ "PreCompact": [
102
+ {
103
+ "matcher": "manual",
104
+ "hooks": [
105
+ {
106
+ "type": "command",
107
+ "command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/pre-compact-tracker.sh"
108
+ }
109
+ ]
110
+ },
111
+ {
112
+ "matcher": "auto",
113
+ "hooks": [
114
+ {
115
+ "type": "command",
116
+ "command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/pre-compact-tracker.sh"
117
+ }
118
+ ]
119
+ }
120
+ ],
121
+ "PostCompact": [
122
+ {
123
+ "matcher": "manual",
124
+ "hooks": [
125
+ {
126
+ "type": "command",
127
+ "command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/context-compact-tracker.sh"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ "matcher": "auto",
133
+ "hooks": [
134
+ {
135
+ "type": "command",
136
+ "command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/context-compact-tracker.sh"
137
+ }
138
+ ]
139
+ }
100
140
  ]
101
141
  }
102
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlooker-community/ecosystem",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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",
@@ -8,11 +8,6 @@
8
8
  "include-component-in-tag": false,
9
9
  "include-v-in-tag": true,
10
10
  "extra-files": [
11
- {
12
- "type": "json",
13
- "path": "package.json",
14
- "jsonpath": "$.version"
15
- },
16
11
  {
17
12
  "type": "json",
18
13
  "path": ".claude-plugin/plugin.json",
@@ -21,7 +16,7 @@
21
16
  {
22
17
  "type": "json",
23
18
  "path": ".claude-plugin/marketplace.json",
24
- "jsonpath": "$.plugins[0].version"
19
+ "jsonpath": "$.plugins..version"
25
20
  }
26
21
  ],
27
22
  "changelog-sections": [
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # Onlooker Context Compact Tracker
3
+ # Invoked by PostCompact (matchers: manual, auto) after context compaction.
4
+ #
5
+ # Persists compact summaries, emits canonical session.compact, and finalizes
6
+ # compact tracker state.
7
+ #
8
+ # Usage:
9
+ # echo "$INPUT" | context-compact-tracker.sh
10
+
11
+ set -uo pipefail # No -e: never alter compaction result
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ source "$SCRIPT_DIR/../lib/validate-path.sh"
15
+ source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
16
+ source "$SCRIPT_DIR/../lib/tool-history.sh"
17
+ source "$SCRIPT_DIR/../lib/session-tracker.sh"
18
+ source "$SCRIPT_DIR/../lib/compact-tracker.sh"
19
+
20
+ hook_register "context-compact-tracker" "Context Compact Tracker" "Records compaction results and emits session.compact"
21
+
22
+ INPUT=$(cat)
23
+ hook_set_context "$INPUT" "PostCompact"
24
+
25
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
26
+
27
+ compact_tracker_append_summary "$SESSION_ID" "$INPUT" || hook_failure "Failed to append compact summary"
28
+ compact_tracker_record_post "$SESSION_ID" "$INPUT" || hook_failure "Failed to finalize compact state"
29
+
30
+ PAYLOAD=$(compact_tracker_build_compact_payload "$SESSION_ID" "$INPUT")
31
+ if [[ -n "$PAYLOAD" ]]; then
32
+ session_tracker_emit "$SESSION_ID" "session.compact" "$PAYLOAD" \
33
+ || hook_failure "Failed to emit session.compact"
34
+ fi
35
+
36
+ hook_success
37
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Onlooker Pre-Compact Tracker
3
+ # Invoked by PreCompact (matchers: manual, auto) before context compaction.
4
+ #
5
+ # Records pending compact state and estimated tokens_before from the transcript.
6
+ # Always approves compaction unless extended later with policy checks.
7
+ #
8
+ # Usage:
9
+ # echo "$INPUT" | pre-compact-tracker.sh
10
+
11
+ set -uo pipefail # No -e: never block compaction unless policy added
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ source "$SCRIPT_DIR/../lib/validate-path.sh"
15
+ source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
16
+ source "$SCRIPT_DIR/../lib/tool-history.sh"
17
+ source "$SCRIPT_DIR/../lib/session-tracker.sh"
18
+ source "$SCRIPT_DIR/../lib/compact-tracker.sh"
19
+
20
+ hook_register "pre-compact-tracker" "Pre-Compact Tracker" "Records compaction intent before context is compacted"
21
+
22
+ INPUT=$(cat)
23
+ hook_set_context "$INPUT" "PreCompact"
24
+
25
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
26
+ TRIGGER=$(echo "$INPUT" | jq -r '.trigger // "auto"')
27
+
28
+ compact_tracker_record_pre "$SESSION_ID" "$INPUT" || hook_failure "Failed to record pre-compact state"
29
+
30
+ json_response() {
31
+ jq -n --arg decision "$1" --arg reason "$2" '{ "decision": $decision, "reason": $reason }'
32
+ }
33
+
34
+ json_response "approve" "Compaction tracked (${TRIGGER})"
35
+ hook_success
36
+ exit 0
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bash
2
+ # Context compaction helpers — pre/post compact state and session.compact events.
3
+ #
4
+ # Source after validate-path.sh, onlooker-schema.sh, tool-history.sh, session-tracker.sh:
5
+ # source "$CLAUDE_PLUGIN_ROOT/scripts/lib/compact-tracker.sh"
6
+
7
+ # Rough token estimate from byte length (chars / 4).
8
+ # Usage: tokens=$(compact_tracker_estimate_tokens "$text_or_path" [is_file])
9
+ compact_tracker_estimate_tokens() {
10
+ local value="${1:-}"
11
+ local is_file="${2:-false}"
12
+ local bytes=0
13
+
14
+ if [[ -z "$value" ]]; then
15
+ echo 0
16
+ return 0
17
+ fi
18
+
19
+ if [[ "$is_file" == "true" && -f "$value" ]]; then
20
+ bytes=$(wc -c <"$value" 2>/dev/null | tr -d ' ') || bytes=0
21
+ elif [[ "$is_file" == "true" ]]; then
22
+ echo 0
23
+ return 0
24
+ else
25
+ bytes=${#value}
26
+ fi
27
+
28
+ [[ ! "$bytes" =~ ^[0-9]+$ ]] && bytes=0
29
+ echo $((bytes / 4))
30
+ }
31
+
32
+ # Per-session compact state file path.
33
+ compact_tracker_state_file() {
34
+ local session_id="${1:-}"
35
+ printf '%s/%s' "$ONLOOKER_COMPACT_TRACKERS_DIR" "$session_id"
36
+ }
37
+
38
+ # Record pre-compact metadata before compaction runs.
39
+ # Usage: compact_tracker_record_pre "$SESSION_ID" "$INPUT_JSON"
40
+ compact_tracker_record_pre() {
41
+ local session_id="${1:-}"
42
+ local input_json="${2:-}"
43
+ [[ -z "$session_id" || "$session_id" == "null" || -z "$input_json" ]] && return 0
44
+
45
+ ensure_dir_exists "$ONLOOKER_COMPACT_TRACKERS_DIR" || return 1
46
+ turn_state_ensure_session "$session_id" || return 1
47
+
48
+ local trigger custom_instructions transcript_path tokens_before turn_number now_ms state_file
49
+ trigger=$(echo "$input_json" | jq -r '.trigger // "auto"' 2>/dev/null) || trigger="auto"
50
+ custom_instructions=$(echo "$input_json" | jq -r '.custom_instructions // ""' 2>/dev/null) || custom_instructions=""
51
+ transcript_path=$(echo "$input_json" | jq -r '.transcript_path // ""' 2>/dev/null) || transcript_path=""
52
+ now_ms=$(session_tracker_now_ms)
53
+ state_file=$(compact_tracker_state_file "$session_id")
54
+
55
+ tokens_before=0
56
+ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
57
+ tokens_before=$(compact_tracker_estimate_tokens "$transcript_path" true)
58
+ fi
59
+
60
+ if [[ -f "$ONLOOKER_SESSION_TRACKERS_DIR/$session_id" ]]; then
61
+ turn_number=$(jq -r '.turn_number // 1' "$ONLOOKER_SESSION_TRACKERS_DIR/$session_id" 2>/dev/null) || turn_number=1
62
+ else
63
+ turn_number=1
64
+ fi
65
+
66
+ local prior_count=0
67
+ if [[ -f "$state_file" ]]; then
68
+ prior_count=$(jq -r '.compact_count // 0' "$state_file" 2>/dev/null) || prior_count=0
69
+ fi
70
+ [[ ! "$prior_count" =~ ^[0-9]+$ ]] && prior_count=0
71
+
72
+ jq -n \
73
+ --argjson started_ms "$now_ms" \
74
+ --arg trigger "$trigger" \
75
+ --arg instructions "$custom_instructions" \
76
+ --argjson tokens_before "$tokens_before" \
77
+ --argjson turn_number "$turn_number" \
78
+ --argjson compact_count $((prior_count + 1)) \
79
+ '{
80
+ pending: true,
81
+ started_ms: $started_ms,
82
+ trigger: $trigger,
83
+ custom_instructions: (if $instructions != "" then $instructions else null end),
84
+ tokens_before: $tokens_before,
85
+ turn_number: $turn_number,
86
+ compact_count: $compact_count
87
+ }' >"$state_file"
88
+ }
89
+
90
+ # Append compact summary to session summaries dir (JSONL).
91
+ # Usage: compact_tracker_append_summary "$SESSION_ID" "$INPUT_JSON"
92
+ compact_tracker_append_summary() {
93
+ local session_id="${1:-}"
94
+ local input_json="${2:-}"
95
+ [[ -z "$session_id" || "$session_id" == "null" ]] && return 0
96
+
97
+ local summary trigger
98
+ summary=$(echo "$input_json" | jq -r '.compact_summary // ""' 2>/dev/null) || summary=""
99
+ [[ -z "$summary" ]] && return 0
100
+
101
+ trigger=$(echo "$input_json" | jq -r '.trigger // "auto"' 2>/dev/null) || trigger="auto"
102
+
103
+ ensure_dir_exists "$ONLOOKER_SESSION_SUMMARIES_DIR" || return 1
104
+ local summary_file="${ONLOOKER_SESSION_SUMMARIES_DIR}/${session_id}.jsonl"
105
+ local record
106
+ record=$(jq -n \
107
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S)" \
108
+ --arg trigger "$trigger" \
109
+ --arg summary "$summary" \
110
+ '{recorded_at: $ts, trigger: $trigger, compact_summary: $summary}')
111
+
112
+ printf '%s\n' "$record" >>"$summary_file" 2>/dev/null
113
+ }
114
+
115
+ # Build session.compact payload from pre state and post-compact summary.
116
+ # Usage: payload=$(compact_tracker_build_compact_payload "$SESSION_ID" "$INPUT_JSON")
117
+ compact_tracker_build_compact_payload() {
118
+ local session_id="${1:-}"
119
+ local input_json="${2:-}"
120
+ [[ -z "$session_id" || "$session_id" == "null" ]] && return 1
121
+
122
+ local summary tokens_before tokens_after ratio state_file
123
+ summary=$(echo "$input_json" | jq -r '.compact_summary // ""' 2>/dev/null) || summary=""
124
+ tokens_after=$(compact_tracker_estimate_tokens "$summary" false)
125
+ (( tokens_after < 1 )) && tokens_after=1
126
+
127
+ state_file=$(compact_tracker_state_file "$session_id")
128
+ if [[ -f "$state_file" ]]; then
129
+ tokens_before=$(jq -r '.tokens_before // 0' "$state_file" 2>/dev/null) || tokens_before=0
130
+ fi
131
+ [[ ! "$tokens_before" =~ ^[0-9]+$ ]] && tokens_before=0
132
+
133
+ if (( tokens_before < tokens_after )); then
134
+ tokens_before=$((tokens_after * 2))
135
+ fi
136
+ (( tokens_before < 1 )) && tokens_before=1
137
+
138
+ ratio=$(awk "BEGIN {printf \"%.4f\", $tokens_after / $tokens_before}")
139
+
140
+ jq -n \
141
+ --argjson tokens_before "$tokens_before" \
142
+ --argjson tokens_after "$tokens_after" \
143
+ --argjson ratio "$ratio" \
144
+ '{
145
+ tokens_before: $tokens_before,
146
+ tokens_after: $tokens_after,
147
+ compression_ratio: $ratio
148
+ }'
149
+ }
150
+
151
+ # Finalize compact state after PostCompact.
152
+ # Usage: compact_tracker_record_post "$SESSION_ID" "$INPUT_JSON"
153
+ compact_tracker_record_post() {
154
+ local session_id="${1:-}"
155
+ local input_json="${2:-}"
156
+ [[ -z "$session_id" || "$session_id" == "null" ]] && return 0
157
+
158
+ local state_file now_ms summary_len trigger
159
+ state_file=$(compact_tracker_state_file "$session_id")
160
+ now_ms=$(session_tracker_now_ms)
161
+ summary_len=$(echo "$input_json" | jq -r '.compact_summary // ""' 2>/dev/null | wc -c | tr -d ' ') || summary_len=0
162
+ trigger=$(echo "$input_json" | jq -r '.trigger // "auto"' 2>/dev/null) || trigger="auto"
163
+
164
+ if [[ ! -f "$state_file" ]]; then
165
+ compact_tracker_record_pre "$session_id" "$input_json"
166
+ state_file=$(compact_tracker_state_file "$session_id")
167
+ fi
168
+
169
+ local temp_file
170
+ temp_file=$(mktemp)
171
+ if ! jq \
172
+ --argjson completed_ms "$now_ms" \
173
+ --argjson summary_chars "$summary_len" \
174
+ --arg trigger "$trigger" \
175
+ '.pending = false
176
+ | .completed_ms = $completed_ms
177
+ | .last_trigger = $trigger
178
+ | .last_summary_chars = $summary_chars' \
179
+ "$state_file" >"$temp_file" 2>/dev/null; then
180
+ rm -f "$temp_file"
181
+ return 1
182
+ fi
183
+ mv "$temp_file" "$state_file"
184
+
185
+ # Reset per-turn tool sequence after compaction (new context window).
186
+ local tracker_file="$ONLOOKER_SESSION_TRACKERS_DIR/$session_id"
187
+ if [[ -f "$tracker_file" ]]; then
188
+ temp_file=$(mktemp)
189
+ if jq --argjson now_ms "$now_ms" \
190
+ '.turn_tool_seq = 0 | .last_compact_ms = $now_ms' \
191
+ "$tracker_file" >"$temp_file" 2>/dev/null; then
192
+ mv "$temp_file" "$tracker_file"
193
+ else
194
+ rm -f "$temp_file"
195
+ fi
196
+ fi
197
+ }
@@ -0,0 +1,73 @@
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
+ source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
9
+ source "${REPO_ROOT}/scripts/lib/tool-history.sh"
10
+ source "${REPO_ROOT}/scripts/lib/session-tracker.sh"
11
+ source "${REPO_ROOT}/scripts/lib/compact-tracker.sh"
12
+ }
13
+
14
+ @test "pre-compact-tracker approves and records pending compact state" {
15
+ local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/pre-compact-manual.json"
16
+ local session_id="compact-session-001"
17
+ local transcript="/tmp/onlooker-compact-transcript-${BATS_TEST_NUMBER}.jsonl"
18
+ local state_file="${ONLOOKER_COMPACT_TRACKERS_DIR}/${session_id}"
19
+
20
+ printf '%s\n' '{"type":"user","message":"hello"}' '{"type":"assistant","message":"world"}' >"$transcript"
21
+ local runtime_input="${BATS_TEST_TMPDIR}/pre-compact-input.json"
22
+ jq --arg path "$transcript" '.transcript_path = $path' "$fixture" >"$runtime_input"
23
+ rm -f "$state_file"
24
+
25
+ run bash -c "cat '${runtime_input}' | '${REPO_ROOT}/scripts/hooks/pre-compact-tracker.sh' 2>/dev/null"
26
+ [ "$status" -eq 0 ]
27
+ echo "$output" | jq -e '.decision == "approve"' >/dev/null
28
+
29
+ jq -e '.pending == true
30
+ and .trigger == "manual"
31
+ and .compact_count == 1
32
+ and (.tokens_before | type) == "number"' \
33
+ "$state_file" >/dev/null
34
+
35
+ rm -f "$transcript"
36
+ }
37
+
38
+ @test "context-compact-tracker emits session.compact and saves summary" {
39
+ local pre_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/pre-compact-manual.json"
40
+ local post_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/post-compact-manual.json"
41
+ local session_id="compact-session-001"
42
+ local transcript="/tmp/onlooker-compact-transcript-post-${BATS_TEST_NUMBER}.jsonl"
43
+ local state_file="${ONLOOKER_COMPACT_TRACKERS_DIR}/${session_id}"
44
+ local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/${session_id}.jsonl"
45
+ local summary_file="${ONLOOKER_SESSION_SUMMARIES_DIR}/${session_id}.jsonl"
46
+
47
+ printf '%s\n' '{"type":"user","message":"long context"}' >"$transcript"
48
+ local pre_input post_input
49
+ pre_input=$(jq --arg path "$transcript" '.transcript_path = $path' "$pre_fixture")
50
+ post_input=$(jq --arg path "$transcript" '.transcript_path = $path' "$post_fixture")
51
+
52
+ rm -f "$state_file" "$history_file" "$summary_file"
53
+ printf '%s' "$pre_input" | "${REPO_ROOT}/scripts/hooks/pre-compact-tracker.sh" >/dev/null 2>&1
54
+
55
+ local post_runtime="${BATS_TEST_TMPDIR}/post-compact-input.json"
56
+ printf '%s' "$post_input" >"$post_runtime"
57
+ run bash -c "cat '${post_runtime}' | '${REPO_ROOT}/scripts/hooks/context-compact-tracker.sh' 2>/dev/null"
58
+ [ "$status" -eq 0 ]
59
+
60
+ jq -e '.pending == false and .last_trigger == "manual"' "$state_file" >/dev/null
61
+ jq -e '.event_type == "session.compact"
62
+ and (.payload.tokens_before >= .payload.tokens_after)
63
+ and (.payload.compression_ratio | type) == "number"' \
64
+ "$history_file" >/dev/null
65
+ run jq -e '.compact_summary | length > 0' "$summary_file"
66
+ [ "$status" -eq 0 ]
67
+
68
+ rm -f "$transcript"
69
+ }
70
+
71
+ @test "compact_tracker_estimate_tokens estimates from string length" {
72
+ [ "$(compact_tracker_estimate_tokens "abcdefghij" false)" -eq 2 ]
73
+ }
@@ -10,6 +10,19 @@ setup_file() {
10
10
  [ "$status" -eq 0 ]
11
11
  }
12
12
 
13
+ @test "claude plugin versions match package.json" {
14
+ local pkg_ver
15
+ pkg_ver=$(jq -r '.version' "${REPO_ROOT}/package.json")
16
+
17
+ run jq -e --arg v "$pkg_ver" '.version == $v' "${REPO_ROOT}/.claude-plugin/plugin.json"
18
+ [ "$status" -eq 0 ]
19
+
20
+ run jq -e --arg v "$pkg_ver" \
21
+ '[.plugins[].version] | unique | length == 1 and .[0] == $v' \
22
+ "${REPO_ROOT}/.claude-plugin/marketplace.json"
23
+ [ "$status" -eq 0 ]
24
+ }
25
+
13
26
  @test "hooks.json wildcard matcher references tool-sequence-tracker" {
14
27
  run jq -e '.hooks.PreToolUse[0].matcher == "*"' "${REPO_ROOT}/hooks/hooks.json"
15
28
  [ "$status" -eq 0 ]
@@ -116,6 +129,38 @@ setup_file() {
116
129
  [ "$status" -eq 0 ]
117
130
  }
118
131
 
132
+ @test "hooks.json PreCompact references pre-compact-tracker for manual and auto" {
133
+ run jq -e '.hooks.PreCompact | length == 2' "${REPO_ROOT}/hooks/hooks.json"
134
+ [ "$status" -eq 0 ]
135
+
136
+ local hook_cmd
137
+ hook_cmd=$(jq -r '.hooks.PreCompact[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
138
+ [[ "$hook_cmd" == *pre-compact-tracker.sh ]]
139
+
140
+ run jq -e '.hooks.PreCompact[0].matcher == "manual" and .hooks.PreCompact[1].matcher == "auto"' \
141
+ "${REPO_ROOT}/hooks/hooks.json"
142
+ [ "$status" -eq 0 ]
143
+
144
+ local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
145
+ script_path="${script_path//\"/}"
146
+ run test -x "$script_path"
147
+ [ "$status" -eq 0 ]
148
+ }
149
+
150
+ @test "hooks.json PostCompact references context-compact-tracker for manual and auto" {
151
+ run jq -e '.hooks.PostCompact | length == 2' "${REPO_ROOT}/hooks/hooks.json"
152
+ [ "$status" -eq 0 ]
153
+
154
+ local hook_cmd
155
+ hook_cmd=$(jq -r '.hooks.PostCompact[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
156
+ [[ "$hook_cmd" == *context-compact-tracker.sh ]]
157
+
158
+ local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
159
+ script_path="${script_path//\"/}"
160
+ run test -x "$script_path"
161
+ [ "$status" -eq 0 ]
162
+ }
163
+
119
164
  @test "hooks.json SessionEnd references session-end-tracker" {
120
165
  run jq -e '.hooks.SessionEnd[0].matcher == "*"' "${REPO_ROOT}/hooks/hooks.json"
121
166
  [ "$status" -eq 0 ]
@@ -0,0 +1,8 @@
1
+ {
2
+ "session_id": "compact-session-001",
3
+ "transcript_path": "/tmp/compact-transcript.jsonl",
4
+ "cwd": "/project/repo",
5
+ "hook_event_name": "PostCompact",
6
+ "trigger": "manual",
7
+ "compact_summary": "The user implemented session hooks including SessionStart, SessionEnd, and UserPromptSubmit trackers for turn and duration telemetry."
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "session_id": "compact-session-001",
3
+ "transcript_path": "/tmp/compact-transcript.jsonl",
4
+ "cwd": "/project/repo",
5
+ "hook_event_name": "PreCompact",
6
+ "trigger": "manual",
7
+ "custom_instructions": "Focus on API changes"
8
+ }
@@ -1,132 +0,0 @@
1
- name: Publish npm Package
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*.*.*'
7
- # Manual trigger for emergency releases.
8
- workflow_dispatch:
9
- inputs:
10
- version:
11
- description: 'Version to publish (without v prefix)'
12
- required: true
13
- type: string
14
- tag:
15
- description: 'npm dist-tag (latest, beta, next)'
16
- required: true
17
- default: 'latest'
18
- type: choice
19
- options:
20
- - latest
21
- - beta
22
- - next
23
-
24
- permissions:
25
- contents: read
26
- id-token: write # for npm provenance
27
-
28
- jobs:
29
- validate:
30
- name: Validate
31
- runs-on: ubuntu-latest
32
- outputs:
33
- version: ${{ steps.version.outputs.value }}
34
- npm_tag: ${{ steps.tag.outputs.value }}
35
- is_prerelease: ${{ steps.prerelease.outputs.value }}
36
- steps:
37
- - uses: actions/checkout@v4
38
-
39
- - name: Resolve version
40
- id: version
41
- run: |
42
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
43
- echo "value=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
44
- else
45
- echo "value=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
46
- fi
47
-
48
- - name: Resolve npm tag
49
- id: tag
50
- run: |
51
- VERSION=${{ steps.version.outputs.value }}
52
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
53
- echo "value=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
54
- elif [[ "$VERSION" == *"-beta"* ]]; then
55
- echo "value=beta" >> "$GITHUB_OUTPUT"
56
- elif [[ "$VERSION" == *"-rc"* ]]; then
57
- echo "value=next" >> "$GITHUB_OUTPUT"
58
- else
59
- echo "value=latest" >> "$GITHUB_OUTPUT"
60
- fi
61
-
62
- - name: Check if prerelease
63
- id: prerelease
64
- run: |
65
- VERSION=${{ steps.version.outputs.value }}
66
- if [[ "$VERSION" == *"-"* ]]; then
67
- echo "value=true" >> "$GITHUB_OUTPUT"
68
- else
69
- echo "value=false" >> "$GITHUB_OUTPUT"
70
- fi
71
-
72
- - name: Verify package.json version matches
73
- run: |
74
- PKG_VERSION=$(node -p "require('./package.json').version")
75
- TAG_VERSION=${{ steps.version.outputs.value }}
76
- if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
77
- echo "ERROR: package.json version ($PKG_VERSION) does not match tag ($TAG_VERSION)"
78
- exit 1
79
- fi
80
- publish:
81
- name: Publish to npm
82
- runs-on: ubuntu-latest
83
- needs: [validate]
84
- environment: npm-publish
85
- steps:
86
- - uses: actions/checkout@v4
87
-
88
- - uses: actions/setup-node@v4
89
- with:
90
- node-version: '22'
91
- registry-url: 'https://registry.npmjs.org'
92
- cache: npm
93
-
94
- - run: npm ci
95
-
96
- - name: Publish
97
- run: |
98
- npm publish \
99
- --tag ${{ needs.validate.outputs.npm_tag }} \
100
- --access public \
101
- --provenance
102
- env:
103
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
104
-
105
- release:
106
- name: Create GitHub Release
107
- runs-on: ubuntu-latest
108
- needs: [validate, publish]
109
- permissions:
110
- contents: write
111
- steps:
112
- - uses: actions/checkout@v4
113
-
114
- - name: Extract changelog entry
115
- id: changelog
116
- run: |
117
- VERSION=${{ needs.validate.outputs.version }}
118
- NOTES=$(awk "/^## \[?${VERSION}\]?/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md)
119
- if [ -z "$NOTES" ]; then
120
- NOTES="See CHANGELOG.md for details."
121
- fi
122
- echo "notes<<EOF" >> "$GITHUB_OUTPUT"
123
- echo "$NOTES" >> "$GITHUB_OUTPUT"
124
- echo "EOF" >> "$GITHUB_OUTPUT"
125
-
126
- - name: Create GitHub release
127
- uses: softprops/action-gh-release@v2
128
- with:
129
- name: v${{ needs.validate.outputs.version }}
130
- body: ${{ steps.changelog.outputs.notes }}
131
- prerelease: ${{ needs.validate.outputs.is_prerelease == 'true' }}
132
- draft: false