@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,52 @@
|
|
|
1
|
+
import { type TranscriptsConfig } from '../memory/config.js';
|
|
2
|
+
export declare const CONSENT_VERSION = 1;
|
|
3
|
+
export declare const DEFAULT_RETENTION_DAYS = 180;
|
|
4
|
+
/**
|
|
5
|
+
* Platform-appropriate, NON-cloud-synced data dir for transcript archives.
|
|
6
|
+
* Deliberately not ~/.veto: on consumer Windows that can be redirected into
|
|
7
|
+
* OneDrive. %LOCALAPPDATA% / Application Support / $XDG_DATA_HOME are local.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defaultTranscriptsDir(): string;
|
|
10
|
+
/** The effective archive dir: user override when set, else the platform default. */
|
|
11
|
+
export declare function effectiveTranscriptsDir(cfg?: TranscriptsConfig): string;
|
|
12
|
+
/** True only when capture is enabled AND consent matches the current version. */
|
|
13
|
+
export declare function isCaptureEnabled(cfg?: TranscriptsConfig): boolean;
|
|
14
|
+
/** Enabled under an older consent version → capture is paused pending re-consent. */
|
|
15
|
+
export declare function needsReconsent(cfg?: TranscriptsConfig): boolean;
|
|
16
|
+
export type EnableResult = {
|
|
17
|
+
dir: string;
|
|
18
|
+
retention_days: number;
|
|
19
|
+
consent_version: number;
|
|
20
|
+
reconsented: boolean;
|
|
21
|
+
};
|
|
22
|
+
/** Turn capture ON and record consent (version + timestamp). Idempotent. */
|
|
23
|
+
export declare function enableCapture(): EnableResult;
|
|
24
|
+
/** Turn capture OFF. The consent record is retained as an audit trail. */
|
|
25
|
+
export declare function disableCapture(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Returns a one-time note the FIRST time a real capture happens, then null
|
|
28
|
+
* forever after (records first_capture_at). Lets the save response tell the user
|
|
29
|
+
* capture is working without nagging on every save.
|
|
30
|
+
*/
|
|
31
|
+
export declare function firstCaptureNote(): string | null;
|
|
32
|
+
export type CaptureStatus = {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
effective: boolean;
|
|
35
|
+
needsReconsent: boolean;
|
|
36
|
+
dir: string;
|
|
37
|
+
usingDefaultDir: boolean;
|
|
38
|
+
retention_days: number;
|
|
39
|
+
consent_version: number | null;
|
|
40
|
+
consent_at: string | null;
|
|
41
|
+
cloudSyncWarning: string | null;
|
|
42
|
+
};
|
|
43
|
+
export declare function captureStatus(): CaptureStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Heuristic: warn if the archive path looks like it lives inside a cloud-synced
|
|
46
|
+
* tree, so "machine-local forever" isn't quietly violated. Names the provider
|
|
47
|
+
* when matched; null when the path looks local.
|
|
48
|
+
*/
|
|
49
|
+
export declare function detectCloudSync(dir: string): string | null;
|
|
50
|
+
/** Plain-language consent disclosure shown at enable time (what/where/retention/sensitivity/control). */
|
|
51
|
+
export declare function consentText(dir: string, retentionDays: number): string;
|
|
52
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/transcripts/config.ts"],"names":[],"mappings":"AAUA,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGnF,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAU9C;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,iBAA2C,GAAG,MAAM,CAEhG;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,iBAA2C,GAAG,OAAO,CAE1F;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,GAAG,GAAE,iBAA2C,GAAG,OAAO,CAExF;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,4EAA4E;AAC5E,wBAAgB,aAAa,IAAI,YAAY,CAa5C;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,IAAI,CAGrC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAMhD;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF,wBAAgB,aAAa,IAAI,aAAa,CAc7C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY1D;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAwBtE"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Transcript-capture consent & config (VERSION-3 item 6, Step 1).
|
|
2
|
+
//
|
|
3
|
+
// Capture is OFF until `veto transcripts enable` is run. Enabling records the
|
|
4
|
+
// current consent version + timestamp; a later bump of CONSENT_VERSION forces a
|
|
5
|
+
// re-consent (the disclosure materially changed). All settings persist in the
|
|
6
|
+
// shared ~/.veto/config.json under `transcripts` — no DB table, so Step 1
|
|
7
|
+
// touches neither veto.db nor the (not-yet-created) sidecar transcripts.db.
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { getConfig, setConfig } from '../memory/config.js';
|
|
11
|
+
// Bump when the consent disclosure materially changes → users are re-prompted.
|
|
12
|
+
export const CONSENT_VERSION = 1;
|
|
13
|
+
export const DEFAULT_RETENTION_DAYS = 180;
|
|
14
|
+
/**
|
|
15
|
+
* Platform-appropriate, NON-cloud-synced data dir for transcript archives.
|
|
16
|
+
* Deliberately not ~/.veto: on consumer Windows that can be redirected into
|
|
17
|
+
* OneDrive. %LOCALAPPDATA% / Application Support / $XDG_DATA_HOME are local.
|
|
18
|
+
*/
|
|
19
|
+
export function defaultTranscriptsDir() {
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
const base = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
22
|
+
return join(base, 'veto', 'transcripts');
|
|
23
|
+
}
|
|
24
|
+
if (process.platform === 'darwin') {
|
|
25
|
+
return join(homedir(), 'Library', 'Application Support', 'veto', 'transcripts');
|
|
26
|
+
}
|
|
27
|
+
const base = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
28
|
+
return join(base, 'veto', 'transcripts');
|
|
29
|
+
}
|
|
30
|
+
/** The effective archive dir: user override when set, else the platform default. */
|
|
31
|
+
export function effectiveTranscriptsDir(cfg = getConfig().transcripts) {
|
|
32
|
+
return cfg.dir && cfg.dir.trim() ? cfg.dir : defaultTranscriptsDir();
|
|
33
|
+
}
|
|
34
|
+
/** True only when capture is enabled AND consent matches the current version. */
|
|
35
|
+
export function isCaptureEnabled(cfg = getConfig().transcripts) {
|
|
36
|
+
return cfg.enabled && cfg.consent_version === CONSENT_VERSION;
|
|
37
|
+
}
|
|
38
|
+
/** Enabled under an older consent version → capture is paused pending re-consent. */
|
|
39
|
+
export function needsReconsent(cfg = getConfig().transcripts) {
|
|
40
|
+
return cfg.enabled && cfg.consent_version !== CONSENT_VERSION;
|
|
41
|
+
}
|
|
42
|
+
/** Turn capture ON and record consent (version + timestamp). Idempotent. */
|
|
43
|
+
export function enableCapture() {
|
|
44
|
+
const current = getConfig().transcripts;
|
|
45
|
+
const reconsented = current.enabled && current.consent_version !== CONSENT_VERSION;
|
|
46
|
+
const retention_days = current.retention_days > 0 ? current.retention_days : DEFAULT_RETENTION_DAYS;
|
|
47
|
+
const next = {
|
|
48
|
+
...current,
|
|
49
|
+
enabled: true,
|
|
50
|
+
retention_days,
|
|
51
|
+
consent_version: CONSENT_VERSION,
|
|
52
|
+
consent_at: new Date().toISOString(),
|
|
53
|
+
};
|
|
54
|
+
setConfig({ transcripts: next });
|
|
55
|
+
return { dir: effectiveTranscriptsDir(next), retention_days, consent_version: CONSENT_VERSION, reconsented };
|
|
56
|
+
}
|
|
57
|
+
/** Turn capture OFF. The consent record is retained as an audit trail. */
|
|
58
|
+
export function disableCapture() {
|
|
59
|
+
const current = getConfig().transcripts;
|
|
60
|
+
setConfig({ transcripts: { ...current, enabled: false } });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns a one-time note the FIRST time a real capture happens, then null
|
|
64
|
+
* forever after (records first_capture_at). Lets the save response tell the user
|
|
65
|
+
* capture is working without nagging on every save.
|
|
66
|
+
*/
|
|
67
|
+
export function firstCaptureNote() {
|
|
68
|
+
const current = getConfig().transcripts;
|
|
69
|
+
if (current.first_capture_at)
|
|
70
|
+
return null;
|
|
71
|
+
setConfig({ transcripts: { ...current, first_capture_at: new Date().toISOString() } });
|
|
72
|
+
return 'Veto archived this session to your local transcript store (opt-in, on this machine only). '
|
|
73
|
+
+ 'Recall past detail later with veto_session_replay; manage with `veto transcripts`.';
|
|
74
|
+
}
|
|
75
|
+
export function captureStatus() {
|
|
76
|
+
const cfg = getConfig().transcripts;
|
|
77
|
+
const dir = effectiveTranscriptsDir(cfg);
|
|
78
|
+
return {
|
|
79
|
+
enabled: cfg.enabled,
|
|
80
|
+
effective: isCaptureEnabled(cfg),
|
|
81
|
+
needsReconsent: needsReconsent(cfg),
|
|
82
|
+
dir,
|
|
83
|
+
usingDefaultDir: !(cfg.dir && cfg.dir.trim()),
|
|
84
|
+
retention_days: cfg.retention_days > 0 ? cfg.retention_days : DEFAULT_RETENTION_DAYS,
|
|
85
|
+
consent_version: cfg.consent_at ? cfg.consent_version : null,
|
|
86
|
+
consent_at: cfg.consent_at,
|
|
87
|
+
cloudSyncWarning: detectCloudSync(dir),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Heuristic: warn if the archive path looks like it lives inside a cloud-synced
|
|
92
|
+
* tree, so "machine-local forever" isn't quietly violated. Names the provider
|
|
93
|
+
* when matched; null when the path looks local.
|
|
94
|
+
*/
|
|
95
|
+
export function detectCloudSync(dir) {
|
|
96
|
+
const p = dir.toLowerCase();
|
|
97
|
+
const markers = [
|
|
98
|
+
[/onedrive/, 'OneDrive'],
|
|
99
|
+
[/dropbox/, 'Dropbox'],
|
|
100
|
+
[/google ?drive|[\\/]my drive/, 'Google Drive'],
|
|
101
|
+
[/[\\/]icloud|com~apple~clouddocs/, 'iCloud'],
|
|
102
|
+
];
|
|
103
|
+
for (const [re, name] of markers) {
|
|
104
|
+
if (re.test(p))
|
|
105
|
+
return name;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** Plain-language consent disclosure shown at enable time (what/where/retention/sensitivity/control). */
|
|
110
|
+
export function consentText(dir, retentionDays) {
|
|
111
|
+
return [
|
|
112
|
+
'Veto transcript capture — what you are turning on:',
|
|
113
|
+
'',
|
|
114
|
+
' • WHAT: when you save a session, Veto takes a COPY of your host CLI\'s own',
|
|
115
|
+
' conversation transcript — the AI\'s underlying memory of that session:',
|
|
116
|
+
' every message you sent, every reply, and all tool activity — and builds',
|
|
117
|
+
' a searchable index over it so a future session can recall exact detail.',
|
|
118
|
+
' • THE COPY IS SEPARATE: the original file is left untouched, but Veto\'s copy',
|
|
119
|
+
' lives on independently. Clearing your AI client\'s history, or the client',
|
|
120
|
+
' rotating its own logs, will NOT remove it. Only `veto transcripts purge`',
|
|
121
|
+
' (or the retention window below) deletes a Veto archive.',
|
|
122
|
+
' • WHERE: archives are stored on THIS machine only, at:',
|
|
123
|
+
` ${dir}`,
|
|
124
|
+
' Nothing is uploaded, sent to a server, or shared. No API keys involved.',
|
|
125
|
+
` • RETENTION: archives older than ${retentionDays} days are pruned automatically`,
|
|
126
|
+
' (configurable). You can change or turn this off at any time.',
|
|
127
|
+
' • SENSITIVITY: an archive inherits the sensitivity of everything you typed or',
|
|
128
|
+
' pasted during that session — including secrets and third parties\' data.',
|
|
129
|
+
' Detected secrets are masked in the search index and never returned to an',
|
|
130
|
+
' AI, but treat the archive itself as private data.',
|
|
131
|
+
' • CONTROL: `veto transcripts disable` stops capture; `veto transcripts purge`',
|
|
132
|
+
' deletes archives. Capture stays OFF until you enable it.',
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/transcripts/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ArchiveRow } from './schema.js';
|
|
2
|
+
export declare function _resetExpandCache(): void;
|
|
3
|
+
export type ExpandResult = {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
reason?: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
secretsRedacted?: number;
|
|
8
|
+
source?: string;
|
|
9
|
+
sourceSessionId?: string;
|
|
10
|
+
provenance?: string;
|
|
11
|
+
fromSeq?: number;
|
|
12
|
+
toSeq?: number;
|
|
13
|
+
};
|
|
14
|
+
/** Serve a byte range from an archive, masked. Bounds are validated first. */
|
|
15
|
+
export declare function expandBytes(archive: ArchiveRow, offset: number, length: number): ExpandResult;
|
|
16
|
+
/** Expand a single event to its exact source line (masked), with a turn/timestamp citation. */
|
|
17
|
+
export declare function expandEvent(eventId: string): ExpandResult;
|
|
18
|
+
/** Expand a seq range to the exact contiguous L0 lines it covers (masked). */
|
|
19
|
+
export declare function expandRange(archiveId: string, fromSeq: number, toSeq: number): ExpandResult;
|
|
20
|
+
//# sourceMappingURL=expand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../src/transcripts/expand.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,aAAa,CAAC;AAYxD,wBAAgB,iBAAiB,IAAI,IAAI,CAAmB;AAE5D,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkBF,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAa7F;AAED,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CASzD;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAa3F"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Mask-on-expansion: serve exact L0 bytes, always masked (VERSION-3 item 6, Step 7).
|
|
2
|
+
//
|
|
3
|
+
// L0 is stored RAW (Rule 0), but nothing leaves this module unmasked. Every byte
|
|
4
|
+
// range served out of an archive passes through mask() first, so even though a
|
|
5
|
+
// secret may sit in the raw archive on disk, it can never transit into an AI
|
|
6
|
+
// context through Veto. Offsets are validated against the archive length before
|
|
7
|
+
// any slice (S6) so a corrupt index can never read out of bounds. This is the
|
|
8
|
+
// read-side counterpart to Step 6's mask-on-write, and it is tested here BEFORE
|
|
9
|
+
// the recall tool (Step 11) is allowed to trust it.
|
|
10
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
11
|
+
import { gunzipSync } from 'node:zlib';
|
|
12
|
+
import { getTranscriptsDb } from './store.js';
|
|
13
|
+
import { mask } from './mask.js';
|
|
14
|
+
// Small 1-entry cache so a recall that expands several ranges gunzips once.
|
|
15
|
+
let _cache = null;
|
|
16
|
+
function loadArchive(path) {
|
|
17
|
+
const { mtimeMs } = statSync(path);
|
|
18
|
+
if (_cache && _cache.path === path && _cache.mtimeMs === mtimeMs)
|
|
19
|
+
return _cache.buf;
|
|
20
|
+
const buf = gunzipSync(readFileSync(path));
|
|
21
|
+
_cache = { path, mtimeMs, buf };
|
|
22
|
+
return buf;
|
|
23
|
+
}
|
|
24
|
+
export function _resetExpandCache() { _cache = null; }
|
|
25
|
+
function getArchiveById(id) {
|
|
26
|
+
const db = getTranscriptsDb();
|
|
27
|
+
return db.prepare(`SELECT * FROM archives WHERE id = ?`).get(id) ?? null;
|
|
28
|
+
}
|
|
29
|
+
function cite(archive, loc) {
|
|
30
|
+
const parts = [archive.source, `session ${archive.source_session_id}`];
|
|
31
|
+
if (loc.seq !== undefined) {
|
|
32
|
+
parts.push(loc.toSeq !== undefined && loc.toSeq !== loc.seq ? `turns ${loc.seq}-${loc.toSeq}` : `turn ${loc.seq}`);
|
|
33
|
+
}
|
|
34
|
+
if (loc.ts)
|
|
35
|
+
parts.push(loc.ts);
|
|
36
|
+
if (loc.offset !== undefined && loc.length !== undefined)
|
|
37
|
+
parts.push(`bytes ${loc.offset}-${loc.offset + loc.length}`);
|
|
38
|
+
return `[${parts.join(' · ')}]`;
|
|
39
|
+
}
|
|
40
|
+
/** Serve a byte range from an archive, masked. Bounds are validated first. */
|
|
41
|
+
export function expandBytes(archive, offset, length) {
|
|
42
|
+
if (!existsSync(archive.archive_path))
|
|
43
|
+
return { ok: false, reason: 'archive_missing' };
|
|
44
|
+
let buf;
|
|
45
|
+
try {
|
|
46
|
+
buf = loadArchive(archive.archive_path);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { ok: false, reason: 'unreadable' };
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0 || offset + length > buf.length) {
|
|
52
|
+
return { ok: false, reason: 'out_of_bounds' };
|
|
53
|
+
}
|
|
54
|
+
const m = mask(buf.toString('utf8', offset, offset + length));
|
|
55
|
+
return {
|
|
56
|
+
ok: true, text: m.text, secretsRedacted: m.count,
|
|
57
|
+
source: archive.source, sourceSessionId: archive.source_session_id,
|
|
58
|
+
provenance: cite(archive, { offset, length }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Expand a single event to its exact source line (masked), with a turn/timestamp citation. */
|
|
62
|
+
export function expandEvent(eventId) {
|
|
63
|
+
const db = getTranscriptsDb();
|
|
64
|
+
const ev = db.prepare(`SELECT * FROM events WHERE id = ?`).get(eventId) ?? null;
|
|
65
|
+
if (!ev)
|
|
66
|
+
return { ok: false, reason: 'event_not_found' };
|
|
67
|
+
const archive = getArchiveById(ev.archive_id);
|
|
68
|
+
if (!archive)
|
|
69
|
+
return { ok: false, reason: 'archive_not_found' };
|
|
70
|
+
const r = expandBytes(archive, ev.raw_offset, ev.raw_length);
|
|
71
|
+
if (r.ok) {
|
|
72
|
+
r.provenance = cite(archive, { seq: ev.seq, ts: ev.ts_utc });
|
|
73
|
+
r.fromSeq = ev.seq;
|
|
74
|
+
r.toSeq = ev.seq;
|
|
75
|
+
}
|
|
76
|
+
return r;
|
|
77
|
+
}
|
|
78
|
+
/** Expand a seq range to the exact contiguous L0 lines it covers (masked). */
|
|
79
|
+
export function expandRange(archiveId, fromSeq, toSeq) {
|
|
80
|
+
const db = getTranscriptsDb();
|
|
81
|
+
const archive = getArchiveById(archiveId);
|
|
82
|
+
if (!archive)
|
|
83
|
+
return { ok: false, reason: 'archive_not_found' };
|
|
84
|
+
const rows = db.prepare(`SELECT raw_offset, raw_length FROM events WHERE archive_id = ? AND seq BETWEEN ? AND ? ORDER BY seq`).all(archiveId, Math.min(fromSeq, toSeq), Math.max(fromSeq, toSeq));
|
|
85
|
+
if (rows.length === 0)
|
|
86
|
+
return { ok: false, reason: 'no_events' };
|
|
87
|
+
const start = Math.min(...rows.map(r => r.raw_offset));
|
|
88
|
+
const end = Math.max(...rows.map(r => r.raw_offset + r.raw_length));
|
|
89
|
+
const r = expandBytes(archive, start, end - start);
|
|
90
|
+
if (r.ok) {
|
|
91
|
+
r.provenance = cite(archive, { seq: fromSeq, toSeq });
|
|
92
|
+
r.fromSeq = fromSeq;
|
|
93
|
+
r.toSeq = toSeq;
|
|
94
|
+
}
|
|
95
|
+
return r;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=expand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../src/transcripts/expand.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type EventRow } from './schema.js';
|
|
2
|
+
export declare const PARSER_VERSION = 2;
|
|
3
|
+
export type IngestStatus = 'indexed' | 'already_indexed' | 'not_found' | 'archive_missing' | 'session_mismatch' | 'error';
|
|
4
|
+
export type IngestResult = {
|
|
5
|
+
status: IngestStatus;
|
|
6
|
+
events?: number;
|
|
7
|
+
secretsRedacted?: number;
|
|
8
|
+
reason?: string;
|
|
9
|
+
};
|
|
10
|
+
/** (Re)derive events for one archive. Best-effort: never throws. */
|
|
11
|
+
export declare function ingestArchive(archiveId: string): IngestResult;
|
|
12
|
+
/** Ensure an archive is indexed at the current parser version (lazy trigger for recall). */
|
|
13
|
+
export declare function ensureIndexed(archiveId: string): IngestResult;
|
|
14
|
+
/** Ingest by source session id (looks up its archive). */
|
|
15
|
+
export declare function ingestSession(sourceSessionId: string, source?: string): IngestResult;
|
|
16
|
+
export declare function getEvents(archiveId: string): EventRow[];
|
|
17
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/transcripts/ingest.ts"],"names":[],"mappings":"AAeA,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAO/E,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,MAAM,MAAM,YAAY,GACpB,SAAS,GAAG,iBAAiB,GAAG,WAAW,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,OAAO,CAAC;AAEnG,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAWF,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAgF7D;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAE7D;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAOtF;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE,CAGvD"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Ingest: derive normalized events from an L0 archive (VERSION-3 item 6, Step 6).
|
|
2
|
+
//
|
|
3
|
+
// Idempotent + lazy via the archive's parser_version + indexed_through_seq
|
|
4
|
+
// watermark: a fresh or grown archive (Step 5 resets both) gets (re)indexed; an
|
|
5
|
+
// unchanged, already-indexed archive is a no-op; a parser upgrade re-derives.
|
|
6
|
+
// Session-id verification (council/devil): if the archive's session id does not
|
|
7
|
+
// appear anywhere in the parsed file, the mapping pointed at the wrong transcript
|
|
8
|
+
// and we SKIP rather than index a mismatched file.
|
|
9
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
10
|
+
import { gunzipSync } from 'node:zlib';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { getTranscriptsDb } from './store.js';
|
|
13
|
+
import { parseClaudeTranscript } from './adapters/claude.js';
|
|
14
|
+
import { tokenize } from './tokenize.js';
|
|
15
|
+
import { SEARCHABLE_KINDS } from './schema.js';
|
|
16
|
+
const SEARCHABLE = new Set(SEARCHABLE_KINDS);
|
|
17
|
+
// Bump when the parser changes in a way that should re-derive existing archives.
|
|
18
|
+
// v2: the portable search index replaced events_fts — archives ingested at v1
|
|
19
|
+
// have events but no search_docs/postings rows, so they must re-derive.
|
|
20
|
+
export const PARSER_VERSION = 2;
|
|
21
|
+
function getArchiveById(archiveId) {
|
|
22
|
+
const db = getTranscriptsDb();
|
|
23
|
+
return db.prepare(`SELECT * FROM archives WHERE id = ?`).get(archiveId) ?? null;
|
|
24
|
+
}
|
|
25
|
+
function needsIndexing(row) {
|
|
26
|
+
return row.parser_version !== PARSER_VERSION;
|
|
27
|
+
}
|
|
28
|
+
/** (Re)derive events for one archive. Best-effort: never throws. */
|
|
29
|
+
export function ingestArchive(archiveId) {
|
|
30
|
+
try {
|
|
31
|
+
const db = getTranscriptsDb();
|
|
32
|
+
const row = getArchiveById(archiveId);
|
|
33
|
+
if (!row)
|
|
34
|
+
return { status: 'not_found' };
|
|
35
|
+
if (!needsIndexing(row))
|
|
36
|
+
return { status: 'already_indexed', events: row.indexed_through_seq };
|
|
37
|
+
if (!existsSync(row.archive_path))
|
|
38
|
+
return { status: 'archive_missing' };
|
|
39
|
+
const buf = gunzipSync(readFileSync(row.archive_path));
|
|
40
|
+
const { events, sessionIds, secretsRedacted } = parseClaudeTranscript(buf);
|
|
41
|
+
if (sessionIds.size > 0 && !sessionIds.has(row.source_session_id)) {
|
|
42
|
+
return { status: 'session_mismatch', reason: 'archive session id not present in transcript' };
|
|
43
|
+
}
|
|
44
|
+
const ins = db.prepare(`INSERT INTO events (id, archive_id, source_session_id, seq, line_index, block_index, kind,
|
|
45
|
+
source_type, role, tool_name, text, secret_count, event_uuid, parent_uuid, is_sidechain,
|
|
46
|
+
ts_source, ts_utc, raw_offset, raw_length)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
48
|
+
const insDoc = db.prepare(`INSERT INTO search_docs (event_id, archive_id, source_session_id, project_dir, seq, kind, len)
|
|
49
|
+
VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id`);
|
|
50
|
+
const insTerm = db.prepare(`INSERT INTO search_terms (term) VALUES (?)
|
|
51
|
+
ON CONFLICT(term) DO UPDATE SET term = term RETURNING id`);
|
|
52
|
+
const insPosting = db.prepare(`INSERT INTO search_postings (term_id, doc_id, tf) VALUES (?, ?, ?)`);
|
|
53
|
+
// Term ids are cached per ingest run — a session reuses its vocabulary
|
|
54
|
+
// heavily, so most lookups never hit the upsert.
|
|
55
|
+
const termIds = new Map();
|
|
56
|
+
const termId = (term) => {
|
|
57
|
+
let id = termIds.get(term);
|
|
58
|
+
if (id === undefined) {
|
|
59
|
+
id = Number(insTerm.get(term).id);
|
|
60
|
+
termIds.set(term, id);
|
|
61
|
+
}
|
|
62
|
+
return id;
|
|
63
|
+
};
|
|
64
|
+
db.exec('BEGIN');
|
|
65
|
+
try {
|
|
66
|
+
db.prepare(`DELETE FROM events WHERE archive_id = ?`).run(archiveId);
|
|
67
|
+
db.prepare(`DELETE FROM search_postings WHERE doc_id IN (SELECT id FROM search_docs WHERE archive_id = ?)`).run(archiveId);
|
|
68
|
+
db.prepare(`DELETE FROM search_docs WHERE archive_id = ?`).run(archiveId);
|
|
69
|
+
for (const e of events) {
|
|
70
|
+
const eventId = randomUUID();
|
|
71
|
+
ins.run(eventId, archiveId, row.source_session_id, e.seq, e.lineIndex, e.blockIndex, e.kind, e.sourceType, e.role, e.toolName, e.text, e.secretCount, e.eventUuid, e.parentUuid, e.isSidechain ? 1 : 0, e.tsSource, e.tsUtc, e.rawOffset, e.rawLength);
|
|
72
|
+
// Index the searchable, non-empty events (text is already masked).
|
|
73
|
+
if (SEARCHABLE.has(e.kind) && e.text && e.text.trim()) {
|
|
74
|
+
const tokens = tokenize(e.text);
|
|
75
|
+
const tf = new Map();
|
|
76
|
+
for (const t of tokens)
|
|
77
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
78
|
+
const docId = Number(insDoc.get(eventId, archiveId, row.source_session_id, row.project_dir, e.seq, e.kind, tokens.length).id);
|
|
79
|
+
for (const [term, count] of tf)
|
|
80
|
+
insPosting.run(termId(term), docId, count);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
db.prepare(`UPDATE archives SET indexed_through_seq = ?, parser_version = ? WHERE id = ?`)
|
|
84
|
+
.run(events.length, PARSER_VERSION, archiveId);
|
|
85
|
+
db.exec('COMMIT');
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
db.exec('ROLLBACK');
|
|
89
|
+
throw e;
|
|
90
|
+
}
|
|
91
|
+
return { status: 'indexed', events: events.length, secretsRedacted };
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return { status: 'error', reason: e instanceof Error ? e.message : String(e) };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Ensure an archive is indexed at the current parser version (lazy trigger for recall). */
|
|
98
|
+
export function ensureIndexed(archiveId) {
|
|
99
|
+
return ingestArchive(archiveId);
|
|
100
|
+
}
|
|
101
|
+
/** Ingest by source session id (looks up its archive). */
|
|
102
|
+
export function ingestSession(sourceSessionId, source = 'claude') {
|
|
103
|
+
const db = getTranscriptsDb();
|
|
104
|
+
const row = db.prepare(`SELECT id FROM archives WHERE source = ? AND source_session_id = ?`).get(source, sourceSessionId) ?? null;
|
|
105
|
+
if (!row)
|
|
106
|
+
return { status: 'not_found' };
|
|
107
|
+
return ingestArchive(row.id);
|
|
108
|
+
}
|
|
109
|
+
export function getEvents(archiveId) {
|
|
110
|
+
const db = getTranscriptsDb();
|
|
111
|
+
return db.prepare(`SELECT * FROM events WHERE archive_id = ? ORDER BY seq`).all(archiveId);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/transcripts/ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Segment } from './toc.js';
|
|
2
|
+
import { type SessionFacts } from './pyramid.js';
|
|
3
|
+
export type ArchiveSummary = {
|
|
4
|
+
sourceSessionId: string;
|
|
5
|
+
source: string;
|
|
6
|
+
projectDir: string | null;
|
|
7
|
+
events: number;
|
|
8
|
+
sourceBytes: number;
|
|
9
|
+
archiveBytes: number;
|
|
10
|
+
capturedAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
indexed: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function listArchives(opts?: {
|
|
15
|
+
projectDir?: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
}): ArchiveSummary[];
|
|
18
|
+
export type ArchiveDetail = {
|
|
19
|
+
summary: ArchiveSummary;
|
|
20
|
+
toc: Segment[];
|
|
21
|
+
facts: SessionFacts;
|
|
22
|
+
} | null;
|
|
23
|
+
export declare function showArchive(sourceSessionId: string, source?: string): ArchiveDetail;
|
|
24
|
+
export type PurgeResult = {
|
|
25
|
+
archives: number;
|
|
26
|
+
events: number;
|
|
27
|
+
indexRows: number;
|
|
28
|
+
files: number;
|
|
29
|
+
mappings: number;
|
|
30
|
+
};
|
|
31
|
+
export declare function purgeSession(sourceSessionId: string, source?: string): PurgeResult;
|
|
32
|
+
export declare function purgeProject(projectDir: string): PurgeResult;
|
|
33
|
+
export declare function purgeAll(): PurgeResult;
|
|
34
|
+
export type DiskUsage = {
|
|
35
|
+
archives: number;
|
|
36
|
+
archiveBytes: number;
|
|
37
|
+
dbBytes: number;
|
|
38
|
+
totalBytes: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function transcriptsDiskUsage(): DiskUsage;
|
|
41
|
+
export declare function fmtBytes(n: number): string;
|
|
42
|
+
//# sourceMappingURL=manage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manage.d.ts","sourceRoot":"","sources":["../../src/transcripts/manage.ts"],"names":[],"mappings":"AAWA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAG7D,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAUF,wBAAgB,YAAY,CAAC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,cAAc,EAAE,CAOjG;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG,IAAI,CAAC;AAEpG,wBAAgB,WAAW,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,aAAa,CAKrF;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAyCnH,wBAAgB,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,WAAW,CAUpF;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAI5D;AAED,wBAAgB,QAAQ,IAAI,WAAW,CAQtC;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAExG,wBAAgB,oBAAoB,IAAI,SAAS,CAMhD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI1C"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Management: list / show / purge + disk usage (VERSION-3 item 6, Step 13).
|
|
2
|
+
//
|
|
3
|
+
// Purge is a TRUE cascading delete — for each archive it removes the events, the
|
|
4
|
+
// search index rows (docs + postings), the archive row, the session mapping, and
|
|
5
|
+
// the L0 file, leaving no orphan rows (Step 14 asserts zero orphans). Row deletes
|
|
6
|
+
// run in one transaction; the L0 file is unlinked best-effort afterwards (a
|
|
7
|
+
// leftover file is harmless disk, a dangling row is not).
|
|
8
|
+
import { statSync, existsSync, rmSync } from 'node:fs';
|
|
9
|
+
import { getTranscriptsDb, transcriptsDbPath } from './store.js';
|
|
10
|
+
import { normalizeProjectDir } from '../memory/local.js';
|
|
11
|
+
import { buildTOC } from './toc.js';
|
|
12
|
+
import { buildFacts } from './pyramid.js';
|
|
13
|
+
function toSummary(r) {
|
|
14
|
+
return {
|
|
15
|
+
sourceSessionId: r.source_session_id, source: r.source, projectDir: r.project_dir,
|
|
16
|
+
events: r.indexed_through_seq, sourceBytes: r.source_bytes, archiveBytes: r.archive_bytes,
|
|
17
|
+
capturedAt: r.captured_at, updatedAt: r.updated_at, indexed: r.parser_version > 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function listArchives(opts = {}) {
|
|
21
|
+
const db = getTranscriptsDb();
|
|
22
|
+
const limit = Math.min(Math.max(opts.limit ?? 50, 1), 500);
|
|
23
|
+
const rows = opts.projectDir
|
|
24
|
+
? db.prepare(`SELECT * FROM archives WHERE project_dir = ? ORDER BY updated_at DESC LIMIT ?`).all(normalizeProjectDir(opts.projectDir), limit)
|
|
25
|
+
: db.prepare(`SELECT * FROM archives ORDER BY updated_at DESC LIMIT ?`).all(limit);
|
|
26
|
+
return rows.map(toSummary);
|
|
27
|
+
}
|
|
28
|
+
export function showArchive(sourceSessionId, source = 'claude') {
|
|
29
|
+
const db = getTranscriptsDb();
|
|
30
|
+
const r = db.prepare(`SELECT * FROM archives WHERE source = ? AND source_session_id = ?`).get(source, sourceSessionId);
|
|
31
|
+
if (!r)
|
|
32
|
+
return null;
|
|
33
|
+
return { summary: toSummary(r), toc: buildTOC(r.id), facts: buildFacts(r.id) };
|
|
34
|
+
}
|
|
35
|
+
function purgeArchiveRows(archives) {
|
|
36
|
+
const db = getTranscriptsDb();
|
|
37
|
+
const res = { archives: 0, events: 0, indexRows: 0, files: 0, mappings: 0 };
|
|
38
|
+
const paths = [];
|
|
39
|
+
db.exec('BEGIN');
|
|
40
|
+
try {
|
|
41
|
+
for (const a of archives) {
|
|
42
|
+
res.events += db.prepare(`SELECT COUNT(*) n FROM events WHERE archive_id = ?`).get(a.id).n;
|
|
43
|
+
// Two plain queries, not one with a reused ?1 — node 22.13's sqlite
|
|
44
|
+
// binding doesn't dedupe numbered parameters (SQLITE_RANGE on CI).
|
|
45
|
+
res.indexRows += db.prepare(`SELECT COUNT(*) n FROM search_docs WHERE archive_id = ?`).get(a.id).n;
|
|
46
|
+
res.indexRows += db.prepare(`SELECT COUNT(*) n FROM search_postings WHERE doc_id IN (SELECT id FROM search_docs WHERE archive_id = ?)`).get(a.id).n;
|
|
47
|
+
db.prepare(`DELETE FROM events WHERE archive_id = ?`).run(a.id);
|
|
48
|
+
db.prepare(`DELETE FROM search_postings WHERE doc_id IN (SELECT id FROM search_docs WHERE archive_id = ?)`).run(a.id);
|
|
49
|
+
db.prepare(`DELETE FROM search_docs WHERE archive_id = ?`).run(a.id);
|
|
50
|
+
db.prepare(`DELETE FROM archives WHERE id = ?`).run(a.id);
|
|
51
|
+
const m = db.prepare(`DELETE FROM session_map WHERE source = ? AND source_session_id = ?`).run(a.source, a.source_session_id);
|
|
52
|
+
res.mappings += Number(m.changes ?? 0);
|
|
53
|
+
res.archives += 1;
|
|
54
|
+
if (a.archive_path)
|
|
55
|
+
paths.push(a.archive_path);
|
|
56
|
+
}
|
|
57
|
+
db.exec('COMMIT');
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
db.exec('ROLLBACK');
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
// Rows are gone and committed; remove the L0 files best-effort.
|
|
64
|
+
for (const p of paths) {
|
|
65
|
+
if (existsSync(p)) {
|
|
66
|
+
try {
|
|
67
|
+
rmSync(p);
|
|
68
|
+
res.files += 1;
|
|
69
|
+
}
|
|
70
|
+
catch { /* leftover file is harmless */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return res;
|
|
74
|
+
}
|
|
75
|
+
export function purgeSession(sourceSessionId, source = 'claude') {
|
|
76
|
+
const db = getTranscriptsDb();
|
|
77
|
+
const arch = db.prepare(`SELECT * FROM archives WHERE source = ? AND source_session_id = ?`).get(source, sourceSessionId);
|
|
78
|
+
const result = purgeArchiveRows(arch ? [arch] : []);
|
|
79
|
+
// Purge the mapping even if there was no archive yet (capture never ran).
|
|
80
|
+
if (!arch) {
|
|
81
|
+
const m = db.prepare(`DELETE FROM session_map WHERE source = ? AND source_session_id = ?`).run(source, sourceSessionId);
|
|
82
|
+
result.mappings += Number(m.changes ?? 0);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export function purgeProject(projectDir) {
|
|
87
|
+
const db = getTranscriptsDb();
|
|
88
|
+
const archives = db.prepare(`SELECT * FROM archives WHERE project_dir = ?`).all(normalizeProjectDir(projectDir));
|
|
89
|
+
return purgeArchiveRows(archives);
|
|
90
|
+
}
|
|
91
|
+
export function purgeAll() {
|
|
92
|
+
const db = getTranscriptsDb();
|
|
93
|
+
const archives = db.prepare(`SELECT * FROM archives`).all();
|
|
94
|
+
const res = purgeArchiveRows(archives);
|
|
95
|
+
// Sweep any orphan mappings left with no archive.
|
|
96
|
+
const m = db.prepare(`DELETE FROM session_map`).run();
|
|
97
|
+
res.mappings = Math.max(res.mappings, Number(m.changes ?? 0));
|
|
98
|
+
return res;
|
|
99
|
+
}
|
|
100
|
+
export function transcriptsDiskUsage() {
|
|
101
|
+
const db = getTranscriptsDb();
|
|
102
|
+
const agg = db.prepare(`SELECT COUNT(*) n, COALESCE(SUM(archive_bytes), 0) b FROM archives`).get();
|
|
103
|
+
let dbBytes = 0;
|
|
104
|
+
try {
|
|
105
|
+
dbBytes = statSync(transcriptsDbPath()).size;
|
|
106
|
+
}
|
|
107
|
+
catch { /* db not on disk (memory) */ }
|
|
108
|
+
return { archives: agg.n, archiveBytes: agg.b, dbBytes, totalBytes: dbBytes + agg.b };
|
|
109
|
+
}
|
|
110
|
+
export function fmtBytes(n) {
|
|
111
|
+
if (n < 1024)
|
|
112
|
+
return `${n} B`;
|
|
113
|
+
if (n < 1024 * 1024)
|
|
114
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
115
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=manage.js.map
|