refactor(skill): extract retrieval-semantics.md, compress pitfalls.md Move query design principles (scope/plan/structure/evidence) and field
semantics (context types, ordering, project scopes) out of pitfalls.md into a new retrieval-semantics.md. Pitfalls.md becomes a compact debug checklist: missing columns, FTS errors, over-large output, empty results. SKILL.md query routing updated to point at the three reference tiers.
This commit is contained in:
@@ -50,8 +50,9 @@ Before writing a query, classify the task. Progressive disclosure is useful, but
|
|||||||
skipping the relevant reference usually costs extra query rounds.
|
skipping the relevant reference usually costs extra query rounds.
|
||||||
|
|
||||||
- Read `references/schema.md` before raw `sql()` unless the needed table/column relationship is already explicit here. Do this before running the SQL, not after a missing-column error.
|
- Read `references/schema.md` before raw `sql()` unless the needed table/column relationship is already explicit here. Do this before running the SQL, not after a missing-column error.
|
||||||
- Read `references/query-patterns.md` before broad "how did X evolve / what did we do / what problems happened / what was the conclusion" synthesis, and for one-shot synthesis retrieval, workflow trees, failed tool counts or failure groups, file history synthesis, summary neighbors, subagent recall, raw windows, and empty-result handling.
|
- Read `references/retrieval-semantics.md` before multi-step retrieval, scoped project/file/session searches, or synthesis/conclusion/history questions. It defines the query design frame.
|
||||||
- Read `references/pitfalls.md` when a scoped result is empty or tiny, when a query may over-fetch, when a term is hyphenated, when project scope is ambiguous, or when helper row fields are unclear.
|
- Read `references/query-patterns.md` when you need copyable query scripts: one-shot synthesis, learned detail passes, workflow trees, failure groups, file history, summaries, subagents, raw windows, or empty results.
|
||||||
|
- Read `references/pitfalls.md` after an error or when helper fields, FTS syntax, aliases, or row shapes are unclear.
|
||||||
|
|
||||||
If a helper row shape is unclear, first run a tiny scoped query and return
|
If a helper row shape is unclear, first run a tiny scoped query and return
|
||||||
`Object.keys(row)` or a compact sample. Do not invent field names.
|
`Object.keys(row)` or a compact sample. Do not invent field names.
|
||||||
@@ -134,26 +135,14 @@ tiny sample before relying on less common filters.
|
|||||||
|
|
||||||
Keep queries scoped, bounded, and structural.
|
Keep queries scoped, bounded, and structural.
|
||||||
|
|
||||||
- Preserve explicit project/session/file/time scopes. Empty or tiny scoped results are real results; do not broaden unless the user asks.
|
- Scope First: classify the locator as scope, artifact, or semantic. Use the narrowest structural locator before FTS; empty scoped results are valid unless the user asks to broaden.
|
||||||
- Treat project scope as three distinct semantics: exact `sessions.project` slug, exact `sessions.project_path`, or fuzzy `LIKE` search. Use `sql()` for exact slug/path membership; helper `project` means fuzzy `LIKE`.
|
- Plan Before Probe: for conclusion, broad history, failure investigation, or file evolution, write a bounded retrieval script instead of spending turns on intermediate results.
|
||||||
- Start with cheap locators: `sessions()`, `summaries()`, `search()`, or a small SQL query.
|
- Structure Before Text: compute counts, joins, grouping, dedupe, and projection in SQL or JS; keep runtime JSON compact, ideally under 10k-12k chars for synthesis tasks.
|
||||||
- Expand incrementally with `context()`, `trace()`, neighbor SQL, or `raw()` windows.
|
- Evidence Before Conclusion: return compact evidence with stable IDs (`session_id`, `uuid`, `tool_call_id`, `run_id`, `agent_id`) and short snippets, then synthesize in the final answer.
|
||||||
- For conclusion, broad history, failure investigation, or file evolution questions, prefer one bounded query script that locates, expands, dedupes, groups, and returns compact evidence rows. If a second detail pass is needed, derive its filters or facets from the first pass; do not dump whole session windows by default.
|
|
||||||
- Return compact evidence with stable IDs (`session_id`, `uuid`, `tool_call_id`, `run_id`, `agent_id`) and short snippets.
|
|
||||||
- Avoid `thread()` unless the user explicitly asks for a full transcript or all smaller probes are insufficient.
|
|
||||||
- Keep runtime JSON small, ideally under 10k-12k chars for synthesis tasks. Do not return all sessions, all summaries, all tool calls, complete workflow trees, full raw messages, or whole tool results.
|
|
||||||
- When counting or aggregating, compute counts in SQL or in the query script and return those counts. Do not hand-count from long rows in prose.
|
|
||||||
- For recent failures or "which tasks failed" questions, aggregate by session/task and return counts plus sparse examples. Do not return raw failure rows.
|
|
||||||
- For broad "how did X evolve / what did we do / what problems happened" history synthesis, use a bounded facet sweep from `references/query-patterns.md`. For concept recall, session lookup, or exact term recall, keep compact `search()` first.
|
|
||||||
|
|
||||||
High-frequency field contracts:
|
If field, context, ordering, FTS, or helper semantics affect the query, read
|
||||||
|
`references/retrieval-semantics.md` before coding. If a query errors, read
|
||||||
- `summaries()` uses `source` and `content`, not `summary_type` or `text`.
|
`references/pitfalls.md` before retrying.
|
||||||
- `search().context` is temporal neighbor context, not causal or parent-chain context.
|
|
||||||
- `fileHistory()` includes `Read`; filter to `Edit`/`Write` for causal change history.
|
|
||||||
- `workflowTree()` may expose raw `script` and `result_json`; omit them unless the user asks for raw workflow details.
|
|
||||||
- `fileHistory()` is ordered oldest first. For recent file changes, use SQL with `ORDER BY m.timestamp DESC`.
|
|
||||||
- FTS5 tokenizes hyphens and treats `search(text)` as raw `MATCH` syntax. For `workflow-script`, search the quoted tokenized phrase such as `"workflow script"` or use SQL `LIKE` for exact hyphen matching.
|
|
||||||
|
|
||||||
## Minimal Patterns
|
## Minimal Patterns
|
||||||
|
|
||||||
|
|||||||
+38
-161
@@ -1,65 +1,40 @@
|
|||||||
# Obelisk Pitfalls
|
# Obelisk Pitfalls
|
||||||
|
|
||||||
Use this when a query may over-fetch, when a scoped query returns few or zero
|
Use this after a query error, suspicious empty result, over-large output, or
|
||||||
rows, or when helper fields are unclear.
|
unclear helper row shape. For query design, read `retrieval-semantics.md` first.
|
||||||
|
|
||||||
## Scope Is A Contract
|
## Missing Columns And Wrong Aliases
|
||||||
|
|
||||||
If the user gives a project, session, file, or time range, keep every query
|
Common wrong guesses:
|
||||||
inside that scope. Do not broaden because a scoped result is small.
|
|
||||||
|
|
||||||
There are three different project-like scopes:
|
- Summaries: use `source` and `content`; do not use `summary_type` or `text`.
|
||||||
|
- Tool call name: use `tool_calls.name`. `tool_name` is only an alias in `SELECT tc.name AS tool_name`; `tc.tool_name` is not a column.
|
||||||
|
- Tool call timestamps: `tool_calls` has no timestamp. Join `messages m ON m.uuid = tc.message_uuid`.
|
||||||
|
- Tool result timestamps: `tool_results` has no timestamp. Join `messages m ON m.uuid = tr.message_uuid`.
|
||||||
|
- Workflow agent message counts: `workflowTree()` returns `messageCount` for agents.
|
||||||
|
|
||||||
- `sessions.project`: stored Claude Code project slug.
|
When uncertain, inspect a tiny sample instead of guessing:
|
||||||
- `sessions.project_path`: reconstructed absolute project path.
|
|
||||||
- `messages.cwd`: working directory for a specific message.
|
|
||||||
|
|
||||||
`project` filters in helpers are SQL `LIKE` patterns over `sessions.project`.
|
|
||||||
`%quiet-zero%` can match benchmark or generated workspaces that merely contain
|
|
||||||
that string. Prefer exact `sql()` filters when the user asks for exact project
|
|
||||||
membership:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
sql(`
|
const rows = summaries({ limit: 1 });
|
||||||
SELECT id, title, project, project_path, ended_at
|
return rows.length ? Object.keys(rows[0]) : [];
|
||||||
FROM sessions
|
|
||||||
WHERE project_path = ?
|
|
||||||
ORDER BY ended_at DESC
|
|
||||||
LIMIT 20
|
|
||||||
`, '/Users/tomiya/Code/quiet-zero')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Use fuzzy project search only when the task is discovery or the user explicitly
|
## FTS5 Syntax Errors
|
||||||
asked to search broadly. If you broaden, make the broadening visible in the
|
|
||||||
returned evidence.
|
|
||||||
|
|
||||||
Self-noise examples to filter when the user asks for real historical sessions:
|
`search(text)` uses raw FTS5 `MATCH`. Hyphenated terms and punctuation can be
|
||||||
|
parsed as syntax.
|
||||||
- `SkillOpt-outputs`
|
|
||||||
- `obelisk_train`
|
|
||||||
- `obelisk-eval`
|
|
||||||
|
|
||||||
## FTS5 Hyphens And Syntax
|
|
||||||
|
|
||||||
`search(text)` passes text to FTS5 `MATCH`. Hyphenated terms can be parsed as
|
|
||||||
operators or separate tokens, and special characters can raise FTS syntax
|
|
||||||
errors.
|
|
||||||
|
|
||||||
For `workflow-script`, use a quoted tokenized phrase:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// tokenized phrase for FTS
|
||||||
search('"workflow script"', { limit: 10 })
|
search('"workflow script"', { limit: 10 })
|
||||||
```
|
```
|
||||||
|
|
||||||
Use exact phrases for phrase semantics, separate terms for token semantics, and
|
For literal punctuation, use SQL `LIKE` under the same scope:
|
||||||
SQL `LIKE` for literal punctuation. Do not silently fallback from a scoped FTS
|
|
||||||
query to all sessions.
|
|
||||||
|
|
||||||
For exact hyphen matching, use SQL `LIKE` on `messages.text` under a scope:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
sql(`
|
sql(`
|
||||||
SELECT m.uuid, s.id AS session_id, s.title, substr(m.text,1,240) AS snippet
|
SELECT m.uuid, s.id AS session_id, s.title, substr(m.text,1,180) AS snippet
|
||||||
FROM messages m
|
FROM messages m
|
||||||
JOIN sessions s ON s.id = m.session_id
|
JOIN sessions s ON s.id = m.session_id
|
||||||
WHERE s.project LIKE ?
|
WHERE s.project LIKE ?
|
||||||
@@ -69,116 +44,36 @@ sql(`
|
|||||||
`, '%quiet-zero%', '%workflow-script%')
|
`, '%quiet-zero%', '%workflow-script%')
|
||||||
```
|
```
|
||||||
|
|
||||||
`rank` is already applied by `ORDER BY rank`; lower rank sorts earlier in this
|
## Over-Large Runtime JSON
|
||||||
runtime. Prefer returned order over comparing "closer to zero" manually.
|
|
||||||
|
|
||||||
## Context Is Not Always Causal
|
If runtime stdout is large, fix the query instead of reading it in chunks.
|
||||||
|
|
||||||
`search().context` returns temporal neighbors: nearby messages by timestamp in
|
- Lower `LIMIT`.
|
||||||
the same session. It is useful for quick orientation, but it is not the parent
|
- Shorten snippets to 160-240 chars.
|
||||||
chain and may cross side branches, subagents, or workflow boundaries.
|
- Group in SQL/JS and return counts plus sparse examples.
|
||||||
|
- For `fileHistory()`, filter to `Edit`/`Write` before projecting evidence.
|
||||||
|
- For `workflowTree()`, omit `script`, `result_json`, and full agent messages unless explicitly requested.
|
||||||
|
- Use `raw(uuid, { offset, limit })` only after identifying one specific message UUID.
|
||||||
|
|
||||||
Use:
|
## Empty Results
|
||||||
|
|
||||||
- `context(uuid)` for message, parent chain, session, subagent, and workflow.
|
An empty array can be the correct answer for exact scopes or sentinels.
|
||||||
- `trace(uuid)` for just the parent chain.
|
|
||||||
- SQL timestamp neighbors for horizontal expansion inside one session.
|
|
||||||
|
|
||||||
## Ordering Defaults Matter
|
When the user asks for a scoped project/file/session or exact term:
|
||||||
|
|
||||||
Some helpers return newest first; others do not.
|
1. run the scoped query;
|
||||||
|
2. return `[]` or compact counts;
|
||||||
|
3. say no matching prior result was found;
|
||||||
|
4. do not call `recent()`, all-project `summaries()`, or `thread()` as fallback unless the user asks.
|
||||||
|
|
||||||
- `sessions()` returns newest sessions first.
|
## Counting From Snippets
|
||||||
- `summaries()` returns newest summaries first.
|
|
||||||
- `workflows()` returns newest workflows first.
|
If the user asks "how many", "counts", "top N", or "group by", compute it in
|
||||||
- `failures()` returns newest failures first, but should still be treated as an evidence helper rather than a precise count helper.
|
SQL or in the query script. Do not infer counts from visible snippets.
|
||||||
- `fileHistory()` orders by message timestamp ascending. If the user asks for recent changes, use SQL explicitly:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
sql(`
|
sql(`
|
||||||
SELECT tc.id, tc.name, tc.file_path, m.timestamp, s.id AS session_id, s.title
|
SELECT tc.name AS tool_name, COUNT(*) AS n
|
||||||
FROM tool_calls tc
|
|
||||||
JOIN messages m ON m.uuid = tc.message_uuid
|
|
||||||
JOIN sessions s ON s.id = tc.session_id
|
|
||||||
WHERE tc.file_path = ?
|
|
||||||
AND tc.name IN ('Edit', 'Write', 'NotebookEdit')
|
|
||||||
ORDER BY m.timestamp DESC
|
|
||||||
LIMIT 20
|
|
||||||
`, '/absolute/path/to/file')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conversation Turns Are Expensive
|
|
||||||
|
|
||||||
SQLite queries are cheap; repeated conversation turns are not. Every turn can
|
|
||||||
write intermediate query output into conversation context and make later turns
|
|
||||||
read it again.
|
|
||||||
|
|
||||||
For conclusion, broad history, failure investigation, or file evolution tasks,
|
|
||||||
prefer one bounded query script that does the mechanical retrieval work inside
|
|
||||||
the script:
|
|
||||||
|
|
||||||
1. locate candidates with `search()`, `summaries()`, or SQL;
|
|
||||||
2. expand only selected hits with `context()`, `trace()`, or neighbor SQL;
|
|
||||||
3. dedupe and group by `session_id`, facet, file, or tool;
|
|
||||||
4. return compact evidence rows plus counts/limits.
|
|
||||||
|
|
||||||
Do not show every intermediate result to the conversation. Return the final
|
|
||||||
compact evidence view, then use the model for the conclusion.
|
|
||||||
|
|
||||||
## Session Windows Are Not Evidence Plans
|
|
||||||
|
|
||||||
After finding a relevant session, avoid defaulting to `LIMIT 25` or `LIMIT 40`
|
|
||||||
message windows. That is transcript browsing in miniature: it often brings back
|
|
||||||
thinking, transitions, and repeated context instead of the evidence needed for
|
|
||||||
the question.
|
|
||||||
|
|
||||||
Preferred detail pass:
|
|
||||||
|
|
||||||
1. extract candidate terms, files, tools, or decisions from the first pass;
|
|
||||||
2. query by learned facets inside the candidate sessions;
|
|
||||||
3. return 2-4 rows per facet, 8-12 rows total, with 160-220 char snippets.
|
|
||||||
|
|
||||||
If the vocabulary is still unclear, use a small session window as fallback:
|
|
||||||
5-8 rows per session, filtered by timestamp, role, or discovered terms when
|
|
||||||
possible, and explain the fallback in `query_plan`.
|
|
||||||
|
|
||||||
## Compact Vs Raw
|
|
||||||
|
|
||||||
Default to compact evidence. Raw/full access is a conscious escalation.
|
|
||||||
|
|
||||||
- `workflowTree()` may include `script`, `result_json`, parsed `result`, and all agents. Project only the fields needed for the answer.
|
|
||||||
- `thread(sessionId)` dumps a whole session; use it only as a last resort.
|
|
||||||
- `raw(uuid)` can recover long original JSONL lines; use small windows and cite `totalLength`/`hasMore`.
|
|
||||||
- Tool results and tool inputs can be large. Return short snippets.
|
|
||||||
|
|
||||||
## Field Names To Avoid Guessing
|
|
||||||
|
|
||||||
Common wrong guesses:
|
|
||||||
|
|
||||||
- Summaries: use `source` and `content`; do not use `summary_type` or `text`.
|
|
||||||
- Tool call name: use `tool_calls.name`. `tool_name` is only a safe alias in `SELECT tc.name AS tool_name`; `tc.tool_name` is not a table column.
|
|
||||||
- Tool result timestamps: `tool_results` has no timestamp. Join `messages`.
|
|
||||||
- Tool call timestamps: `tool_calls` has no timestamp. Join `messages`.
|
|
||||||
- Workflow agent message counts: `workflowTree()` returns `messageCount` for agents.
|
|
||||||
|
|
||||||
When uncertain:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const rows = summaries({ limit: 1 });
|
|
||||||
return rows.length ? Object.keys(rows[0]) : [];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Counting Must Be Structural
|
|
||||||
|
|
||||||
If the user asks "how many", "counts", "top N", or "group by", compute it in SQL
|
|
||||||
or in the query script and return the computed data. Do not infer counts from
|
|
||||||
visible snippets in prose.
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
```js
|
|
||||||
sql(`
|
|
||||||
SELECT tc.name, COUNT(*) AS n
|
|
||||||
FROM tool_results tr
|
FROM tool_results tr
|
||||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||||
WHERE tr.is_error = 1
|
WHERE tr.is_error = 1
|
||||||
@@ -187,21 +82,3 @@ sql(`
|
|||||||
LIMIT 10
|
LIMIT 10
|
||||||
`)
|
`)
|
||||||
```
|
```
|
||||||
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const rows = failures({ limit: 20 });
|
|
||||||
return rows; // then count by eye in the final answer
|
|
||||||
```
|
|
||||||
|
|
||||||
## Empty Results
|
|
||||||
|
|
||||||
An empty array is often the correct answer.
|
|
||||||
|
|
||||||
When the user asks for a scoped project/file/session or an exact sentinel:
|
|
||||||
|
|
||||||
1. Run the scoped query.
|
|
||||||
2. Return `[]` or compact counts.
|
|
||||||
3. Say no matching prior result was found.
|
|
||||||
4. Do not call `recent()`, all-project `summaries()`, or `thread()` as fallback unless the user asks.
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Obelisk Retrieval Semantics
|
||||||
|
|
||||||
|
Read this before designing a non-trivial query. This is the query design frame;
|
||||||
|
`pitfalls.md` is only the debug checklist.
|
||||||
|
|
||||||
|
## Four Principles
|
||||||
|
|
||||||
|
### Scope First
|
||||||
|
|
||||||
|
Classify the user's request before choosing tools.
|
||||||
|
|
||||||
|
| User signal | Locator mode | Start with | Avoid first |
|
||||||
|
|-------------|--------------|------------|-------------|
|
||||||
|
| project name/path, session, cwd, file, time range | scope | `sessions()`, exact SQL on `project_path`, `sessionId`, `fileHistory()` | broad FTS |
|
||||||
|
| workflow, subagent, tool call, summary, edit | artifact | `workflows()`, `subagents()`, `summaries()`, `tool_calls`, `tool_results` | all-session search |
|
||||||
|
| concept, conclusion, design history, vague memory | semantic | `search()`, summaries, bounded facet sweep | session dumps |
|
||||||
|
|
||||||
|
One-shot retrieval is not all-shot retrieval. A query script may perform
|
||||||
|
multiple steps, but the first locator should be the narrowest semantic fit. If a
|
||||||
|
scope locator finds the relevant project/session/file, do not also run broad FTS
|
||||||
|
unless scoped evidence is insufficient and `query_plan` says why.
|
||||||
|
|
||||||
|
Project-like fields are distinct:
|
||||||
|
|
||||||
|
- `sessions.project`: stored Claude Code project slug.
|
||||||
|
- `sessions.project_path`: reconstructed absolute project path.
|
||||||
|
- `messages.cwd`: working directory at message time.
|
||||||
|
- helper `project`: SQL `LIKE` over `sessions.project`, not exact membership.
|
||||||
|
|
||||||
|
For exact project membership, use `sql()` with `s.project = ?` or
|
||||||
|
`s.project_path = ?`. Empty or tiny scoped results are valid results; do not
|
||||||
|
broaden unless the user asks or your `query_plan` explicitly marks a fallback.
|
||||||
|
|
||||||
|
### Plan Before Probe
|
||||||
|
|
||||||
|
For conclusion, broad history, failure investigation, or file evolution tasks,
|
||||||
|
prefer a retrieval script over interactive probing.
|
||||||
|
|
||||||
|
Good shape:
|
||||||
|
|
||||||
|
1. locate candidates with scope/artifact/semantic locators;
|
||||||
|
2. expand only selected hits;
|
||||||
|
3. dedupe and group in the script;
|
||||||
|
4. return compact evidence rows plus counts and limits.
|
||||||
|
|
||||||
|
If a second detail pass is needed, derive filters or facets from the first pass:
|
||||||
|
candidate sessions, discovered vocabulary, files, tools, timestamps, or
|
||||||
|
decisions. Prefer a learned faceted detail pass over `LIMIT 25` session windows.
|
||||||
|
If vocabulary is still unclear, use a small filtered window and say so in
|
||||||
|
`query_plan`.
|
||||||
|
|
||||||
|
### Structure Before Text
|
||||||
|
|
||||||
|
Use the database shape before asking the model to read text.
|
||||||
|
|
||||||
|
- Count and aggregate in SQL or JS (`GROUP BY`, `COUNT`, `MAX`, `ORDER BY`, `LIMIT`).
|
||||||
|
- Join metadata from the owner table instead of inventing fields.
|
||||||
|
- Project compact rows; do not return whole sessions, complete workflow trees, full raw messages, or entire tool results.
|
||||||
|
- Keep synthesis runtime JSON around 10k-12k chars when possible.
|
||||||
|
- For recent failures, aggregate by session/task and return sparse examples.
|
||||||
|
- For file evolution, filter `fileHistory()` to `Edit`/`Write`, group by session or phase, and return short deltas.
|
||||||
|
|
||||||
|
Ordering and context are semantic:
|
||||||
|
|
||||||
|
- `sessions()`, `summaries()`, `workflows()`, and `failures()` are newest first.
|
||||||
|
- `fileHistory()` is oldest first.
|
||||||
|
- `search().context` is temporal neighbors in one session, not causal context.
|
||||||
|
- `context(uuid)` and `trace(uuid)` are for parent-chain/causal expansion.
|
||||||
|
|
||||||
|
### Evidence Before Conclusion
|
||||||
|
|
||||||
|
Obelisk stores original structure, not precompiled claims. It has sessions,
|
||||||
|
messages, summaries, tool calls/results, files, subagents, workflows, parent
|
||||||
|
chains, and raw JSONL windows. It does not store "claim", "stance",
|
||||||
|
"contradiction", or "conclusion" entities.
|
||||||
|
|
||||||
|
For semantic questions, build a task-local evidence view:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
query_plan: { mode, scope, facets, limits },
|
||||||
|
evidence: [
|
||||||
|
{ type, id, session_id, timestamp, facet, snippet }
|
||||||
|
],
|
||||||
|
omitted: 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then synthesize the conclusion in the final answer. Do not pretend the evidence
|
||||||
|
view is a stored Obelisk entity.
|
||||||
|
|
||||||
|
## Text Search Semantics
|
||||||
|
|
||||||
|
`search(text)` passes text to SQLite FTS5 `MATCH`.
|
||||||
|
|
||||||
|
- Hyphens tokenize: for `workflow-script`, use `"workflow script"` or SQL `LIKE` for literal punctuation.
|
||||||
|
- Special characters may produce FTS syntax errors; simplify or quote the FTS query under the same scope.
|
||||||
|
- Exact phrase, token search, and literal punctuation are different semantics.
|
||||||
|
- Results are ordered by `ORDER BY rank`; lower rank sorts earlier. Prefer returned order over "closer to zero" comparisons.
|
||||||
Reference in New Issue
Block a user