refactor(skills): retrofit the corpus to the ADR-0020 context contract (#129)

Retrofits all 39 skills to ADR-0020's description/body context contract, then fixes what six rounds of independent review found in that retrofit — including four ways the hot gate itself failed open.

Closes #99, #107, #108, #110, #111, #114, #115, #120.

## The retrofit (waves 1-5)

| | Start | Now |
|---|---|---|
| Description FAILs (>400 chars) | 26 | **0** |
| Body FAILs (>900 words, body-only) | 9 | **0** |
| Dangling routing targets | 2 | **0** |
| `Kyberforge.CompositionNote` | 10 | **0** |
| Preload tax | 21,005 chars | **~10,500** |

Under the 12,000-char success criterion. Per-wave detail is on #99.

## The review fixes

**The gate failed open four ways, three of them found after the retrofit shipped.** An unrecognised follower token made a dangling target vanish. A skill directory with no `SKILL.md` resolved as a valid target, so a commit could be green locally and red in a fresh clone — three existing fixtures were relying on that, one of which made the install-leak A/B pass vacuously. Then the free-standing `/name` sweep turned out to be gated on the sentence carrying a boundary marker, so route notation in any other sentence was invisible — not an ERROR, not a SUGGESTION, not an INFO — which left the documented "`/name` always blocks" promise false from a second direction. All four fixed and pinned.

**Two checks were silently not running.** `validate-provenance.sh` checks 7-8 were dead across nine skills. Waking them exposed a deeper problem: they assume `Research doc:` names a source index, but 30 of 121 entries point at topic content documents, so every new check-7 INFO was a false positive and check 8 was saved from a false-FAIL flood only by an *unannounced* skip. Checks 7/8 are now scoped to source indexes and every skip announces itself (#121).

**The retrofit's own anti-goal, four times.** ADR-0020 warns that a blunt gate gets satisfied by deleting content rather than relocating it. `diagnose` and `skill-audit` relocated prose and then read it unconditionally; `prototype` and `vale-config` deleted rules outright that survived nowhere. All four addressed.

## Verification

- `bash tests/run-tests.sh --strict` — 24 suites, 0 skipped, 0 failed
- `bash tests/run-bats.sh` — 325 tests, 0 failures
- `pre-commit run --all-files` — 17/17
- `pre-commit run --hook-stage pre-push --all-files` — 16/16, with `apm marketplace check` and `apm pack --check-clean` run against the remote, not skipped
- `scripts/skill-size-check.sh` over all 39 skills — rc 0, 0 ERROR/FAIL, SUGGESTION-only
- Preload tax measured at **10,498 chars**, max description 390 — both inside budget
- Every new test proven non-vacuous by a deliberate mutation of the behaviour it covers

**Per-commit sync, stated accurately:** the ten commits from the latest review round each pass `check-plugin-content-sync` in isolation, verified by checking each out in a detached worktree with a clean between. The earlier gitea window (`dfacf05..bedbd1d`, nine commits) does **not** — its mirror was regenerated in one batch at `bbc7300`. An earlier revision of this description claimed the property held for every commit; it does not, and a bisect through that window lands on a red commit. **Squash-merge** to collapse it, or accept that this range is not bisectable.

## Version bump

Six plugins and the catalog take a **patch**, not a minor. The branch is **89 commits — 40 `fix` / 30 `refactor` / 12 `docs` / 5 `chore` / 2 `test` — zero `feat`, zero `!`, zero `BREAKING CHANGE`** — and adds no skill, agent, command or hook. (Two earlier revisions of this section cited a stale histogram, most recently 78 commits; the figures above are measured at HEAD.) Both rules this repo ships (`forge/references/version-bump.md`, landing in this PR, and `git-commits/references/conventional-commits-spec.md`) make that a patch, and the catalog set is unchanged at 7 entries.

Not settled by that: four published files were removed from the installed tree, three moved, and `caveman` gained `disable-model-invocation`, retiring its old triggers. Under a strict reading those are major-class and currently ship under `refactor:` with no marker. Whether the deployed skill surface is a public contract is written down nowhere — worth deciding, but it outlives this PR.

## Deliberately not in scope

#112 (cherry-pick ownership, now resolved in favour of `git-commits`), #113 (`rtk git` normalisation), #116 (research fan-out), #101 (audit-skill merge), #122 (non-spec skill-root files), #123 (no PRD producer) stay open. #117 is the one worth reading: the contract's remedy is to move prose into `references/`, which is exactly where neither the size gate nor Vale looks — and the blind spot is wider than #117 currently records, since there is no root `.vale.ini` at all, so every ADR, `CONTEXT.md` and `README.md` is unlinted too.

That blind spot let this branch carry two `level: error` `Kyberforge.SentenceOpenerThereIs` violations into `references/` files it created — `provider-adapter-author/references/provider-matrix.md:31` and `agent-audit/references/finding-criteria.md:95`. Both are reworded in `afadaae`, confirmed by routing each file through the audit's own `vale-wrap.sh` (1 error each before, 0 after). Five further occurrences sit in `references/` files already on `main`; those are the pre-existing corpus and stay with #117, which is the real fix.

Also unfixed and not this PR's: `apm install` appends a duplicate `SessionStart` entry to `.claude/settings.json`, so a fresh clone cannot get pre-push green without an edit AGENTS.md warns against. Reproduces identically on `main`.

Co-authored-by: Defame1297 <gitea@rkdr.net>
Reviewed-on: https://git.dev.rkdr.net/Defame1297/holocron/pulls/129
Co-authored-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net>
Co-committed-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net>
This commit was merged in pull request #129.
This commit is contained in:
Claude Code AI - Gitea MCP
2026-09-01 13:47:46 +00:00
committed by Defame1297
parent 0e91a3ae66
commit 598a7c326a
420 changed files with 15303 additions and 4740 deletions

View File

@@ -433,3 +433,477 @@ EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
}
# ---------------------------------------------------------------------------
# Checks 3 and 4: None ("could not parse") is NOT [] ("explicitly (none)")
#
# parse_contributing_files returns three distinguishable answers and checks 3
# and 4 have to honour all three. `[]` is the author writing "(none)" — the
# skip is correct and silent. None is a Contributing files block the parser
# cannot read, and skipping THAT silently disables both checks on the one entry
# least likely to be right, which is the failure mode the parser's own
# docstring warns about. The assertions below are therefore about the INFO
# appearing; a silent exit 0 is exactly the bug.
# ---------------------------------------------------------------------------
@test "INFO: an unparsable Contributing files block names the slug instead of skipping checks 3 and 4 silently" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
**Contributing files:**
* .apm/agents/ghost.agent.md (asterisk bullets are not the bullet form)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "INFO: an entry with no Contributing files field at all is reported, not skipped silently" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "checks 3 and 4 skipped silently: an explicit '(none)' emits no INFO" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
make_sources_md "$root" "my-source" "(none — not used directly)"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output ""
}
@test "checks 3 and 4 still run: a parseable Contributing files list is not diverted to the INFO" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
make_sources_md "$root" "my-source" ".apm/agents/nonexistent.agent.md"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "FAIL"
assert_output --partial "Contributing file '.apm/agents/nonexistent.agent.md' does not exist"
refute_output --partial "could not be parsed"
}
# ---------------------------------------------------------------------------
# The INFO tier itself: kind-aware printing and a kind-aware exit code
#
# INFO is new here — before it, findings was a 5-tuple and print_findings
# stamped every entry FAIL. The two cases below pin the tier rather than any
# one check: an INFO must print under the INFO prefix and leave the exit code
# at 0, and a real FAIL must keep printing under the FAIL prefix and still exit
# non-zero even when an INFO is sitting in the same findings list.
# ---------------------------------------------------------------------------
@test "INFO tier: an INFO alone prints as INFO with a Note and does not set a failing exit code" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO Contributing-file checks skipped for 'my-source'"
assert_output --partial "Note:"
refute_output --partial "FAIL"
}
@test "FAIL tier: a genuine FAIL alongside an INFO still prints as FAIL and exits non-zero" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Contributing files:** .apm/agents/nonexistent.agent.md
- **Research doc:** (none)
- **Status:** \`extracted\`
## ghost-source
- **URL:** https://example.com/ghost-source
- **Description:** Another test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "FAIL Contributing file '.apm/agents/nonexistent.agent.md' does not exist"
assert_output --partial "Why:"
assert_output --partial "INFO Contributing-file checks skipped for 'ghost-source'"
assert_output --partial "Note:"
}
# ---------------------------------------------------------------------------
# The exit-2 tier, and its boundary with the silent exit 0
#
# Ported from the skill-audit sibling, which had already split usage and
# environment errors (exit 2) away from findings (exit 1). SKILL.md tells the
# auditor to surface a non-zero exit, so a usage error leaving exit 1 with
# nothing on stdout was indistinguishable from a clean-but-failing run.
#
# The reconciliation this script needs and the sibling does not: "the walk-up
# found no type:-bearing apm.yml" is NOT bad input. It is a verdict about a
# real, readable agent file — user or project scope, where plugin-scope
# provenance does not apply — and scripts/check-scope-walkup-sync.sh fixture 6
# pins it as exit 0 with empty output. Every exit-2 gate is therefore decided
# from the ARGUMENT ALONE, before the walk-up runs, so the two can never
# collide. The two tests at the end of this block assert both halves.
# ---------------------------------------------------------------------------
@test "exit 2: no arguments is a usage error, not a finding" {
run bash "$SCRIPT"
[ "$status" -eq 2 ]
assert_output --partial "agent-file is required"
}
@test "exit 2: a second positional argument is rejected instead of silently dropped" {
local root="$TMPDIR/package"
make_package "$root"
make_clean_agent "$root"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md" --some-typo
[ "$status" -eq 2 ]
assert_output --partial "expected exactly one argument"
}
@test "exit 2: a nonexistent path is an error, not a silent pass" {
run bash "$SCRIPT" "$TMPDIR/no-such-agent.agent.md"
[ "$status" -eq 2 ]
assert_output --partial "no such file"
}
@test "exit 2: a directory is not an agent file" {
local root="$TMPDIR/package"
make_package "$root"
run bash "$SCRIPT" "$root/.apm/agents"
[ "$status" -eq 2 ]
assert_output --partial "not a regular file"
}
@test "exit 2: an unrecognized extension is rejected before the walk-up runs" {
local root="$TMPDIR/package"
make_package "$root"
echo "not an agent" > "$root/.apm/agents/my-agent.txt"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.txt"
[ "$status" -eq 2 ]
assert_output --partial "unrecognized extension"
}
@test "exit 2: a PATH with no python3 names the missing dependency instead of exiting 127" {
local root="$TMPDIR/package"
make_package "$root"
make_clean_agent "$root"
local emptybin="$TMPDIR/emptybin"
mkdir -p "$emptybin"
local bash_bin
bash_bin="$(command -v bash)"
run env -i PATH="$emptybin" HOME="$HOME" "$bash_bin" "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
[ "$status" -eq 2 ]
# The needle is the DIAGNOSTIC, not the bare word: with no preflight, bash's
# own "python3: command not found" would satisfy a bare-word match.
assert_output --partial "python3 is required"
}
@test "reconciliation: a REAL agent file at non-plugin scope still exits 0 silently, never 2" {
# scripts/check-scope-walkup-sync.sh fixture 6 in miniature. The exit-2 tier
# must not widen to cover "find_plugin_root returned None": the file exists,
# is readable and is correctly named — it is simply user/project scope.
local dir="$TMPDIR/anc"
mkdir -p "$dir"
cat > "$dir/apm.yml" <<EOF
name: outer-package
version: 0.1.0
type: skill
EOF
local fake_home="$dir/fakehome"
mkdir -p "$fake_home/.apm/agents"
cat > "$fake_home/.apm/agents/my-agent.agent.md" <<EOF
---
name: my-agent
description: A valid agent description.
source_keys:
- my-source
---
You are a test agent.
EOF
run env HOME="$fake_home" bash "$SCRIPT" "$fake_home/.apm/agents/my-agent.agent.md"
[ "$status" -eq 0 ]
assert_output ""
}
@test "reconciliation: a nonexistent path inside a non-plugin-scope tree exits 2, not the old silent 0" {
# The other half. Before the exit-2 tier, a typo'd path anywhere outside a
# package took the not-plugin-scope exit and reported a silent pass, so the
# typo and a clean agent produced identical output and identical status.
local fake_home="$TMPDIR/plainhome"
mkdir -p "$fake_home/.apm/agents"
run env HOME="$fake_home" bash "$SCRIPT" "$fake_home/.apm/agents/typo.agent.md"
[ "$status" -eq 2 ]
assert_output --partial "no such file"
}
# ---------------------------------------------------------------------------
# PLACEHOLDER_RE: the trailing character class was CONSUMING
#
# `(?<!\`)FILL IN:[^\`\n]` required a character after the colon, so a `FILL IN:`
# at end of line matched nothing and escaped checks 1 and 5 entirely — and
# `- **Description:** FILL IN:` is the most likely spelling of a half-written
# entry. The lookahead states the same exclusion without eating a character.
# ---------------------------------------------------------------------------
@test "FAIL: a FILL IN: placeholder at end of line is caught, not skipped" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** FILL IN:
- **Contributing files:** .apm/agents/my-agent.agent.md
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "Unfilled FILL IN: placeholder"
}
@test "FAIL: a Research doc value that is a bare end-of-line FILL IN: is caught" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Contributing files:** .apm/agents/my-agent.agent.md
- **Research doc:** FILL IN:
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "Research doc field is empty or placeholder"
}
# ---------------------------------------------------------------------------
# Encoding, read side: read_text() pins UTF-8 and strips a BOM
#
# The old code used bare open() calls inheriting locale.getpreferredencoding(),
# which is ASCII under LC_ALL=C, and wrapped exactly one of them in
# `except Exception: return []` — so an unreadable agent file was reported as
# having no source_keys and therefore as CLEAN. The other call sites had no
# handler at all and died with a traceback.
# ---------------------------------------------------------------------------
@test "FAIL: an undecodable agent file is reported, not swallowed into a clean pass" {
local root="$TMPDIR/package"
make_package "$root"
printf '\xff\xfe---\nname: my-agent\n---\n' > "$root/.apm/agents/my-agent.agent.md"
make_sources_md "$root" "my-source" "(none)"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "not valid UTF-8"
refute_output --partial "Traceback"
}
@test "FAIL: an undecodable contributing file is reported, not a traceback" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
printf '\xff\xfe---\nname: other\n---\n' > "$root/.apm/agents/other.agent.md"
make_sources_md "$root" "my-source" ".apm/agents/other.agent.md"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "not valid UTF-8"
refute_output --partial "Traceback"
}
@test "exit 2: an undecodable apm.yml names the file instead of dying mid walk-up" {
local root="$TMPDIR/package"
make_package "$root"
make_clean_agent "$root"
printf 'name: t\nversion: 0.1.0\ntype: skill\n# \xff\xfe\n' > "$root/apm.yml"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
[ "$status" -eq 2 ]
assert_output --partial "not valid UTF-8"
refute_output --partial "Traceback"
}
@test "a BOM-prefixed agent file still has its source_keys read (check 2 runs)" {
# A leading BOM defeats parse_frontmatter()'s ^--- anchor, so no frontmatter
# parsed means no source_keys parsed means nothing to validate — check 2
# went silently missing on exactly the file it was pointed at.
local root="$TMPDIR/package"
make_package "$root"
printf '\xef\xbb\xbf---\nname: my-agent\ndescription: A valid agent description.\nsource_keys:\n - ghost-source\n---\n\nYou are a test agent.\n' \
> "$root/.apm/agents/my-agent.agent.md"
make_sources_md "$root" "my-source" "(none)"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "source_keys slug 'ghost-source' not found in sources.md"
}
@test "under LC_ALL=C a sources.md carrying an em dash is read, not a UnicodeDecodeError" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source — with an em dash.
- **Contributing files:** .apm/agents/ghost.agent.md
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run env LC_ALL=C PYTHONUTF8=0 bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "Contributing file '.apm/agents/ghost.agent.md' does not exist"
refute_output --partial "Traceback"
}
# ---------------------------------------------------------------------------
# Encoding, write side: sys.stdout/stderr.reconfigure(encoding='utf-8')
#
# Pinning only the reads moved the crash from the read to the WRITE. Every
# finding this script prints contains an em dash, so under LC_ALL=C
# print_findings() died with UnicodeEncodeError after every check had already
# run — losing the whole report at the last step.
# ---------------------------------------------------------------------------
@test "under LC_ALL=C the findings report is printed, not lost to a UnicodeEncodeError" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
make_sources_md "$root" "my-source" ".apm/agents/ghost.agent.md"
run env LC_ALL=C PYTHONUTF8=0 bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "FAIL Contributing file '.apm/agents/ghost.agent.md' does not exist"
assert_output --partial "Why:"
refute_output --partial "UnicodeEncodeError"
}
# ---------------------------------------------------------------------------
# Half-validated entries announced instead of passing silently
# ---------------------------------------------------------------------------
@test "INFO: a duplicated '## slug' says only the first block was checked" {
# Every per-slug parser locates its block with pattern.search(), so a slug
# written twice resolves to the FIRST block every time: the second block's
# fields are never validated, and the entry looked fully checked.
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Contributing files:** (none)
- **Research doc:** (none)
- **Status:** \`extracted\`
## my-source
- **URL:** https://example.com/dup
- **Description:** A duplicate entry.
- **Contributing files:** .apm/agents/ghost.agent.md
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "Duplicate '## my-source' entry in sources.md"
# The second block's ghost contributing file is genuinely never checked —
# the INFO is what makes that visible rather than a silent half-pass.
refute_output --partial "does not exist"
}
@test "INFO: a second '- **Research doc:**' line in one entry is announced, not ignored" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Contributing files:** (none)
- **Research doc:** (none)
- **Research doc:** docs/research/added-later.md
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "Multiple '- **Research doc:**' lines for 'my-source'"
}
# ---------------------------------------------------------------------------
# Finding dedup
#
# The agent file is read once for its own source_keys and again as a
# contributing file, so an unreadable one produced the identical finding twice.
# Distinct findings about the same file still both appear.
# ---------------------------------------------------------------------------
@test "the same unreadable file reached by two checks is reported once, not twice" {
local root="$TMPDIR/package"
make_package "$root"
printf '\xff\xfe---\nname: my-agent\n---\n' > "$root/.apm/agents/my-agent.agent.md"
make_sources_md "$root" "my-source" ".apm/agents/my-agent.agent.md"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
local count
count="$(printf '%s\n' "$output" | grep -c "^FAIL File is not valid UTF-8" || true)"
[ "$count" -eq 1 ]
}

View File

@@ -943,3 +943,23 @@ EOF
refute_output --partial "Traceback"
refute_output --partial "FileNotFoundError"
}
# ---------------------------------------------------------------------------
# Encoding, write side: sys.stdout/stderr.reconfigure(encoding='utf-8')
#
# read_text() in the shared resolver block pins the READS to UTF-8. That moved
# the LC_ALL=C crash to the WRITE: this script's own message text carries em
# dashes (the ADR-0020 boundary SUGGESTION is one), so the streams' ASCII
# default raised UnicodeEncodeError while PRINTING — after every check had
# already run. Here it also flipped a clean exit 0 into a traceback and exit 1.
# ---------------------------------------------------------------------------
@test "under LC_ALL=C the report is printed, not lost to a UnicodeEncodeError" {
local root="$TMPDIR/locale-pkg"
make_apm_agent "$root" "locale-agent"
run env LC_ALL=C PYTHONUTF8=0 bash "$SCRIPT" "$root/.apm/agents/locale-agent.agent.md"
assert_success
assert_output --partial "description has no boundary clause"
refute_output --partial "UnicodeEncodeError"
refute_output --partial "Traceback"
}