From 3c6891f06c53fd7e0d188dd43560503bef37f4a7 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sun, 16 Aug 2026 16:09:56 +0200 Subject: [PATCH] search all vaults for links; require task-auditor to verify quotes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two independent fixes, both found on 2026-08-16 and both left uncommitted in the shared master checkout. session-close Phase 8.6 resolved wikilinks only within the vault owning the file, so every cross-vault link read as broken. A Personal task linking [[Boss Memory]] was flagged unresolved; the page lives in the Boss vault and is referenced by 20+ files across two others. A false 'broken link' costs a needless investigation and erodes trust in the whole verdict. The same snippet also had a shell trap: the vault paths must be read into an array. The shell here is often zsh, which does not word-split an unquoted string, so a bare grep passed every path as one argument. grep failed, stderr was swallowed, and every link read UNRESOLVED — a false flag indistinguishable from a real one. task-auditor could report findings quoting text absent from the file. It now re-reads each cited line and drops any finding whose quote it cannot locate verbatim. --- CHANGELOG.md | 5 +++++ agents/task-auditor.md | 2 +- commands/session-close.md | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a3678e..98f1551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased + +- fix: `session-close` Phase 8.6's link check searched only the vault owning the file, so every legitimate cross-vault wikilink read as broken. Observed 2026-08-16: a `Personal` task linking `[[Boss Memory]]` was flagged unresolved because the search covered only `Personal` and `Trading` — the page lives in the `Boss` vault and is referenced by 20+ files across two others. Both the orphan check and the broken-outbound-link check now search every path in `VAULT_CONFIG` and treat a hit in any vault as resolved. Also fixes a shell trap in the same snippet: the paths must be read into an **array**, because the shell here is often zsh, which does not word-split an unquoted `"$paths"` string — a bare `grep ... $ALL_VAULT_PATHS` passes every path as one argument, grep fails, stderr is swallowed, and the empty result is indistinguishable from a clean one. Added a sanity check to run before trusting a negative. +- fix: `task-auditor` could report findings quoting text that does not exist in the file. It now must re-read each cited line and confirm the quote appears verbatim, dropping any finding whose quote it cannot locate. A well-formed quote with a line number is the most credible-looking form of a wrong finding, so reconstructing one from what the file "should" contain is worse than omitting it. + ## v0.111.2 - fix: scenario 005's "no approval turn" assertion could not distinguish a real failure from two unrelated causes, and two walks on 2026-08-16 duly contradicted each other. A prompt between resume and invocation has three sources: the classification bug (what the scenario tests), the trust-folder gate (a precondition the walk never proved), and — new since v0.110.0/v0.111.1 — the Phase 5.5 permission-mode precheck, which now runs for *every* task. The assertion now names all three and tells the walker to identify which before recording FAIL, plus a dedicated `🔐 Permission mode:` assertion: the fixture's only command is `/vault-cli:next-task`, so Phase 5.5's own rule ("emit nothing … do not warn on read-only or docs-only tasks") means seeing it is a Phase 5.5 trigger defect to file separately, not a classification failure. Also hardened two things the contradicting walks got wrong: the trust precondition must be *proven in the environment the walk runs in* (a fresh tmux / isolated HOME / sub-agent shell does not inherit it), and `claude_session_id` is now marked necessary-but-not-sufficient — one walk reported PASS from it alone after losing the scrollback. `PLUGIN_VER` is now required in the result, since this flow's behavior moved twice in a single day. diff --git a/agents/task-auditor.md b/agents/task-auditor.md index 2bb39b4..c0776ba 100644 --- a/agents/task-auditor.md +++ b/agents/task-auditor.md @@ -19,7 +19,7 @@ Expert Obsidian task auditor specializing in evaluating task pages against the T - **ALWAYS use bash grep for content verification**: `grep -rn "pattern" "dir/" --include="*.md"` - ALWAYS read the Task Writing Guide first before evaluation - ALWAYS read the actual task file before evaluation -- Report findings with specific line numbers and quotes +- Report findings with specific line numbers and quotes — and **verify every quote before reporting it**: re-read the cited line and confirm the quoted text appears there verbatim. A finding whose quote you cannot locate in the file is a fabrication; drop it. Never reconstruct a quote from what the file "should" contain — a well-formed quote with a line number is the most credible-looking form of a wrong finding - Distinguish between critical issues (broken structure) and recommendations (quality improvements) - Consider task complexity when judging - simple tasks need less elaborate content - Remember: Tasks are SHOULD-do (obligation), not WANT-do (that's goals) diff --git a/commands/session-close.md b/commands/session-close.md index 180891d..30b4fdb 100644 --- a/commands/session-close.md +++ b/commands/session-close.md @@ -388,13 +388,24 @@ For each touched vault page (cap 5): **1. Orphan check (HIGH)** — does any *other* vault page link to it? ```bash +# Search EVERY vault path in VAULT_CONFIG, not just the one owning the file. +# Read into an ARRAY — the shell here is often zsh, which does NOT word-split an +# unquoted "$paths" string. A bare `grep ... $ALL_VAULT_PATHS` passes all paths as +# ONE argument, grep fails, stderr is swallowed, and every link reads UNRESOLVED — +# a false flag indistinguishable from a real one. Verified 2026-08-16. +ALL_VAULT_PATHS=("${(@f)$(vault-cli config list --output json | jq -r '.[].path')}") # zsh +# bash equivalent: mapfile -t ALL_VAULT_PATHS < <(vault-cli config list --output json | jq -r '.[].path') # basename without .md, matched as a [[wikilink]] (with or without alias/heading) -grep -rlF "[[$BASENAME" "$VAULT_PATH" --include='*.md' | grep -vF "$FILE" | head -1 +grep -rlF "[[$BASENAME" "${ALL_VAULT_PATHS[@]}" --include='*.md' | grep -vF "$FILE" | head -1 ``` +**Sanity-check the search before trusting a negative.** `echo "${#ALL_VAULT_PATHS[@]}"` must be ≥1, and a known-good link must resolve. An empty result from a broken search looks exactly like a clean result from a working one — see [[Checks That Report False Green]]. + Zero inbound links on a **newly created** page = orphan. Flag HIGH — it won't be found again. -**2. Broken outbound links (HIGH)** — extract `[[Target]]` targets from the page; verify each resolves to a file somewhere in the vault (`find/glob` by basename). Unresolved target = broken link or typo. Flag with the target name. +**2. Broken outbound links (HIGH)** — extract `[[Target]]` targets from the page; verify each resolves to a file in **any** vault in `VAULT_CONFIG` (`find/glob` by basename). Unresolved target = broken link or typo. Flag with the target name. + +**Cross-vault links are normal — search all vaults for both checks.** Scoping resolution to the owning vault reports every legitimate cross-vault wikilink as broken. Observed 2026-08-16: a task in `Personal` linking `[[Boss Memory]]` was flagged unresolved because the check searched only `Personal` and `Trading`; the page lives in the `Boss` vault and is referenced by 20+ files across two others. A false "broken link" costs the operator a needless investigation and erodes trust in the whole verdict — search wide, and treat a hit in any vault as resolved. **3. One-way link to a hub/canonical page (LOW)** — if the page links to a hub/index/concept page (`page_type: hub`, or a `*Hub*`/`*Concept*`/`*Pipeline*` page) that does **not** link back, the new page is invisible from the hub. Suggest a reciprocal backlink. Soft signal — suggest, don't insist.