Skip to content

Commit 8de766f

Browse files
committed
fix(review): align tool contracts and smoke docs
1 parent ba58fa5 commit 8de766f

9 files changed

Lines changed: 82 additions & 65 deletions

docs/SmokeTests.md

Lines changed: 37 additions & 36 deletions
Large diffs are not rendered by default.

docs/superpowers/plans/2026-04-11-session-notes-anti-drift.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ explicit by returning `action` and the relevant identifiers/counts directly.
102102
when you resume an interrupted topic, need the exact wording of a pinned user
103103
instruction, or want to verify what you previously noted before acting on it.
104104

105-
If `id` is provided, returns that single note. If `id` is omitted, returns all
106-
notes for the current session. Returns
107-
`{ notes: [{ note_id, text, created_at, updated_at }] }`.
105+
Use this after `session_search` returns a matching note hit. `session_notes_read`
106+
requires `id` and returns `{ note: { id, text, created_at, updated_at } }`.
107+
When the note does not exist, it returns `{ note: null }`.
108108

109109
Always prefer reading a pinned note over reciting its contents from recall —
110110
notes are the source of truth for intentionally preserved context.
@@ -130,9 +130,10 @@ response. When `id` is omitted and no notes exist, returns `{ notes: [] }`
130130
- Before re-solving a problem that may already have a solution in session history
131131
- To check whether pinned session notes already contain the context you need
132132

133-
Results may include indexed memory content (type: "memory") and, when pinned
134-
session notes exist, matching notes (type: "note"). Note results include a
135-
`note_id` — use `session_notes_read` with that id to reopen the full note
133+
Results may include exact indexed hits (type: "entry"), summaries
134+
(type: "summary"), and, when pinned session notes exist, matching notes
135+
(type: "note"). Note results include an `id` — use `session_notes_read`
136+
with that id to reopen the full note
136137
text. Not every query will return note results; notes only appear when they
137138
match the search query and the session has pinned notes.
138139

@@ -159,9 +160,10 @@ tracked session has `biasState` `"new-session"` or `"post-compaction"`. See Task
159160
- Before re-solving a problem that may already have a solution in session history
160161
- To check whether pinned session notes already contain the context you need
161162

162-
Results may include indexed memory content (type: "memory") and, when pinned
163-
session notes exist, matching notes (type: "note"). Note results include a
164-
`note_id` — use `session_notes_read` with that id to reopen the full note
163+
Results may include exact indexed hits (type: "entry"), summaries
164+
(type: "summary"), and, when pinned session notes exist, matching notes
165+
(type: "note"). Note results include an `id` — use `session_notes_read`
166+
with that id to reopen the full note
165167
text. Not every query will return note results; notes only appear when they
166168
match the search query and the session has pinned notes.
167169

@@ -429,21 +431,20 @@ compaction-envelope tests for notes will be added to this existing file.
429431
`{ action: "deleted", note_id }`
430432
- `session_notes_write` with empty text + replace `"*"` → returns
431433
`{ action: "replaced", cleared_count }`
432-
- `session_notes_read` without id → returns all notes
433-
- `session_notes_read` with id → returns single note
434-
- `session_notes_read` with no notes → returns `{ notes: [] }`
434+
- `session_notes_read` with id → returns a single note
435+
- `session_notes_read` with a missing note → returns `{ note: null }`
435436
- Responses validate against the Zod response schemas
436437

437438
- [ ] **Step 3: Write failing tests for `session_search` note merge**
438439

439-
- `session_search` returns note hits with `type: "note"` and `note_id`
440-
- Existing memory results have `type: "memory"` (or undefined for backward
441-
compat)
440+
- `session_search` returns note hits with `type: "note"` and `id`
441+
- Existing indexed hits use `type: "entry"`; summary-only hits use
442+
`type: "summary"`
442443
- Note hits and memory hits coexist in the results array, sorted by score
443444
descending
444445
- Note hits include snippet from note text
445-
- When no notes exist, search returns only memory results (no empty note
446-
entries)
446+
- When no notes exist, search returns only entry/summary results (no empty
447+
note entries)
447448

448449
- [ ] **Step 4: Accept `SessionNotesService` as runtime option**
449450

@@ -459,8 +460,8 @@ compaction-envelope tests for notes will be added to this existing file.
459460

460461
In the `session_search` handler, after `searchLocalCorpus()`, also call
461462
`notesService.searchNotes()`. Merge results:
462-
- Memory hits: `type: "memory"` (or omit for backward compat)
463-
- Note hits: `type: "note"`, `note_id` set, `corpus_ref` set to note ref
463+
- Indexed hits: `type: "entry"`; summary hits: `type: "summary"`
464+
- Note hits: `type: "note"`, `id` set, `corpus_ref` set to note ref
464465
- Sort merged results by score descending — both sources produce `0``1`
465466
floats so interleaving by score is meaningful
466467
- Cap total results conservatively to avoid overwhelming output

docs/superpowers/specs/2026-04-11-session-notes-anti-drift-design.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Use two Redis hashes:
4444
- `created_at`
4545
- `updated_at`
4646

47-
2. `project:{groupId}:notes`
47+
2. `session:notes:${groupId}`
4848

4949
- same-project cross-session note store
5050
- field: `id`
@@ -74,12 +74,12 @@ scoped.
7474

7575
Public note identity is `id`, not `note_id`.
7676

77-
`id` must be unique within `project:{groupId}:notes`.
77+
`id` must be unique within `session:notes:${groupId}`.
7878

7979
On note creation:
8080

8181
1. Generate a UUID.
82-
2. Check whether `project:{groupId}:notes` already contains that `id`.
82+
2. Check whether `session:notes:${groupId}` already contains that `id`.
8383
3. If yes, generate a new UUID and retry until unique.
8484
4. Persist the new note to both stores.
8585

docs/superpowers/specs/2026-05-10-session-notes-freshness-without-ttl-design.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ The desired behavior is:
3939

4040
- `session:{rootSessionId}:notes` stores current-session note bodies and is used
4141
for compaction note injection
42-
- `project:{groupId}:notes` stores same-project notes for cross-session search
42+
- `session:notes:${groupId}` stores same-project notes for cross-session search
4343
and direct reopen by `id`
4444

4545
### Search Ranking
@@ -220,7 +220,7 @@ Stored fields remain:
220220

221221
### Project Store
222222

223-
`project:{groupId}:notes`
223+
`session:notes:${groupId}`
224224

225225
- remains the cross-session source of truth for same-project search and direct
226226
reopen by `id`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { assertEquals } from "jsr:@std/assert@^1.0.0";
2+
3+
import { createSnapshotSummaryResult } from "./redis-snapshot.ts";
4+
5+
Deno.test("createSnapshotSummaryResult keeps session scope separate from temporal granularity", () => {
6+
const result = createSnapshotSummaryResult({
7+
rootSessionId: "root-1",
8+
created_at: "2026-06-05T00:00:00.000Z",
9+
snippet: "snapshot summary",
10+
});
11+
12+
assertEquals(result.scope, "session");
13+
assertEquals(result.granularity, undefined);
14+
});

src/services/redis-snapshot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const createSnapshotSummaryResult = (input: {
3434
id: input.id ?? input.created_at,
3535
root_session_id: input.rootSessionId,
3636
scope: "session",
37-
granularity: input.granularity ?? "session",
37+
granularity: input.granularity,
3838
source: "snapshot",
3939
});
4040

src/services/session-mcp-runtime.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export const SESSION_NOTES_READ_DESCRIPTION = [
132132
"- When you need the exact wording of a pinned user instruction, plan, or",
133133
" checklist before acting on it.",
134134
"",
135-
"If `id` is provided, returns that single note as",
135+
"returns that single note as",
136136
"`{ note: { id, text, created_at, updated_at } }`; when the id does not exist,",
137137
"returns `{ note: null }`.",
138138
"",
@@ -158,7 +158,7 @@ export const SESSION_SEARCH_BASELINE_DESCRIPTION = [
158158
" history, or contradicting an earlier decision.",
159159
"- To check whether pinned session notes already contain the context you need.",
160160
"",
161-
'Results may include indexed memory content (type: "memory") and, when pinned',
161+
'Results may include exact indexed hits (type: "entry"), summaries (type: "summary"), and, when pinned',
162162
'session notes exist, matching notes (type: "note"). Note results include',
163163
'`id`, `root_session_id`, `scope: "local" | "project"`, `created_at`, and',
164164
"`updated_at` — when a note hit is relevant, immediately call",
@@ -234,7 +234,7 @@ const sessionMcpToolArgs: Record<SessionMcpToolName, PluginToolArgs> = {
234234
replace: pluginSchema.string().min(1).optional(),
235235
},
236236
session_notes_read: {
237-
id: pluginSchema.string().min(1).optional(),
237+
id: pluginSchema.string().min(1),
238238
},
239239
};
240240

src/services/tool-routing.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe("tool routing", () => {
5252
}
5353
assertEquals(decision.reason, "webfetch-denied");
5454
assertStringIncludes(decision.guidance, "WebFetch");
55+
assertStringIncludes(decision.guidance, "session_fetch_and_index");
5556
});
5657

5758
it("rewrites Bash curl commands", () => {

src/services/tool-routing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const routeWebFetch = (): RoutingDecision => ({
9898
action: "deny",
9999
reason: "webfetch-denied",
100100
guidance:
101-
"WebFetch is blocked. Use a safer search/fetch flow instead of raw page fetches.",
101+
"WebFetch is blocked. Use session_fetch_and_index to fetch the URL, then session_search to query the fetched content.",
102102
});
103103

104104
const routeBash = (

0 commit comments

Comments
 (0)