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`. 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. 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