787e354b9b
The `build` job fails on main and on every branch merged from it: doc-site: context/code/pr-review.md is neither published nor a named omission NOT a merge loss -- scripts/build-doc-site.mjs in this branch is byte-identical to main's, and neither lists the file. PR #96 added context/code/pr-review.md without a manifest line, which is exactly the gate improvise-res9 added: a doc added without a line publishes nothing and says nothing, so the generator refuses. Omitted rather than published, and the line is cloud-agents.md's rather than tooling.md's. tooling.md explains checkers a reader of the code will meet; pr-review.md is about THIS REPOSITORY'S PROCESS -- where a Codex review appears, how to tell it from our own agents' comments, why absence is not approval. Nobody visiting a website is about to read a PR thread. Both homes updated, because an omission is restated beside the manifest AND in doc-site.md section 2, and the prose there counted them ("Neither is referenced...") so it went stale at three. This strays into scripts/, which is Wave N's ground. Flagged in the hand-back; it is a one-line data entry either way if the orchestrator would rather publish it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
41 lines
9.5 KiB
HTML
41 lines
9.5 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>The kb tool — improvise docs</title>
|
|
<link rel="stylesheet" href="../style.css" />
|
|
</head>
|
|
<body>
|
|
<div class="shell">
|
|
<nav class="side"><a class="brand" href="../index.html">improvise docs</a><input type="checkbox" id="navtoggle" class="navtoggle" /><label class="navsummary" for="navtoggle">Pages</label><div class="navlinks"><ul><li><a href="../../index.html">← Project home</a></li><li><a href="../../app/">Browser demo</a></li><li><a href="https://github.com/fiddlerwoaroof/improvise">Source on GitHub</a></li></ul><h3>Start here</h3><ul><li><a href="../design-notes.html">Design notes</a></li><li><a href="../design-principles.html">Design principles</a></li><li><a href="../repo-map.html">Repository map</a></li></ul><h3>Decision records</h3><ul><li><a href="../adr/001-concurrent-edits.html">ADR 001 — Concurrent edits</a></li><li><a href="../adr/002-naming-a-cell.html">ADR 002 — Naming a cell</a></li><li><a href="../adr/003-live-backed-models.html">ADR 003 — Live-backed models</a></li></ul><h3>The arguments, by area</h3><ul><li><a href="../code/core.html">improvise-core</a></li><li><a href="../code/formula-and-io.html">improvise-formula and improvise-io</a></li><li><a href="../code/protocol.html">improvise-protocol</a></li><li><a href="../code/command.html">improvise-command</a></li><li><a href="../code/clients.html">The clients</a></li><li><a href="../code/tui.html">The TUI</a></li><li><a href="../code/gp.html">gp-tree and improvise-gp</a></li><li><a href="../code/kb-tool.html" class="here">The kb tool</a></li><li><a href="../code/tooling.html">The repository's own checkers</a></li></ul><h3>Test evidence</h3><ul><li><a href="../test-evidence/core.html">improvise-core</a></li><li><a href="../test-evidence/formula-and-io.html">improvise-formula and improvise-io</a></li><li><a href="../test-evidence/protocol.html">improvise-protocol</a></li><li><a href="../test-evidence/command.html">improvise-command</a></li><li><a href="../test-evidence/clients.html">The clients</a></li><li><a href="../test-evidence/tui.html">The TUI</a></li><li><a href="../test-evidence/gp.html">gp-tree and improvise-gp</a></li><li><a href="../test-evidence/tooling.html">The repository's own gates</a></li></ul><h3>About this site</h3><ul><li><a href="../doc-site.html">About this site</a></li></ul><h3>Reference</h3><ul><li><a href="../tickets.html">Ticket index</a></li></ul></div></nav>
|
|
<main>
|
|
<p class="src">Source: <a href="https://github.com/fiddlerwoaroof/improvise/blob/main/context/code/kb-tool.md">context/code/kb-tool.md</a></p><details class="toc" open><summary>On this page</summary><ul><li class="l1"><a href="#maintaining-the-knowledge-base">Maintaining the knowledge base</a></li><li class="l2"><a href="#why-the-mcp-was-retired">Why the MCP was retired</a></li><li class="l2"><a href="#what-replaced-it">What replaced it</a></li><li class="l2"><a href="#the-guards-and-why-each-exists">The guards, and why each exists</a></li><li class="l2"><a href="#what-no-test-here-reaches">What no test here reaches</a></li></ul></details><h1 id="maintaining-the-knowledge-base">Maintaining the knowledge base</h1>
|
|
<p><a href="https://github.com/fiddlerwoaroof/improvise/blob/main/prolog-sessions/kb.pl"><code>prolog-sessions/kb.pl</code></a> is executable knowledge, checked by CI. This is the argument for how it is written, and why the Prolog MCP stopped being the way.</p>
|
|
<h2 id="why-the-mcp-was-retired"><a class="anchor" href="#why-the-mcp-was-retired">#</a>Why the MCP was retired</h2>
|
|
<p><code>loadProgram</code> <strong>replaces a whole predicate</strong>, so adding one clause means re-sending every existing clause of it through an agent's context window — and a short payload silently drops the rest. Three further blockers: <code>saveSession</code> re-adds the MCP's own <code>host_rpc</code> shims and reserializes the whole file, and <code>assertz</code> raises <code>permission_error(modify, static_procedure)</code> against a consulted predicate, so there is no incremental route through it at all.</p>
|
|
<h2 id="what-replaced-it"><a class="anchor" href="#what-replaced-it">#</a>What replaced it</h2>
|
|
<p><a href="https://github.com/fiddlerwoaroof/improvise/blob/main/scripts/kb"><code>scripts/kb</code></a>, a shell entry point over <a href="https://github.com/fiddlerwoaroof/improvise/blob/main/scripts/kb-merge.pl"><code>scripts/kb-merge.pl</code></a>.</p>
|
|
<p><strong>Clauses are read as DATA, never consulted.</strong> <code>read_term/3</code> in a loop yields a list of terms; merging is list manipulation; <code>portray_clause/1</code> writes them back. Nothing is ever asserted, so the static-procedure wall is not reached and the cost of adding a clause is the clause.</p>
|
|
<p><strong>Canonical form is <code>read_term</code> in, <code>portray_clause</code> out.</strong> This was not designed, it was measured: all 495 facts in the existing file — including every escaped quote — round-trip byte-identically, and the form is a fixed point in one pass. The only change on first use is that rules move from the MCP's two-line layout to one line, which is why the reformat lands with this tool rather than before it: the MCP would re-expand them on its next save and the two would thrash.</p>
|
|
<p><strong>trealla comes from the dev shell.</strong> Reaching it through <code>nix shell</code> per invocation cost more than the work: the tool's first real use timed out past two minutes on a write that takes 0.42 seconds with <code>tpl</code> on <code>PATH</code>.</p>
|
|
<h2 id="the-guards-and-why-each-exists"><a class="anchor" href="#the-guards-and-why-each-exists">#</a>The guards, and why each exists</h2>
|
|
<p>Every one of these was added because injecting its fault showed the tool accepting something it should refuse. None was found by reading.</p>
|
|
<p><strong>The round-trip check.</strong> After writing, the file is read back and required to be the clauses intended. A serializer bug is the wipe-the-knowledge-base failure with a new owner, and this is the only check that sees it. Note it compares <em>variant</em> equality via <code>numbervars</code> over a copy — reading a term back yields fresh variables, so <code>==</code> compares variable identity and reports every rule as a difference.</p>
|
|
<p><strong><code>kb_consistent</code> runs on the CANDIDATE.</strong> The file is replaced only after the check passes, so a delta that breaks the knowledge base leaves it untouched rather than requiring a revert.</p>
|
|
<p><strong>Parse errors are caught explicitly.</strong> An uncaught syntax error drops trealla into its REPL, which <em>hangs</em> with a terminal and <em>exits 0</em> without one — a refusal that reads as acceptance. <a href="https://github.com/fiddlerwoaroof/improvise/blob/main/scripts/kb"><code>scripts/kb</code></a> also closes stdin on every call as a backstop.</p>
|
|
<p><strong>Every run reaches a <code>halt/1</code>.</strong> <code>tpl</code> exits 0 unless a halt is <em>reached</em>: measured on 3.10.3, <code>-g "fail"</code> and <code>-g "throw(boom)"</code> both exit 0, and so do <code>fail,halt</code> and <code>throw(boom),halt</code>, because a goal that fails or throws never reaches the halt beside it. <code>halt(2)</code> alone exits 2, so halt works — the default is the hazard. The goal used is therefore</p>
|
|
<pre><code>(catch(main,E,(write(user_error,E),nl(user_error),halt(2))) -> halt(0) ; halt(1))</code></pre>
|
|
<p>which gives 0, 2 and 1 for success, throw and failure. Without it, <code>if ! merge ...</code> reads a crashed merge as a clean one and replaces the knowledge base.</p>
|
|
<p><strong>Terms must be callable.</strong> <code>42.</code> parses, is not a clause, and reached the knowledge base as a fact whose head is a number. <code>kb_consistent</code> has no opinion about stray terms, so nothing objected.</p>
|
|
<p><strong>A clause can be retired.</strong> <code>kb rm</code> takes keys and refuses if any of them is absent, because a typo'd key would otherwise report success and leave the clause in place. Without it no correction could ever drop a fact — the first thing it was used for was retiring <code>kb_is_maintained_through_the_mcp</code>, whose <em>name</em> was the claim that went false.</p>
|
|
<p><strong>Duplicate keys are refused by default.</strong> A key is the functor plus every argument but the last, so <code>doctrine(Name,_)</code> keys on the name and <code>decision(Id,Key,_)</code> on the pair. <code>replace</code> is the door a correction goes through, and it overwrites in place — a count that grew would be two clauses under one name.</p>
|
|
<h2 id="what-no-test-here-reaches"><a class="anchor" href="#what-no-test-here-reaches">#</a>What no test here reaches</h2>
|
|
<p>The key rule is looser than <code>functional/1</code> for <code>crate/3</code>, which keys on the first argument alone; <code>kb_consistent</code> is the backstop for that case rather than the merge.</p>
|
|
<p>A clause name cited <em>inside another clause's string</em> is unchecked — dropping or renaming a clause silently dangles every clause referring to it (<code>improvise-2f97</code>).</p>
|
|
<p>A shebang would let <code>kb-merge.pl</code> run directly and collapse the two files into one. It does not work on trealla 3.10.3: <code>#!</code> is a syntax error that aborts the consult, so <code>main/0</code> is never defined, and <code>tpl</code> wants <code>-- args</code> <em>after</em> the file, which a shebang cannot inject. Worth re-testing if trealla gains script support.</p>
|
|
</main>
|
|
</div>
|
|
</body>
|
|
</html>
|