The resolver copied Obsidian's filename-anywhere semantics with a raw rglob, which walked .AGENTTRASH/ as well. [[log]] therefore stayed "valid" by landing on the quarantined core/log.md — a page that is out of circulation until a pruning session and, being gitignored, does not exist on any other machine. The gate reported a green bundle while pointing readers at a file git does not carry: exactly the #57 failure, hidden by the check that exists to catch it. Resolution now uses the same filtered set linting uses (pages()), so the two can never disagree about what exists. A companion test pins that filename-anywhere resolution for live pages still works, so the narrowing costs nothing; mutating the resolver back to rglob turns the new test red. Refs #60
159 lines
5.2 KiB
Bash
Executable File
159 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# tests/kb-lint.bats — black-box tests for scripts/kb-lint.sh
|
|
#
|
|
# Contract: exit 0 when a knowledge bundle is OKF v0.1 conformant;
|
|
# one line per violation otherwise. Observable surface: stdout lines
|
|
# and exit status only.
|
|
|
|
setup() {
|
|
LINT="$BATS_TEST_DIRNAME/../scripts/kb-lint.sh"
|
|
KB="$(mktemp -d)/kb"
|
|
mkdir -p "$KB/sub"
|
|
cat > "$KB/index.md" <<'EOF'
|
|
# Index
|
|
|
|
* [Good](good.md) — fine.
|
|
EOF
|
|
cat > "$KB/good.md" <<'EOF'
|
|
---
|
|
type: reference
|
|
title: Good
|
|
---
|
|
Body.
|
|
EOF
|
|
}
|
|
|
|
teardown() { rm -rf "$(dirname "$KB")"; }
|
|
|
|
@test "conformant bundle passes" {
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -eq 0 ]
|
|
}
|
|
|
|
@test "missing frontmatter is reported" {
|
|
printf 'no frontmatter here\n' > "$KB/bad.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"bad.md: missing frontmatter"* ]]
|
|
}
|
|
|
|
@test "frontmatter without type is reported" {
|
|
printf -- '---\ntitle: x\n---\nbody\n' > "$KB/notype.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"notype.md"*"type"* ]]
|
|
}
|
|
|
|
@test "unclosed frontmatter is reported, not silently passed" {
|
|
# regression: a `---` in the body (fenced code, rule) must not be
|
|
# accepted as the closing delimiter of the frontmatter block
|
|
cat > "$KB/unclosed.md" <<'EOF'
|
|
---
|
|
type: reference
|
|
title: Unclosed
|
|
|
|
# Heading after a forgotten closing fence
|
|
|
|
```markdown
|
|
---
|
|
name: example
|
|
---
|
|
```
|
|
EOF
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"unclosed.md"* ]]
|
|
}
|
|
|
|
@test "frontmatter in index.md is reported" {
|
|
printf -- '---\ntype: x\n---\n# I\n' > "$KB/sub/index.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"index.md must not have frontmatter"* ]]
|
|
}
|
|
|
|
@test "broken relative link is reported" {
|
|
printf -- '---\ntype: reference\n---\nsee [x](gone.md)\n' > "$KB/links.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"links.md: broken relative link"* ]]
|
|
}
|
|
|
|
@test "broken wikilink is reported" {
|
|
printf -- '---\ntype: reference\n---\nsee [[nowhere]]\n' > "$KB/wiki.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"wiki.md: broken wikilink"* ]]
|
|
}
|
|
|
|
@test "missing kb directory exits 2" {
|
|
run bash "$LINT" "$KB/no-such-dir"
|
|
[ "$status" -eq 2 ]
|
|
}
|
|
|
|
@test "a second work log in a subdirectory is reported" {
|
|
# One bundle, one work log. Per-directory logs are how a bundle ends up with fifty
|
|
# of them: the entry is never where the next agent looks, and each copy re-dates the
|
|
# same event on its own.
|
|
printf -- '---\ntype: changelog\n---\n## 2026-01-01\n- created a log\n' > "$KB/log.md"
|
|
printf -- '---\ntype: changelog\n---\n## 2026-01-02\n- also a log\n' > "$KB/sub/log.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"one work log"*"at the bundle root"* ]]
|
|
[[ "$output" == *"sub/log.md"* ]]
|
|
}
|
|
|
|
@test "a work log that is not at the bundle root is reported" {
|
|
printf -- '---\ntype: changelog\n---\n## 2026-01-02\n- misplaced log\n' > "$KB/sub/log.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"one work log"*"at the bundle root"* ]]
|
|
}
|
|
|
|
@test "the quarantine trash directory is not part of the bundle" {
|
|
# Deleted pages wait in .AGENTTRASH/ for a pruning session with Frank. Holding them
|
|
# to frontmatter and one-log rules would push an agent to delete history to go green.
|
|
mkdir -p "$KB/.AGENTTRASH"
|
|
printf 'no frontmatter, and a second log besides\n' > "$KB/.AGENTTRASH/log.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"0 violation(s)"* ]]
|
|
}
|
|
|
|
@test "a wikilink must not resolve into the quarantine" {
|
|
# Obsidian resolves [[name]] by filename anywhere under the vault, and the resolver copied
|
|
# that with a raw rglob — so [[log]] stayed "valid" by landing on the copy waiting in
|
|
# .AGENTTRASH/. That is #57's failure wearing a green check: the page is out of circulation
|
|
# until a pruning session with Frank, and it is not even tracked, so on another machine the
|
|
# link points at nothing while the gate here says it is fine.
|
|
mkdir -p "$KB/.AGENTTRASH"
|
|
printf 'no frontmatter, quarantined\n' > "$KB/.AGENTTRASH/log.md"
|
|
printf -- '---\ntype: reference\n---\nSee [[log]] for the history.\n' > "$KB/good.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -ne 0 ]
|
|
[[ "$output" == *"broken wikilink -> [[log]]"* ]]
|
|
}
|
|
|
|
@test "a wikilink to a live page still resolves, in any directory" {
|
|
# The narrowing above must cost nothing: filename-anywhere semantics is the point.
|
|
printf -- '---\ntype: reference\n---\nSee [[good]].\n' > "$KB/sub/other.md"
|
|
run bash "$LINT" "$KB"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"0 violation(s)"* ]]
|
|
}
|
|
|
|
@test "the shipped knowledge base itself is conformant" {
|
|
# The tests above exercise the linter against fixtures; nothing else in the
|
|
# suite ever points it at the real bundle, so a page added without frontmatter
|
|
# or an index that stops listing a file passes CI silently. This is the gate.
|
|
run bash "$BATS_TEST_DIRNAME/../scripts/kb-lint.sh" "$BATS_TEST_DIRNAME/../omp-docs"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"0 violation(s)"* ]]
|
|
}
|
|
|
|
@test "every page in the shipped knowledge base is listed by an index" {
|
|
run python3 "$BATS_TEST_DIRNAME/../scripts/kb-coverage.py" "$BATS_TEST_DIRNAME/../omp-docs"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"0 issue(s)"* ]]
|
|
}
|