@onlooker-community/ecosystem 0.6.0 → 0.7.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/release.yml +47 -3
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +26 -0
- package/hooks/hooks.json +40 -0
- package/package.json +1 -1
- package/release-please-config.json +1 -6
- package/scripts/hooks/context-compact-tracker.sh +37 -0
- package/scripts/hooks/pre-compact-tracker.sh +36 -0
- package/scripts/lib/compact-tracker.sh +197 -0
- package/test/bats/compact-tracker.bats +73 -0
- package/test/bats/config.bats +45 -0
- package/test/fixtures/hook-inputs/post-compact-manual.json +8 -0
- package/test/fixtures/hook-inputs/pre-compact-manual.json +8 -0
- package/.github/workflows/publish-npm.yml +0 -132
|
@@ -11,19 +11,63 @@ on:
|
|
|
11
11
|
permissions:
|
|
12
12
|
contents: write
|
|
13
13
|
pull-requests: write
|
|
14
|
+
# Required for npm publish --provenance (OIDC attestation)
|
|
15
|
+
id-token: write
|
|
14
16
|
|
|
15
17
|
jobs:
|
|
16
18
|
release-please:
|
|
17
19
|
name: Release Please
|
|
18
20
|
runs-on: ubuntu-latest
|
|
19
21
|
steps:
|
|
20
|
-
-
|
|
22
|
+
- name: Create Release PR
|
|
23
|
+
id: release
|
|
24
|
+
uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
|
|
21
25
|
with:
|
|
22
|
-
release-type
|
|
26
|
+
# Do not set release-type here — it overrides release-please-config.json
|
|
27
|
+
# and skips extra-files (plugin.json, marketplace.json). Release type
|
|
28
|
+
# is defined per-package in release-please-config.json.
|
|
29
|
+
config-file: release-please-config.json
|
|
30
|
+
manifest-file: .release-please-manifest.json
|
|
23
31
|
# Use a PAT instead of GITHUB_TOKEN so the release PR triggers
|
|
24
32
|
# downstream workflows (CI, etc.). Events caused by GITHUB_TOKEN
|
|
25
33
|
# deliberately do not fan out to other workflows; that policy
|
|
26
34
|
# leaves release PRs unchecked. Set RELEASE_PLEASE_PAT to a
|
|
27
35
|
# fine-grained token with Contents:write + Pull requests:write
|
|
28
36
|
# scoped to this repo.
|
|
29
|
-
token: ${{ secrets.RELEASE_PLEASE_PAT }}
|
|
37
|
+
token: ${{ secrets.RELEASE_PLEASE_PAT }}
|
|
38
|
+
|
|
39
|
+
- uses: actions/checkout@v6
|
|
40
|
+
if: ${{ steps.release.outputs.releases_created == 'true'}}
|
|
41
|
+
|
|
42
|
+
- uses: actions/setup-node@v6
|
|
43
|
+
if: ${{ steps.release.outputs.releases_created == 'true'}}
|
|
44
|
+
with:
|
|
45
|
+
node-version: '22'
|
|
46
|
+
cache: npm
|
|
47
|
+
|
|
48
|
+
- name: Publish tools packages to npm
|
|
49
|
+
if: ${{ steps.release.outputs.releases_created == 'true' }}
|
|
50
|
+
env:
|
|
51
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
52
|
+
PATHS_RELEASED: ${{ steps.release.outputs.paths_released }}
|
|
53
|
+
run: |
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
if [[ -z "${NPM_TOKEN}" ]]; then
|
|
56
|
+
echo "NPM_TOKEN secret is required to publish to npm." >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> "${HOME}/.npmrc"
|
|
60
|
+
npm ci
|
|
61
|
+
# paths_released is JSON (e.g. ["."], not bare paths)
|
|
62
|
+
mapfile -t release_paths < <(echo "${PATHS_RELEASED}" | jq -r '.[]')
|
|
63
|
+
if [[ "${#release_paths[@]}" -eq 0 ]]; then
|
|
64
|
+
echo "No paths in paths_released; skipping npm publish." >&2
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
for path in "${release_paths[@]}"; do
|
|
68
|
+
if [[ "$path" == "." ]]; then
|
|
69
|
+
npm publish --access public --provenance
|
|
70
|
+
else
|
|
71
|
+
(cd "$path" && npm publish --access public --provenance)
|
|
72
|
+
fi
|
|
73
|
+
done
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.1](https://github.com/onlooker-community/ecosystem/compare/v0.7.0...v0.7.1) (2026-05-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** parse release-please paths_released JSON for npm publish ([749e1a0](https://github.com/onlooker-community/ecosystem/commit/749e1a02b563f37f81a8da21fc3f6e10e179314a))
|
|
9
|
+
|
|
10
|
+
## [0.7.0](https://github.com/onlooker-community/ecosystem/compare/v0.6.0...v0.7.0) (2026-05-22)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **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))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **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))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Chores
|
|
24
|
+
|
|
25
|
+
* enhance release workflow for npm packages ([3b37b56](https://github.com/onlooker-community/ecosystem/commit/3b37b56270a13fec95c2cd6ee8816ba5725a680a))
|
|
26
|
+
* remove npm publish workflow ([5f29c33](https://github.com/onlooker-community/ecosystem/commit/5f29c33baca8c10289d48f8126dc6eb4b4fe8153))
|
|
27
|
+
* remove test job from npm publish workflow ([f25bf9d](https://github.com/onlooker-community/ecosystem/commit/f25bf9d65fbe5066fe9963ce8d075fe81dc8e5c9))
|
|
28
|
+
|
|
3
29
|
## [0.6.0](https://github.com/onlooker-community/ecosystem/compare/v0.5.0...v0.6.0) (2026-05-22)
|
|
4
30
|
|
|
5
31
|
|
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
|
@@ -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
|
|
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
|
+
}
|
package/test/bats/config.bats
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|