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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ Find related context:

```bash
tap node search "billing migration" --keg @acme/engineering
tap node grep 'BILL-[0-9]+' --keg @acme/engineering
tap tag list "release and payments" --keg @acme/engineering
tap node backlinks 42 --keg @acme/engineering
```
Expand Down
20 changes: 18 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,16 @@ writing KEG files directly.
- `tap node read NODE_ID` - display node content and metadata.
- `tap node edit NODE_ID` - edit a node.
- `tap node list` - list indexed nodes.
- `tap node search QUERY` - search node content.
- `tap node search QUERY...` - find the nodes most relevant to a query, ranked
by the hub across every keg you can read, one keg (`--keg`), or listed kegs
(`--kegs @ns/a,@ns/b`, at most 50). The query takes plain terms, `"quoted phrases"`, and
`-exclusions`. Each hit prints reference, score, title, and a highlighted
snippet; `--limit` caps a page (the hub chooses by default), the next page's
cursor is printed to stderr for `--cursor`, `--id-only` prints references,
and `--json` prints the hub's response. MCP's `node_search` takes the same
parameters; under a flight it never searches outside the cover.
- `tap node grep PATTERN` - match node content lines in one keg against a
regex (MCP `node_grep`), for exact identifiers and audits.
- `tap tag list [EXPR]` - list tags or query tagged nodes.
- `tap node backlinks NODE_ID` - show nodes that link to a node.
- `tap node links NODE_ID` - show outgoing links from a node.
Expand All @@ -116,7 +125,14 @@ writing KEG files directly.

- `tap bootstrap` - create or refresh user-level setup.
- `tap use [@namespace/keg]` - set or inspect project/user keg selection.
- `tap keg create @namespace/name` - create a keg.
- `tap keg create @namespace/name` - create a keg. `--kegv 2026-09` creates a
KEGv2 preview keg where the hub enables it; the default, `2025-07`, is a
Markdown KEGv1. A hub with the preview off refuses `--kegv 2026-09` with
`KEG_VERSION_UNSUPPORTED`; a hub that predates versioned kegs creates a
KEGv1 keg, and `tap` reports that the keg exists as KEGv1 rather than
succeeding. A keg's version never changes after creation. Keg listings,
`tap keg info`, and orientation label KEGv2 kegs, and an operation a keg's
version does not offer fails with `KEG_VERSION_UNSUPPORTED`.
- `tap keg list` - list kegs visible on a hub.
- `tap keg grant|grants|revoke` - manage per-keg access.
- `tap keg visibility public|private` - set keg visibility.
Expand Down
3 changes: 2 additions & 1 deletion docs/ai-coding-agents/mcp-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ explicitly configured root failed to initialize.
| --- | --- |
| `node_read` | Read content of one or more nodes |
| `node_list` | List nodes with optional query filtering |
| `node_search` | Full-text search across node content |
| `node_search` | Rank nodes by relevance to a query across every readable KEG, one `keg`, or listed `kegs`; pages with `next_cursor` |
| `node_grep` | Match node content lines against a regex in one KEG |
| `tag_list` | List tags or find nodes by query |
| `node_backlinks` | Find nodes linking to a node |
| `node_links` | List outgoing links from a node |
Expand Down
19 changes: 19 additions & 0 deletions docs/api-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ curl -H 'Tapper-API-Version: 2026-09-28' \
https://hub.example.com/api/v1/whoami
```

## Ranked search route

Ranked search is an optional Hub capability, so a client must tell a Hub
without it from the route's own failures. Every response from
`POST /api/v1/tap/search`, success and error alike, carries
`Tapper-Search: ranked` (`tapapi.SearchRouteHeader` and
`tapapi.SearchRouteHeaderValue`). Tapper reads a 404 or 405 without that
header as a Hub that does not offer ranked search, reported as
`NOT_SUPPORTED` with a pointer to `node_grep`; with the header, the error envelope
maps as usual, so a `NOT_FOUND` names a KEG the caller cannot read.

A search request names at most 50 KEGs (`tapapi.SearchMaxKegs`). A governed
request, one carrying the `Tapper-Orientation` proof, may leave `kegs` empty:
the Hub then searches every readable KEG in the proof's flight cover. Tapper
relies on that for an unscoped `node_search` inside a flight, so a cover wider
than 50 KEGs still searches in one request. Without the proof, Tapper names
the flight's cover itself and refuses a cover wider than 50 KEGs with
`INVALID`, asking for `kegs` to narrow it.

Publish the Tapper commit first; a hub that embeds Tapper then pins that
published SHA on its own side, by its own procedure. This development change
does not authorize tags, releases, merges, or deployment.
12 changes: 9 additions & 3 deletions docs/configuration/keg-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ they are not included in aggregate orientation.
and a nonzero node create or edit resolves to validation mode `block`, that
write must explicitly select one schema. The selection becomes the completed
node's `meta.type`, and the completed content and metadata are validated
against it. An existing stored type does not satisfy the explicit-selection
requirement. Use `--schema TYPE` with `tap node create`, `tap node edit`, or metadata
writes.
against it. A `type` declared in submitted metadata does not satisfy the
explicit-selection requirement. Use `--schema TYPE` with `tap node create`,
`tap node edit`, or metadata writes.

An edit that replaces only content leaves metadata alone, so it keeps the
node's stored type instead: it needs no selection and is validated against that
type. This does not cover an untyped node, which has no type to keep, or
content whose frontmatter declares a different type. Metadata-only edits and
edits of both parts still need a selection.

`human`, `agent`, and `api` each accept `off`, `warn`, or `block`, and continue
to control validation even when strict is enabled. The defaults remain
Expand Down
4 changes: 3 additions & 1 deletion docs/configuration/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ Select a schema for a node write with `tap node create --schema TYPE`, `tap node
`type` in attributes, Markdown frontmatter, or metadata is rejected instead of
silently taking precedence. Under `schemaPolicy.strict`, selection is required
only when the resolved actor validation mode is `block`; it remains optional
for `warn`, `off`, non-strict kegs, and node 0.
for `warn`, `off`, non-strict kegs, and node 0. A content-only edit of a typed
node keeps its stored type and needs no selection; see
[KEG settings](keg-settings.md).

Metadata property rows can define note maturity scoring under the property's
`maturity` key. The property name is the scored metadata attribute, so each row
Expand Down
21 changes: 20 additions & 1 deletion docs/output-formats.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Output Formats

`tap node list`, `tap node search`, `tap tag list`, `tap node links`, and `tap node backlinks` all render
`tap node list`, `tap node grep`, `tap tag list`, `tap node links`, and `tap node backlinks` all render
node listings through one `--format` template, and the MCP tools of the same
names accept the same string.

Expand Down Expand Up @@ -149,6 +149,25 @@ same fields (`entity=plan`, `.hash=`) are evaluated by the Hub as well.
- **Values are never re-scanned.** A node whose title contains `%c` renders it
literally.

## Ranked search output

`tap node search` and `node_search` rank hits across KEGs, so their rows are
not node listings and take no `--format` template. Each hit is one
tab-separated line:

```text
keg:@acme/notes/12 4.250 Festival trip **festival** in Los Angeles
```

The columns are the node reference, the score to three decimals, the title,
and the snippet with matches wrapped in `**` (bold on a terminal). Control
characters collapse to spaces. `--id-only` prints only references, and
`--json` prints the hub's response unchanged, including matched `fields`,
`sources`, highlight offsets, `terms`, and `mode`. Warnings and the next page's
cursor go to stderr, so stdout stays one hit per line. `node_search` text ends
with the same `next cursor: ...` line when another page remains, so text-only
clients can page too.

## See also

- [Query Expressions](query-expressions.md) — the same vocabulary, in predicate
Expand Down
9 changes: 7 additions & 2 deletions integrations/content/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ schema_read returns a schema's YAML data and hash. Follow its requirements.
node_create accepts nodes (1-100), each with a unique key and markdown content
beginning with an H1. meta is a YAML string, not a JSON object. Metadata must
not be embedded as content frontmatter. schema is a sibling of meta; it sets
meta.type and a conflicting type is rejected. Schema selection is required only
when strict policy and agent mode both block.
meta.type and a conflicting type is rejected. On a strict keg (the default for
new kegs, while agent validation blocks) schema is required:
- node_create: always; type in meta alone does not count.
- node_edit with meta only: required.
- node_edit with content only: not needed; the node keeps its current type and
the content is validated against it.
- node_edit with both: required, and both content and meta must satisfy it.

Example node_create arguments:
```json
Expand Down
30 changes: 23 additions & 7 deletions integrations/content/tool-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ it.
| --- | --- |
| `mcp__tapper__orient` | Read-only view of no-flight identity authority or the pinned real root, an optional exact real-flight selection, revision, available KEGs, and current instructions. |
| `mcp__tapper__session_refresh` | Retry activation only after a broken configured root is repaired. It never replaces active no-flight or real-flight authority; narrowing no-flight access requires a new connection. |
| `mcp__tapper__keg_list`, `mcp__tapper__keg_create` | Discover every identity-accessible KEG at its real role with no flight, or the effective projection of a selected real flight; no-flight creation uses namespace membership while real-flight creation also requires `manage_kegs`. |
| `mcp__tapper__keg_list`, `mcp__tapper__keg_create` | Discover every identity-accessible KEG at its real role with no flight, or the effective projection of a selected real flight; no-flight creation uses namespace membership while real-flight creation also requires `manage_kegs`. Creation takes an optional immutable `kegv`: `2025-07` (KEGv1, the default) or `2026-09` (KEGv2 preview, where the Hub enables it); listings label KEGv2 KEGs. |
| `mcp__tapper__flight_create`, `mcp__tapper__flight_edit`, `mcp__tapper__flight_delete` | Manage Hub flights when the selected flight grants `manage_flights`; edits and deletes require the manifest hash returned by `flight_read`, and normal Hub ACLs still apply. |
| `mcp__tapper__flight_list`, `mcp__tapper__flight_read` | Identity-readable discovery; flight_read returns a permission-filtered declared manifest, including explicit empty collections/instructions and hash. Neither tool activates or selects authority. |

Expand All @@ -55,7 +55,8 @@ accessible transitive descendants.

| Tool | Purpose |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mcp__tapper__node_search` | Regex search over node content. Supports `ignore_case`, `limit`, `max_lines`, and `id_only`. |
| `mcp__tapper__node_search` | Find the nodes most relevant to a query, ranked by the Hub across every KEG the call may read, one `keg`, or the listed `kegs`. Takes plain terms, `"quoted phrases"`, and `-exclusions`. Hits carry `ref`, `score`, `title`, matched `fields`, and a `snippet` with highlights; follow `next_cursor`. |
| `mcp__tapper__node_grep` | Regex match over node content lines in one KEG, in node id order, for exact identifiers, symbols, and audits. Supports `ignore_case`, `limit`, `max_lines`, and `id_only`. |
| `mcp__tapper__tag_list` | List tags or filter nodes by a boolean expression over tags, attributes, and dot-prefix stats fields (for example `tapper and .created>2026-01-01`). |
| `mcp__tapper__node_list` | List nodes in a keg with optional filters. |
| `mcp__tapper__node_read` | Read one or more nodes. Each structured row pairs `node_id` and `hash` with that node's `content` and `meta`, so a read feeds straight into `node_edit`. Supports `meta_only`, `content_only`, `stats_only`, and `query` expression selection as an alternative to explicit node IDs. |
Expand All @@ -65,8 +66,10 @@ accessible transitive descendants.
| `mcp__tapper__keg_settings_read` | Read targeted title, description, updated metadata, and instructions for one or more selected KEGs; batches accept up to 100 canonical references. |
| `mcp__tapper__keg_search` | Case-insensitive literal search across identity-accessible canonical refs, titles, and descriptions. Returns at most 50 rows and never grants operational access. |

Pass `id_only: true` to `node_search` and `tag_list` when you only need IDs for follow-up
reads — it keeps token consumption bounded on large result sets.
Start topic lookups with `node_search`; reach for `node_grep` when you need
every line matching an exact pattern. Pass `id_only: true` to `node_grep` and
`tag_list` when you only need IDs for follow-up reads — it keeps token
consumption bounded on large result sets.

## Query expressions

Expand Down Expand Up @@ -146,9 +149,14 @@ from `node_read`, and either document may be omitted to leave it untouched:

```json
{"nodes": [{"node_id": "12", "content": "# Revised\n\nBody", "expected_content_hash": "CONTENT_HASH_FROM_READ"}]}
{"nodes": [{"node_id": "12", "meta": "type: document\n", "expected_meta_hash": "META_HASH_FROM_READ"}]}
{"nodes": [{"node_id": "12", "meta": "type: document\n", "expected_meta_hash": "META_HASH_FROM_READ", "schema": "document"}]}
```

On a strict keg (the default for new kegs) every `node_create` item and every
`node_edit` item that supplies `meta` needs `schema`; `type` in `meta` alone does
not count. A content-only edit keeps the node's current type and is validated
against it, so it needs no `schema`.

`node_delete` carries only ids and the combined hashes:

```json
Expand Down Expand Up @@ -199,14 +207,22 @@ Text content is JSON rendered from the same public object as structuredContent.
The message field preserves prose, diagnostics, and legacy formatted output.
Images remain in MCP image content blocks alongside JSON metadata.

For node_list, node_search, tag_list, node_links, and node_backlinks, follow
For node_list, node_grep, tag_list, node_links, and node_backlinks, follow
next_offset until null,
keeping the other arguments unchanged. has_more=null means another page is
uncertain; a final nonempty page can be followed by an empty page. Pages are
live, not a stable snapshot; reverse reverses each page. limit defaults to 50
(0 means default; -1 unlimited). node_search max_lines defaults to 3 per node; -1
(0 means default; -1 unlimited). node_grep max_lines defaults to 3 per node; -1
returns all matching lines. Use node_read for complete bodies.

node_search pages with an opaque cursor instead: repeat the same query with
next_cursor as cursor until next_cursor is absent (text output ends with a
`next cursor:` line while more remain). An omitted or 0 limit lets the Hub
choose the page size; -1 is not accepted. Hits are ordered by score, then KEG, then node id;
mode reports `keyword` when the Hub ranked without semantic matching. A KEG
outside the flight's cover is never searched, and a Hub without ranked search
fails with a message pointing at node_grep.

keg_search returns warnings, partial, and truncated. flight_search returns
truncated. Refine a truncated metadata query; neither search has a cursor.
Both search the connection-pinned Hub, never other configured Hubs.
Expand Down
8 changes: 5 additions & 3 deletions integrations/content/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ the same operational information, including error diagnostics and recovery.
- operationPerformed=null: the write outcome is unknown. Inspect state before
retrying, including possible creations; do not assume nothing happened.
- Missing listing rows: follow next_offset until null. For metadata search,
refine a query marked truncated. node_search limits matched lines; node_read
reads full
bodies. A tool absent from tools/list is unavailable, not a failed operation.
refine a query marked truncated. node_search pages with next_cursor;
node_grep limits matched lines; node_read reads full bodies. A tool absent
from tools/list is unavailable, not a failed operation.
- "ranked search unavailable" (NOT_SUPPORTED): the Hub predates ranked
search. Use node_grep with a regex for the terms, one KEG at a time.
- Suspected stale indexes: inspect index_list/index_read and keg_check.
Rebuild with index_rebuild only when warranted and authorized; reads never repair indexes.
Loading
Loading