From a53b8b3aa14d52b1b274ef6329dbf2218139fe05 Mon Sep 17 00:00:00 2001 From: "shit-agents[bot]" <248805456+shit-agents[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 05:02:07 +0000 Subject: [PATCH 1/3] spec: know-add-write-path for #161 --- specs/know-add-write-path/spec.md | 195 ++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 specs/know-add-write-path/spec.md diff --git a/specs/know-add-write-path/spec.md b/specs/know-add-write-path/spec.md new file mode 100644 index 0000000..916b932 --- /dev/null +++ b/specs/know-add-write-path/spec.md @@ -0,0 +1,195 @@ +# SPEC: know add — caller stays dumb (WriteGate on stderr) + +Status: **READY** (night-shift 2026-08-31, tightened) +Repo: `conduit-ui/knowledge` +Issues: [#161](https://github.com/conduit-ui/knowledge/issues/161) remainder; [#167](https://github.com/conduit-ui/knowledge/issues/167) title is stale +Thor clone: `/home/jordan/Projects/conduit-ui/repos/knowledge` +Slug: `know-add-write-path` + +**Tonight remainder is #161.** Category is already optional on `master` (`6ce840d`). #167's title ("do not require category") is stale. Remaining contract: WriteGate rejection (and other add failures) go to **stderr**; stdout is the success table only. Duplicate/wrong-shelf is not a caller error. + +Issue #161 bonus `--quiet` prints UUID on success: **out of scope tonight** (residual `--id-only`). `--quiet` in this SPEC silences stderr; exit codes unchanged. + +## Goal + +`know add` / MCP `remember` accept title + content + provenance. WriteGate rejection is stderr + non-zero so pipelines that grep stdout cannot swallow a failed write. Unknown category is dropped. Hash/similar duplicate returns existing id as success (non-interactive). + +## In-scope + +- WriteGate reject → FAILURE, reason on **stderr only**, no stdout table. Do not use Laravel Prompts `error()` (stdout) for gate/dup/invalid-category. +- `--quiet` suppresses stderr; exit code unchanged. +- Unknown / omitted category is not a caller error (already mostly shipped; keep tests). +- Hash duplicate and non-interactive similarity duplicate → SUCCESS + existing id. +- Pest Process tests so stdout/stderr are separate (`artisan()` mixes streams). + +## Out-of-scope + +- WriteGate *criteria* / eval set of known-good entries. +- `--id-only` machine stdout (issue's UUID-on-success idea). +- Asgard librarian, tenant routing, shelves. +- knowledge#164 Ollama mesh router. +- PSTrax. Merge. Secrets. `gh pr merge`. + +## Files likely touched + +``` +app/Commands/KnowledgeAddCommand.php +app/Mcp/Tools/RememberTool.php +tests/Feature/KnowledgeAddCommandTest.php +tests/Unit/Mcp/Tools/RememberToolTest.php +specs/know-add-write-path/ +specs/README.md +``` + +Do not edit `WriteGateService` criteria. + +## Tests + +``` +vendor/bin/pest --filter='KnowledgeAddCommand|RememberTool' +``` + +Must include: WriteGate rejection message on stderr not stdout (Process test); `--quiet` + reject → stderr empty, exit 1. + +## Done-when + +- [ ] `know add … >/tmp/out 2>/tmp/err` on gate reject → `/tmp/out` empty, `/tmp/err` has the reason, exit 1. +- [ ] Filter green. Allowlist only. +- [ ] Unknown category still not a caller error (existing remainder). + +## Must-not + +- No PSTrax. No merge. No secrets. +- Do not fail the caller because `--category` is missing or unknown. +- Do not print WriteGate rejection via Laravel Prompts `error()`. +- Do not call Asgard/librarian. Do not invent shelves. + + +## Already shipped (do not rebuild) + +| Piece | What it is | What it is not | +|---|---|---| +| `know add {title} --content=` | Required write | Category required | +| `--category=` option | Optional. Missing → payload `category: null` | Closed enum that fails the caller | +| Pest `creates a knowledge entry with required fields` | Title + content | Category in the required set | +| MCP `RememberTool` schema | `category` optional | Enum gate on write | +| MCP duplicate catch | `status: duplicate_detected` + `existing_id` as **text**, not `Response::error` | CLI hash-dup still `FAILURE` | +| `--force` | Skip write gate and duplicate check | Quiet stderr | +| WriteGate heuristics | Category `deployment`/`security`/`architecture` as **positive** signals | Caller-facing shelf picker | + +On `master` (2026-07-06, `6ce840d`) category is already optional. #167’s title is stale. Remaining contract is wrong-shelf + duplicate + stderr. + +## One-sentence intent + +**`know add` and MCP `remember` accept title + content + provenance; unknown category is dropped; exact or similar duplicate returns the existing id as success; WriteGate rejection is stderr + non-zero; librarian is not this package.** + +## Locked decisions + +- Required on write: `title`, `content`. Provenance (`--source`, git context, `--ticket`, `--author`) is optional and already wired. +- Category is a **hint**. It is never required. It is never a caller error. +- Unknown / empty / omitted category → persist `category: null`. stderr warning only when the caller **passed** a value that is not in `VALID_CATEGORIES`. Omitted category: no warning. +- Do not persist the invalid string. Do not invent shelves (`patterns`, `decisions`, `gotchas`, …). MCP must match CLI: unknown → `null`. +- Hash duplicate → **SUCCESS**. Print existing id on stdout (scripts capture it). Notice on stderr. No new upsert. No prompt. +- Similarity duplicate, non-interactive → **SUCCESS**, same as hash: return existing id. No `confirm()`. +- Similarity duplicate, interactive TTY without `--force` → keep today’s supersede prompt. Agents and pipelines are non-interactive. +- `--force` still skips gate + duplicate detection and creates a new id. +- WriteGate reject → **FAILURE**, reason on **stderr only**, no stdout table. `--force` bypasses. `--quiet` suppresses stderr (exit code unchanged). +- Do not change WriteGate *criteria* in this SPEC. Heuristics may keep using category. Librarian may rewrite shelves later; not here. +- Laravel Prompts `error()` is stdout for this CLI. Do not use it for gate/dup/invalid-category. Use stderr (`fwrite(STDERR, …)` or the command error output). +- Pest `artisan()` mixes streams. Gate/dup/quiet tests that care about stderr must drive the `know` binary (or Symfony Process) so stdout/stderr are separate. Existing `assertFailed()` / `assertSuccessful()` artisan tests stay for exit codes. +- MCP: duplicate stays a JSON text payload (`duplicate_detected` + `existing_id`), not `Response::error`. WriteGate reject stays `Response::error`. Unknown category → stored `null`, still create if gate passes. +- `#164` mesh OpenAI router is a residual. Do not port `OllamaService` here. + +## CLI contract + +``` +know add --content=<blob> [--category=…] [--quiet] [--force] … +``` + +| Input | stdout | stderr | exit | +|---|---|---|---| +| New entry, no category | table (or id); category shown `N/A` | empty unless other warnings | 0 | +| `--category=testing` (valid) | table; category `testing` | empty | 0 | +| `--category=bogus` | table; category `N/A` | warning: ignored unknown category | 0 | +| Hash or similar dup, not TTY, no `--force` | existing id | notice: already exists | 0 | +| WriteGate reject, no `--force` | empty | `Write gate rejected entry: …` | 1 | +| WriteGate reject + `--quiet` | empty | empty | 1 | +| Missing `--content` | empty | content required | 1 | + +`--quiet` applies to warnings and rejections on stderr. It does not change exit codes. It does not hide a successful table unless we later add a `--id-only` flag (not this SPEC). + +## MCP `remember` contract + +| Input | Result | +|---|---| +| title + content, no category | `status: created`, `category` null | +| `category: "bogus"` | created, stored category null | +| hash/similar duplicate | `status: duplicate_detected`, `existing_id`, not an error | +| WriteGate fail | `Response::error` (already) | + +Schema: `category` remains optional, **no enum**. Do not add CLI’s `VALID_CATEGORIES` as a JSON-schema enum. + +## Allowlist + +``` +app/Commands/KnowledgeAddCommand.php +app/Mcp/Tools/RememberTool.php +tests/Feature/KnowledgeAddCommandTest.php +tests/Unit/Mcp/Tools/RememberToolTest.php +specs/know-add-write-path/ +specs/README.md +``` + +Do not edit `WriteGateService` unless a test cannot observe stderr without it — it should not need a criteria change. + +## Pest (required) + +Keep existing green tests except the ones this SPEC deliberately changes: + +- `it('validates category is valid')` → **replace**: unknown category succeeds, persisted category null, upsert **does** run. +- `it('fails on exact hash duplicate')` → **replace**: exit 0, no second representation of the blob; existing id available. +- Similarity tests that `assertFailed()` on decline remain for **TTY / confirm** only. Add a non-interactive test: no confirm, exit 0, existing id. + +Add: + +- unknown category is not a caller error (CLI + MCP) +- omitted category still creates (already exists; keep) +- WriteGate rejection message is on stderr, not stdout (Process test) +- `--quiet` + WriteGate reject: stderr empty, exit 1 +- MCP unknown category stored null +- MCP duplicate remains non-error JSON + +Filter: + +``` +vendor/bin/pest --filter='KnowledgeAddCommand|RememberTool' +``` + +House tests outside the filter must stay green if you run the full suite; do not “fix” them by widening the allowlist. + +## Forbidden strings / behavior + +- Do not fail the caller because `--category` is missing or unknown. +- Do not print WriteGate rejection via Laravel Prompts `error()` (stdout). +- Do not call Asgard, librarian, Qdrant-as-org-memory, or invent shelves. +- Do not port Ollama to the mesh router. +- Do not add `--category` to MCP required schema. + +## Residuals (not this PR) + +- knowledge#164 — `OllamaService` → mesh OpenAI endpoint +- WriteGate criteria / auto-tagger as librarian +- `--id-only` machine stdout +- Tenant routing + +## Acceptance + +1. Filter green. +2. `git diff --stat` matches the allowlist. +3. `know add "x" --content="enough blob" --category=not-a-shelf` exits 0 and stores `category: null`. +4. Hash duplicate exits 0 and does not upsert a second point. +5. WriteGate reject: `know add … >/tmp/out 2>/tmp/err` → `/tmp/out` empty, `/tmp/err` has the reason, exit 1. + +## Implementer contract (Qwen) + +Read `specs/know-add-write-path/QWEN.md` then this file. Pest first. Allowlist only. Stop when the filter is green. From af8d702d8c1490337e46e0fe30d767a7b83c6068 Mon Sep 17 00:00:00 2001 From: "shit-agents[bot]" <248805456+shit-agents[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 05:02:09 +0000 Subject: [PATCH 2/3] spec: know-add-write-path for #161 --- specs/know-add-write-path/AGENT_PROMPT.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 specs/know-add-write-path/AGENT_PROMPT.md diff --git a/specs/know-add-write-path/AGENT_PROMPT.md b/specs/know-add-write-path/AGENT_PROMPT.md new file mode 100644 index 0000000..1b42001 --- /dev/null +++ b/specs/know-add-write-path/AGENT_PROMPT.md @@ -0,0 +1,12 @@ +Implement SPEC `specs/know-add-write-path/spec.md` exactly. Read `specs/know-add-write-path/QWEN.md` first. + +You are a local Ollama coding agent (qwen-coder-32k). Pest first. Allowlist only. Tonight remainder is #161 stderr. + +Do: +1. Read the spec and `app/Commands/KnowledgeAddCommand.php`, `app/Mcp/Tools/RememberTool.php`, the two listed test files. +2. WriteGate reject → stderr only, exit 1, no stdout table. No Laravel Prompts `error()` for gate/dup/unknown-category. `--quiet` silences stderr, not the exit code. +3. Unknown category succeeds (persisted null). Hash duplicate is exit 0 with existing id. Non-interactive similarity = return existing. +4. Process tests so stdout/stderr are separate. +5. `vendor/bin/pest --filter='KnowledgeAddCommand|RememberTool'` green. Stop. + +Do not: Asgard, librarian, mesh Ollama router, new shelves, JSON-schema enum on MCP category, WriteGate criteria change, `--id-only`, PSTrax, `gh pr merge`. From 42afd4bad7eea21c258eb41123951ce50a292fb7 Mon Sep 17 00:00:00 2001 From: "shit-agents[bot]" <248805456+shit-agents[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 05:02:10 +0000 Subject: [PATCH 3/3] spec: know-add-write-path for #161 --- specs/know-add-write-path/QWEN.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 specs/know-add-write-path/QWEN.md diff --git a/specs/know-add-write-path/QWEN.md b/specs/know-add-write-path/QWEN.md new file mode 100644 index 0000000..a621b22 --- /dev/null +++ b/specs/know-add-write-path/QWEN.md @@ -0,0 +1,27 @@ +# Qwen: implement `specs/know-add-write-path` + +You are implementing one knowledge SPEC. You are not designing. You are not adding features. + +## Read first + +1. `specs/know-add-write-path/spec.md` (normative) +2. `app/Commands/KnowledgeAddCommand.php` +3. `app/Mcp/Tools/RememberTool.php` +4. `tests/Feature/KnowledgeAddCommandTest.php` +5. `tests/Unit/Mcp/Tools/RememberToolTest.php` + +Do not edit `WriteGateService.php` criteria. + +## Do + +Pest first. Allowlist files only. + +`vendor/bin/pest --filter='KnowledgeAddCommand|RememberTool'` must pass. + +Unknown `--category` is not a failure. Hash duplicate is success + existing id. WriteGate reject goes to stderr. `--quiet` silences stderr, not the exit code. + +## Do not + +Asgard, librarian, Ollama mesh router, new category enums, Prompts `error()` for gate/dup/wrong-shelf, extra SPECs. + +Stop when tests are green and `git diff --stat` matches the spec allowlist.