@onlooker-community/ecosystem 0.34.0 → 0.43.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.
- package/.claude/skills/writing-tests/SKILL.md +27 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/release.yml +14 -14
- package/.release-please-manifest.json +7 -7
- package/AGENTS.md +116 -0
- package/CHANGELOG.md +175 -0
- package/CLAUDE.md +5 -0
- package/docs/lesson-promotion-pipeline.md +6 -6
- package/docs/superpowers/plans/2026-08-10-lesson-confirmation.md +1273 -0
- package/docs/superpowers/plans/2026-08-11-lesson-judging.md +1232 -0
- package/docs/superpowers/plans/2026-08-11-lesson-unconfirm.md +458 -0
- package/docs/superpowers/plans/2026-08-12-author-key.md +567 -0
- package/docs/superpowers/plans/2026-08-13-approved-pool.md +747 -0
- package/docs/superpowers/plans/2026-08-13-librarian-cluster.md +392 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-consumers.md +1374 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-schema.md +277 -0
- package/docs/superpowers/plans/2026-08-16-cartographer-undocumented-entity.md +741 -0
- package/docs/superpowers/specs/2026-08-10-lesson-confirmation-design.md +213 -0
- package/docs/superpowers/specs/2026-08-11-lesson-judging-design.md +267 -0
- package/docs/superpowers/specs/2026-08-11-lesson-unconfirm-design.md +152 -0
- package/docs/superpowers/specs/2026-08-12-author-key-design.md +317 -0
- package/docs/superpowers/specs/2026-08-13-approved-pool-design.md +232 -0
- package/docs/superpowers/specs/2026-08-14-criterion-scores-design.md +254 -0
- package/docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md +249 -0
- package/package.json +2 -2
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +6 -0
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +54 -0
- package/plugins/cartographer/README.md +56 -1
- package/plugins/cartographer/config.json +7 -1
- package/plugins/cartographer/scripts/hooks/cartographer-post-write.sh +4 -4
- package/plugins/cartographer/scripts/hooks/cartographer-session-start.sh +7 -4
- package/plugins/cartographer/scripts/lib/cartographer-collect.sh +8 -1
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +26 -0
- package/plugins/cartographer/scripts/lib/cartographer-events.sh +90 -0
- package/plugins/cartographer/scripts/lib/cartographer-filter.sh +105 -0
- package/plugins/cartographer/scripts/lib/cartographer-omission.sh +130 -0
- package/plugins/cartographer/scripts/lib/cartographer-resolve.sh +132 -0
- package/plugins/cartographer/scripts/run-audit.sh +136 -53
- package/plugins/cartographer/skills/cartographer/SKILL.md +33 -9
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-emit.sh +2 -1
- package/plugins/historian/.claude-plugin/plugin.json +1 -1
- package/plugins/historian/CHANGELOG.md +7 -0
- package/plugins/historian/scripts/lib/historian-emit.sh +2 -1
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +71 -0
- package/plugins/librarian/config.json +30 -1
- package/plugins/librarian/docs/adr/002-agent-definitions-are-shared-assets.md +82 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +28 -0
- package/plugins/librarian/scripts/hooks/librarian-session-start.sh +42 -12
- package/plugins/librarian/scripts/lib/librarian-author-key.sh +279 -0
- package/plugins/librarian/scripts/lib/librarian-cli.sh +346 -2
- package/plugins/librarian/scripts/lib/librarian-emit.sh +2 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-judge.sh +349 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-promote.sh +225 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-review.sh +312 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-rubric.sh +56 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +82 -13
- package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +6 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +131 -62
- package/plugins/librarian/skills/librarian/SKILL.md +98 -1
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +63 -0
- package/plugins/tribunal/agents/tribunal-judge-adversarial.md +34 -0
- package/plugins/tribunal/agents/tribunal-judge-security.md +33 -0
- package/plugins/tribunal/agents/tribunal-judge-standard.md +12 -0
- package/plugins/tribunal/config.json +1 -0
- package/plugins/tribunal/scripts/lib/tribunal-aggregate.sh +154 -9
- package/plugins/tribunal/scripts/lib/tribunal-gate.sh +195 -16
- package/plugins/tribunal/skills/tribunal/SKILL.md +5 -3
- package/scripts/lib/prompt-rules.sh +6 -1
- package/test/bats/archivist-inject.bats +1 -1
- package/test/bats/assayer-extract.bats +2 -2
- package/test/bats/bursar-session-start.bats +3 -3
- package/test/bats/cartographer-config.bats +43 -0
- package/test/bats/cartographer-events.bats +267 -0
- package/test/bats/cartographer-filter.bats +169 -0
- package/test/bats/cartographer-lock.bats +3 -3
- package/test/bats/cartographer-omission.bats +202 -0
- package/test/bats/cartographer-resolve.bats +287 -0
- package/test/bats/cartographer-run-audit.bats +325 -0
- package/test/bats/compass-sanitizer.bats +11 -11
- package/test/bats/compass-transcript.bats +2 -2
- package/test/bats/config.bats +15 -15
- package/test/bats/curator-session-start.bats +10 -3
- package/test/bats/emit-payload-default.bats +52 -0
- package/test/bats/governor-ledger.bats +1 -1
- package/test/bats/historian-prompt-submit.bats +1 -1
- package/test/bats/inspector-post-write-hook.bats +4 -4
- package/test/bats/librarian-author-key.bats +477 -0
- package/test/bats/librarian-cli.bats +16 -16
- package/test/bats/librarian-lesson-judge.bats +884 -0
- package/test/bats/librarian-lesson-promote.bats +552 -0
- package/test/bats/librarian-lesson-review.bats +900 -0
- package/test/bats/librarian-lesson-transform.bats +17 -3
- package/test/bats/librarian-session-end.bats +161 -0
- package/test/bats/librarian-session-start.bats +36 -2
- package/test/bats/lineage-config.bats +1 -1
- package/test/bats/lineage-redact.bats +5 -5
- package/test/bats/session-tracker.bats +4 -4
- package/test/bats/tribunal-aggregate.bats +354 -1
- package/test/bats/tribunal-gate.bats +631 -0
- package/test/bats/tribunal-judge-agents.bats +119 -0
- package/test/bats/tribunal-jury.bats +1 -1
- package/test/bats/turn-tracker.bats +1 -1
- package/test/bats/warden-sanitizer.bats +3 -3
- package/test/bats/worktree-tracker.bats +2 -2
- package/test/node/lesson-validate-agreement.test.mjs +35 -6
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# Covers the resolution loop: a finding whose drift is gone stops rendering.
|
|
4
|
+
#
|
|
5
|
+
# The record has carried resolved:false since the plugin shipped and nothing
|
|
6
|
+
# ever flipped it, so fixing the drift a finding reported did not retire the
|
|
7
|
+
# finding — it rendered forever (ecosystem-nhi).
|
|
8
|
+
#
|
|
9
|
+
# Absence of a finding from a run is the evidence used, which is only sound when
|
|
10
|
+
# the run looked everywhere. The guard tests below are the substance of this
|
|
11
|
+
# file: getting resolution wrong in the permissive direction silently hides live
|
|
12
|
+
# findings, which is worse than the stale ones being retired.
|
|
13
|
+
|
|
14
|
+
setup() {
|
|
15
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
16
|
+
setup_test_env
|
|
17
|
+
|
|
18
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/cartographer"
|
|
19
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
20
|
+
# shellcheck disable=SC1091
|
|
21
|
+
source "${PLUGIN_ROOT}/scripts/lib/cartographer-resolve.sh"
|
|
22
|
+
|
|
23
|
+
FINDINGS_DIR="${BATS_TEST_TMPDIR}/findings"
|
|
24
|
+
mkdir -p "$FINDINGS_DIR"
|
|
25
|
+
|
|
26
|
+
# A fixed clock. Real timestamps are epoch seconds, and an audit that starts
|
|
27
|
+
# and finishes inside one second would otherwise make "before the run" and
|
|
28
|
+
# "during the run" indistinguishable.
|
|
29
|
+
AUDIT_START=2000
|
|
30
|
+
|
|
31
|
+
# Where the injected emitter records what it was asked to announce. The sweep
|
|
32
|
+
# runs inside a command substitution in production, so a stub that set a
|
|
33
|
+
# variable would lose it to the subshell — a file survives, which is exactly
|
|
34
|
+
# why the real emitter appends to the event log rather than returning.
|
|
35
|
+
ANNOUNCED="${BATS_TEST_TMPDIR}/announced"
|
|
36
|
+
: > "$ANNOUNCED"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_announce() { printf '%s\n' "$1" >> "$ANNOUNCED"; }
|
|
40
|
+
|
|
41
|
+
# Deliberately writes to stdout, which would corrupt the count the sweep prints
|
|
42
|
+
# if the call site did not redirect it.
|
|
43
|
+
_noisy_announce() { printf '%s\n' "$1" >> "$ANNOUNCED"; printf 'NOISE'; }
|
|
44
|
+
|
|
45
|
+
_announced() { cat "$ANNOUNCED" 2>/dev/null; }
|
|
46
|
+
|
|
47
|
+
# Seed a finding record in the shape run_emit writes.
|
|
48
|
+
_seed() {
|
|
49
|
+
local hash="$1" last_seen="$2" resolved="${3:-false}"
|
|
50
|
+
jq -n --arg h "$hash" --argjson ls "$last_seen" --argjson r "$resolved" \
|
|
51
|
+
'{finding_hash: $h, type: "undocumented_entity", severity: "warning",
|
|
52
|
+
file_a: "CLAUDE.md", file_b: null, description: "d", suggested_fix: "f",
|
|
53
|
+
first_seen_at: 1000, last_seen_at: $ls, resolved: $r}' \
|
|
54
|
+
> "${FINDINGS_DIR}/${hash}.json"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_field() {
|
|
58
|
+
jq -r ".$2" "${FINDINGS_DIR}/$1.json"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@test "a finding not observed this run is resolved" {
|
|
62
|
+
_seed stale 1500
|
|
63
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
64
|
+
[ "$output" = "1" ] || return 1
|
|
65
|
+
[ "$(_field stale resolved)" = "true" ]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@test "a finding observed this run is left open" {
|
|
69
|
+
_seed fresh 2500
|
|
70
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
71
|
+
[ "$output" = "0" ] || return 1
|
|
72
|
+
[ "$(_field fresh resolved)" = "false" ]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@test "resolution stamps resolved_at" {
|
|
76
|
+
_seed stale 1500
|
|
77
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 4242 >/dev/null
|
|
78
|
+
[ "$(_field stale resolved_at)" = "4242" ]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Strict <: a record last seen exactly at the cutoff is ambiguous, and the safe
|
|
82
|
+
# reading is that it was observed. A stale finding is visible and correctable;
|
|
83
|
+
# one wrongly retired is silent.
|
|
84
|
+
@test "a finding last seen exactly at the cutoff is left open" {
|
|
85
|
+
_seed boundary "$AUDIT_START"
|
|
86
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
87
|
+
[ "$output" = "0" ] || return 1
|
|
88
|
+
[ "$(_field boundary resolved)" = "false" ]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@test "an already-resolved finding is not counted again" {
|
|
92
|
+
_seed old 1500 true
|
|
93
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
94
|
+
[ "$output" = "0" ]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@test "resolves only the absent findings in a mixed store" {
|
|
98
|
+
_seed gone 1500
|
|
99
|
+
_seed here 2500
|
|
100
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
101
|
+
[ "$output" = "1" ] || return 1
|
|
102
|
+
[ "$(_field gone resolved)" = "true" ] || return 1
|
|
103
|
+
[ "$(_field here resolved)" = "false" ]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ── The guards ────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
# A targeted post-write audit evaluates one file, so nearly every stored finding
|
|
109
|
+
# is absent for reasons unrelated to being fixed. Without this it would wipe the
|
|
110
|
+
# store on every edit.
|
|
111
|
+
@test "a targeted audit resolves nothing" {
|
|
112
|
+
_seed stale 1500
|
|
113
|
+
run cartographer_resolve_absent_findings \
|
|
114
|
+
"$FINDINGS_DIR" "$AUDIT_START" "/repo/CLAUDE.md" 0
|
|
115
|
+
[ "$output" = "0" ] || return 1
|
|
116
|
+
[ "$(_field stale resolved)" = "false" ]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# A phase that timed out contributes no findings, which looks identical to its
|
|
120
|
+
# findings being gone. A partial run is not evidence of resolution.
|
|
121
|
+
@test "a run with a failed phase resolves nothing" {
|
|
122
|
+
_seed stale 1500
|
|
123
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 1
|
|
124
|
+
[ "$output" = "0" ] || return 1
|
|
125
|
+
[ "$(_field stale resolved)" = "false" ]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@test "both guards together still resolve nothing" {
|
|
129
|
+
_seed stale 1500
|
|
130
|
+
run cartographer_resolve_absent_findings \
|
|
131
|
+
"$FINDINGS_DIR" "$AUDIT_START" "/repo/CLAUDE.md" 2
|
|
132
|
+
[ "$output" = "0" ]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# ── Reopening ─────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
# The dedup sentinel outlives resolution, so reintroduced drift comes back as a
|
|
138
|
+
# KNOWN finding, not a new one. If refreshing did not clear resolved, the
|
|
139
|
+
# renderer would keep hiding it — live drift, permanently invisible.
|
|
140
|
+
@test "re-observing a resolved finding reopens it" {
|
|
141
|
+
_seed recurring 1500 true
|
|
142
|
+
cartographer_refresh_finding "${FINDINGS_DIR}/recurring.json" 3000
|
|
143
|
+
[ "$(_field recurring resolved)" = "false" ] || return 1
|
|
144
|
+
[ "$(_field recurring last_seen_at)" = "3000" ]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@test "reopening clears the resolved_at stamp" {
|
|
148
|
+
_seed recurring 1500
|
|
149
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 4242 >/dev/null
|
|
150
|
+
[ "$(_field recurring resolved_at)" = "4242" ] || return 1
|
|
151
|
+
cartographer_refresh_finding "${FINDINGS_DIR}/recurring.json" 5000
|
|
152
|
+
[ "$(_field recurring resolved_at)" = "null" ]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@test "a reopened finding is resolvable again once the drift goes" {
|
|
156
|
+
_seed recurring 1500 true
|
|
157
|
+
cartographer_refresh_finding "${FINDINGS_DIR}/recurring.json" 3000
|
|
158
|
+
# A later audit that starts after 3000 and does not observe it.
|
|
159
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" 4000 "" 0
|
|
160
|
+
[ "$output" = "1" ] || return 1
|
|
161
|
+
[ "$(_field recurring resolved)" = "true" ]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@test "refresh keeps first_seen_at, so recurrence does not rewrite history" {
|
|
165
|
+
_seed recurring 1500 true
|
|
166
|
+
cartographer_refresh_finding "${FINDINGS_DIR}/recurring.json" 3000
|
|
167
|
+
[ "$(_field recurring first_seen_at)" = "1000" ]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@test "refresh rejects missing arguments and absent files" {
|
|
171
|
+
run cartographer_refresh_finding "" 3000
|
|
172
|
+
[ "$status" -ne 0 ] || return 1
|
|
173
|
+
run cartographer_refresh_finding "${FINDINGS_DIR}/nope.json" 3000
|
|
174
|
+
[ "$status" -ne 0 ]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# ── Edges ─────────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
@test "an empty store resolves nothing" {
|
|
180
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
181
|
+
[ "$output" = "0" ]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@test "a missing findings dir is not an error" {
|
|
185
|
+
run cartographer_resolve_absent_findings \
|
|
186
|
+
"${BATS_TEST_TMPDIR}/nope" "$AUDIT_START" "" 0
|
|
187
|
+
[ "$status" -eq 0 ] || return 1
|
|
188
|
+
[ "$output" = "0" ]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@test "a record with no last_seen_at is treated as unobserved" {
|
|
192
|
+
jq -n '{finding_hash: "bare", resolved: false}' > "${FINDINGS_DIR}/bare.json"
|
|
193
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
194
|
+
[ "$output" = "1" ]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@test "unparsable json is skipped rather than fatal" {
|
|
198
|
+
printf 'not json at all' > "${FINDINGS_DIR}/broken.json"
|
|
199
|
+
_seed stale 1500
|
|
200
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
201
|
+
[ "$status" -eq 0 ] || return 1
|
|
202
|
+
[ "$output" = "1" ]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@test "missing required arguments are rejected" {
|
|
206
|
+
run cartographer_resolve_absent_findings "" "$AUDIT_START" "" 0
|
|
207
|
+
[ "$status" -ne 0 ] || return 1
|
|
208
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "" "" 0
|
|
209
|
+
[ "$status" -ne 0 ]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@test "the record keeps its other fields when resolved" {
|
|
213
|
+
_seed stale 1500
|
|
214
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 >/dev/null
|
|
215
|
+
jq -e '.finding_hash == "stale" and .type == "undocumented_entity"
|
|
216
|
+
and .first_seen_at == 1000 and .description == "d"' \
|
|
217
|
+
"${FINDINGS_DIR}/stale.json" >/dev/null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ── Resolution reaching the bus (ecosystem-w2i) ──────────────────────────────
|
|
222
|
+
|
|
223
|
+
# The guards are the substance here. A targeted or partial run sees too little
|
|
224
|
+
# to treat absence as evidence, so it must announce nothing at all — announcing
|
|
225
|
+
# a resolution the run did not establish tells every consumer to close a finding
|
|
226
|
+
# that is still live.
|
|
227
|
+
@test "the sound-resolution predicate holds only for a full, complete run" {
|
|
228
|
+
cartographer_resolution_is_sound "" 0 || return 1
|
|
229
|
+
! cartographer_resolution_is_sound "CLAUDE.md" 0 || return 1
|
|
230
|
+
! cartographer_resolution_is_sound "" 1 || return 1
|
|
231
|
+
! cartographer_resolution_is_sound "CLAUDE.md" 2
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@test "a retired finding is announced through the injected emitter" {
|
|
235
|
+
_seed stale 1500
|
|
236
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 "" _announce >/dev/null
|
|
237
|
+
[ "$(_announced)" = "stale" ]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@test "every retired finding is announced, not just the first" {
|
|
241
|
+
_seed one 1500
|
|
242
|
+
_seed two 1600
|
|
243
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 "" _announce >/dev/null
|
|
244
|
+
[ "$(_announced | sort | tr '\n' ' ')" = "one two " ]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@test "a targeted audit announces nothing" {
|
|
248
|
+
_seed stale 1500
|
|
249
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "CLAUDE.md" 0 "" _announce >/dev/null
|
|
250
|
+
[ -z "$(_announced)" ]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@test "a partial run announces nothing" {
|
|
254
|
+
_seed stale 1500
|
|
255
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 1 "" _announce >/dev/null
|
|
256
|
+
[ -z "$(_announced)" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@test "a finding still observed this run is not announced" {
|
|
260
|
+
_seed fresh 2500
|
|
261
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 "" _announce >/dev/null
|
|
262
|
+
[ -z "$(_announced)" ]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Delivery is at-least-once but re-announcing a finding that was already retired
|
|
266
|
+
# would report a closure that did not happen this run.
|
|
267
|
+
@test "an already-resolved finding is not announced again" {
|
|
268
|
+
_seed done 1500 true
|
|
269
|
+
cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 "" _announce >/dev/null
|
|
270
|
+
[ -z "$(_announced)" ]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@test "the sweep still works with no emitter injected" {
|
|
274
|
+
_seed stale 1500
|
|
275
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0
|
|
276
|
+
[ "$output" = "1" ] || return 1
|
|
277
|
+
[ "$(_field stale resolved)" = "true" ]
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# The sweep returns its count on stdout and the caller reads it through a
|
|
281
|
+
# command substitution, so anything the emitter prints lands in that number.
|
|
282
|
+
@test "a chatty emitter cannot corrupt the count" {
|
|
283
|
+
_seed one 1500
|
|
284
|
+
_seed two 1600
|
|
285
|
+
run cartographer_resolve_absent_findings "$FINDINGS_DIR" "$AUDIT_START" "" 0 "" _noisy_announce
|
|
286
|
+
[ "$output" = "2" ]
|
|
287
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# End-to-end coverage of the audit orchestrator.
|
|
4
|
+
#
|
|
5
|
+
# Nothing drove run-audit.sh before this file, and that absence is why two
|
|
6
|
+
# separate defects lived in it undetected: a crash on macOS bash 3.2 whenever
|
|
7
|
+
# the exclude list was empty (ecosystem-3xf), and config never being loaded at
|
|
8
|
+
# all, so every top-level setting was silently ignored (ecosystem-88v). Both are
|
|
9
|
+
# invisible to the unit tests, which exercise the libraries the orchestrator
|
|
10
|
+
# calls rather than the orchestrator itself.
|
|
11
|
+
#
|
|
12
|
+
# The audit shells out to `claude` for its three analysis phases. The stub below
|
|
13
|
+
# returns an empty findings array, which is enough: what is under test here is
|
|
14
|
+
# whether the orchestrator resolves and propagates its own configuration, not
|
|
15
|
+
# what a model says about the corpus.
|
|
16
|
+
|
|
17
|
+
setup() {
|
|
18
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
19
|
+
setup_test_env
|
|
20
|
+
|
|
21
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/cartographer"
|
|
22
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
23
|
+
AUDIT="${PLUGIN_ROOT}/scripts/run-audit.sh"
|
|
24
|
+
|
|
25
|
+
FIXTURE_REPO="${BATS_TEST_TMPDIR}/repo"
|
|
26
|
+
mkdir -p "${FIXTURE_REPO}/sub" "${FIXTURE_REPO}/.claude"
|
|
27
|
+
# Path-like tokens and a global counterpart so all three LLM analyzers have
|
|
28
|
+
# something to chew on. Without them stale_ref and scope_collision
|
|
29
|
+
# short-circuit before calling the model, and the call counts below could
|
|
30
|
+
# not tell "skipped by the filter" apart from "had nothing to do".
|
|
31
|
+
printf '# Root\nAlways read scripts/lib/config-loader.sh first.\nNever use src/legacy/gone.ts.\n' \
|
|
32
|
+
> "${FIXTURE_REPO}/CLAUDE.md"
|
|
33
|
+
printf '# Sub\nSee plugins/tribunal/README.md for details.\n' > "${FIXTURE_REPO}/sub/CLAUDE.md"
|
|
34
|
+
mkdir -p "${CLAUDE_HOME}"
|
|
35
|
+
printf '# Global\nAlways prefer tabs.\n' > "${CLAUDE_HOME}/CLAUDE.md"
|
|
36
|
+
|
|
37
|
+
export CARTOGRAPHER_DIR="${BATS_TEST_TMPDIR}/cartographer"
|
|
38
|
+
mkdir -p "$CARTOGRAPHER_DIR"
|
|
39
|
+
AUDIT_LOG="${CARTOGRAPHER_DIR}/audit.log"
|
|
40
|
+
|
|
41
|
+
# The stub records each invocation so tests can assert on how many model calls
|
|
42
|
+
# an audit actually made — the only honest evidence that a filter skipped an
|
|
43
|
+
# analyzer rather than merely discarding its output.
|
|
44
|
+
export CLAUDE_CALL_LOG="${BATS_TEST_TMPDIR}/claude-calls"
|
|
45
|
+
: > "$CLAUDE_CALL_LOG"
|
|
46
|
+
|
|
47
|
+
STUB_BIN="${BATS_TEST_TMPDIR}/bin"
|
|
48
|
+
mkdir -p "$STUB_BIN"
|
|
49
|
+
cat > "${STUB_BIN}/claude" <<'STUB'
|
|
50
|
+
#!/usr/bin/env bash
|
|
51
|
+
cat >/dev/null
|
|
52
|
+
echo call >> "$CLAUDE_CALL_LOG"
|
|
53
|
+
printf '[]'
|
|
54
|
+
STUB
|
|
55
|
+
chmod +x "${STUB_BIN}/claude"
|
|
56
|
+
export PATH="${STUB_BIN}:${PATH}"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
EVENTS_LOG_PATH() { printf '%s' "${ONLOOKER_DIR}/logs/onlooker-events.jsonl"; }
|
|
60
|
+
|
|
61
|
+
# Seed a finding from an earlier run. last_seen_at sits far in the past, so the
|
|
62
|
+
# current audit — whose stub returns no findings — will not observe it.
|
|
63
|
+
_seed_stored_finding() {
|
|
64
|
+
local hash="$1" last_seen="${2:-1000}"
|
|
65
|
+
mkdir -p "${CARTOGRAPHER_DIR}/findings"
|
|
66
|
+
jq -n --arg h "$hash" --argjson ls "$last_seen" \
|
|
67
|
+
'{finding_hash:$h, type:"undocumented_entity", severity:"warning",
|
|
68
|
+
file_a:"CLAUDE.md", file_b:null, description:"d", suggested_fix:"f",
|
|
69
|
+
first_seen_at:500, last_seen_at:$ls, resolved:false}' \
|
|
70
|
+
> "${CARTOGRAPHER_DIR}/findings/${hash}.json"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_resolved_event_count() {
|
|
74
|
+
local n
|
|
75
|
+
n=$(grep -c '"event_type":"cartographer.issue.resolved"' "$(EVENTS_LOG_PATH)" 2>/dev/null) || n=0
|
|
76
|
+
printf '%s' "$n"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_last_audit_complete() {
|
|
80
|
+
grep '"event_type":"cartographer.audit.complete"' "$(EVENTS_LOG_PATH)" 2>/dev/null | tail -n 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_llm_calls() {
|
|
84
|
+
wc -l < "$CLAUDE_CALL_LOG" | tr -d ' '
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_settings() {
|
|
88
|
+
printf '%s' "$1" > "${FIXTURE_REPO}/.claude/settings.json"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_run_audit() {
|
|
92
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@test "a full audit completes and records the run" {
|
|
96
|
+
run _run_audit
|
|
97
|
+
[ "$status" -eq 0 ] || return 1
|
|
98
|
+
grep -q 'completed successfully' "$AUDIT_LOG" || return 1
|
|
99
|
+
[ -f "${CARTOGRAPHER_DIR}/last_audit_at" ]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ── Config actually reaches the orchestrator ─────────────────────────────────
|
|
103
|
+
|
|
104
|
+
# exclude_paths is the setting that stings: run_discover hands it straight to
|
|
105
|
+
# cartographer_collect_files, so ignoring it means the discovery walk always
|
|
106
|
+
# used the shipped defaults no matter what the user configured, quietly breaking
|
|
107
|
+
# the replace-not-merge contract in the plugin's own ADR-004.
|
|
108
|
+
@test "repo exclude_paths narrows the discovery walk" {
|
|
109
|
+
_settings '{"cartographer": {"exclude_paths": ["sub"]}}'
|
|
110
|
+
_run_audit
|
|
111
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@test "without an exclude override both instruction files are discovered" {
|
|
115
|
+
_run_audit
|
|
116
|
+
grep -q 'phase=discover files=2' "$AUDIT_LOG"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# The orchestrator warns when total_timeout_seconds cannot cover three phases.
|
|
120
|
+
# The warning only fires if the CONFIGURED phase timeout was read: at the 60s
|
|
121
|
+
# default the shipped 600s total is comfortably sufficient and nothing is
|
|
122
|
+
# logged, so its presence is proof the accessor saw 999 rather than the default.
|
|
123
|
+
@test "phase_timeout_seconds is read from config, not the accessor default" {
|
|
124
|
+
_settings '{"cartographer": {"phase_timeout_seconds": 999}}'
|
|
125
|
+
_run_audit
|
|
126
|
+
grep -q 'total_timeout_seconds=600 is less than 3× phase_timeout_seconds=999' "$AUDIT_LOG"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@test "no warning at the shipped defaults" {
|
|
130
|
+
_run_audit
|
|
131
|
+
run grep -c 'is less than 3×' "$AUDIT_LOG"
|
|
132
|
+
[ "$output" = "0" ]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@test "user-level settings reach the orchestrator too" {
|
|
136
|
+
mkdir -p "${CLAUDE_HOME}"
|
|
137
|
+
printf '%s' '{"cartographer": {"exclude_paths": ["sub"]}}' \
|
|
138
|
+
> "${CLAUDE_HOME}/settings.json"
|
|
139
|
+
_run_audit
|
|
140
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ── The log stays readable ───────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
# run-audit.sh never exported PLUGIN_ROOT, so every analysis sub-shell that
|
|
146
|
+
# sourced cartographer-config.sh resolved config-loader.sh against an empty
|
|
147
|
+
# prefix and died, appending three lines of noise per phase to audit.log on
|
|
148
|
+
# every audit. Nobody read it because the analyzers took their settings as
|
|
149
|
+
# parameters and kept working.
|
|
150
|
+
@test "the audit log carries no config-loader errors" {
|
|
151
|
+
_run_audit
|
|
152
|
+
run grep -cE 'No such file or directory|command not found' "$AUDIT_LOG"
|
|
153
|
+
[ "$output" = "0" ]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@test "the audit log carries no unbound variable errors" {
|
|
157
|
+
_settings '{"cartographer": {"exclude_paths": []}}'
|
|
158
|
+
_run_audit
|
|
159
|
+
run grep -c 'unbound variable' "$AUDIT_LOG"
|
|
160
|
+
[ "$output" = "0" ]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# An empty exclude list is a legitimate configuration, and expanding a genuinely
|
|
164
|
+
# empty array under `set -u` is an unbound-variable error on bash 3.2 — the
|
|
165
|
+
# macOS system bash, and what #!/usr/bin/env bash resolves to there. This is the
|
|
166
|
+
# ecosystem-3xf crash, which Linux CI could never have seen.
|
|
167
|
+
@test "an empty exclude list does not abort the audit" {
|
|
168
|
+
_settings '{"cartographer": {"exclude_paths": []}}'
|
|
169
|
+
run _run_audit
|
|
170
|
+
[ "$status" -eq 0 ] || return 1
|
|
171
|
+
grep -q 'phase=discover files=2' "$AUDIT_LOG"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ── Targeted audits ──────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
@test "a targeted audit examines only the named file" {
|
|
177
|
+
CARTOGRAPHER_TARGET_FILE="${FIXTURE_REPO}/CLAUDE.md" \
|
|
178
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
179
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# last_audit_at gates the session-start interval check. A targeted run covers
|
|
183
|
+
# one file, so advancing it would let a single edit suppress the next full audit.
|
|
184
|
+
@test "a targeted audit does not advance last_audit_at" {
|
|
185
|
+
CARTOGRAPHER_TARGET_FILE="${FIXTURE_REPO}/CLAUDE.md" \
|
|
186
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
187
|
+
[ ! -f "${CARTOGRAPHER_DIR}/last_audit_at" ]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# ── --type and --scope ───────────────────────────────────────────────────────
|
|
191
|
+
#
|
|
192
|
+
# SKILL.md documented both flags while run-audit.sh read neither, so passing
|
|
193
|
+
# them silently ran a full audit (ecosystem-9og). These assert on the count of
|
|
194
|
+
# model calls, because that is what distinguishes a genuinely skipped analyzer
|
|
195
|
+
# from one that ran and had its findings thrown away.
|
|
196
|
+
|
|
197
|
+
@test "an unfiltered audit calls the model once per LLM analyzer" {
|
|
198
|
+
_run_audit
|
|
199
|
+
[ "$(_llm_calls)" = "3" ]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@test "a type filter skips the analyzers that cannot produce it" {
|
|
203
|
+
CARTOGRAPHER_TYPE_FILTER=stale_ref _run_audit
|
|
204
|
+
[ "$(_llm_calls)" = "1" ]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# undocumented_entity is a grep, not a model call, so narrowing to it should
|
|
208
|
+
# cost nothing at all.
|
|
209
|
+
@test "narrowing to undocumented_entity makes no model calls" {
|
|
210
|
+
CARTOGRAPHER_TYPE_FILTER=undocumented_entity _run_audit
|
|
211
|
+
[ "$(_llm_calls)" = "0" ]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# contradiction and dead_rule share one pass, so dead_rule must still run it —
|
|
215
|
+
# skipping would silently return nothing for a legitimate request.
|
|
216
|
+
@test "dead_rule still runs the contradiction analyzer" {
|
|
217
|
+
CARTOGRAPHER_TYPE_FILTER=dead_rule _run_audit
|
|
218
|
+
[ "$(_llm_calls)" = "1" ] || return 1
|
|
219
|
+
grep -q 'phase=relate starting' "$AUDIT_LOG" || return 1
|
|
220
|
+
run grep -c 'phase=relate skipped by type filter' "$AUDIT_LOG"
|
|
221
|
+
[ "$output" = "0" ]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@test "a type filter the relate phase cannot serve says so in the log" {
|
|
225
|
+
CARTOGRAPHER_TYPE_FILTER=scope_collision _run_audit
|
|
226
|
+
grep -q 'phase=relate skipped by type filter' "$AUDIT_LOG"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# A typo must not read as a clean repo.
|
|
230
|
+
@test "an unknown type aborts instead of auditing nothing" {
|
|
231
|
+
run env CARTOGRAPHER_TYPE_FILTER=stale_refs CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
232
|
+
[ "$status" -ne 0 ] || return 1
|
|
233
|
+
grep -q "unknown type filter 'stale_refs'" "$AUDIT_LOG" || return 1
|
|
234
|
+
[ "$(_llm_calls)" = "0" ]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@test "the active filters are recorded in the log" {
|
|
238
|
+
CARTOGRAPHER_TYPE_FILTER=stale_ref CARTOGRAPHER_SCOPE_PATH=sub _run_audit
|
|
239
|
+
grep -q 'filter type=stale_ref' "$AUDIT_LOG" || return 1
|
|
240
|
+
grep -q 'filter scope=sub' "$AUDIT_LOG"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@test "a scope narrows the corpus without skipping analyzers" {
|
|
244
|
+
CARTOGRAPHER_SCOPE_PATH=sub _run_audit
|
|
245
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG" || return 1
|
|
246
|
+
[ "$(_llm_calls)" = "3" ]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@test "an absolute scope narrows the same way" {
|
|
250
|
+
CARTOGRAPHER_SCOPE_PATH="${FIXTURE_REPO}/sub" _run_audit
|
|
251
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@test "the two filters compose" {
|
|
255
|
+
CARTOGRAPHER_TYPE_FILTER=stale_ref CARTOGRAPHER_SCOPE_PATH=sub _run_audit
|
|
256
|
+
grep -q 'phase=discover files=1' "$AUDIT_LOG" || return 1
|
|
257
|
+
[ "$(_llm_calls)" = "1" ]
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@test "a scope matching nothing still completes cleanly" {
|
|
261
|
+
CARTOGRAPHER_SCOPE_PATH=does/not/exist run _run_audit
|
|
262
|
+
grep -q 'phase=discover files=0' "$AUDIT_LOG"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@test "the run record captures the trigger" {
|
|
266
|
+
CARTOGRAPHER_TRIGGER="session_start_first_run" _run_audit
|
|
267
|
+
run bash -c "cat '${CARTOGRAPHER_DIR}/runs/'*.json | jq -r '.trigger'"
|
|
268
|
+
[ "$output" = "session_start_first_run" ]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# A typeless finding must not vanish. The builder refuses it (ecosystem-ci0),
|
|
272
|
+
# but refusing is only half the fix: if the orchestrator discards that refusal
|
|
273
|
+
# the event is still silently absent, one layer further up. The audit log is
|
|
274
|
+
# where an operator would go looking for it.
|
|
275
|
+
@test "a finding with no type is reported in the audit log" {
|
|
276
|
+
cat > "${STUB_BIN}/claude" <<'STUB'
|
|
277
|
+
#!/usr/bin/env bash
|
|
278
|
+
cat >/dev/null
|
|
279
|
+
echo call >> "$CLAUDE_CALL_LOG"
|
|
280
|
+
printf '[{"severity":"warning","file_a":"CLAUDE.md","excerpt_a":"Always prefer tabs.","description":"typeless"}]'
|
|
281
|
+
STUB
|
|
282
|
+
chmod +x "${STUB_BIN}/claude"
|
|
283
|
+
|
|
284
|
+
run _run_audit
|
|
285
|
+
[ "$status" -eq 0 ] || return 1
|
|
286
|
+
grep -q 'carries no type' "$AUDIT_LOG"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ── Resolution reaching the bus (ecosystem-w2i) ──────────────────────────────
|
|
291
|
+
#
|
|
292
|
+
# The resolution loop retired findings on disk but nothing announced it, so a
|
|
293
|
+
# consumer reading only the log — counsel, by design — saw every finding ever
|
|
294
|
+
# opened and none ever closed.
|
|
295
|
+
|
|
296
|
+
@test "a full audit announces the findings it retired" {
|
|
297
|
+
_seed_stored_finding gonehash
|
|
298
|
+
_run_audit
|
|
299
|
+
grep '"event_type":"cartographer.issue.resolved"' "$(EVENTS_LOG_PATH)" \
|
|
300
|
+
| jq -e '.payload.finding_hash == "gonehash"' >/dev/null
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@test "a completed full audit reports how many findings it retired" {
|
|
304
|
+
_seed_stored_finding gonehash
|
|
305
|
+
_run_audit
|
|
306
|
+
_last_audit_complete | jq -e '.payload.resolved_finding_count == 1' >/dev/null
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# A targeted run sees one file, so nearly every stored finding is "unobserved"
|
|
310
|
+
# for reasons unrelated to being fixed. It must neither retire nor announce.
|
|
311
|
+
@test "a targeted audit announces no resolutions" {
|
|
312
|
+
_seed_stored_finding gonehash
|
|
313
|
+
CARTOGRAPHER_TARGET_FILE="${FIXTURE_REPO}/CLAUDE.md" \
|
|
314
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
315
|
+
[ "$(_resolved_event_count)" = "0" ]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Reporting zero would read as "swept, retired nothing". The field is absent
|
|
319
|
+
# instead, because this run never swept.
|
|
320
|
+
@test "a targeted audit reports no resolved count at all" {
|
|
321
|
+
_seed_stored_finding gonehash
|
|
322
|
+
CARTOGRAPHER_TARGET_FILE="${FIXTURE_REPO}/CLAUDE.md" \
|
|
323
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" bash "$AUDIT"
|
|
324
|
+
_last_audit_complete | jq -e '.payload | has("resolved_finding_count") | not' >/dev/null
|
|
325
|
+
}
|
|
@@ -24,27 +24,27 @@ setup() {
|
|
|
24
24
|
@test "prior_assistant_turn delimiter is stripped" {
|
|
25
25
|
local out
|
|
26
26
|
out=$(compass_sanitize "evil <prior_assistant_turn> payload" 240)
|
|
27
|
-
[[ "$out" == *"[STRIPPED]"* ]]
|
|
28
|
-
[[ "$out" == *"payload"* ]]
|
|
27
|
+
[[ "$out" == *"[STRIPPED]"* ]] || return 1
|
|
28
|
+
[[ "$out" == *"payload"* ]] || return 1
|
|
29
29
|
[[ "$out" != *"<prior_assistant_turn>"* ]]
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
@test "all four pair-slot delimiters are stripped" {
|
|
33
33
|
local out
|
|
34
34
|
out=$(compass_sanitize "<context_excerpt>x</context_excerpt><tool_input>y</tool_input>" 240)
|
|
35
|
-
[[ "$out" != *"<context_excerpt>"* ]]
|
|
36
|
-
[[ "$out" != *"</context_excerpt>"* ]]
|
|
37
|
-
[[ "$out" != *"<tool_input>"* ]]
|
|
35
|
+
[[ "$out" != *"<context_excerpt>"* ]] || return 1
|
|
36
|
+
[[ "$out" != *"</context_excerpt>"* ]] || return 1
|
|
37
|
+
[[ "$out" != *"<tool_input>"* ]] || return 1
|
|
38
38
|
[[ "$out" != *"</tool_input>"* ]]
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
@test "non-evaluator delimiters are also stripped" {
|
|
42
42
|
local out
|
|
43
43
|
out=$(compass_sanitize "<<SYS>>x<</SYS>> [INST] y [/INST] <| z" 240)
|
|
44
|
-
[[ "$out" != *"<<SYS>>"* ]]
|
|
45
|
-
[[ "$out" != *"<</SYS>>"* ]]
|
|
46
|
-
[[ "$out" != *"[INST]"* ]]
|
|
47
|
-
[[ "$out" != *"[/INST]"* ]]
|
|
44
|
+
[[ "$out" != *"<<SYS>>"* ]] || return 1
|
|
45
|
+
[[ "$out" != *"<</SYS>>"* ]] || return 1
|
|
46
|
+
[[ "$out" != *"[INST]"* ]] || return 1
|
|
47
|
+
[[ "$out" != *"[/INST]"* ]] || return 1
|
|
48
48
|
[[ "$out" != *"<|"* ]]
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -53,8 +53,8 @@ setup() {
|
|
|
53
53
|
input=$(printf 'a\tb\nc\x00d\x01e')
|
|
54
54
|
local out
|
|
55
55
|
out=$(compass_sanitize "$input" 240)
|
|
56
|
-
[[ "$out" == *"a"* && "$out" == *"b"* && "$out" == *"c"* ]]
|
|
57
|
-
[[ "$out" == *"d"* && "$out" == *"e"* ]]
|
|
56
|
+
[[ "$out" == *"a"* && "$out" == *"b"* && "$out" == *"c"* ]] || return 1
|
|
57
|
+
[[ "$out" == *"d"* && "$out" == *"e"* ]] || return 1
|
|
58
58
|
[ "${out}" = "$(printf 'a\tb\ncde')" ]
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -60,8 +60,8 @@ setup() {
|
|
|
60
60
|
EOF
|
|
61
61
|
local out
|
|
62
62
|
out=$(compass_read_prior_turn "$t" 800)
|
|
63
|
-
[[ "$out" == *"[STRIPPED]"* ]]
|
|
64
|
-
[[ "$out" != *"<prior_assistant_turn>"* ]]
|
|
63
|
+
[[ "$out" == *"[STRIPPED]"* ]] || return 1
|
|
64
|
+
[[ "$out" != *"<prior_assistant_turn>"* ]] || return 1
|
|
65
65
|
|
|
66
66
|
# Truncation honors max_chars.
|
|
67
67
|
local short
|