refactor(docs): restructure SKILL.md into progressive-disclosure layers
Split the monolithic skill prompt into three tiers: - Core API (search/context/sql) stays in the first prompt - Structured helpers listed as one-liners with filter signatures - Detailed patterns and pitfalls extracted to references/ Add references/query-patterns.md (copyable CodeAct recipes) and references/pitfalls.md (scope, FTS, ordering, compactness traps). Clarify project scope semantics (slug vs path vs cwd) throughout. Add ORDER BY timestamp DESC to failures() for newest-first default.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Obelisk Pitfalls
|
||||
|
||||
Use this when a query may over-fetch, when a scoped query returns few or zero
|
||||
rows, or when helper fields are unclear.
|
||||
|
||||
## Scope Is A Contract
|
||||
|
||||
If the user gives a project, session, file, or time range, keep every query
|
||||
inside that scope. Do not broaden because a scoped result is small.
|
||||
|
||||
There are three different project-like scopes:
|
||||
|
||||
- `sessions.project`: stored Claude Code project slug.
|
||||
- `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
|
||||
sql(`
|
||||
SELECT id, title, project, project_path, ended_at
|
||||
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
|
||||
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:
|
||||
|
||||
- `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
|
||||
search('"workflow script"', { limit: 10 })
|
||||
```
|
||||
|
||||
Use exact phrases for phrase semantics, separate terms for token semantics, and
|
||||
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
|
||||
sql(`
|
||||
SELECT m.uuid, s.id AS session_id, s.title, substr(m.text,1,240) AS snippet
|
||||
FROM messages m
|
||||
JOIN sessions s ON s.id = m.session_id
|
||||
WHERE s.project LIKE ?
|
||||
AND m.text LIKE ?
|
||||
ORDER BY m.timestamp DESC
|
||||
LIMIT 10
|
||||
`, '%quiet-zero%', '%workflow-script%')
|
||||
```
|
||||
|
||||
`rank` is already applied by `ORDER BY rank`; lower rank sorts earlier in this
|
||||
runtime. Prefer returned order over comparing "closer to zero" manually.
|
||||
|
||||
## Context Is Not Always Causal
|
||||
|
||||
`search().context` returns temporal neighbors: nearby messages by timestamp in
|
||||
the same session. It is useful for quick orientation, but it is not the parent
|
||||
chain and may cross side branches, subagents, or workflow boundaries.
|
||||
|
||||
Use:
|
||||
|
||||
- `context(uuid)` for message, parent chain, session, subagent, and workflow.
|
||||
- `trace(uuid)` for just the parent chain.
|
||||
- SQL timestamp neighbors for horizontal expansion inside one session.
|
||||
|
||||
## Ordering Defaults Matter
|
||||
|
||||
Some helpers return newest first; others do not.
|
||||
|
||||
- `sessions()` returns newest sessions first.
|
||||
- `summaries()` returns newest summaries first.
|
||||
- `workflows()` returns newest workflows first.
|
||||
- `failures()` returns newest failures first, but should still be treated as an evidence helper rather than a precise count helper.
|
||||
- `fileHistory()` orders by message timestamp ascending. If the user asks for recent changes, use SQL explicitly:
|
||||
|
||||
```js
|
||||
sql(`
|
||||
SELECT tc.id, tc.name, tc.file_path, m.timestamp, s.id AS session_id, s.title
|
||||
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')
|
||||
```
|
||||
|
||||
## 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 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
|
||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||
WHERE tr.is_error = 1
|
||||
GROUP BY tc.name
|
||||
ORDER BY n DESC
|
||||
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,352 @@
|
||||
# Obelisk Query Patterns
|
||||
|
||||
These are copyable CodeAct patterns for `runtime.mjs --query` scripts. They are
|
||||
not new APIs. Adapt them to the user's scope and return compact evidence.
|
||||
|
||||
## Bounded Search To Context
|
||||
|
||||
Use `search()` to locate candidates, then expand only the strongest hits.
|
||||
|
||||
```js
|
||||
const hits = search('"runtime query"', { project: '%quiet-zero%', limit: 8 });
|
||||
return hits.slice(0, 5).map(h => {
|
||||
const c = context(h.message.uuid);
|
||||
return {
|
||||
session_id: h.session.id,
|
||||
session_title: h.session.title,
|
||||
uuid: h.message.uuid,
|
||||
timestamp: h.message.timestamp,
|
||||
snippet: h.message.text?.slice(0, 240),
|
||||
parentChain: (c?.parentChain || []).slice(-3).map(m => ({
|
||||
uuid: m.uuid,
|
||||
role: m.role,
|
||||
snippet: m.text?.slice(0, 120),
|
||||
})),
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Facet Sweep For Broad History
|
||||
|
||||
Use this only for broad synthesis questions such as "how did X evolve", "what
|
||||
did we do on X", or "what problems happened". Do not use it for concept recall,
|
||||
exact session lookup, exact term recall, or tasks that ask for compact search
|
||||
hits.
|
||||
|
||||
Keep the sweep small: 3-4 facets, `limit: 3` per facet, and at most 12 compact
|
||||
evidence rows.
|
||||
|
||||
```js
|
||||
const name = 'obelisk';
|
||||
const facets = [
|
||||
'runtime CLI script',
|
||||
'schema SQLite FTS',
|
||||
'skill API helper docs',
|
||||
'test failure problem',
|
||||
];
|
||||
|
||||
const rows = [];
|
||||
for (const facet of facets) {
|
||||
for (const h of search(`${name} ${facet}`, { project: '%quiet-zero%', limit: 3 })) {
|
||||
rows.push({ facet, h });
|
||||
}
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
return rows
|
||||
.filter(({ h }) => {
|
||||
const key = h.message.uuid || `${h.session.id}:${h.message.timestamp}`;
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
})
|
||||
.slice(0, 12)
|
||||
.map(({ facet, h }) => ({
|
||||
facet,
|
||||
session_id: h.session.id,
|
||||
session_title: h.session.title,
|
||||
project: h.session.project,
|
||||
uuid: h.message.uuid,
|
||||
timestamp: h.message.timestamp,
|
||||
snippet: h.message.text?.slice(0, 180),
|
||||
}));
|
||||
```
|
||||
|
||||
## Summary Rows And Neighbors
|
||||
|
||||
Use `source`, `content`, `session_id`, `project`, and `session_title`.
|
||||
|
||||
```js
|
||||
const rows = summaries({ project: '%quiet-zero%', limit: 8 });
|
||||
return rows.map(s => ({
|
||||
id: s.id,
|
||||
session_id: s.session_id,
|
||||
session_title: s.session_title,
|
||||
project: s.project,
|
||||
source: s.source,
|
||||
timestamp: s.timestamp,
|
||||
snippet: s.content?.slice(0, 240),
|
||||
}));
|
||||
```
|
||||
|
||||
To inspect messages around one summary:
|
||||
|
||||
```js
|
||||
const s = summaries({ project: '%quiet-zero%', limit: 1 })[0];
|
||||
if (!s) return { results: [] };
|
||||
const before = sql(
|
||||
`SELECT uuid, role, timestamp, substr(text,1,200) AS snippet
|
||||
FROM messages
|
||||
WHERE session_id=? AND timestamp<?
|
||||
ORDER BY timestamp DESC LIMIT 3`,
|
||||
s.session_id,
|
||||
s.timestamp
|
||||
);
|
||||
const after = sql(
|
||||
`SELECT uuid, role, timestamp, substr(text,1,200) AS snippet
|
||||
FROM messages
|
||||
WHERE session_id=? AND timestamp>?
|
||||
ORDER BY timestamp ASC LIMIT 3`,
|
||||
s.session_id,
|
||||
s.timestamp
|
||||
);
|
||||
return { summary: s, before, after };
|
||||
```
|
||||
|
||||
## File History Synthesis
|
||||
|
||||
`fileHistory()` contains reads as well as writes. For "why/how did this file
|
||||
change", scan a bounded `Edit`/`Write` set first, then return only compact
|
||||
evidence. Do not choose the answer from only the first few rows if the question
|
||||
asks for evolution.
|
||||
|
||||
```js
|
||||
const rows = fileHistory('/absolute/path/to/file', { limit: 100 });
|
||||
const writes = rows.filter(r => ['Edit', 'Write'].includes(r.toolCall?.name));
|
||||
const reads = rows.filter(r => r.toolCall?.name === 'Read');
|
||||
|
||||
const bySession = new Map();
|
||||
for (const r of writes) {
|
||||
const sid = r.session.id;
|
||||
const group = bySession.get(sid) || {
|
||||
session_id: sid,
|
||||
session_title: r.session.title,
|
||||
project: r.session.project,
|
||||
write_edit_count: 0,
|
||||
first_timestamp: r.timestamp,
|
||||
last_timestamp: r.timestamp,
|
||||
themes: [],
|
||||
evidence: [],
|
||||
};
|
||||
group.write_edit_count++;
|
||||
group.first_timestamp = group.first_timestamp < r.timestamp ? group.first_timestamp : r.timestamp;
|
||||
group.last_timestamp = group.last_timestamp > r.timestamp ? group.last_timestamp : r.timestamp;
|
||||
const snippet = String(r.toolCall.input_json || '').slice(0, 360);
|
||||
if (group.themes.length < 8) group.themes.push(snippet);
|
||||
if (group.evidence.length < 3) {
|
||||
group.evidence.push({
|
||||
tool: r.toolCall.name,
|
||||
tool_id: r.toolCall.id,
|
||||
timestamp: r.timestamp,
|
||||
snippet,
|
||||
});
|
||||
}
|
||||
bySession.set(sid, group);
|
||||
}
|
||||
|
||||
return {
|
||||
counts: { reads: reads.length, writes_edits: writes.length },
|
||||
sessions: [...bySession.values()].slice(0, 10),
|
||||
};
|
||||
```
|
||||
|
||||
## Failed Tool Counts
|
||||
|
||||
For precise counts, aggregate in SQL. Do not hand-count long result rows in the
|
||||
final answer.
|
||||
|
||||
```js
|
||||
const counts = sql(`
|
||||
SELECT
|
||||
tc.name AS tool_name,
|
||||
COUNT(*) AS failure_count,
|
||||
MAX(m.timestamp) AS last_failure_at
|
||||
FROM tool_results tr
|
||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||
JOIN messages m ON m.uuid = tr.message_uuid
|
||||
JOIN sessions s ON s.id = tr.session_id
|
||||
WHERE tr.is_error = 1
|
||||
AND s.project LIKE ?
|
||||
GROUP BY tc.name
|
||||
ORDER BY failure_count DESC, last_failure_at DESC
|
||||
LIMIT 20
|
||||
`, '%quiet-zero%');
|
||||
|
||||
const examples = sql(`
|
||||
SELECT
|
||||
tr.tool_use_id,
|
||||
tc.name AS tool_name,
|
||||
m.timestamp,
|
||||
s.id AS session_id,
|
||||
s.title AS session_title,
|
||||
substr(tr.content, 1, 180) AS error_snippet
|
||||
FROM tool_results tr
|
||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||
JOIN messages m ON m.uuid = tr.message_uuid
|
||||
JOIN sessions s ON s.id = tr.session_id
|
||||
WHERE tr.is_error = 1
|
||||
AND s.project LIKE ?
|
||||
ORDER BY m.timestamp DESC
|
||||
LIMIT 8
|
||||
`, '%quiet-zero%');
|
||||
|
||||
return { counts, examples };
|
||||
```
|
||||
|
||||
## Failure Investigation Groups
|
||||
|
||||
For questions like "recent failed tool calls", "which tasks failed", or "group
|
||||
failures by task/session", group structurally and return sparse examples. Use
|
||||
SQL for counts; treat `failures()` as an evidence helper, not a precise counter.
|
||||
|
||||
```js
|
||||
const project = '%quiet-zero%';
|
||||
|
||||
const groups = sql(`
|
||||
SELECT
|
||||
s.id AS session_id,
|
||||
s.title AS session_title,
|
||||
s.project,
|
||||
COUNT(*) AS failure_count,
|
||||
MAX(m.timestamp) AS last_failure_at
|
||||
FROM tool_results tr
|
||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||
JOIN messages m ON m.uuid = tr.message_uuid
|
||||
JOIN sessions s ON s.id = tr.session_id
|
||||
WHERE tr.is_error = 1
|
||||
AND s.project LIKE ?
|
||||
GROUP BY s.id
|
||||
ORDER BY last_failure_at DESC
|
||||
LIMIT 10
|
||||
`, project);
|
||||
|
||||
const examples = sql(`
|
||||
SELECT
|
||||
tr.tool_use_id AS tool_call_id,
|
||||
tc.name AS tool_name,
|
||||
s.id AS session_id,
|
||||
m.timestamp,
|
||||
substr(tr.content, 1, 180) AS error_snippet
|
||||
FROM tool_results tr
|
||||
JOIN tool_calls tc ON tc.id = tr.tool_use_id
|
||||
JOIN messages m ON m.uuid = tr.message_uuid
|
||||
JOIN sessions s ON s.id = tr.session_id
|
||||
WHERE tr.is_error = 1
|
||||
AND s.project LIKE ?
|
||||
ORDER BY m.timestamp DESC
|
||||
LIMIT 12
|
||||
`, project);
|
||||
|
||||
return { groups, examples };
|
||||
```
|
||||
|
||||
## Workflow Tree Compact View
|
||||
|
||||
Find the run with `workflows()` under scope, then project `workflowTree()` into
|
||||
compact fields. Do not return raw `script`, `result_json`, or the full tree.
|
||||
|
||||
```js
|
||||
const runs = workflows({ project: '%quiet-zero%', limit: 30 });
|
||||
const target = runs.find(w =>
|
||||
/session[-_ ]journal/i.test(`${w.workflow_name || ''} ${w.task_id || ''} ${w.run_id || ''}`)
|
||||
);
|
||||
if (!target) {
|
||||
return {
|
||||
found: false,
|
||||
candidates: runs.slice(0, 8).map(w => ({
|
||||
run_id: w.run_id,
|
||||
workflow_name: w.workflow_name,
|
||||
timestamp: w.timestamp,
|
||||
agent_count: w.agent_count,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const tree = workflowTree(target.run_id);
|
||||
return {
|
||||
run_id: target.run_id,
|
||||
workflow_name: target.workflow_name,
|
||||
status: tree?.status ?? target.status,
|
||||
timestamp: tree?.timestamp ?? target.timestamp,
|
||||
agent_count: tree?.agent_count ?? tree?.agents?.length ?? target.agent_count,
|
||||
agents: (tree?.agents || []).map(a => ({
|
||||
agent_id: a.agent_id,
|
||||
phase: a.phase,
|
||||
label: a.label,
|
||||
state: a.state,
|
||||
tokens: a.tokens,
|
||||
messageCount: a.messageCount,
|
||||
})),
|
||||
};
|
||||
```
|
||||
|
||||
## Subagent Metadata Recall
|
||||
|
||||
Use `subagents()` for metadata. Do not expand transcripts unless the user asks.
|
||||
|
||||
```js
|
||||
const rows = subagents({ project: '%quiet-zero%', limit: 50 });
|
||||
return rows
|
||||
.filter(r => /obelisk/i.test(`${r.description || ''} ${r.agent_type || ''}`))
|
||||
.map(r => ({
|
||||
agent_id: r.agent_id,
|
||||
agent_type: r.agent_type,
|
||||
description: r.description,
|
||||
session_id: r.session_id,
|
||||
messageCount: r.messageCount,
|
||||
total_tokens: r.total_tokens,
|
||||
}));
|
||||
```
|
||||
|
||||
## Empty Result Without Fallback
|
||||
|
||||
If the user asks for an exact sentinel, scoped project, or exact file, an empty
|
||||
result is valid. Report it; do not broaden automatically.
|
||||
|
||||
```js
|
||||
const needle = 'obelisk-impossible-sentinel-20260602';
|
||||
const hits = search(`"${needle.replace(/-/g, ' ')}"`, { limit: 10 });
|
||||
const real = hits.filter(h => {
|
||||
const scope = `${h.session?.project || ''} ${h.message?.cwd || ''}`;
|
||||
return !/SkillOpt[-/. ]outputs|obelisk_train|obelisk-eval/i.test(scope);
|
||||
});
|
||||
return real.map(h => ({
|
||||
session_id: h.session.id,
|
||||
session_title: h.session.title,
|
||||
project: h.session.project,
|
||||
uuid: h.message.uuid,
|
||||
snippet: h.message.text?.slice(0, 200),
|
||||
}));
|
||||
```
|
||||
|
||||
## Raw Window
|
||||
|
||||
Use `raw()` only after identifying a specific message UUID.
|
||||
|
||||
```js
|
||||
const row = sql(`
|
||||
SELECT uuid, length(text) AS indexed_len
|
||||
FROM messages
|
||||
WHERE length(text) >= 10000
|
||||
LIMIT 1
|
||||
`)[0];
|
||||
if (!row) return null;
|
||||
const first = raw(row.uuid, { offset: 0, limit: 4000 });
|
||||
return {
|
||||
uuid: row.uuid,
|
||||
indexed_len: row.indexed_len,
|
||||
totalLength: first?.totalLength,
|
||||
hasMore: first?.hasMore,
|
||||
text: first?.text?.slice(0, 500),
|
||||
};
|
||||
```
|
||||
+24
-6
@@ -211,12 +211,22 @@ Full-text search across all message text using FTS5.
|
||||
| `text` | `string` | FTS5 query (terms, phrases, prefix) |
|
||||
| `opts.limit` | `number` | Max results (default 20) |
|
||||
| `opts.sessionId` | `string` | Restrict to one session |
|
||||
| `opts.project` | `string` | Restrict to a project slug |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
||||
| `opts.after` | `string` | ISO 8601 lower bound on timestamp |
|
||||
| `opts.before` | `string` | ISO 8601 upper bound on timestamp |
|
||||
| `opts.cwd` | `string` | Filter by working directory (supports LIKE) |
|
||||
|
||||
**Returns:** `Array<{ message, session, rank, context }>` where `context` is the 6 nearest messages by timestamp. `rank` is the FTS5 relevance score (negative; closer to 0 = more relevant).
|
||||
**Scope note:** `sessions.project` is the stored Claude Code project slug,
|
||||
`sessions.project_path` is the reconstructed absolute project path, and
|
||||
`messages.cwd` is the working directory at message time. Helper `project`
|
||||
filters are fuzzy `LIKE` filters over `sessions.project`. For exact project
|
||||
membership, use `sql()` with `s.project = ?` or `s.project_path = ?`.
|
||||
|
||||
**Returns:** `Array<{ message, session, rank, context }>` where `context` is the
|
||||
6 nearest messages by timestamp in the same session. It is temporal neighbor
|
||||
context, not a parent chain. `rank` is the FTS5 relevance score used by
|
||||
`ORDER BY rank`; lower values sort earlier, so treat the returned order as the
|
||||
relevance order unless you are deliberately using FTS5 ranking details.
|
||||
|
||||
```js
|
||||
const hits = search('MCTS exploration');
|
||||
@@ -285,7 +295,7 @@ All subagent spawns, with message counts. For backward compatibility, passing a
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `opts.sessionId` | `string` | Restrict to one session |
|
||||
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
||||
| `opts.limit` | `number` | Max results (default 100) |
|
||||
|
||||
**Returns:** `Array<{ ...subagent_row, messageCount }>`.
|
||||
@@ -302,7 +312,7 @@ Workflow executions. For backward compatibility, passing a string is treated as
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `opts.sessionId` | `string` | Restrict to one session |
|
||||
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
||||
| `opts.after` | `string` | ISO 8601 lower bound on timestamp |
|
||||
| `opts.before` | `string` | ISO 8601 upper bound on timestamp |
|
||||
| `opts.limit` | `number` | Max results (default 100) |
|
||||
@@ -338,6 +348,9 @@ All tool calls that touched a specific file, across every session.
|
||||
|
||||
**Returns:** `Array<{ toolCall, session, timestamp }>`.
|
||||
|
||||
Default order is oldest first (`ORDER BY m.timestamp`). For recent file changes,
|
||||
use raw SQL with `ORDER BY m.timestamp DESC`.
|
||||
|
||||
```js
|
||||
const edits = fileHistory('/Users/tomiya/Code/quiet-zero/src/mcts.ts', { after: '2026-05-28' });
|
||||
return edits.map(e => ({ tool: e.toolCall.name, session: e.session.title, time: e.timestamp }));
|
||||
@@ -350,13 +363,15 @@ Tool calls whose results contain error patterns (`Error`, `ENOENT`, `failed`, `p
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `opts.sessionId` | `string` | Restrict to one session |
|
||||
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
||||
| `opts.after` | `string` | ISO 8601 lower bound |
|
||||
| `opts.before` | `string` | ISO 8601 upper bound |
|
||||
| `opts.limit` | `number` | Max results (default 50) |
|
||||
|
||||
**Returns:** `Array<{ toolCall, result, session, nextMessages }>`.
|
||||
|
||||
Default order is newest first by the result message timestamp.
|
||||
|
||||
```js
|
||||
const fails = failures({ project: '%quiet-zero%', limit: 10 });
|
||||
return fails.map(f => ({ tool: f.toolCall?.name, error: f.result.content?.slice(0, 200) }));
|
||||
@@ -379,7 +394,7 @@ Query sessions with filters. For backward compatibility, passing a number is tre
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `opts.project` | `string` | Filter by project slug (supports LIKE, e.g. `'%quiet-zero%'`) |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
||||
| `opts.after` | `string` | ISO 8601 lower bound on `started_at` |
|
||||
| `opts.before` | `string` | ISO 8601 upper bound on `started_at` |
|
||||
| `opts.limit` | `number` | Max results (default 50) |
|
||||
@@ -389,6 +404,9 @@ Query sessions with filters. For backward compatibility, passing a number is tre
|
||||
|
||||
**Returns:** `Array<session_row>` ordered by `ended_at` descending.
|
||||
|
||||
For exact slug/path membership, use raw SQL with `project = ?` or
|
||||
`project_path = ?`.
|
||||
|
||||
```js
|
||||
const qz = sessions({ project: '%quiet-zero%', limit: 5 });
|
||||
return qz.map(s => ({ title: s.title, branch: s.git_branch, ended: s.ended_at }));
|
||||
|
||||
Reference in New Issue
Block a user