@druumen/sessions-db 0.1.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/CHANGELOG.md +249 -0
- package/LICENSE +201 -0
- package/NOTICE +10 -0
- package/README.md +250 -0
- package/cli/_write-helpers.mjs +99 -0
- package/cli/alias.mjs +115 -0
- package/cli/argparse.mjs +296 -0
- package/cli/close.mjs +116 -0
- package/cli/find.mjs +185 -0
- package/cli/format.mjs +277 -0
- package/cli/link-parent.mjs +133 -0
- package/cli/link.mjs +132 -0
- package/cli/rebuild.mjs +98 -0
- package/cli/sessions-db-session-start-main.mjs +454 -0
- package/cli/sessions-db-session-start.mjs +56 -0
- package/cli/sessions-db.mjs +119 -0
- package/cli/sweep.mjs +171 -0
- package/cli/tree.mjs +127 -0
- package/lib/git-context.mjs +479 -0
- package/lib/identity.mjs +616 -0
- package/lib/index.mjs +145 -0
- package/lib/init.mjs +185 -0
- package/lib/lock.mjs +86 -0
- package/lib/operations.mjs +490 -0
- package/lib/paths.mjs +199 -0
- package/lib/projection.mjs +496 -0
- package/lib/sanitize.mjs +131 -0
- package/lib/storage.mjs +759 -0
- package/lib/sweep.mjs +209 -0
- package/lib/transcript.mjs +230 -0
- package/lib/types.mjs +276 -0
- package/lib/uuid.mjs +116 -0
- package/lib/watch.mjs +217 -0
- package/package.json +53 -0
- package/types/git-context.d.mts +98 -0
- package/types/identity.d.mts +658 -0
- package/types/index.d.mts +10 -0
- package/types/index.d.ts +127 -0
- package/types/init.d.mts +53 -0
- package/types/lock.d.mts +18 -0
- package/types/operations.d.mts +204 -0
- package/types/paths.d.mts +54 -0
- package/types/projection.d.mts +79 -0
- package/types/sanitize.d.mts +39 -0
- package/types/storage.d.mts +276 -0
- package/types/sweep.d.mts +58 -0
- package/types/transcript.d.mts +59 -0
- package/types/types.d.mts +255 -0
- package/types/uuid.d.mts +17 -0
- package/types/watch.d.mts +33 -0
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @druumen/sessions-db — public TypeScript entry point.
|
|
3
|
+
*
|
|
4
|
+
* This file is HAND-CRAFTED and committed to the repo (not regenerated by
|
|
5
|
+
* `tsc`). The `tsc` build emits per-source-file declarations as `.d.mts`
|
|
6
|
+
* (because the sources are `.mjs`); this `.d.ts` curates the public type
|
|
7
|
+
* surface so cockpit-class consumers can write:
|
|
8
|
+
*
|
|
9
|
+
* import type {
|
|
10
|
+
* KnownSession,
|
|
11
|
+
* Projection,
|
|
12
|
+
* SessionEvent,
|
|
13
|
+
* ActivityState,
|
|
14
|
+
* Outcome,
|
|
15
|
+
* } from '@druumen/sessions-db';
|
|
16
|
+
*
|
|
17
|
+
* and resolve everything through one entry — without having to memorise
|
|
18
|
+
* which lib/* file each typedef lives in.
|
|
19
|
+
*
|
|
20
|
+
* The `lib/index.mjs` runtime entry is intentionally a Day-1 stub
|
|
21
|
+
* (re-exports happen on Day 3 — once the public *function* surface is
|
|
22
|
+
* settled). This `.d.ts` is safe to ship today because it is types-only
|
|
23
|
+
* and has zero runtime side effects.
|
|
24
|
+
*
|
|
25
|
+
* Convention for tsc-emitted neighbours:
|
|
26
|
+
* - Auto-generated: `types/<name>.d.mts` (mirrors `lib/<name>.mjs`)
|
|
27
|
+
* - Hand-crafted: `types/index.d.ts` (this file)
|
|
28
|
+
*
|
|
29
|
+
* `package.json` `"types"` points at `./types/index.d.ts` so this is the
|
|
30
|
+
* resolution entry. The neighbouring `.d.mts` files exist for direct
|
|
31
|
+
* sub-path imports (`@druumen/sessions-db/lib/storage.d.mts`) but cockpit
|
|
32
|
+
* should prefer this curated surface for forward compat.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Type vocabulary — re-exported from `lib/types.d.mts` (which `tsc` lifted
|
|
37
|
+
// from the `@typedef` block in `lib/types.mjs`). This is the canonical
|
|
38
|
+
// surface for cockpit consumers.
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
// Branded scalars
|
|
43
|
+
SessionStableId,
|
|
44
|
+
ClaudeSessionId,
|
|
45
|
+
EventId,
|
|
46
|
+
Iso8601,
|
|
47
|
+
// Enums
|
|
48
|
+
ActivityState,
|
|
49
|
+
Outcome,
|
|
50
|
+
IdentitySource,
|
|
51
|
+
IdentityConfidence,
|
|
52
|
+
EventOp,
|
|
53
|
+
// Composite shapes
|
|
54
|
+
TranscriptFile,
|
|
55
|
+
IdentityResolution,
|
|
56
|
+
ParentCandidate,
|
|
57
|
+
KnownSession,
|
|
58
|
+
ProjectionMeta,
|
|
59
|
+
Projection,
|
|
60
|
+
SessionEvent,
|
|
61
|
+
} from './types.d.mts';
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Function signatures — re-exported from per-source `.d.mts` neighbours.
|
|
65
|
+
//
|
|
66
|
+
// We re-export TYPES of functions (typeof) rather than the runtime symbols
|
|
67
|
+
// because Day 2 keeps `lib/index.mjs` a runtime stub. Day 3 will flip
|
|
68
|
+
// `lib/index.mjs` into a real re-export hub and at that point `tsc` will
|
|
69
|
+
// regenerate `types/index.d.mts` with the same shape — but consumers who
|
|
70
|
+
// imported through this `.d.ts` see no breakage because the type names
|
|
71
|
+
// are identical.
|
|
72
|
+
//
|
|
73
|
+
// The `typeof import(...)` pattern is the standard TypeScript ambient
|
|
74
|
+
// re-export when the source is JS-with-JSDoc.
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
// uuid.mjs
|
|
78
|
+
export type GenerateSessionId = typeof import('./uuid.d.mts').generateSessionId;
|
|
79
|
+
export type IsSessionId = typeof import('./uuid.d.mts').isSessionId;
|
|
80
|
+
export type ExtractTimestamp = typeof import('./uuid.d.mts').extractTimestamp;
|
|
81
|
+
|
|
82
|
+
// projection.mjs
|
|
83
|
+
export type ApplyEvent = typeof import('./projection.d.mts').applyEvent;
|
|
84
|
+
export type EmptyProjection = typeof import('./projection.d.mts').emptyProjection;
|
|
85
|
+
export type EmptySession = typeof import('./projection.d.mts').emptySession;
|
|
86
|
+
export type RebuildFromEvents = typeof import('./projection.d.mts').rebuildFromEvents;
|
|
87
|
+
|
|
88
|
+
// storage.mjs
|
|
89
|
+
export type NewEvent = typeof import('./storage.d.mts').newEvent;
|
|
90
|
+
export type AppendEvent = typeof import('./storage.d.mts').appendEvent;
|
|
91
|
+
export type ReadAllEvents = typeof import('./storage.d.mts').readAllEvents;
|
|
92
|
+
export type LoadProjection = typeof import('./storage.d.mts').loadProjection;
|
|
93
|
+
export type SaveProjection = typeof import('./storage.d.mts').saveProjection;
|
|
94
|
+
export type RebuildProjection = typeof import('./storage.d.mts').rebuildProjection;
|
|
95
|
+
export type TryUpdateProjection = typeof import('./storage.d.mts').tryUpdateProjection;
|
|
96
|
+
export type RecordSessionSeen = typeof import('./storage.d.mts').recordSessionSeen;
|
|
97
|
+
|
|
98
|
+
// identity.mjs
|
|
99
|
+
export type ResolveIdentity = typeof import('./identity.d.mts').resolveIdentity;
|
|
100
|
+
export type FindByClaudeSessionId = typeof import('./identity.d.mts').findByClaudeSessionId;
|
|
101
|
+
export type FindByTranscriptLineage = typeof import('./identity.d.mts').findByTranscriptLineage;
|
|
102
|
+
export type ScanFingerprintCandidates = typeof import('./identity.d.mts').scanFingerprintCandidates;
|
|
103
|
+
export type CollectParentCandidates = typeof import('./identity.d.mts').collectParentCandidates;
|
|
104
|
+
export type CapParentCandidates = typeof import('./identity.d.mts').capParentCandidates;
|
|
105
|
+
export type ClassifyCorroborators = typeof import('./identity.d.mts').classifyCorroborators;
|
|
106
|
+
export type MeetsThreshold = typeof import('./identity.d.mts').meetsThreshold;
|
|
107
|
+
|
|
108
|
+
// sweep.mjs
|
|
109
|
+
export type ComputeSweepTransitions = typeof import('./sweep.d.mts').computeSweepTransitions;
|
|
110
|
+
export type ComputeEffectiveLastProgress = typeof import('./sweep.d.mts').computeEffectiveLastProgress;
|
|
111
|
+
|
|
112
|
+
// sanitize.mjs
|
|
113
|
+
export type SanitizeFirstPrompt = typeof import('./sanitize.d.mts').sanitizeFirstPrompt;
|
|
114
|
+
export type StripSystemReminders = typeof import('./sanitize.d.mts').stripSystemReminders;
|
|
115
|
+
export type StripIdeWrappers = typeof import('./sanitize.d.mts').stripIdeWrappers;
|
|
116
|
+
|
|
117
|
+
// transcript.mjs
|
|
118
|
+
export type ParseTranscriptFile = typeof import('./transcript.d.mts').parseTranscriptFile;
|
|
119
|
+
export type ListTranscriptFiles = typeof import('./transcript.d.mts').listTranscriptFiles;
|
|
120
|
+
export type WorkspaceHashFromCwd = typeof import('./transcript.d.mts').workspaceHashFromCwd;
|
|
121
|
+
|
|
122
|
+
// git-context.mjs
|
|
123
|
+
export type GitContextFn = typeof import('./git-context.d.mts').gitContext;
|
|
124
|
+
|
|
125
|
+
// paths.mjs (Day 4 — storage path resolution chain)
|
|
126
|
+
export type ResolveStoragePaths = typeof import('./paths.d.mts').resolveStoragePaths;
|
|
127
|
+
export type PathsFromRoot = typeof import('./paths.d.mts').pathsFromRoot;
|
package/types/init.d.mts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize sessions-db storage at the given root.
|
|
3
|
+
*
|
|
4
|
+
* Resolution semantics (Day 4):
|
|
5
|
+
*
|
|
6
|
+
* - `opts.paths` (legacy form) — fully-formed override: each `eventsJsonl`
|
|
7
|
+
* / `projectionJson` is anchored on `opts.rootPath` (or treated as
|
|
8
|
+
* absolute if it starts with `/`). Backward-compatible with Day 3
|
|
9
|
+
* callers that passed `paths: { eventsJsonl: 'custom/events.jsonl', ... }`.
|
|
10
|
+
*
|
|
11
|
+
* - `opts.rootPath` (Day 4 form, no `opts.paths`) — `rootPath` IS the
|
|
12
|
+
* storage directory; files live directly under it as
|
|
13
|
+
* `<rootPath>/sessions-db-events.jsonl` and `<rootPath>/sessions-db.json`.
|
|
14
|
+
* This is what cockpit's Setup Wizard passes (typically resolved to
|
|
15
|
+
* `<workspace>/.dru-code/`).
|
|
16
|
+
*
|
|
17
|
+
* - No opts (default) — delegates to `resolveStoragePaths()` which runs
|
|
18
|
+
* the env > existing-storage > cwd/.dru-code chain. Useful for ad-hoc
|
|
19
|
+
* "init wherever the resolver thinks it should go" scripts.
|
|
20
|
+
*
|
|
21
|
+
* @param {{
|
|
22
|
+
* rootPath?: string,
|
|
23
|
+
* paths?: { eventsJsonl?: string, projectionJson?: string, lockFile?: string },
|
|
24
|
+
* }} [opts]
|
|
25
|
+
* @returns {Promise<{
|
|
26
|
+
* ok: boolean,
|
|
27
|
+
* created?: { dir: boolean, eventsJsonl: boolean, projectionJson: boolean },
|
|
28
|
+
* paths?: { eventsJsonl: string, projectionJson: string },
|
|
29
|
+
* source?: string,
|
|
30
|
+
* error?: string,
|
|
31
|
+
* }>}
|
|
32
|
+
*/
|
|
33
|
+
export function initProjection(opts?: {
|
|
34
|
+
rootPath?: string;
|
|
35
|
+
paths?: {
|
|
36
|
+
eventsJsonl?: string;
|
|
37
|
+
projectionJson?: string;
|
|
38
|
+
lockFile?: string;
|
|
39
|
+
};
|
|
40
|
+
}): Promise<{
|
|
41
|
+
ok: boolean;
|
|
42
|
+
created?: {
|
|
43
|
+
dir: boolean;
|
|
44
|
+
eventsJsonl: boolean;
|
|
45
|
+
projectionJson: boolean;
|
|
46
|
+
};
|
|
47
|
+
paths?: {
|
|
48
|
+
eventsJsonl: string;
|
|
49
|
+
projectionJson: string;
|
|
50
|
+
};
|
|
51
|
+
source?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>;
|
package/types/lock.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire an exclusive lock on `lockPath`.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} lockPath - Absolute path to the lock file. Parent dir must
|
|
5
|
+
* exist; we do not mkdir-p (callers control layout).
|
|
6
|
+
* @param {{ timeoutMs?: number, retryMs?: number }} [opts]
|
|
7
|
+
* @returns {Promise<{ release: () => void }>} - Resolves with a release
|
|
8
|
+
* handle. `release()` is idempotent: calling it twice is a no-op.
|
|
9
|
+
*
|
|
10
|
+
* Throws on timeout: `Error("acquireLock: timeout after <ms>ms (path=...)").`
|
|
11
|
+
* Re-throws unexpected fs errors verbatim (anything other than EEXIST).
|
|
12
|
+
*/
|
|
13
|
+
export function acquireLock(lockPath: string, opts?: {
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
retryMs?: number;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
release: () => void;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set or clear the human-readable alias on a session.
|
|
3
|
+
*
|
|
4
|
+
* Either `alias` (non-empty string) or `clear: true` must be provided —
|
|
5
|
+
* mutually exclusive. Validation matches the CLI's argparse behavior so the
|
|
6
|
+
* library consumer surface is symmetric with the CLI surface.
|
|
7
|
+
*
|
|
8
|
+
* @param {{
|
|
9
|
+
* stableId: string,
|
|
10
|
+
* alias?: string,
|
|
11
|
+
* clear?: boolean,
|
|
12
|
+
* rootPath?: string,
|
|
13
|
+
* root?: string,
|
|
14
|
+
* paths?: object,
|
|
15
|
+
* }} opts
|
|
16
|
+
* @returns {Promise<{ ok: boolean, event_id?: string, error?: string }>}
|
|
17
|
+
*/
|
|
18
|
+
export function setAlias(opts: {
|
|
19
|
+
stableId: string;
|
|
20
|
+
alias?: string;
|
|
21
|
+
clear?: boolean;
|
|
22
|
+
rootPath?: string;
|
|
23
|
+
root?: string;
|
|
24
|
+
paths?: object;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
event_id?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Link a session to one or more tasks / projects (additive, idempotent).
|
|
32
|
+
*
|
|
33
|
+
* At least one of `tasks` / `projects` must be a non-empty array. The
|
|
34
|
+
* reducer already de-dupes against existing entries so re-running with the
|
|
35
|
+
* same payload is a no-op on projection state (but still writes an audit
|
|
36
|
+
* event).
|
|
37
|
+
*
|
|
38
|
+
* @param {{
|
|
39
|
+
* stableId: string,
|
|
40
|
+
* tasks?: string[],
|
|
41
|
+
* projects?: string[],
|
|
42
|
+
* rootPath?: string,
|
|
43
|
+
* root?: string,
|
|
44
|
+
* paths?: object,
|
|
45
|
+
* }} opts
|
|
46
|
+
* @returns {Promise<{ ok: boolean, event_id?: string, error?: string }>}
|
|
47
|
+
*/
|
|
48
|
+
export function linkTask(opts: {
|
|
49
|
+
stableId: string;
|
|
50
|
+
tasks?: string[];
|
|
51
|
+
projects?: string[];
|
|
52
|
+
rootPath?: string;
|
|
53
|
+
root?: string;
|
|
54
|
+
paths?: object;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
ok: boolean;
|
|
57
|
+
event_id?: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Unlink one or more tasks / projects from a session (set-based filter,
|
|
62
|
+
* idempotent). Removing an id that isn't present is a no-op on projection
|
|
63
|
+
* state but still produces an audit event — operator intent is recorded
|
|
64
|
+
* regardless of resulting state change.
|
|
65
|
+
*
|
|
66
|
+
* @param {{
|
|
67
|
+
* stableId: string,
|
|
68
|
+
* tasks?: string[],
|
|
69
|
+
* projects?: string[],
|
|
70
|
+
* rootPath?: string,
|
|
71
|
+
* root?: string,
|
|
72
|
+
* paths?: object,
|
|
73
|
+
* }} opts
|
|
74
|
+
* @returns {Promise<{ ok: boolean, event_id?: string, error?: string }>}
|
|
75
|
+
*/
|
|
76
|
+
export function unlinkTask(opts: {
|
|
77
|
+
stableId: string;
|
|
78
|
+
tasks?: string[];
|
|
79
|
+
projects?: string[];
|
|
80
|
+
rootPath?: string;
|
|
81
|
+
root?: string;
|
|
82
|
+
paths?: object;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
ok: boolean;
|
|
85
|
+
event_id?: string;
|
|
86
|
+
error?: string;
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Set or clear the hub-spoke parent relationship for a session.
|
|
90
|
+
*
|
|
91
|
+
* Either `parentId` (non-empty string, distinct from `childId`) or `clear:
|
|
92
|
+
* true` must be provided. When setting a parent we:
|
|
93
|
+
* - reject self-cycle (parentId === childId, exit-1 in CLI)
|
|
94
|
+
* - verify parent exists
|
|
95
|
+
* - walk parent's ancestor chain up to MAX_PARENT_CHAIN_DEPTH and reject
|
|
96
|
+
* if `childId` appears anywhere — that would close a cycle of length
|
|
97
|
+
* ≥ 2 (e.g. existing A→B + proposed `setParent({childId: B, parentId: A})`
|
|
98
|
+
* would form A→B→A).
|
|
99
|
+
*
|
|
100
|
+
* The MAX_PARENT_CHAIN_DEPTH bound is a defense against a stale projection
|
|
101
|
+
* cycle (rare; would require an earlier guard bypass). 50 is generous —
|
|
102
|
+
* real hub-spoke chains are 1-3 hops.
|
|
103
|
+
*
|
|
104
|
+
* @param {{
|
|
105
|
+
* childId: string,
|
|
106
|
+
* parentId?: string,
|
|
107
|
+
* clear?: boolean,
|
|
108
|
+
* rootPath?: string,
|
|
109
|
+
* root?: string,
|
|
110
|
+
* paths?: object,
|
|
111
|
+
* }} opts
|
|
112
|
+
* @returns {Promise<{ ok: boolean, event_id?: string, error?: string }>}
|
|
113
|
+
*/
|
|
114
|
+
export function setParent(opts: {
|
|
115
|
+
childId: string;
|
|
116
|
+
parentId?: string;
|
|
117
|
+
clear?: boolean;
|
|
118
|
+
rootPath?: string;
|
|
119
|
+
root?: string;
|
|
120
|
+
paths?: object;
|
|
121
|
+
}): Promise<{
|
|
122
|
+
ok: boolean;
|
|
123
|
+
event_id?: string;
|
|
124
|
+
error?: string;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Close (or reopen) a session with a terminal outcome.
|
|
128
|
+
*
|
|
129
|
+
* Outcome enum is enforced (matches projection schema): open | done |
|
|
130
|
+
* blocked | abandoned | merged | superseded. `open` is allowed — operators
|
|
131
|
+
* may reopen a previously-closed session by passing `outcome: 'open'`; the
|
|
132
|
+
* reducer's closed_at always tracks the latest close event so the reopen is
|
|
133
|
+
* visible in the audit trail.
|
|
134
|
+
*
|
|
135
|
+
* @param {{
|
|
136
|
+
* stableId: string,
|
|
137
|
+
* outcome: string,
|
|
138
|
+
* reason?: string,
|
|
139
|
+
* rootPath?: string,
|
|
140
|
+
* root?: string,
|
|
141
|
+
* paths?: object,
|
|
142
|
+
* }} opts
|
|
143
|
+
* @returns {Promise<{ ok: boolean, event_id?: string, error?: string }>}
|
|
144
|
+
*/
|
|
145
|
+
export function closeSession(opts: {
|
|
146
|
+
stableId: string;
|
|
147
|
+
outcome: string;
|
|
148
|
+
reason?: string;
|
|
149
|
+
rootPath?: string;
|
|
150
|
+
root?: string;
|
|
151
|
+
paths?: object;
|
|
152
|
+
}): Promise<{
|
|
153
|
+
ok: boolean;
|
|
154
|
+
event_id?: string;
|
|
155
|
+
error?: string;
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Compute and (optionally) apply activity_state transitions across all
|
|
159
|
+
* sessions in the projection.
|
|
160
|
+
*
|
|
161
|
+
* Returns:
|
|
162
|
+
* - dryRun: true → `{ ok: true, dryRun: true, transitions }` with the
|
|
163
|
+
* planned transitions list (no events written).
|
|
164
|
+
* - dryRun: false → `{ ok: boolean, applied, failed, summary }` after
|
|
165
|
+
* attempting each transition through `tryUpdateProjection`. `ok` is
|
|
166
|
+
* true when zero failures.
|
|
167
|
+
*
|
|
168
|
+
* Lock model: each transition acquires the projection lock independently
|
|
169
|
+
* via `tryUpdateProjection`. For typical sweep volumes (single digits per
|
|
170
|
+
* run) this is fine; if the workspace grows huge a future `--batch` mode
|
|
171
|
+
* can fold all transitions into a single under-lock pass.
|
|
172
|
+
*
|
|
173
|
+
* @param {{
|
|
174
|
+
* rootPath?: string,
|
|
175
|
+
* root?: string,
|
|
176
|
+
* paths?: object,
|
|
177
|
+
* idleThresholdDays?: number,
|
|
178
|
+
* archiveThresholdDays?: number,
|
|
179
|
+
* dryRun?: boolean,
|
|
180
|
+
* now?: number,
|
|
181
|
+
* }} [opts]
|
|
182
|
+
* @returns {Promise<
|
|
183
|
+
* | { ok: true, dryRun: true, transitions: Array<object> }
|
|
184
|
+
* | { ok: boolean, applied: Array<object>, failed: Array<object>, summary: object }
|
|
185
|
+
* >}
|
|
186
|
+
*/
|
|
187
|
+
export function runSweep(opts?: {
|
|
188
|
+
rootPath?: string;
|
|
189
|
+
root?: string;
|
|
190
|
+
paths?: object;
|
|
191
|
+
idleThresholdDays?: number;
|
|
192
|
+
archiveThresholdDays?: number;
|
|
193
|
+
dryRun?: boolean;
|
|
194
|
+
now?: number;
|
|
195
|
+
}): Promise<{
|
|
196
|
+
ok: true;
|
|
197
|
+
dryRun: true;
|
|
198
|
+
transitions: Array<object>;
|
|
199
|
+
} | {
|
|
200
|
+
ok: boolean;
|
|
201
|
+
applied: Array<object>;
|
|
202
|
+
failed: Array<object>;
|
|
203
|
+
summary: object;
|
|
204
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve storage paths from caller opts + env + autodiscover.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ rootPath?: string, cwd?: string }} [opts]
|
|
5
|
+
* @returns {{
|
|
6
|
+
* root: string,
|
|
7
|
+
* eventsJsonl: string,
|
|
8
|
+
* projectionJson: string,
|
|
9
|
+
* lockFile: string,
|
|
10
|
+
* source: 'arg' | 'env' | 'tickets-logs' | 'dru-code' | 'default',
|
|
11
|
+
* }}
|
|
12
|
+
*/
|
|
13
|
+
export function resolveStoragePaths(opts?: {
|
|
14
|
+
rootPath?: string;
|
|
15
|
+
cwd?: string;
|
|
16
|
+
}): {
|
|
17
|
+
root: string;
|
|
18
|
+
eventsJsonl: string;
|
|
19
|
+
projectionJson: string;
|
|
20
|
+
lockFile: string;
|
|
21
|
+
source: "arg" | "env" | "tickets-logs" | "dru-code" | "default";
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Helper for callers that already have a fully-resolved root and want to
|
|
25
|
+
* compute file paths (tests, custom integrations). Public so consumers can
|
|
26
|
+
* mirror the layout invariant without importing internal helpers.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} root absolute or relative; resolved against cwd if relative
|
|
29
|
+
* @returns {{ root: string, eventsJsonl: string, projectionJson: string, lockFile: string }}
|
|
30
|
+
*/
|
|
31
|
+
export function pathsFromRoot(root: string): {
|
|
32
|
+
root: string;
|
|
33
|
+
eventsJsonl: string;
|
|
34
|
+
projectionJson: string;
|
|
35
|
+
lockFile: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Hard cap on cwd-ascend depth. Twelve levels is generous — a typical
|
|
39
|
+
* worktree depth is 1-3, monorepos may go to 5-6. Pinning at 12 means the
|
|
40
|
+
* worst-case stat budget is 24 (two candidate paths × 12 levels) before
|
|
41
|
+
* we fall through to the default. Set deliberately conservative so the
|
|
42
|
+
* resolver never accidentally walks to `/` on a slow networked mount.
|
|
43
|
+
*/
|
|
44
|
+
export const MAX_ASCEND_DEPTH: 12;
|
|
45
|
+
/**
|
|
46
|
+
* The three on-disk filenames (relative to whichever root the resolver
|
|
47
|
+
* picks). Frozen so callers can't accidentally mutate. Exported for tests
|
|
48
|
+
* + the rare library consumer that wants to know the canonical names.
|
|
49
|
+
*/
|
|
50
|
+
export const STORAGE_FILENAMES: Readonly<{
|
|
51
|
+
eventsJsonl: "sessions-db-events.jsonl";
|
|
52
|
+
projectionJson: "sessions-db.json";
|
|
53
|
+
lockFile: "sessions-db.json.lock";
|
|
54
|
+
}>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an empty projection skeleton. Sessions map starts empty; metadata
|
|
3
|
+
* has `event_count = 0` and `last_event_id = null`.
|
|
4
|
+
*
|
|
5
|
+
* @returns {{ _meta: object, sessions: Record<string, object> }}
|
|
6
|
+
*/
|
|
7
|
+
export function emptyProjection(): {
|
|
8
|
+
_meta: object;
|
|
9
|
+
sessions: Record<string, object>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Build a default session record. Caller passes the stable_id and the
|
|
13
|
+
* `created_at` timestamp (typically the first observing event's `ts`).
|
|
14
|
+
*
|
|
15
|
+
* @param {string} stableId
|
|
16
|
+
* @param {string} ts - ISO timestamp string used for both created_at and
|
|
17
|
+
* last_progress_at.
|
|
18
|
+
*/
|
|
19
|
+
export function emptySession(stableId: string, ts: string): {
|
|
20
|
+
stable_id: string;
|
|
21
|
+
alias: any;
|
|
22
|
+
claude_session_ids: any[];
|
|
23
|
+
transcript_files: any[];
|
|
24
|
+
fingerprints: {
|
|
25
|
+
first_human_prompt_v1: any;
|
|
26
|
+
lineage_prefix_v1: any;
|
|
27
|
+
};
|
|
28
|
+
parent_session_id: any;
|
|
29
|
+
parent_candidate_ids: any[];
|
|
30
|
+
parent_candidates_omitted_count: number;
|
|
31
|
+
identity_resolution: any;
|
|
32
|
+
worktree_path_observed: any;
|
|
33
|
+
worktree_realpath: any;
|
|
34
|
+
worktree_registry_name: any;
|
|
35
|
+
git_common_dir: any;
|
|
36
|
+
branch_at_start: any;
|
|
37
|
+
branch_current: any;
|
|
38
|
+
head_at_start: any;
|
|
39
|
+
head_last_seen: any;
|
|
40
|
+
tasks: any[];
|
|
41
|
+
projects: any[];
|
|
42
|
+
activity_state: string;
|
|
43
|
+
outcome: string;
|
|
44
|
+
closed_at: any;
|
|
45
|
+
closed_reason: any;
|
|
46
|
+
created_at: string;
|
|
47
|
+
last_progress_at: string;
|
|
48
|
+
first_prompt_preview: any;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Apply a single event to a projection (mutating). Returns the same
|
|
52
|
+
* projection reference for fluent chaining.
|
|
53
|
+
*
|
|
54
|
+
* Unknown ops are tolerated — they update _meta but otherwise no-op so a
|
|
55
|
+
* future schema bump applied against an older binary degrades cleanly. We
|
|
56
|
+
* still bump `event_count` so the rebuild detector remains accurate.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} projection
|
|
59
|
+
* @param {{ ts: string, event_id: string, op: string, stable_id: string,
|
|
60
|
+
* payload?: object }} event
|
|
61
|
+
* @returns {object} projection
|
|
62
|
+
*/
|
|
63
|
+
export function applyEvent(projection: object, event: {
|
|
64
|
+
ts: string;
|
|
65
|
+
event_id: string;
|
|
66
|
+
op: string;
|
|
67
|
+
stable_id: string;
|
|
68
|
+
payload?: object;
|
|
69
|
+
}): object;
|
|
70
|
+
/**
|
|
71
|
+
* Fold an event array into a fresh projection. Used both for full rebuilds
|
|
72
|
+
* (storage.rebuildProjection) and for unit tests.
|
|
73
|
+
*
|
|
74
|
+
* @param {Array<object>} events
|
|
75
|
+
*/
|
|
76
|
+
export function rebuildFromEvents(events: Array<object>): {
|
|
77
|
+
_meta: object;
|
|
78
|
+
sessions: Record<string, object>;
|
|
79
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip every `<system-reminder>...</system-reminder>` block from `s`, plus
|
|
3
|
+
* the related harness/system envelopes (`<system>`, `<thinking>`, `<tool_use>`,
|
|
4
|
+
* `<tool_result>`, `<parameter>`).
|
|
5
|
+
*
|
|
6
|
+
* @param {string} s
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function stripSystemReminders(s: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Strip IDE/harness wrappers (`<ide_opened_file>...`, `<ide_selection>...`,
|
|
12
|
+
* `<command-name>...</command-message>`).
|
|
13
|
+
* @param {string} s
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export function stripIdeWrappers(s: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Sanitise a raw first-prompt string for safe persistence.
|
|
19
|
+
*
|
|
20
|
+
* Order matters and is the result of an adversarial review:
|
|
21
|
+
* 1. NFKC normalise FIRST. Fullwidth bracket variants (e.g.
|
|
22
|
+
* `<system-reminder>`) only fold into ASCII `<>` after NFKC; if we
|
|
23
|
+
* stripped before normalising the wrapper would survive the strip pass
|
|
24
|
+
* and then leak its body once normalisation happens.
|
|
25
|
+
* 2. Strip system-reminders + system envelopes.
|
|
26
|
+
* 3. Strip IDE/harness wrappers.
|
|
27
|
+
* 4. Defensive second pass: re-strip both families. Removing one wrapper
|
|
28
|
+
* can splice together text that now reads as a fresh wrapper (e.g.
|
|
29
|
+
* `<sys` + IDE block + `tem>...</system>`); the second pass closes that.
|
|
30
|
+
* 5. Trim and collapse runs of 3+ newlines to a paragraph break.
|
|
31
|
+
* 6. Truncate to `maxLen` (default 200) on a code-point boundary, append `…`.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} raw
|
|
34
|
+
* @param {{ maxLen?: number }} [opts]
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function sanitizeFirstPrompt(raw: string, opts?: {
|
|
38
|
+
maxLen?: number;
|
|
39
|
+
}): string;
|