From 4849f46eb21017ba3aff4ecea97784c626eaf327 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Oct 2026 21:49:15 -0700 Subject: [PATCH 1/3] feat(kb-gates): one work log per bundle, and the quarantine is not bundle content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- omp-docs/core/index.md | 4 +--- omp-docs/core/log.md | 21 ------------------ scripts/kb-coverage.py | 14 +++++++++++- scripts/kb-lint.sh | 24 +++++++++++++++++++-- tests/kb-coverage.bats | 49 ++++++++++++++++++++++++++++++++++++++++++ tests/kb-lint.bats | 29 +++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 27 deletions(-) delete mode 100644 omp-docs/core/log.md create mode 100755 tests/kb-coverage.bats diff --git a/omp-docs/core/index.md b/omp-docs/core/index.md index 56f4eb1..b6dfe70 100644 --- a/omp-docs/core/index.md +++ b/omp-docs/core/index.md @@ -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. diff --git a/omp-docs/core/log.md b/omp-docs/core/log.md deleted file mode 100644 index f0509ab..0000000 --- a/omp-docs/core/log.md +++ /dev/null @@ -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 diff --git a/scripts/kb-coverage.py b/scripts/kb-coverage.py index e1809ed..f665481 100755 --- a/scripts/kb-coverage.py +++ b/scripts/kb-coverage.py @@ -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() diff --git a/scripts/kb-lint.sh b/scripts/kb-lint.sh index aa27243..ef25c22 100755 --- a/scripts/kb-lint.sh +++ b/scripts/kb-lint.sh @@ -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) diff --git a/tests/kb-coverage.bats b/tests/kb-coverage.bats new file mode 100755 index 0000000..49f551d --- /dev/null +++ b/tests/kb-coverage.bats @@ -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)"* ]] +} diff --git a/tests/kb-lint.bats b/tests/kb-lint.bats index b7868fb..e145b54 100755 --- a/tests/kb-lint.bats +++ b/tests/kb-lint.bats @@ -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 From bfd6a6340e816a7a403d950a3cad6060958f1a59 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Oct 2026 21:49:16 -0700 Subject: [PATCH 2/3] docs(okf): the work log is one file at the bundle root, and an entry is a lesson not a manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec listed `log.md` as a reserved name and the maintenance page said every bundle "has a `log.md`" — neither said there is only one, so a subdirectory could invent its own and look compliant. Which is how the sprawl Frank banned started: with fifty logs the entry is never where the next reader looks, and every copy re-dates the same event. The maintenance page also taught the wrong format: "one line per change with file path", exampled as `/docs/ghostty.md — Updated keybind table`, i.e. a manifest duplicating git, with absolute paths that kb-lint cannot resolve because OKF links are relative. Replaced with the law this bundle already runs: what changed plus the durable lesson, a few lines, relative links, laws and incidents rather than bookkeeping. --- omp-docs/references/okf-maintenance.md | 52 ++++++++++++++++++-------- omp-docs/references/okf-spec.md | 10 ++++- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/omp-docs/references/okf-maintenance.md b/omp-docs/references/okf-maintenance.md index 609da21..b361b3a 100644 --- a/omp-docs/references/okf-maintenance.md +++ b/omp-docs/references/okf-maintenance.md @@ -21,32 +21,46 @@ Knowledge bundles rot. It is the nature of knowledge to attract cobwebs, much li ## Change Tracking with `log.md` -Every OKF bundle has a `log.md` file. It is not optional. It is the bundle's memory. +Every OKF bundle has a `log.md`, and it is the bundle's **only** work log: one file, +at the bundle root. A subdirectory never gets its own — not `core/log.md`, not +`notes.md`, not a per-project, per-branch or per-run diary. Sprawl is not caution: +with fifty logs the entry is never where the next agent looks, and each copy re-dates +the same event on its own authority. `scripts/kb-lint.sh` reports any `log.md` that is +not the root's. -**Format:** Date-grouped entries, newest first. One line per change with timestamp, file path, and what changed. +**Format:** one dated section per day, newest first; a few lines per change — what +happened, and the durable lesson. It is not a file manifest. ```markdown # Bundle Change Log -## 2026-01-15 -- `/docs/ghostty.md` — Updated keybind table after v1.2 release -- `/docs/pi-permission-system/` — Added `policy.md` and `audit.md` -- `/docs/index.md` — Synced with directory contents +## 2026-01-15 — the gate was reading the wrong files + +A gate that gets an empty file list reports success. Law: a check MUST fail closed; +a silently-passing gate is worse than none, because it buys the belief of safety. +Details: [secret gate](../tools/index.md). ``` **Rules:** -- **Newest entries first.** Putting old entries at the top inverts the timeline and makes it impossible to see what just happened. -- **One line per change.** A single line is sufficient. The git history is there if someone wants the gory details. -- **Group by date.** Batch all changes from the same day together. -- **Always date-stamp the section.** Use `## YYYY-MM-DD`. -- **Mention `index.md` whenever you change a bundle.** If you added or removed a file, update the index and note it in the log. +- **Newest entries first.** Old entries at the top invert the timeline. +- **What changed + the durable lesson, a few lines.** Full narratives are recoverable + from git (`git log --follow log.md`); never re-expand one in place. +- **Laws and incidents, never bookkeeping.** No per-file inventories, no run-by-run + numbers, no transcripts. Measured numbers still have to pass the purpose test. +- **Link, do not duplicate.** Knowledge that outlives the entry belongs on its concept + page; the entry points there. +- **One dated section per day** (`## YYYY-MM-DD`), and mention `index.md` whenever a + file is added or removed. ### Common Mistakes -- ❌ Writing `"Updated ghostty.md"` — Updated it with what? Be specific. -- ❌ Forgetting to write to `log.md` — This is the equivalent of doing surgery without keeping a record. -- ❌ Using relative paths — Use absolute paths (`/docs/ghostty.md`). -- ❌ Leaving stale entries from manual edits — The log is the single source of truth. +- ❌ `Updated ghostty.md` — updated it with what, and what did it teach? +- ❌ Forgetting the entry — the change lands undocumented and the next agent rediscovers + the failure the hard way. +- ❌ Starting a second log in a subdirectory — the sprawl that makes logging useless. +- ❌ Invented absolute paths in links — links resolve relative to the page, and the + linter checks them. +- ❌ Re-expanding an old entry in place — the entry is a pointer; git is the transcript. --- @@ -84,6 +98,14 @@ OKF is built on the principle that a file is one concept. - ❌ Don't create a file called `misc.md` — `misc` is a graveyard, not a category. - ❌ Don't use `# Other` or `# Additional Notes` sections — these are taping things to the wall. +## Quarantine, Not Deletion + +A page is never deleted outright: it moves to `.AGENTTRASH/` beside where it lived and +waits for a pruning session with Frank present. The quarantine is not part of the +bundle — `scripts/kb-lint.sh` and `scripts/kb-coverage.py` skip any path containing +`.AGENTTRASH`. That exemption is deliberate: a gate that goes green only once the +quarantined copy is gone is a gate that deletes history. + --- ## Auto-Generation Strategies diff --git a/omp-docs/references/okf-spec.md b/omp-docs/references/okf-spec.md index e5099c5..6add03b 100644 --- a/omp-docs/references/okf-spec.md +++ b/omp-docs/references/okf-spec.md @@ -42,7 +42,7 @@ An OKF knowledge bundle is a directory tree. Every concept is one file. The file ``` my-bundle/ ├── index.md # Optional directory listing -├── log.md # Optional update history +├── log.md # The bundle's ONE update history — root only ├── concept-one.md # A concept at the root level └── subdirectory/ ├── index.md # Optional subdirectory listing @@ -55,7 +55,13 @@ my-bundle/ | Filename | Purpose | |----------|---------| | `index.md` | Directory listing for progressive disclosure | -| `log.md` | Chronological update history | +| `log.md` | Chronological update history — **exactly one, at the bundle root** | + +**One work log per bundle.** `log.md` is reserved, and it is reserved for a single +file: the bundle root's. A subdirectory never keeps its own change log — a bundle +that logs in fifty places has no log, because the entry is never where the next +reader looks and every copy re-dates the same event. `scripts/kb-lint.sh` reports +any `log.md` that is not the root's. --- From 59920f849de495df3fae11d96d5a7fecabb589c8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Oct 2026 21:49:16 -0700 Subject: [PATCH 3/3] docs(agent): re-add the work-log rule, restricted to one file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule 1 lost its work-log sentence in the point-of-use rewrite. Restoring it plainly would re-create the old failure, where one agent kept logs in fifty-odd files and none of them was the place to look. So the restored rule names the file: a single page in the knowledge base — omp-docs/log.md here, whichever change-log page a repo's index names elsewhere — read before starting, written in the same commit as the change it describes, under that page's own entry law. Explicitly banned: per-directory logs, per-project or per-run logs, NOTES.md/PROGRESS.md/STATUS.md/worklog/, dated diaries. If a KB has no log page, the fix is one at its root plus an index entry, never a log somewhere else. The bundle's own log records the change and the two-log incident it came out of. --- agent/APPEND_SYSTEM.md | 7 +++++++ omp-docs/log.md | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/agent/APPEND_SYSTEM.md b/agent/APPEND_SYSTEM.md index 48f4e21..66782a1 100644 --- a/agent/APPEND_SYSTEM.md +++ b/agent/APPEND_SYSTEM.md @@ -69,6 +69,13 @@ Documentation exists for ONE reason: **point of use**. A page earns its bytes on 4. Always update the documentation when you've completed a task — and when updating means deleting a page or a paragraph, delete it. 5. Compact the work log when necessary; a work log holds laws and incidents, never bookkeeping. +#### THE WORK LOG IS ONE FILE. +The work log is a single page in the knowledge base: `omp-docs/log.md` in this repo, or whichever change-log page the repo's docs index names. Write the entry when the change lands — in the same commit as the change — and read that page to recover where things stand before starting. It follows its own entry law: a dated entry is what changed plus the durable lesson, a few lines. + +- NEVER create a second log: not per directory, not per project, feature, branch or run, not `NOTES.md` / `PROGRESS.md` / `STATUS.md` / `worklog/`, not a dated diary. Logging in fifty places is not logging — the entry is never where the next reader looks, and each copy re-dates the same event on its own authority. In `dank/command-center` the KB linter reports any `log.md` that is not the bundle root's. +- A log holds laws and incidents, never bookkeeping: no per-file inventories, no run-by-run numbers, no transcripts. A number in a log still has to pass the purpose test above. +- If a knowledge base has no log page, add one at its root and list it in the index. That is the fix — never a log somewhere else. + #### USE PROGRESSIVE DISCLOSURE. Always ensure that all produced documentation is laid out in files following WIKI conventions: start with a top level README.md index file that provides a brief, concise introduction to the item being documented, and then SPLIT OFF individual aspects, features, or arguments into their own interlinked pages so that future agents can read the documentation in a piecewise fashion, only taking in the details they need. Where relevant, crosslink any created documentation with other relevant documentation. This structure may also be nested, if necessary. diff --git a/omp-docs/log.md b/omp-docs/log.md index 207ad8b..0223a9e 100644 --- a/omp-docs/log.md +++ b/omp-docs/log.md @@ -16,6 +16,27 @@ lesson, a few lines. Full incident narratives are recoverable from git (`git log --follow omp-docs/log.md`); knowledge that outlives an entry lives on its concept page, linked here. Never re-expand an entry in place. +This page is the bundle's **only** work log: one file, at the root. Never start a +second one in a subdirectory or under another name — `scripts/kb-lint.sh` reports any +`log.md` that is not this one. + +## 2026-10-20 — one work log per bundle + +The bundle had two: this page and `core/log.md`, created "for tracking core +directory modifications" — two entries of pure migration bookkeeping (file moved, +directory created, log created), and a date for the OKF conversion that neither this +page nor git agrees with: 2026-07-15 there, 2026-01-15 here, while `git log` dates the +OKF rebuild 2026-08-28. `core/index.md` was still pointing readers at it, which is how +a stale page stays alive. Trashed to `omp-docs/core/.AGENTTRASH/log.md` — its content +is recoverable from git, which is the entire argument against a second log. + +Laws, now written down in [references/okf-spec.md](references/okf-spec.md) and +[references/okf-maintenance.md](references/okf-maintenance.md), and gated: a bundle has +ONE work log, `log.md`, at its root, and `scripts/kb-lint.sh` reports any `log.md` that +is not the root's — so the sprawl that made logging useless once cannot restart +quietly. The agent-side duty is in `agent/APPEND_SYSTEM.md` § 1: log here, read here, +never start another. + ## 2026-10-20 — two instruments rescued from `$HOME` script sediment `$HOME` had accumulated 123 loose scripts at its root (archived to