@jigyasudham/veto 2.9.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -4
- package/dist/cli/statusline.d.ts +9 -0
- package/dist/cli/statusline.d.ts.map +1 -1
- package/dist/cli/statusline.js +59 -3
- package/dist/cli/statusline.js.map +1 -1
- package/dist/cli.js +135 -5
- package/dist/cli.js.map +1 -1
- package/dist/memory/config.d.ts +10 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +48 -8
- package/dist/memory/config.js.map +1 -1
- package/dist/memory/local.d.ts.map +1 -1
- package/dist/memory/local.js +32 -10
- package/dist/memory/local.js.map +1 -1
- package/dist/memory/sync.js +2 -2
- package/dist/memory/sync.js.map +1 -1
- package/dist/server/handlers/core.d.ts.map +1 -1
- package/dist/server/handlers/core.js +14 -1
- package/dist/server/handlers/core.js.map +1 -1
- package/dist/server/handlers/session.d.ts.map +1 -1
- package/dist/server/handlers/session.js +51 -2
- package/dist/server/handlers/session.js.map +1 -1
- package/dist/tools/definitions.d.ts +23 -3
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +11 -3
- package/dist/tools/definitions.js.map +1 -1
- package/dist/transcripts/adapters/claude.d.ts +27 -0
- package/dist/transcripts/adapters/claude.d.ts.map +1 -0
- package/dist/transcripts/adapters/claude.js +194 -0
- package/dist/transcripts/adapters/claude.js.map +1 -0
- package/dist/transcripts/archive.d.ts +23 -0
- package/dist/transcripts/archive.d.ts.map +1 -0
- package/dist/transcripts/archive.js +101 -0
- package/dist/transcripts/archive.js.map +1 -0
- package/dist/transcripts/config.d.ts +52 -0
- package/dist/transcripts/config.d.ts.map +1 -0
- package/dist/transcripts/config.js +135 -0
- package/dist/transcripts/config.js.map +1 -0
- package/dist/transcripts/expand.d.ts +20 -0
- package/dist/transcripts/expand.d.ts.map +1 -0
- package/dist/transcripts/expand.js +97 -0
- package/dist/transcripts/expand.js.map +1 -0
- package/dist/transcripts/ingest.d.ts +17 -0
- package/dist/transcripts/ingest.d.ts.map +1 -0
- package/dist/transcripts/ingest.js +113 -0
- package/dist/transcripts/ingest.js.map +1 -0
- package/dist/transcripts/manage.d.ts +42 -0
- package/dist/transcripts/manage.d.ts.map +1 -0
- package/dist/transcripts/manage.js +117 -0
- package/dist/transcripts/manage.js.map +1 -0
- package/dist/transcripts/mapping.d.ts +23 -0
- package/dist/transcripts/mapping.d.ts.map +1 -0
- package/dist/transcripts/mapping.js +42 -0
- package/dist/transcripts/mapping.js.map +1 -0
- package/dist/transcripts/mask.d.ts +26 -0
- package/dist/transcripts/mask.d.ts.map +1 -0
- package/dist/transcripts/mask.js +121 -0
- package/dist/transcripts/mask.js.map +1 -0
- package/dist/transcripts/on-save.d.ts +12 -0
- package/dist/transcripts/on-save.d.ts.map +1 -0
- package/dist/transcripts/on-save.js +37 -0
- package/dist/transcripts/on-save.js.map +1 -0
- package/dist/transcripts/pyramid.d.ts +38 -0
- package/dist/transcripts/pyramid.d.ts.map +1 -0
- package/dist/transcripts/pyramid.js +113 -0
- package/dist/transcripts/pyramid.js.map +1 -0
- package/dist/transcripts/recall.d.ts +39 -0
- package/dist/transcripts/recall.d.ts.map +1 -0
- package/dist/transcripts/recall.js +85 -0
- package/dist/transcripts/recall.js.map +1 -0
- package/dist/transcripts/schema.d.ts +69 -0
- package/dist/transcripts/schema.d.ts.map +1 -0
- package/dist/transcripts/schema.js +146 -0
- package/dist/transcripts/schema.js.map +1 -0
- package/dist/transcripts/search.d.ts +22 -0
- package/dist/transcripts/search.d.ts.map +1 -0
- package/dist/transcripts/search.js +134 -0
- package/dist/transcripts/search.js.map +1 -0
- package/dist/transcripts/store.d.ts +21 -0
- package/dist/transcripts/store.d.ts.map +1 -0
- package/dist/transcripts/store.js +105 -0
- package/dist/transcripts/store.js.map +1 -0
- package/dist/transcripts/toc.d.ts +18 -0
- package/dist/transcripts/toc.d.ts.map +1 -0
- package/dist/transcripts/toc.js +96 -0
- package/dist/transcripts/toc.js.map +1 -0
- package/dist/transcripts/tokenize.d.ts +14 -0
- package/dist/transcripts/tokenize.d.ts.map +1 -0
- package/dist/transcripts/tokenize.js +70 -0
- package/dist/transcripts/tokenize.js.map +1 -0
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare const TRANSCRIPTS_SCHEMA_VERSION = 3;
|
|
2
|
+
export declare const EVENT_KINDS: readonly ["user_message", "assistant_message", "tool_call", "tool_result", "reasoning", "meta", "unknown"];
|
|
3
|
+
export type EventKind = (typeof EVENT_KINDS)[number];
|
|
4
|
+
export type Migration = {
|
|
5
|
+
version: number;
|
|
6
|
+
up: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const MIGRATIONS: Migration[];
|
|
9
|
+
export declare const SEARCHABLE_KINDS: EventKind[];
|
|
10
|
+
export type ArchiveRow = {
|
|
11
|
+
id: string;
|
|
12
|
+
source: string;
|
|
13
|
+
source_session_id: string;
|
|
14
|
+
project_dir: string | null;
|
|
15
|
+
veto_session_id: string | null;
|
|
16
|
+
archive_path: string;
|
|
17
|
+
content_sha256: string;
|
|
18
|
+
source_bytes: number;
|
|
19
|
+
archive_bytes: number;
|
|
20
|
+
source_format_hint: string | null;
|
|
21
|
+
parser_version: number;
|
|
22
|
+
indexed_through_seq: number;
|
|
23
|
+
captured_at: string;
|
|
24
|
+
updated_at: string;
|
|
25
|
+
};
|
|
26
|
+
export type SessionMapRow = {
|
|
27
|
+
source: string;
|
|
28
|
+
source_session_id: string;
|
|
29
|
+
transcript_path: string;
|
|
30
|
+
project_dir: string | null;
|
|
31
|
+
last_seen_at: string;
|
|
32
|
+
};
|
|
33
|
+
export type SearchDocRow = {
|
|
34
|
+
id: number;
|
|
35
|
+
event_id: string;
|
|
36
|
+
archive_id: string;
|
|
37
|
+
source_session_id: string;
|
|
38
|
+
project_dir: string | null;
|
|
39
|
+
seq: number;
|
|
40
|
+
kind: EventKind;
|
|
41
|
+
len: number;
|
|
42
|
+
};
|
|
43
|
+
export type SearchPostingRow = {
|
|
44
|
+
term_id: number;
|
|
45
|
+
doc_id: number;
|
|
46
|
+
tf: number;
|
|
47
|
+
};
|
|
48
|
+
export type EventRow = {
|
|
49
|
+
id: string;
|
|
50
|
+
archive_id: string;
|
|
51
|
+
source_session_id: string;
|
|
52
|
+
seq: number;
|
|
53
|
+
line_index: number;
|
|
54
|
+
block_index: number;
|
|
55
|
+
kind: EventKind;
|
|
56
|
+
source_type: string | null;
|
|
57
|
+
role: string | null;
|
|
58
|
+
tool_name: string | null;
|
|
59
|
+
text: string | null;
|
|
60
|
+
secret_count: number;
|
|
61
|
+
event_uuid: string | null;
|
|
62
|
+
parent_uuid: string | null;
|
|
63
|
+
is_sidechain: number;
|
|
64
|
+
ts_source: string | null;
|
|
65
|
+
ts_utc: string | null;
|
|
66
|
+
raw_offset: number;
|
|
67
|
+
raw_length: number;
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/transcripts/schema.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAG5C,eAAO,MAAM,WAAW,4GAEd,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,MAAM,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,eAAO,MAAM,UAAU,EAAE,SAAS,EA+HjC,CAAC;AAIF,eAAO,MAAM,gBAAgB,EAAE,SAAS,EAAsE,CAAC;AAE/G,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Sidecar transcripts.db — schema & additive migrations (VERSION-3 item 6, Step 2).
|
|
2
|
+
//
|
|
3
|
+
// A SEPARATE database file from veto.db. The HUD / veto-vscode read-contract
|
|
4
|
+
// lives entirely in veto.db and is never touched by transcript capture — that
|
|
5
|
+
// isolation is the whole point of the sidecar. Migrations are additive and
|
|
6
|
+
// versioned via `PRAGMA user_version`: each later step APPENDS a migration
|
|
7
|
+
// rather than editing an existing one, so the schema grows without churn and
|
|
8
|
+
// re-open is idempotent.
|
|
9
|
+
// Highest migration version defined below.
|
|
10
|
+
export const TRANSCRIPTS_SCHEMA_VERSION = 3;
|
|
11
|
+
// The common vocabulary every source normalizes into.
|
|
12
|
+
export const EVENT_KINDS = [
|
|
13
|
+
'user_message', 'assistant_message', 'tool_call', 'tool_result', 'reasoning', 'meta', 'unknown',
|
|
14
|
+
];
|
|
15
|
+
export const MIGRATIONS = [
|
|
16
|
+
{
|
|
17
|
+
// v1 — the two foundational anchor tables. Events / segments / FTS get their
|
|
18
|
+
// own later migrations (Steps 6/9/10) so we don't big-bang the schema.
|
|
19
|
+
version: 1,
|
|
20
|
+
up: `
|
|
21
|
+
-- L0 archive index: one row per captured source-CLI session.
|
|
22
|
+
CREATE TABLE IF NOT EXISTS archives (
|
|
23
|
+
id TEXT PRIMARY KEY,
|
|
24
|
+
source TEXT NOT NULL DEFAULT 'claude', -- claude|codex|gemini
|
|
25
|
+
source_session_id TEXT NOT NULL,
|
|
26
|
+
project_dir TEXT, -- normalized (lowercase drive)
|
|
27
|
+
veto_session_id TEXT, -- link to veto.db sessions.id when known
|
|
28
|
+
archive_path TEXT NOT NULL, -- absolute path to the .gz on disk
|
|
29
|
+
content_sha256 TEXT NOT NULL, -- of raw source bytes (dedup key)
|
|
30
|
+
source_bytes INTEGER NOT NULL DEFAULT 0, -- uncompressed size
|
|
31
|
+
archive_bytes INTEGER NOT NULL DEFAULT 0, -- gz size on disk
|
|
32
|
+
source_format_hint TEXT, -- e.g. 'claude-jsonl'
|
|
33
|
+
parser_version INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
indexed_through_seq INTEGER NOT NULL DEFAULT 0, -- lazy-index watermark
|
|
35
|
+
captured_at TEXT NOT NULL,
|
|
36
|
+
updated_at TEXT NOT NULL,
|
|
37
|
+
UNIQUE(source, source_session_id)
|
|
38
|
+
);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_archives_project ON archives(project_dir);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_archives_sha ON archives(content_sha256);
|
|
41
|
+
|
|
42
|
+
-- Live host-session -> transcript-file mapping, UPSERTed fire-and-forget by
|
|
43
|
+
-- the statusline (Step 4) so save-time capture knows which file to read.
|
|
44
|
+
CREATE TABLE IF NOT EXISTS session_map (
|
|
45
|
+
source TEXT NOT NULL DEFAULT 'claude',
|
|
46
|
+
source_session_id TEXT NOT NULL,
|
|
47
|
+
transcript_path TEXT NOT NULL,
|
|
48
|
+
project_dir TEXT,
|
|
49
|
+
last_seen_at TEXT NOT NULL,
|
|
50
|
+
PRIMARY KEY (source, source_session_id)
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_session_map_project ON session_map(project_dir);
|
|
53
|
+
`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
// v2 — normalized events, one row per conversational unit derived from L0.
|
|
57
|
+
// text is ALWAYS masked (Step 3). raw_offset/raw_length point into the
|
|
58
|
+
// UNCOMPRESSED L0 bytes for exact expansion (Step 11). Unknown/unparsed lines
|
|
59
|
+
// are stored+ordered (kind='unknown') so format drift never loses data.
|
|
60
|
+
version: 2,
|
|
61
|
+
up: `
|
|
62
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
63
|
+
id TEXT PRIMARY KEY,
|
|
64
|
+
archive_id TEXT NOT NULL,
|
|
65
|
+
source_session_id TEXT NOT NULL,
|
|
66
|
+
seq INTEGER NOT NULL, -- monotonic per archive, per event
|
|
67
|
+
line_index INTEGER NOT NULL, -- source JSONL line (0-based)
|
|
68
|
+
block_index INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
kind TEXT NOT NULL, -- EVENT_KINDS
|
|
70
|
+
source_type TEXT, -- verbatim source 'type' (user/assistant/system/…)
|
|
71
|
+
role TEXT,
|
|
72
|
+
tool_name TEXT, -- for tool_call/tool_result
|
|
73
|
+
text TEXT, -- normalized + MASKED content for FTS
|
|
74
|
+
secret_count INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
event_uuid TEXT, -- native uuid (Claude)
|
|
76
|
+
parent_uuid TEXT, -- native parentUuid (tree)
|
|
77
|
+
is_sidechain INTEGER NOT NULL DEFAULT 0,
|
|
78
|
+
ts_source TEXT, -- verbatim timestamp string
|
|
79
|
+
ts_utc TEXT, -- normalized ISO
|
|
80
|
+
raw_offset INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
raw_length INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
UNIQUE(archive_id, seq),
|
|
83
|
+
FOREIGN KEY (archive_id) REFERENCES archives(id)
|
|
84
|
+
);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_events_archive ON events(archive_id, seq);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(source_session_id);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
// v3 — portable lexical index: plain tables + JS scoring (B2, redone).
|
|
92
|
+
// The FTS5 version of this migration never shipped: PR #28 died on
|
|
93
|
+
// `no such module: fts5` because node:sqlite bundles SQLite with NODE's
|
|
94
|
+
// build flags, so ANY compile-time feature (FTS5, even math functions)
|
|
95
|
+
// is a version lottery. Rule: core SQL only. BM25 is scored in JS.
|
|
96
|
+
//
|
|
97
|
+
// Postings carry no positions — the query path ANDs independent terms and
|
|
98
|
+
// never phrase-matches, so positions are pure cost. Snippets are rebuilt
|
|
99
|
+
// in JS from events.text for the few returned rows. No stored df: purge /
|
|
100
|
+
// re-ingest would have to decrement it transactionally or IDF drifts;
|
|
101
|
+
// COUNT(*) on the postings PK answers it per query term instead.
|
|
102
|
+
//
|
|
103
|
+
// NOTE: never CREATE or DROP events_fts here — touching a virtual table
|
|
104
|
+
// loads its module, which throws on exactly the Nodes this redo supports.
|
|
105
|
+
// Databases that ran the old dev-only v3 keep an inert orphan table.
|
|
106
|
+
version: 3,
|
|
107
|
+
up: `
|
|
108
|
+
-- One row per indexed event. project_dir/session denormalized so the
|
|
109
|
+
-- hot query is a single join (same shape events_fts had). The INTEGER id
|
|
110
|
+
-- exists so postings never carry a 36-char uuid — measured, that uuid
|
|
111
|
+
-- (stored twice per posting: PK + purge index) was ~3x the entire
|
|
112
|
+
-- index's justified size.
|
|
113
|
+
CREATE TABLE IF NOT EXISTS search_docs (
|
|
114
|
+
id INTEGER PRIMARY KEY,
|
|
115
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
116
|
+
archive_id TEXT NOT NULL,
|
|
117
|
+
source_session_id TEXT NOT NULL,
|
|
118
|
+
project_dir TEXT,
|
|
119
|
+
seq INTEGER NOT NULL,
|
|
120
|
+
kind TEXT NOT NULL,
|
|
121
|
+
len INTEGER NOT NULL -- token count, for BM25 length norm
|
|
122
|
+
);
|
|
123
|
+
CREATE INDEX IF NOT EXISTS idx_search_docs_archive ON search_docs(archive_id);
|
|
124
|
+
CREATE INDEX IF NOT EXISTS idx_search_docs_scope
|
|
125
|
+
ON search_docs(project_dir, source_session_id);
|
|
126
|
+
|
|
127
|
+
-- Term dictionary: term text stored once, compact integer join key.
|
|
128
|
+
CREATE TABLE IF NOT EXISTS search_terms (
|
|
129
|
+
id INTEGER PRIMARY KEY,
|
|
130
|
+
term TEXT NOT NULL UNIQUE
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE TABLE IF NOT EXISTS search_postings (
|
|
134
|
+
term_id INTEGER NOT NULL,
|
|
135
|
+
doc_id INTEGER NOT NULL,
|
|
136
|
+
tf INTEGER NOT NULL,
|
|
137
|
+
PRIMARY KEY (term_id, doc_id)
|
|
138
|
+
) WITHOUT ROWID;
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_postings_doc ON search_postings(doc_id);
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
// Event kinds worth indexing for recall — spine + tool digests. Reasoning
|
|
144
|
+
// (verbose thinking) and meta/unknown are excluded.
|
|
145
|
+
export const SEARCHABLE_KINDS = ['user_message', 'assistant_message', 'tool_call', 'tool_result'];
|
|
146
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/transcripts/schema.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,yBAAyB;AAEzB,2CAA2C;AAC3C,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS;CACvF,CAAC;AAKX,MAAM,CAAC,MAAM,UAAU,GAAgB;IACrC;QACE,6EAA6E;QAC7E,uEAAuE;QACvE,OAAO,EAAE,CAAC;QACV,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCH;KACF;IACD;QACE,2EAA2E;QAC3E,uEAAuE;QACvE,8EAA8E;QAC9E,wEAAwE;QACxE,OAAO,EAAE,CAAC;QACV,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2BH;KACF;IACD;QACE,uEAAuE;QACvE,mEAAmE;QACnE,wEAAwE;QACxE,uEAAuE;QACvE,mEAAmE;QACnE,EAAE;QACF,0EAA0E;QAC1E,yEAAyE;QACzE,0EAA0E;QAC1E,sEAAsE;QACtE,iEAAiE;QACjE,EAAE;QACF,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,OAAO,EAAE,CAAC;QACV,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCH;KACF;CACF,CAAC;AAEF,0EAA0E;AAC1E,oDAAoD;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAgB,CAAC,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type SearchHit = {
|
|
2
|
+
eventId: string;
|
|
3
|
+
archiveId: string;
|
|
4
|
+
sourceSessionId: string;
|
|
5
|
+
seq: number;
|
|
6
|
+
kind: string;
|
|
7
|
+
snippet: string;
|
|
8
|
+
score: number;
|
|
9
|
+
};
|
|
10
|
+
export type SearchOptions = {
|
|
11
|
+
projectDir?: string;
|
|
12
|
+
sourceSessionId?: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Build a highlighted excerpt in JS — the moral equivalent of FTS5's
|
|
17
|
+
* snippet(…, '[', ']', '…', 12): the SNIPPET_TOKENS-token window containing
|
|
18
|
+
* the most matched terms, matches wrapped in [ ], ellipses when clipped.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildSnippet(text: string, terms: Set<string>): string;
|
|
21
|
+
export declare function searchEvents(query: string, opts?: SearchOptions): SearchHit[];
|
|
22
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/transcripts/search.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAiCrE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,SAAS,EAAE,CAiEjF"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Portable BM25 search over indexed events (VERSION-3 item 6, Step 10 — redone).
|
|
2
|
+
//
|
|
3
|
+
// Plain-SQLite inverted index (search_docs / search_terms / search_postings),
|
|
4
|
+
// scored in JS. Core SQL only — no FTS5, no math functions, nothing behind a
|
|
5
|
+
// compile-time flag; that is the lesson of PR #28 (`no such module: fts5`).
|
|
6
|
+
// Query terms are bound parameters resolved against the term dictionary, so
|
|
7
|
+
// there is no query language and no injection surface at all.
|
|
8
|
+
//
|
|
9
|
+
// Contract change vs the FTS5 version: score is positive, HIGHER is better
|
|
10
|
+
// (FTS5's bm25() was negative-lower-is-better). Snippets are built in JS from
|
|
11
|
+
// events.text for the returned rows only.
|
|
12
|
+
import { getTranscriptsDb } from './store.js';
|
|
13
|
+
import { normalizeProjectDir } from '../memory/local.js';
|
|
14
|
+
import { tokenize } from './tokenize.js';
|
|
15
|
+
const K1 = 1.2;
|
|
16
|
+
const B = 0.75;
|
|
17
|
+
// Terms present in more than half the corpus carry ~no signal but force
|
|
18
|
+
// materializing huge posting lists (there is no LIMIT that is safe before
|
|
19
|
+
// ranking). Dropped at the dictionary, before any postings are read — unless
|
|
20
|
+
// every query term is that common, in which case they are all kept.
|
|
21
|
+
const DF_CAP_RATIO = 0.5;
|
|
22
|
+
const SNIPPET_TOKENS = 12;
|
|
23
|
+
/** BM25, scored in JS because log() in SQL is a compile-time lottery. */
|
|
24
|
+
function bm25(tf, df, n, len, avgdl) {
|
|
25
|
+
const idf = Math.log(1 + (n - df + 0.5) / (df + 0.5));
|
|
26
|
+
return idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (avgdl > 0 ? len / avgdl : 1)));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a highlighted excerpt in JS — the moral equivalent of FTS5's
|
|
30
|
+
* snippet(…, '[', ']', '…', 12): the SNIPPET_TOKENS-token window containing
|
|
31
|
+
* the most matched terms, matches wrapped in [ ], ellipses when clipped.
|
|
32
|
+
*/
|
|
33
|
+
export function buildSnippet(text, terms) {
|
|
34
|
+
const re = /[\p{L}\p{N}_\-./@+]+/gu;
|
|
35
|
+
const tokens = [];
|
|
36
|
+
for (const m of text.matchAll(re)) {
|
|
37
|
+
const raw = m[0];
|
|
38
|
+
const matched = tokenize(raw).some(t => terms.has(t));
|
|
39
|
+
tokens.push({ start: m.index, end: m.index + raw.length, matched });
|
|
40
|
+
}
|
|
41
|
+
if (tokens.length === 0)
|
|
42
|
+
return text.slice(0, 120);
|
|
43
|
+
// Best SNIPPET_TOKENS-token window = most matches, earliest wins ties.
|
|
44
|
+
let best = 0;
|
|
45
|
+
let bestCount = -1;
|
|
46
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
47
|
+
const windowEnd = Math.min(i + SNIPPET_TOKENS, tokens.length);
|
|
48
|
+
let count = 0;
|
|
49
|
+
for (let j = i; j < windowEnd; j += 1)
|
|
50
|
+
if (tokens[j].matched)
|
|
51
|
+
count += 1;
|
|
52
|
+
if (count > bestCount) {
|
|
53
|
+
bestCount = count;
|
|
54
|
+
best = i;
|
|
55
|
+
}
|
|
56
|
+
if (windowEnd === tokens.length)
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const windowEnd = Math.min(best + SNIPPET_TOKENS, tokens.length);
|
|
60
|
+
let out = '';
|
|
61
|
+
let pos = tokens[best].start;
|
|
62
|
+
for (let j = best; j < windowEnd; j += 1) {
|
|
63
|
+
const t = tokens[j];
|
|
64
|
+
out += text.slice(pos, t.start);
|
|
65
|
+
out += t.matched ? `[${text.slice(t.start, t.end)}]` : text.slice(t.start, t.end);
|
|
66
|
+
pos = t.end;
|
|
67
|
+
}
|
|
68
|
+
const prefix = tokens[best].start > 0 ? '…' : '';
|
|
69
|
+
const suffix = windowEnd < tokens.length || tokens[windowEnd - 1].end < text.length ? '…' : '';
|
|
70
|
+
return (prefix + out.replace(/\s+/g, ' ').trim() + suffix);
|
|
71
|
+
}
|
|
72
|
+
export function searchEvents(query, opts = {}) {
|
|
73
|
+
const db = getTranscriptsDb();
|
|
74
|
+
const terms = [...new Set(tokenize(query))];
|
|
75
|
+
if (terms.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const stats = db.prepare(`SELECT COUNT(*) AS n, COALESCE(AVG(len), 0) AS avgdl FROM search_docs`).get();
|
|
78
|
+
if (stats.n === 0)
|
|
79
|
+
return [];
|
|
80
|
+
// Resolve terms against the dictionary in one round trip.
|
|
81
|
+
const marks = terms.map(() => '?').join(', ');
|
|
82
|
+
const found = db.prepare(`SELECT id, term FROM search_terms WHERE term IN (${marks})`).all(...terms);
|
|
83
|
+
if (found.length === 0)
|
|
84
|
+
return [];
|
|
85
|
+
// df per term — an index-only range count on the postings PK.
|
|
86
|
+
const dfStmt = db.prepare(`SELECT COUNT(*) AS n FROM search_postings WHERE term_id = ?`);
|
|
87
|
+
const withDf = found.map(t => ({ ...t, df: dfStmt.get(t.id).n }));
|
|
88
|
+
let kept = withDf.filter(t => t.df / stats.n <= DF_CAP_RATIO);
|
|
89
|
+
if (kept.length === 0)
|
|
90
|
+
kept = withDf; // every term is that common — keep them all
|
|
91
|
+
const where = [`p.term_id IN (${kept.map(() => '?').join(', ')})`];
|
|
92
|
+
const params = kept.map(t => t.id);
|
|
93
|
+
if (opts.projectDir) {
|
|
94
|
+
where.push('d.project_dir = ?');
|
|
95
|
+
params.push(normalizeProjectDir(opts.projectDir));
|
|
96
|
+
}
|
|
97
|
+
if (opts.sourceSessionId) {
|
|
98
|
+
where.push('d.source_session_id = ?');
|
|
99
|
+
params.push(opts.sourceSessionId);
|
|
100
|
+
}
|
|
101
|
+
const rows = db.prepare(`SELECT p.term_id, p.tf, d.event_id, d.archive_id, d.source_session_id, d.project_dir, d.seq, d.kind, d.len
|
|
102
|
+
FROM search_postings p JOIN search_docs d ON d.id = p.doc_id
|
|
103
|
+
WHERE ${where.join(' AND ')}`).all(...params);
|
|
104
|
+
if (rows.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const dfById = new Map(kept.map(t => [t.id, t.df]));
|
|
107
|
+
const acc = new Map();
|
|
108
|
+
for (const r of rows) {
|
|
109
|
+
const df = dfById.get(r.term_id) ?? 1;
|
|
110
|
+
const prev = acc.get(r.event_id);
|
|
111
|
+
const inc = bm25(r.tf, df, stats.n, r.len, stats.avgdl);
|
|
112
|
+
if (prev)
|
|
113
|
+
prev.score += inc;
|
|
114
|
+
else
|
|
115
|
+
acc.set(r.event_id, { doc: r, score: inc });
|
|
116
|
+
}
|
|
117
|
+
const limit = Math.min(Math.max(opts.limit ?? 10, 1), 50);
|
|
118
|
+
const top = [...acc.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
119
|
+
// Snippets: fetch text for the returned rows only.
|
|
120
|
+
const ids = top.map(t => t.doc.event_id);
|
|
121
|
+
const textRows = db.prepare(`SELECT id, text FROM events WHERE id IN (${ids.map(() => '?').join(', ')})`).all(...ids);
|
|
122
|
+
const textById = new Map(textRows.map(r => [r.id, r.text ?? '']));
|
|
123
|
+
const termSet = new Set(kept.map(t => t.term));
|
|
124
|
+
return top.map(({ doc, score }) => ({
|
|
125
|
+
eventId: doc.event_id,
|
|
126
|
+
archiveId: doc.archive_id,
|
|
127
|
+
sourceSessionId: doc.source_session_id,
|
|
128
|
+
seq: doc.seq,
|
|
129
|
+
kind: doc.kind,
|
|
130
|
+
snippet: buildSnippet(textById.get(doc.event_id) ?? '', termSet),
|
|
131
|
+
score,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/transcripts/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { TRANSCRIPTS_SCHEMA_VERSION } from './schema.js';
|
|
3
|
+
/** True when node:sqlite can be loaded — capture is silently unavailable otherwise. */
|
|
4
|
+
export declare function transcriptsAvailable(): boolean;
|
|
5
|
+
/** Root dir for all transcript data (archives + sidecar DB). Env override for tests. */
|
|
6
|
+
export declare function transcriptsRoot(): string;
|
|
7
|
+
/** Path to the sidecar index DB. Env override for tests (mirrors VETO_TEST_DB). */
|
|
8
|
+
export declare function transcriptsDbPath(): string;
|
|
9
|
+
/** Dir holding the L0 .gz archive files (populated in Step 5). */
|
|
10
|
+
export declare function archiveDir(): string;
|
|
11
|
+
export declare function resetTranscriptsDb(): void;
|
|
12
|
+
export declare function getTranscriptsDb(): DatabaseSync;
|
|
13
|
+
/**
|
|
14
|
+
* Apply every migration newer than the DB's current user_version, each in its
|
|
15
|
+
* own transaction. Idempotent: re-running is a no-op once caught up. Returns the
|
|
16
|
+
* resulting schema version.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runMigrations(db: DatabaseSync): number;
|
|
19
|
+
export declare function schemaVersion(db?: DatabaseSync): number;
|
|
20
|
+
export { TRANSCRIPTS_SCHEMA_VERSION };
|
|
21
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/transcripts/store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,OAAO,EAAc,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAYrE,uFAAuF;AACvF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wFAAwF;AACxF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,kEAAkE;AAClE,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAKD,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED,wBAAgB,gBAAgB,IAAI,YAAY,CAc/C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAiBtD;AAOD,wBAAgB,aAAa,CAAC,EAAE,GAAE,YAAiC,GAAG,MAAM,CAE3E;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Sidecar transcripts.db connection + additive migration runner
|
|
2
|
+
// (VERSION-3 item 6, Step 2).
|
|
3
|
+
//
|
|
4
|
+
// Opened lazily (node:sqlite is a Node 22.5+ built-in; loaded via createRequire
|
|
5
|
+
// so bundlers skip it and older runtimes degrade gracefully). WAL + busy_timeout
|
|
6
|
+
// let the HUD keep reading veto.db while capture writes here without lock
|
|
7
|
+
// contention. This module NEVER opens veto.db — the two DBs stay isolated.
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { mkdirSync } from 'node:fs';
|
|
11
|
+
import { effectiveTranscriptsDir } from './config.js';
|
|
12
|
+
import { MIGRATIONS, TRANSCRIPTS_SCHEMA_VERSION } from './schema.js';
|
|
13
|
+
const _require = createRequire(import.meta.url);
|
|
14
|
+
let _DbSync = null;
|
|
15
|
+
function requireDbSync() {
|
|
16
|
+
if (!_DbSync) {
|
|
17
|
+
_DbSync = _require('node:sqlite').DatabaseSync;
|
|
18
|
+
}
|
|
19
|
+
return _DbSync;
|
|
20
|
+
}
|
|
21
|
+
/** True when node:sqlite can be loaded — capture is silently unavailable otherwise. */
|
|
22
|
+
export function transcriptsAvailable() {
|
|
23
|
+
try {
|
|
24
|
+
requireDbSync();
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Root dir for all transcript data (archives + sidecar DB). Env override for tests. */
|
|
32
|
+
export function transcriptsRoot() {
|
|
33
|
+
return process.env.VETO_TRANSCRIPTS_DIR ?? effectiveTranscriptsDir();
|
|
34
|
+
}
|
|
35
|
+
/** Path to the sidecar index DB. Env override for tests (mirrors VETO_TEST_DB). */
|
|
36
|
+
export function transcriptsDbPath() {
|
|
37
|
+
return process.env.VETO_TRANSCRIPTS_DB ?? join(transcriptsRoot(), 'index.db');
|
|
38
|
+
}
|
|
39
|
+
/** Dir holding the L0 .gz archive files (populated in Step 5). */
|
|
40
|
+
export function archiveDir() {
|
|
41
|
+
return join(transcriptsRoot(), 'archives');
|
|
42
|
+
}
|
|
43
|
+
let _db = null;
|
|
44
|
+
let _openedPath = null;
|
|
45
|
+
export function resetTranscriptsDb() {
|
|
46
|
+
if (_db) {
|
|
47
|
+
try {
|
|
48
|
+
_db.close();
|
|
49
|
+
}
|
|
50
|
+
catch { /* ignore */ }
|
|
51
|
+
_db = null;
|
|
52
|
+
_openedPath = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function getTranscriptsDb() {
|
|
56
|
+
const path = transcriptsDbPath();
|
|
57
|
+
if (_db && _openedPath === path)
|
|
58
|
+
return _db;
|
|
59
|
+
if (_db)
|
|
60
|
+
resetTranscriptsDb();
|
|
61
|
+
const DbSync = requireDbSync();
|
|
62
|
+
if (path !== ':memory:')
|
|
63
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
64
|
+
const db = new DbSync(path);
|
|
65
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
66
|
+
db.exec('PRAGMA busy_timeout = 5000');
|
|
67
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
68
|
+
runMigrations(db);
|
|
69
|
+
_db = db;
|
|
70
|
+
_openedPath = path;
|
|
71
|
+
return db;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply every migration newer than the DB's current user_version, each in its
|
|
75
|
+
* own transaction. Idempotent: re-running is a no-op once caught up. Returns the
|
|
76
|
+
* resulting schema version.
|
|
77
|
+
*/
|
|
78
|
+
export function runMigrations(db) {
|
|
79
|
+
let applied = readUserVersion(db);
|
|
80
|
+
for (const m of MIGRATIONS) {
|
|
81
|
+
if (m.version > applied) {
|
|
82
|
+
db.exec('BEGIN');
|
|
83
|
+
try {
|
|
84
|
+
db.exec(m.up);
|
|
85
|
+
db.exec(`PRAGMA user_version = ${m.version}`);
|
|
86
|
+
db.exec('COMMIT');
|
|
87
|
+
applied = m.version;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
db.exec('ROLLBACK');
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return applied;
|
|
96
|
+
}
|
|
97
|
+
function readUserVersion(db) {
|
|
98
|
+
const row = db.prepare('PRAGMA user_version').get();
|
|
99
|
+
return row?.user_version ?? 0;
|
|
100
|
+
}
|
|
101
|
+
export function schemaVersion(db = getTranscriptsDb()) {
|
|
102
|
+
return readUserVersion(db);
|
|
103
|
+
}
|
|
104
|
+
export { TRANSCRIPTS_SCHEMA_VERSION };
|
|
105
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/transcripts/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Segment = {
|
|
2
|
+
index: number;
|
|
3
|
+
fromSeq: number;
|
|
4
|
+
toSeq: number;
|
|
5
|
+
firstTs: string | null;
|
|
6
|
+
lastTs: string | null;
|
|
7
|
+
title: string;
|
|
8
|
+
userMessages: number;
|
|
9
|
+
assistantMessages: number;
|
|
10
|
+
toolCalls: number;
|
|
11
|
+
files: string[];
|
|
12
|
+
tools: string[];
|
|
13
|
+
hasError: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function buildTOC(archiveId: string): Segment[];
|
|
16
|
+
/** Compact one-line-per-segment rendering for the recall tool's first response. */
|
|
17
|
+
export declare function renderTOC(segments: Segment[]): string;
|
|
18
|
+
//# sourceMappingURL=toc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toc.d.ts","sourceRoot":"","sources":["../../src/transcripts/toc.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AASF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAkDrD;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAQrD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Metadata table-of-contents / phase segmentation (VERSION-3 item 6, Step 9).
|
|
2
|
+
//
|
|
3
|
+
// B1 of the vectorless-retrieval pipeline: split a session into phases so the
|
|
4
|
+
// recall tool can hand the host AI a compact map to navigate BEFORE any text
|
|
5
|
+
// search. Boundaries are deterministic — a new segment begins at each real user
|
|
6
|
+
// message (a user prompt + the assistant's response + tool activity until the
|
|
7
|
+
// next prompt). Events before the first user prompt form a preamble segment.
|
|
8
|
+
// Computed on demand from the events table (no extra storage).
|
|
9
|
+
import { getTranscriptsDb } from './store.js';
|
|
10
|
+
import { EDIT_TOOLS, FILE_RE, ERROR_RE } from './pyramid.js';
|
|
11
|
+
const TITLE_MAX = 80;
|
|
12
|
+
function preview(text) {
|
|
13
|
+
const t = text.replace(/\s+/g, ' ').trim();
|
|
14
|
+
return t.length > TITLE_MAX ? t.slice(0, TITLE_MAX - 1) + '…' : t;
|
|
15
|
+
}
|
|
16
|
+
export function buildTOC(archiveId) {
|
|
17
|
+
const db = getTranscriptsDb();
|
|
18
|
+
const rows = db.prepare(`SELECT seq, kind, tool_name, text, ts_utc FROM events WHERE archive_id = ? ORDER BY seq`).all(archiveId);
|
|
19
|
+
if (rows.length === 0)
|
|
20
|
+
return [];
|
|
21
|
+
const segments = [];
|
|
22
|
+
let cur = null;
|
|
23
|
+
const open = (first, title) => {
|
|
24
|
+
cur = {
|
|
25
|
+
index: segments.length, fromSeq: first.seq, toSeq: first.seq,
|
|
26
|
+
firstTs: first.ts_utc, lastTs: first.ts_utc, title,
|
|
27
|
+
userMessages: 0, assistantMessages: 0, toolCalls: 0, files: [], tools: [], hasError: false,
|
|
28
|
+
_files: new Set(), _tools: new Set(),
|
|
29
|
+
};
|
|
30
|
+
segments.push(cur);
|
|
31
|
+
};
|
|
32
|
+
const close = () => {
|
|
33
|
+
if (cur) {
|
|
34
|
+
cur.files = [...cur._files];
|
|
35
|
+
cur.tools = [...cur._tools];
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
for (const r of rows) {
|
|
39
|
+
if (r.kind === 'user_message') {
|
|
40
|
+
close();
|
|
41
|
+
open(r, preview(r.text ?? '') || `segment ${segments.length + 1}`);
|
|
42
|
+
}
|
|
43
|
+
else if (!cur) {
|
|
44
|
+
// Preamble: events before the first user message.
|
|
45
|
+
open(r, '(session start)');
|
|
46
|
+
}
|
|
47
|
+
const c = cur;
|
|
48
|
+
c.toSeq = r.seq;
|
|
49
|
+
if (r.ts_utc) {
|
|
50
|
+
if (!c.firstTs || r.ts_utc < c.firstTs)
|
|
51
|
+
c.firstTs = r.ts_utc;
|
|
52
|
+
if (!c.lastTs || r.ts_utc > c.lastTs)
|
|
53
|
+
c.lastTs = r.ts_utc;
|
|
54
|
+
}
|
|
55
|
+
switch (r.kind) {
|
|
56
|
+
case 'user_message':
|
|
57
|
+
c.userMessages++;
|
|
58
|
+
break;
|
|
59
|
+
case 'assistant_message':
|
|
60
|
+
c.assistantMessages++;
|
|
61
|
+
break;
|
|
62
|
+
case 'tool_call': {
|
|
63
|
+
c.toolCalls++;
|
|
64
|
+
const name = r.tool_name ?? 'tool';
|
|
65
|
+
c._tools.add(name);
|
|
66
|
+
if (r.text && EDIT_TOOLS.has(name)) {
|
|
67
|
+
const m = r.text.match(FILE_RE);
|
|
68
|
+
if (m)
|
|
69
|
+
c._files.add(m[1]);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'tool_result':
|
|
74
|
+
if (r.text && ERROR_RE.test(r.text))
|
|
75
|
+
c.hasError = true;
|
|
76
|
+
break;
|
|
77
|
+
default: break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
close();
|
|
81
|
+
return segments;
|
|
82
|
+
}
|
|
83
|
+
/** Compact one-line-per-segment rendering for the recall tool's first response. */
|
|
84
|
+
export function renderTOC(segments) {
|
|
85
|
+
return segments.map(s => {
|
|
86
|
+
const bits = [`#${s.index} [${s.fromSeq}-${s.toSeq}]`];
|
|
87
|
+
if (s.hasError)
|
|
88
|
+
bits.push('⚠err');
|
|
89
|
+
if (s.files.length)
|
|
90
|
+
bits.push(`files:${s.files.slice(0, 4).join(',')}`);
|
|
91
|
+
if (s.tools.length)
|
|
92
|
+
bits.push(`tools:${s.tools.slice(0, 4).join(',')}`);
|
|
93
|
+
return `${bits.join(' ')} ${s.title}`;
|
|
94
|
+
}).join('\n');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=toc.js.map
|