feat(kb-gates): one work log per bundle, and the quarantine is not bundle content
The bundle had two work logs. `core/log.md` was created "for tracking core directory modifications" and holds two entries of pure migration bookkeeping (file moved, directory created, log created) — every one recoverable from `git log` — plus a date for the OKF conversion (2026-07-15) that matches neither the bundle log (2026-01-15) nor git history, where the OKF rebuild lands 2026-08-28. `core/index.md` was still linking it, which is exactly how a stale page stays in circulation. kb-lint now reports any `log.md` that is not the bundle root's; the page moved to omp-docs/core/.AGENTTRASH/ per the trash convention, and the core index points one level up. Both gates also walked `.AGENTTRASH/`, so trashing a page turned them red: kb-lint demanded frontmatter from a quarantined page and kb-coverage demanded an index for the trash directory. A gate that only goes green once the quarantined copy is deleted is a gate that deletes history, so both skip any path containing `.AGENTTRASH` — pinned by a test in each suite, and kb-coverage finally gets its own test file.
This commit is contained in:
@@ -16,6 +16,4 @@ These files define the structural conventions that govern the agent system itsel
|
||||
* [OMP Status Line Customization](statusline.md) — How presets, composer shapes, and segment arrays actually interact; the fleet's tuned bar. (moved from /pi/, 2026-10-19)
|
||||
* [OMP Subagent Limits & Fan-out Policy](subagent-limits.md) — Live caps: concurrency 2, no nested spawns (the `maxRecursionDepth: 0` trap), per-endpoint in-flight ceilings, isolation policy. (moved from /pi/, 2026-10-19)
|
||||
|
||||
## Change Tracking
|
||||
|
||||
* [Bundle Change Log](log.md) — Chronological update history.
|
||||
**Change log:** the bundle keeps exactly one, one level up — [Bundle Change Log](../log.md). A directory here never has its own.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
type: changelog
|
||||
title: Core Bundle Change Log
|
||||
description: Change log for the core documentation directory — tracks migrations and structural changes.
|
||||
tags:
|
||||
- core
|
||||
- changelog
|
||||
- okf
|
||||
timestamp: 2026-07-18
|
||||
---
|
||||
|
||||
# Core Bundle Change Log
|
||||
|
||||
## 2026-07-18
|
||||
- **Migration**: Converted `index.md` (root) to `core/index-doc.md` — moved root bundle index into core domain
|
||||
- **Migration**: Converted `agents-md-format.md` (root) to `core/agents-md-format.md` — agents-md format belongs in core
|
||||
- **Creation**: Established `core/` directory with `index.md` as the domain entry point
|
||||
- **Creation**: This change log for tracking core directory modifications
|
||||
|
||||
## 2026-07-15
|
||||
- **Initialization**: OKF v0.1 conversion of the entire docs bundle — frontmatter applied, structure established, first `log.md` entries created
|
||||
+13
-1
@@ -4,7 +4,15 @@ import re, sys
|
||||
from pathlib import Path
|
||||
kb = Path(sys.argv[1])
|
||||
issues = []
|
||||
dirs = sorted({p.parent for p in kb.rglob("*.md")})
|
||||
|
||||
|
||||
def kept(p):
|
||||
# `.AGENTTRASH/` is quarantine, not bundle content: a gate that flags it pushes an
|
||||
# agent to delete a page that is only waiting for a pruning session with the operator.
|
||||
return ".AGENTTRASH" not in p.parts
|
||||
|
||||
|
||||
dirs = sorted({p.parent for p in kb.rglob("*.md") if kept(p)})
|
||||
for d in dirs:
|
||||
idx = d / "index.md"
|
||||
if not idx.is_file() and d != kb:
|
||||
@@ -21,12 +29,16 @@ for d in dirs:
|
||||
# wikilinks too
|
||||
wl_names = {m.group(1).strip().rsplit("/", 1)[-1] for m in re.finditer(r"\[\[([^\]|#]+)", text)}
|
||||
for sib in sorted(d.iterdir()):
|
||||
if sib.name == ".AGENTTRASH":
|
||||
continue
|
||||
if sib.suffix != ".md" or sib.name == "index.md":
|
||||
continue
|
||||
if sib.resolve() not in linked and sib.stem not in wl_names:
|
||||
issues.append(f"{d.relative_to(kb)}/index.md: does not link {sib.name}")
|
||||
# subdirs of d linked as their index?
|
||||
for sub in sorted(d.iterdir()):
|
||||
if sub.name == ".AGENTTRASH":
|
||||
continue
|
||||
if sub.is_dir() and (sub / "index.md").is_file():
|
||||
subidx = (sub / "index.md").resolve()
|
||||
dirref = sub.resolve()
|
||||
|
||||
+22
-2
@@ -12,7 +12,15 @@ from pathlib import Path
|
||||
kb = Path(sys.argv[1])
|
||||
violations = []
|
||||
|
||||
for p in sorted(kb.rglob("*.md")):
|
||||
def pages():
|
||||
"""Markdown pages that belong to the bundle. `.AGENTTRASH/` is a quarantine, not
|
||||
content: linting a deleted-but-not-yet-pruned page would demand its destruction to
|
||||
make the gate go green, which is the wrong incentive."""
|
||||
for p in kb.rglob("*.md"):
|
||||
if ".AGENTTRASH" not in p.parts:
|
||||
yield p
|
||||
|
||||
for p in pages():
|
||||
rel = p.relative_to(kb)
|
||||
text = p.read_text(errors="replace")
|
||||
lines = text.splitlines()
|
||||
@@ -38,9 +46,21 @@ for p in sorted(kb.rglob("*.md")):
|
||||
if not isinstance(fm, dict) or not str(fm.get("type", "")).strip():
|
||||
violations.append(f"{rel}: frontmatter lacks required 'type:'")
|
||||
|
||||
|
||||
# One work log per bundle, at the bundle root. A `log.md` is a reserved name, but
|
||||
# reserved does not mean "allowed in every directory": per-directory logs are how a
|
||||
# bundle ends up with fifty of them, the entry never where the next agent looks, and
|
||||
# each copy dating the same event differently. A bundle with no log at all is left to
|
||||
# the maintenance page to nag about; this gate's job is sprawl.
|
||||
logs = sorted(str(p.relative_to(kb)) for p in pages() if p.name == "log.md")
|
||||
if len(logs) > 1 or (logs and logs[0] != "log.md"):
|
||||
violations.append(
|
||||
"a bundle has one work log, log.md, at the bundle root; found: " + ", ".join(logs)
|
||||
)
|
||||
|
||||
# link integrity: fenced code blocks hold example paths, not live links — strip first.
|
||||
# Vendored spec is exempt: its example paths are illustrations, not live links.
|
||||
for p in sorted(kb.rglob("*.md")):
|
||||
for p in pages():
|
||||
if str(p.relative_to(kb)) == "references/okf-spec.md":
|
||||
continue
|
||||
text = re.sub(r"^```[^\n]*$^```[^\n]*$", "", p.read_text(errors="replace"), flags=re.M)
|
||||
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/kb-coverage.bats — black-box tests for scripts/kb-coverage.py
|
||||
#
|
||||
# Contract: exit 0 when every directory of a knowledge bundle has an index.md and each
|
||||
# index links its siblings. Observable surface: stdout lines and exit status.
|
||||
|
||||
setup() {
|
||||
CHECK="$BATS_TEST_DIRNAME/../scripts/kb-coverage.py"
|
||||
KB="$(mktemp -d)/kb"
|
||||
mkdir -p "$KB/sub"
|
||||
printf '# Index\n\n* [Good](good.md)\n* [Sub](sub/)\n' > "$KB/index.md"
|
||||
printf '# Sub\n\n* [Deep](deep.md)\n' > "$KB/sub/index.md"
|
||||
printf 'body\n' > "$KB/good.md"
|
||||
printf 'body\n' > "$KB/sub/deep.md"
|
||||
}
|
||||
|
||||
teardown() { rm -rf "$(dirname "$KB")"; }
|
||||
|
||||
@test "a bundle whose directories index their contents passes" {
|
||||
run python3 "$CHECK" "$KB"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"0 issue(s)"* ]]
|
||||
}
|
||||
|
||||
@test "a directory nobody indexes is reported" {
|
||||
mkdir "$KB/stray"
|
||||
printf 'body\n' > "$KB/stray/thing.md"
|
||||
run python3 "$CHECK" "$KB"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"stray: no index.md"* ]]
|
||||
}
|
||||
|
||||
@test "an index that forgets a sibling page is reported" {
|
||||
printf 'body\n' > "$KB/orphan.md"
|
||||
run python3 "$CHECK" "$KB"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"does not link orphan.md"* ]]
|
||||
}
|
||||
|
||||
@test "the quarantine trash directory is not part of the bundle" {
|
||||
# Pages deleted from the KB wait in .AGENTTRASH/ for a pruning session with Frank.
|
||||
# Requiring an index there, or listing its contents, would push an agent to delete
|
||||
# the quarantined copy just to make the gate green.
|
||||
mkdir -p "$KB/.AGENTTRASH"
|
||||
printf 'retired page\n' > "$KB/.AGENTTRASH/old-page.md"
|
||||
run python3 "$CHECK" "$KB"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"0 issue(s)"* ]]
|
||||
}
|
||||
@@ -91,6 +91,35 @@ EOF
|
||||
[ "$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 "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
|
||||
|
||||
Reference in New Issue
Block a user