Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 102 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,28 @@ validate the packed contents.

### add
```bash
knowledge add <title> <content> [--url <url>] [-t <tag>]
knowledge add <title> <content> [--url <url>] [-t <tag>]...
```
Add a new knowledge item.

`-t/--tag` is repeatable and also accepts a comma-separated list, so these are
equivalent and both store three tags:

```bash
knowledge add "Title" "Body" -t convention -t naming -t channels
knowledge add "Title" "Body" -t "convention,naming,channels"
```

Tags are deduped case-insensitively, and the `[INFO] Item added` line reports the
stored tag count so a partial write cannot pass unnoticed.

A `-t` whose value is missing, empty, or only separators (`-t`, `-t ""`, `-t " , "`)
**exits 1**. It previously exited 0 and silently stored nothing, so this is a
deliberate exit-code change: dropping a tag the caller asked for is the defect, and
an empty `-t "$VAR"` expansion should fail where the caller can see it. Commas are
handled the opposite way — they split rather than error — because a comma-joined
value is unambiguous and recoverable, whereas an empty value carries no intent.

### list
```bash
knowledge list|ls [options]
Expand All @@ -289,12 +307,42 @@ state, or keyword retrieval across active compatibility notes too.
| `-p, --page <n>` | Page number (default: 1) |
| `-l, --limit <n>` | Items per page (default: 20) |
| `-s, --search <text>` | Filter by title or content |
| `-t, --tag <tag>` | Filter by tag |
| `-t, --tag <tag>` | Filter by tag; repeatable/comma-separated, an item must match **all** of them |
| `--sort created\|title` | Sort field (default: created) |
| `--desc` | Sort descending |
| `--archived` | Return archived items *only* |
| `--include-archived` | Return live **and** archived items (default: live only) |
| `--verbose` | Print the full paginated item objects |
| `--json` | Print the stable machine-readable list response |

Each `-t` value matches an item when the item's stored tags contain the **whole value**
*or* all of its **comma-split names** — a union. This is deliberately **not** the rule
[`untag`](#untag) uses: `untag` is **exclusive**, matching the whole value first and
falling back to the split names *only* when no stored tag equals the whole value.

Both rules are right for their own command. `untag` writes, so it must take one shape per
run — that is exactly what makes its documented "re-run to clear the split names"
behaviour true. `list` only reads, so matching both shapes destroys nothing, while
matching only one would hide items from the very query used to find them. Items written
before the multi-tag parse fix can carry a single literal `"a,b,c"` tag, which none of the
split names equals, so a split-only filter does not merely miss the damaged item: it
answers with a *different* item that carries the three names separately, at `total: 1` and
exit 0, giving the operator no signal that the result changed. The union therefore finds
both shapes in one query:

```bash
# returns items tagged with the literal "a,b,c" AND items tagged a + b + c separately
knowledge list -t "a,b,c" --json
```

Prefer `--json` when you are looking for glued tags: the table renders `["a,b,c"]` and
`["a","b","c"]` as `[a,b,c]` and `[a, b, c]`, which differ only by two spaces.

`list` excludes **archived** items by default, so the query above will not surface a glued
tag that sits on an archived item. Add `--include-archived` to sweep live and archived
items together, or `--archived` for archived items only — use the former when auditing the
corpus for tag damage rather than reading live knowledge.

### inventory
```bash
knowledge inventory [--scope local|global|project] [--limit <n>] [--include-archived] [--verbose] [--json]
Expand Down Expand Up @@ -323,7 +371,13 @@ Update an existing item.
| `--title <title>` | New title |
| `--content <content>` | New content |
| `--url <url>` | New source URL |
| `-t, --tag <tag>` | Add a tag |
| `-t, --tag <tag>` | Add tag(s); repeatable/comma-separated. Tags are appended, never replaced |

When `-t` is passed, the result reports how many tags were actually added — `added` in
JSON and `Updated <id> (added 2 tags)` in `message`. Appending a tag that is already
present is idempotent, so this stays exit 0 (unlike `untag`, where removing nothing is an
error), but a bare `Updated <id>` could not distinguish 2 added from 0 and reported the
count nowhere at all.

### archive / restore
```bash
Expand All @@ -334,15 +388,56 @@ Archive hides an item from default `list` output without deleting it.

### upsert
```bash
knowledge upsert [title] [content] [--id <id>] [--title <title>] [--content <content>]
knowledge upsert [title] [content] [--id <id>] [--title <title>] [--content <content>] [-t <tag>]...
```
Create or update an item by ID.
Create or update an item by ID. `-t` appends tags and reports an `added` count the same
way [`update`](#update) does, on the create path as well as the update path — so a caller
never has to branch on `created` to know whether `added` is meaningful.

### untag
```bash
knowledge untag --id <id> -t <tag>
knowledge untag --id <id> -t <tag>...
```
Remove tag(s) from an item. `-t/--tag` is repeatable and accepts a
comma-separated list, so several tags can be removed in one call.

Each `-t` value is matched **whole first, and only split on commas if no stored tag
equals it**. Items written before the multi-tag parse fix was landed can carry a
single literal `"a,b,c"` tag, which none of the split names would match — so a
split-only `untag` would remove nothing while still exiting 0. One command therefore
covers both shapes:

```bash
# stored tags ["a,b,c"] -> removes the one literal tag (whole-value match)
# stored tags ["a","b","c"] -> removes all three (falls back to splitting)
knowledge untag --id <id> -t "a,b,c"
```

When an item somehow carries both shapes, the whole-value match wins and the literal
tag is removed first; re-run to clear the split names. That exclusivity is the point: one
shape per run is what makes re-running meaningful, so `untag` deliberately does **not**
match both shapes at once. [`list -t`](#list) does — a union — because a filter has nothing
to remove and suppressing a shape would hide items. Do not "unify" the two rules.

Removing nothing **exits 1**. `removed: 0` at exit 0 with a `Removed tag from <id>`
message is the same class of defect as the original bug — a success signal that
cannot distinguish 1 removed from 0 — so an absent tag is an error, as a missing
`--id` already is. When some requested tags matched and others did not, the call
succeeds and the unmatched names are reported both in `message` (`Removed 1 tag from
<id> (not found: "nope")`) and in `not_found`. `message` carries them because without
`--json` or `--verbose` that is the only line printed, so a `not_found`-only report is
invisible to exactly the callers most likely to be reading it.

Tag names are quoted on **both** paths — the stored tags in the failure message, and the
unmatched names in the partial-miss success line. Unquoted, a single glued `"a,b,c"` tag
renders identically to three separate ones, so the message appears to deny a tag that is
plainly listed, and `(not found: p, q)` cannot be told apart from one missing tag literally
named `p, q`:

```
Error: No matching tag on k_x: "iapp" not in ["iapp,integrations,architecture"]
Removed 1 tag from k_y (not found: "p", "q")
```
Remove one tag from an item.

### delete
```bash
Expand Down
Loading
Loading