@chendpoc/pi-memory 0.1.12 → 0.1.13
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 +88 -16
- package/dist/cache/memoryCaches.d.ts +32 -0
- package/dist/cache/memoryCaches.d.ts.map +1 -0
- package/dist/cache/memoryCaches.js +59 -0
- package/dist/cache/memoryCaches.js.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +37 -1
- package/dist/config.js.map +1 -1
- package/dist/consolidation/enqueue.d.ts +33 -0
- package/dist/consolidation/enqueue.d.ts.map +1 -0
- package/dist/consolidation/enqueue.js +145 -0
- package/dist/consolidation/enqueue.js.map +1 -0
- package/dist/consolidation/index.d.ts +19 -0
- package/dist/consolidation/index.d.ts.map +1 -0
- package/dist/consolidation/index.js +15 -0
- package/dist/consolidation/index.js.map +1 -0
- package/dist/consolidation/lock.d.ts +5 -0
- package/dist/consolidation/lock.d.ts.map +1 -0
- package/dist/consolidation/lock.js +33 -0
- package/dist/consolidation/lock.js.map +1 -0
- package/dist/consolidation/log.d.ts +8 -0
- package/dist/consolidation/log.d.ts.map +1 -0
- package/dist/consolidation/log.js +32 -0
- package/dist/consolidation/log.js.map +1 -0
- package/dist/consolidation/memoryIndex.d.ts +18 -0
- package/dist/consolidation/memoryIndex.d.ts.map +1 -0
- package/dist/consolidation/memoryIndex.js +100 -0
- package/dist/consolidation/memoryIndex.js.map +1 -0
- package/dist/consolidation/phase2/runPhase2.d.ts +23 -0
- package/dist/consolidation/phase2/runPhase2.d.ts.map +1 -0
- package/dist/consolidation/phase2/runPhase2.js +196 -0
- package/dist/consolidation/phase2/runPhase2.js.map +1 -0
- package/dist/consolidation/scheduler/launchd.d.ts +5 -0
- package/dist/consolidation/scheduler/launchd.d.ts.map +1 -0
- package/dist/consolidation/scheduler/launchd.js +54 -0
- package/dist/consolidation/scheduler/launchd.js.map +1 -0
- package/dist/consolidation/scheduler/runConsolidate.d.ts +26 -0
- package/dist/consolidation/scheduler/runConsolidate.d.ts.map +1 -0
- package/dist/consolidation/scheduler/runConsolidate.js +83 -0
- package/dist/consolidation/scheduler/runConsolidate.js.map +1 -0
- package/dist/consolidation/scheduler/setupSchedule.d.ts +3 -0
- package/dist/consolidation/scheduler/setupSchedule.d.ts.map +1 -0
- package/dist/consolidation/scheduler/setupSchedule.js +79 -0
- package/dist/consolidation/scheduler/setupSchedule.js.map +1 -0
- package/dist/consolidation/scheduler/systemd.d.ts +9 -0
- package/dist/consolidation/scheduler/systemd.d.ts.map +1 -0
- package/dist/consolidation/scheduler/systemd.js +57 -0
- package/dist/consolidation/scheduler/systemd.js.map +1 -0
- package/dist/consolidation/scheduler/types.d.ts +24 -0
- package/dist/consolidation/scheduler/types.d.ts.map +1 -0
- package/dist/consolidation/scheduler/types.js +2 -0
- package/dist/consolidation/scheduler/types.js.map +1 -0
- package/dist/consolidation/scope.d.ts +12 -0
- package/dist/consolidation/scope.d.ts.map +1 -0
- package/dist/consolidation/scope.js +51 -0
- package/dist/consolidation/scope.js.map +1 -0
- package/dist/consolidation/stage1/deltaExtract.d.ts +3 -0
- package/dist/consolidation/stage1/deltaExtract.d.ts.map +1 -0
- package/dist/consolidation/stage1/deltaExtract.js +19 -0
- package/dist/consolidation/stage1/deltaExtract.js.map +1 -0
- package/dist/consolidation/stage1/drainQueue.d.ts +21 -0
- package/dist/consolidation/stage1/drainQueue.d.ts.map +1 -0
- package/dist/consolidation/stage1/drainQueue.js +113 -0
- package/dist/consolidation/stage1/drainQueue.js.map +1 -0
- package/dist/consolidation/stage1/extractSession.d.ts +6 -0
- package/dist/consolidation/stage1/extractSession.d.ts.map +1 -0
- package/dist/consolidation/stage1/extractSession.js +47 -0
- package/dist/consolidation/stage1/extractSession.js.map +1 -0
- package/dist/consolidation/stage1/store.d.ts +45 -0
- package/dist/consolidation/stage1/store.d.ts.map +1 -0
- package/dist/consolidation/stage1/store.js +297 -0
- package/dist/consolidation/stage1/store.js.map +1 -0
- package/dist/consolidation/types.d.ts +49 -0
- package/dist/consolidation/types.d.ts.map +1 -0
- package/dist/consolidation/types.js +2 -0
- package/dist/consolidation/types.js.map +1 -0
- package/dist/fallback/llmRerank.d.ts.map +1 -1
- package/dist/fallback/llmRerank.js +9 -1
- package/dist/fallback/llmRerank.js.map +1 -1
- package/dist/fallback/sessionIndex.d.ts.map +1 -1
- package/dist/fallback/sessionIndex.js +15 -63
- package/dist/fallback/sessionIndex.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/local/graphQuery.d.ts +10 -0
- package/dist/local/graphQuery.d.ts.map +1 -1
- package/dist/local/graphQuery.js +25 -0
- package/dist/local/graphQuery.js.map +1 -1
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +356 -22
- package/dist/pi-extension.js.map +1 -1
- package/dist/preflight/detectIntents.d.ts.map +1 -1
- package/dist/preflight/detectIntents.js +7 -0
- package/dist/preflight/detectIntents.js.map +1 -1
- package/dist/preflight/hook.d.ts +18 -0
- package/dist/preflight/hook.d.ts.map +1 -1
- package/dist/preflight/hook.js +52 -7
- package/dist/preflight/hook.js.map +1 -1
- package/dist/preflight/render.d.ts +16 -2
- package/dist/preflight/render.d.ts.map +1 -1
- package/dist/preflight/render.js +24 -4
- package/dist/preflight/render.js.map +1 -1
- package/dist/service.d.ts +16 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +57 -0
- package/dist/service.js.map +1 -1
- package/dist/session/activeBranch.d.ts +17 -0
- package/dist/session/activeBranch.d.ts.map +1 -0
- package/dist/session/activeBranch.js +251 -0
- package/dist/session/activeBranch.js.map +1 -0
- package/dist/settings.d.ts +5 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +18 -1
- package/dist/settings.js.map +1 -1
- package/dist/tools/memoryAppend.d.ts +14 -4
- package/dist/tools/memoryAppend.d.ts.map +1 -1
- package/dist/tools/memoryAppend.js +46 -7
- package/dist/tools/memoryAppend.js.map +1 -1
- package/dist/tools/memoryRecall.d.ts +2 -2
- package/dist/tools/memoryRecall.d.ts.map +1 -1
- package/dist/tools/memoryRecall.js +35 -9
- package/dist/tools/memoryRecall.js.map +1 -1
- package/dist/trainer/extractFacts.d.ts +1 -1
- package/dist/trainer/index.d.ts +1 -1
- package/dist/trainer/index.d.ts.map +1 -1
- package/dist/trainer/index.js +1 -1
- package/dist/trainer/index.js.map +1 -1
- package/dist/trainer/scheduler.d.ts +2 -0
- package/dist/trainer/scheduler.d.ts.map +1 -1
- package/dist/trainer/scheduler.js +1 -0
- package/dist/trainer/scheduler.js.map +1 -1
- package/dist/trainer/sessionLoader.d.ts +3 -0
- package/dist/trainer/sessionLoader.d.ts.map +1 -1
- package/dist/trainer/sessionLoader.js +37 -74
- package/dist/trainer/sessionLoader.js.map +1 -1
- package/package.json +14 -4
- package/src/cli.ts +144 -0
- package/src/config.ts +89 -1
- package/src/consolidation/enqueue.ts +205 -0
- package/src/consolidation/index.ts +90 -0
- package/src/consolidation/lock.ts +41 -0
- package/src/consolidation/log.ts +43 -0
- package/src/consolidation/memoryIndex.ts +127 -0
- package/src/consolidation/phase2/runPhase2.ts +255 -0
- package/src/consolidation/scheduler/launchd.ts +62 -0
- package/src/consolidation/scheduler/runConsolidate.ts +115 -0
- package/src/consolidation/scheduler/setupSchedule.ts +106 -0
- package/src/consolidation/scheduler/systemd.ts +78 -0
- package/src/consolidation/scheduler/types.ts +27 -0
- package/src/consolidation/scope.ts +58 -0
- package/src/consolidation/stage1/deltaExtract.ts +29 -0
- package/src/consolidation/stage1/drainQueue.ts +143 -0
- package/src/consolidation/stage1/extractSession.ts +58 -0
- package/src/consolidation/stage1/store.ts +411 -0
- package/src/consolidation/types.ts +52 -0
- package/src/fallback/sessionIndex.ts +14 -51
- package/src/index.ts +6 -0
- package/src/pi-extension.ts +309 -10
- package/src/preflight/hook.ts +16 -2
- package/src/preflight/render.ts +7 -0
- package/src/session/activeBranch.ts +294 -0
- package/src/settings.ts +37 -2
- package/src/tools/memoryAppend.ts +60 -7
- package/src/trainer/index.ts +1 -1
- package/src/trainer/sessionLoader.ts +38 -87
package/src/config.ts
CHANGED
|
@@ -21,6 +21,32 @@ export interface TrainerConfig {
|
|
|
21
21
|
auto_interval: string | null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface ConsolidationScheduleConfig {
|
|
25
|
+
/** Local hour for the background consolidation job (default 3). */
|
|
26
|
+
hour: number;
|
|
27
|
+
/** Local minute for the background consolidation job (default 0). */
|
|
28
|
+
minute: number;
|
|
29
|
+
/** Structured JSONL log path (default ~/.pi/memory/consolidation.log). */
|
|
30
|
+
log_path: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ConsolidationConfig {
|
|
34
|
+
/** Whether session_shutdown enqueues sessions for offline consolidation. */
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
/** Minimum non-empty user turns before enqueue (default 3). */
|
|
37
|
+
min_user_turns: number;
|
|
38
|
+
/** MEMORY.md session cap by line count (default 200). */
|
|
39
|
+
memory_index_max_lines: number;
|
|
40
|
+
/** MEMORY.md session cap by UTF-8 bytes (default 25KB). */
|
|
41
|
+
memory_index_max_bytes: number;
|
|
42
|
+
/** Maximum stage1 rows selected per Phase2 run (default 20). */
|
|
43
|
+
phase2_top_n: number;
|
|
44
|
+
/** M4 prune threshold by last usage (default 90 days). */
|
|
45
|
+
max_unused_days: number;
|
|
46
|
+
/** Daily OS scheduler defaults. */
|
|
47
|
+
schedule: ConsolidationScheduleConfig;
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
export interface MemoryConfig {
|
|
25
51
|
provider: MemoryProvider;
|
|
26
52
|
tlmPath: string;
|
|
@@ -32,6 +58,7 @@ export interface MemoryConfig {
|
|
|
32
58
|
sessionsDir: string;
|
|
33
59
|
memoryMdPaths: string[];
|
|
34
60
|
trainer: TrainerConfig;
|
|
61
|
+
consolidation: ConsolidationConfig;
|
|
35
62
|
}
|
|
36
63
|
|
|
37
64
|
export const defaultTrainerConfig: TrainerConfig = {
|
|
@@ -40,10 +67,37 @@ export const defaultTrainerConfig: TrainerConfig = {
|
|
|
40
67
|
auto_interval: null,
|
|
41
68
|
};
|
|
42
69
|
|
|
70
|
+
export function defaultConsolidationConfig(
|
|
71
|
+
overrides: Partial<ConsolidationConfig> & {
|
|
72
|
+
schedule?: Partial<ConsolidationScheduleConfig>;
|
|
73
|
+
} = {},
|
|
74
|
+
): ConsolidationConfig {
|
|
75
|
+
const defaultSchedule: ConsolidationScheduleConfig = {
|
|
76
|
+
hour: 3,
|
|
77
|
+
minute: 0,
|
|
78
|
+
log_path: path.join(defaultBundleRoot(), "consolidation.log"),
|
|
79
|
+
};
|
|
80
|
+
const { schedule, ...rest } = overrides;
|
|
81
|
+
return {
|
|
82
|
+
enabled: true,
|
|
83
|
+
min_user_turns: 3,
|
|
84
|
+
memory_index_max_lines: 200,
|
|
85
|
+
memory_index_max_bytes: 25_600,
|
|
86
|
+
phase2_top_n: 20,
|
|
87
|
+
max_unused_days: 90,
|
|
88
|
+
schedule: { ...defaultSchedule, ...schedule },
|
|
89
|
+
...rest,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
43
93
|
export function defaultMemoryConfig(
|
|
44
94
|
overrides: Partial<MemoryConfig> = {},
|
|
45
95
|
): MemoryConfig {
|
|
46
|
-
const {
|
|
96
|
+
const {
|
|
97
|
+
trainer: trainerOverrides,
|
|
98
|
+
consolidation: consolidationOverrides,
|
|
99
|
+
...rest
|
|
100
|
+
} = overrides;
|
|
47
101
|
return {
|
|
48
102
|
provider: "local",
|
|
49
103
|
tlmPath: "tlm",
|
|
@@ -55,6 +109,7 @@ export function defaultMemoryConfig(
|
|
|
55
109
|
sessionsDir: defaultSessionsDir(),
|
|
56
110
|
memoryMdPaths: [path.join(defaultPiHome(), "MEMORY.md")],
|
|
57
111
|
trainer: { ...defaultTrainerConfig, ...trainerOverrides },
|
|
112
|
+
consolidation: defaultConsolidationConfig(consolidationOverrides),
|
|
58
113
|
...rest,
|
|
59
114
|
};
|
|
60
115
|
}
|
|
@@ -65,6 +120,12 @@ export function normalizeMemoryConfig(
|
|
|
65
120
|
): MemoryConfig {
|
|
66
121
|
const base = defaultMemoryConfig();
|
|
67
122
|
const rawTrainer = (raw.trainer ?? {}) as Partial<TrainerConfig>;
|
|
123
|
+
const rawConsolidation = (raw.consolidation ?? {}) as
|
|
124
|
+
Partial<ConsolidationConfig> & {
|
|
125
|
+
schedule?: Partial<ConsolidationScheduleConfig>;
|
|
126
|
+
};
|
|
127
|
+
const rawSchedule = (rawConsolidation.schedule ?? {}) as
|
|
128
|
+
Partial<ConsolidationScheduleConfig>;
|
|
68
129
|
return {
|
|
69
130
|
provider: (raw.provider as MemoryProvider) ?? base.provider,
|
|
70
131
|
tlmPath: expandPath(String(raw.tlmPath ?? base.tlmPath)),
|
|
@@ -88,5 +149,32 @@ export function normalizeMemoryConfig(
|
|
|
88
149
|
? rawTrainer.auto_interval
|
|
89
150
|
: base.trainer.auto_interval,
|
|
90
151
|
},
|
|
152
|
+
consolidation: {
|
|
153
|
+
enabled: rawConsolidation.enabled ?? base.consolidation.enabled,
|
|
154
|
+
min_user_turns: Number(
|
|
155
|
+
rawConsolidation.min_user_turns ?? base.consolidation.min_user_turns,
|
|
156
|
+
),
|
|
157
|
+
memory_index_max_lines: Number(
|
|
158
|
+
rawConsolidation.memory_index_max_lines ??
|
|
159
|
+
base.consolidation.memory_index_max_lines,
|
|
160
|
+
),
|
|
161
|
+
memory_index_max_bytes: Number(
|
|
162
|
+
rawConsolidation.memory_index_max_bytes ??
|
|
163
|
+
base.consolidation.memory_index_max_bytes,
|
|
164
|
+
),
|
|
165
|
+
phase2_top_n: Number(
|
|
166
|
+
rawConsolidation.phase2_top_n ?? base.consolidation.phase2_top_n,
|
|
167
|
+
),
|
|
168
|
+
max_unused_days: Number(
|
|
169
|
+
rawConsolidation.max_unused_days ?? base.consolidation.max_unused_days,
|
|
170
|
+
),
|
|
171
|
+
schedule: {
|
|
172
|
+
hour: Number(rawSchedule.hour ?? base.consolidation.schedule.hour),
|
|
173
|
+
minute: Number(rawSchedule.minute ?? base.consolidation.schedule.minute),
|
|
174
|
+
log_path: expandPath(
|
|
175
|
+
String(rawSchedule.log_path ?? base.consolidation.schedule.log_path),
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
91
179
|
};
|
|
92
180
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ConsolidationStatus,
|
|
5
|
+
JobReport,
|
|
6
|
+
PendingSession,
|
|
7
|
+
ConsolidationStatusReport,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
import { openConsolidationStore, type SqliteDatabase } from "./stage1/store.js";
|
|
10
|
+
|
|
11
|
+
export interface EnqueueSessionInput {
|
|
12
|
+
session_id: string;
|
|
13
|
+
session_file: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
git_root: string | null;
|
|
16
|
+
project_hash: string | null;
|
|
17
|
+
parent_session_id: string | null;
|
|
18
|
+
parent_session_file: string | null;
|
|
19
|
+
user_turn_count: number;
|
|
20
|
+
ended_at: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface EnqueueOptions {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
minUserTurns?: number;
|
|
26
|
+
/** Injected sqlite handle for tests. */
|
|
27
|
+
db?: SqliteDatabase;
|
|
28
|
+
/** Force replacing existing done/skipped sessions. */
|
|
29
|
+
force?: boolean;
|
|
30
|
+
/** Override status for the newly enqueued job (default: pending). */
|
|
31
|
+
status?: ConsolidationStatus;
|
|
32
|
+
/** Optional now source for created_at / updated_at. */
|
|
33
|
+
now?: Date | string;
|
|
34
|
+
/** Test seam: production defaults to checking session file existence. */
|
|
35
|
+
checkFileExists?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toIso(value: Date | string | undefined): string {
|
|
39
|
+
const dt = value ? new Date(value) : new Date();
|
|
40
|
+
return dt.toISOString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isMissingFile(file: string): boolean {
|
|
44
|
+
return typeof file !== "string" || !file.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isAllowedStatus(status: string): status is ConsolidationStatus {
|
|
48
|
+
return status === "pending" || status === "processing" || status === "done" || status === "skipped" ||
|
|
49
|
+
status === "failed";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function enqueueSession(
|
|
53
|
+
dbPath: string,
|
|
54
|
+
meta: EnqueueSessionInput,
|
|
55
|
+
opts: EnqueueOptions = {},
|
|
56
|
+
): JobReport {
|
|
57
|
+
const enabled = opts.enabled !== false;
|
|
58
|
+
const minUserTurns = opts.minUserTurns ?? 3;
|
|
59
|
+
const now = toIso(opts.now);
|
|
60
|
+
|
|
61
|
+
if (!enabled) {
|
|
62
|
+
return {
|
|
63
|
+
session_id: meta.session_id,
|
|
64
|
+
enqueued: false,
|
|
65
|
+
action: "skipped",
|
|
66
|
+
status: "skipped",
|
|
67
|
+
reason: "consolidation_disabled",
|
|
68
|
+
now,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isMissingFile(meta.session_file)) {
|
|
73
|
+
return {
|
|
74
|
+
session_id: meta.session_id,
|
|
75
|
+
enqueued: false,
|
|
76
|
+
action: "skipped",
|
|
77
|
+
status: "skipped",
|
|
78
|
+
reason: "missing_session_file",
|
|
79
|
+
now,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (opts.checkFileExists !== false && !fs.existsSync(meta.session_file)) {
|
|
84
|
+
return {
|
|
85
|
+
session_id: meta.session_id,
|
|
86
|
+
enqueued: false,
|
|
87
|
+
action: "skipped",
|
|
88
|
+
status: "skipped",
|
|
89
|
+
reason: "session_file_not_found",
|
|
90
|
+
now,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (meta.user_turn_count < minUserTurns) {
|
|
95
|
+
return {
|
|
96
|
+
session_id: meta.session_id,
|
|
97
|
+
enqueued: false,
|
|
98
|
+
action: "skipped",
|
|
99
|
+
status: "skipped",
|
|
100
|
+
reason: "insufficient_user_turns",
|
|
101
|
+
now,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const store = openConsolidationStore(dbPath, opts.db);
|
|
106
|
+
if (!store) {
|
|
107
|
+
return {
|
|
108
|
+
session_id: meta.session_id,
|
|
109
|
+
enqueued: false,
|
|
110
|
+
action: "skipped",
|
|
111
|
+
status: "skipped",
|
|
112
|
+
reason: "missing_sqlite",
|
|
113
|
+
now,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const incomingStatus: ConsolidationStatus = isAllowedStatus(opts.status ?? "") ? opts.status! : "pending";
|
|
118
|
+
const existing = store.getPendingSession(meta.session_id);
|
|
119
|
+
const isFinal = existing?.status === "done" || existing?.status === "skipped";
|
|
120
|
+
|
|
121
|
+
if (existing && isFinal && !opts.force) {
|
|
122
|
+
store.close();
|
|
123
|
+
return {
|
|
124
|
+
session_id: meta.session_id,
|
|
125
|
+
enqueued: false,
|
|
126
|
+
action: "skipped",
|
|
127
|
+
status: existing.status,
|
|
128
|
+
reason: "already_finalized",
|
|
129
|
+
now,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const existingCreatedAt = existing?.created_at ?? now;
|
|
134
|
+
const payload: Omit<PendingSession, "created_at" | "updated_at"> & {
|
|
135
|
+
created_at: string;
|
|
136
|
+
updated_at: string;
|
|
137
|
+
} = {
|
|
138
|
+
session_id: meta.session_id,
|
|
139
|
+
session_file: meta.session_file,
|
|
140
|
+
cwd: meta.cwd,
|
|
141
|
+
git_root: meta.git_root,
|
|
142
|
+
project_hash: meta.project_hash,
|
|
143
|
+
parent_session_id: meta.parent_session_id,
|
|
144
|
+
parent_session_file: meta.parent_session_file,
|
|
145
|
+
user_turn_count: meta.user_turn_count,
|
|
146
|
+
ended_at: meta.ended_at,
|
|
147
|
+
status: incomingStatus,
|
|
148
|
+
error_message: null,
|
|
149
|
+
created_at: existingCreatedAt,
|
|
150
|
+
updated_at: now,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
store.upsertPendingSession(payload);
|
|
155
|
+
const action = existing ? "updated" : "created";
|
|
156
|
+
return {
|
|
157
|
+
session_id: meta.session_id,
|
|
158
|
+
enqueued: true,
|
|
159
|
+
action,
|
|
160
|
+
status: incomingStatus,
|
|
161
|
+
now,
|
|
162
|
+
};
|
|
163
|
+
} finally {
|
|
164
|
+
store.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ConsolidationStatusQueryOptions {
|
|
169
|
+
db?: SqliteDatabase;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getConsolidationStatus(
|
|
173
|
+
dbPath: string,
|
|
174
|
+
opts: ConsolidationStatusQueryOptions = {},
|
|
175
|
+
): ConsolidationStatusReport {
|
|
176
|
+
const store = openConsolidationStore(dbPath, opts.db);
|
|
177
|
+
if (!store) {
|
|
178
|
+
return {
|
|
179
|
+
pending: 0,
|
|
180
|
+
processing: 0,
|
|
181
|
+
done: 0,
|
|
182
|
+
skipped: 0,
|
|
183
|
+
failed: 0,
|
|
184
|
+
stage1Count: 0,
|
|
185
|
+
lastGeneratedAt: null,
|
|
186
|
+
lastUpdatedAt: null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const byStatus = store.getConsolidationStatusCounts();
|
|
192
|
+
return {
|
|
193
|
+
pending: byStatus.pending,
|
|
194
|
+
processing: byStatus.processing,
|
|
195
|
+
done: byStatus.done,
|
|
196
|
+
skipped: byStatus.skipped,
|
|
197
|
+
failed: byStatus.failed,
|
|
198
|
+
stage1Count: store.getStage1Count(),
|
|
199
|
+
lastGeneratedAt: store.getLastGeneratedAt(),
|
|
200
|
+
lastUpdatedAt: store.getLastUpdatedAt(),
|
|
201
|
+
};
|
|
202
|
+
} finally {
|
|
203
|
+
store.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ConsolidationStatus,
|
|
3
|
+
ConsolidationStatusReport,
|
|
4
|
+
JobReport,
|
|
5
|
+
PendingSession,
|
|
6
|
+
Stage1Output,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
getMemoryIndexStats,
|
|
11
|
+
memoryIndexSnippet,
|
|
12
|
+
readMemoryIndexCap,
|
|
13
|
+
type MemoryIndexCapOptions,
|
|
14
|
+
type MemoryIndexStats,
|
|
15
|
+
} from "./memoryIndex.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
findGitRoot,
|
|
19
|
+
lineMatchesScope,
|
|
20
|
+
parseScopeComment,
|
|
21
|
+
projectHash,
|
|
22
|
+
projectScope,
|
|
23
|
+
scopeForCwd,
|
|
24
|
+
type MemoryScope,
|
|
25
|
+
} from "./scope.js";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
appendConsolidationLog,
|
|
29
|
+
readRecentConsolidationLogs,
|
|
30
|
+
type ConsolidationLogEntry,
|
|
31
|
+
} from "./log.js";
|
|
32
|
+
|
|
33
|
+
export { withFileLock } from "./lock.js";
|
|
34
|
+
|
|
35
|
+
export { enqueueSession, getConsolidationStatus } from "./enqueue.js";
|
|
36
|
+
export type {
|
|
37
|
+
ConsolidationStatusQueryOptions,
|
|
38
|
+
EnqueueOptions,
|
|
39
|
+
EnqueueSessionInput,
|
|
40
|
+
} from "./enqueue.js";
|
|
41
|
+
|
|
42
|
+
export { openConsolidationStore } from "./stage1/store.js";
|
|
43
|
+
export type { SqliteDatabase, ConsolidationStore } from "./stage1/store.js";
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
computeDeltaTurns,
|
|
47
|
+
} from "./stage1/deltaExtract.js";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
drainQueue,
|
|
51
|
+
type DrainQueueOptions,
|
|
52
|
+
type DrainQueueReport,
|
|
53
|
+
} from "./stage1/drainQueue.js";
|
|
54
|
+
|
|
55
|
+
export { extractSessionToStage1 } from "./stage1/extractSession.js";
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
runPhase2,
|
|
59
|
+
type Phase2Options,
|
|
60
|
+
type Phase2Report,
|
|
61
|
+
} from "./phase2/runPhase2.js";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
defaultConsolidationDbPath,
|
|
65
|
+
runConsolidate,
|
|
66
|
+
type ConsolidateOptions,
|
|
67
|
+
type ConsolidateReport,
|
|
68
|
+
} from "./scheduler/runConsolidate.js";
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
buildLaunchdPlist,
|
|
72
|
+
launchdPlistPath,
|
|
73
|
+
} from "./scheduler/launchd.js";
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
buildSystemdService,
|
|
77
|
+
buildSystemdTimer,
|
|
78
|
+
systemdServicePath,
|
|
79
|
+
systemdTimerPath,
|
|
80
|
+
} from "./scheduler/systemd.js";
|
|
81
|
+
|
|
82
|
+
export { setupSchedule } from "./scheduler/setupSchedule.js";
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
ScheduleAction,
|
|
86
|
+
ScheduledFile,
|
|
87
|
+
ScheduleOptions,
|
|
88
|
+
SchedulePlatform,
|
|
89
|
+
SetupScheduleResult,
|
|
90
|
+
} from "./scheduler/types.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const LOCK_RETRIES = 50;
|
|
5
|
+
const LOCK_DELAY_MS = 100;
|
|
6
|
+
|
|
7
|
+
async function sleep(ms: number): Promise<void> {
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function withFileLock<T>(
|
|
12
|
+
lockPath: string,
|
|
13
|
+
fn: () => Promise<T>,
|
|
14
|
+
opts: { retries?: number; delayMs?: number } = {},
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const retries = opts.retries ?? LOCK_RETRIES;
|
|
17
|
+
const delayMs = opts.delayMs ?? LOCK_DELAY_MS;
|
|
18
|
+
let handle: fs.FileHandle | null = null;
|
|
19
|
+
|
|
20
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true, mode: 0o700 });
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < retries; i++) {
|
|
23
|
+
try {
|
|
24
|
+
handle = await fs.open(lockPath, "wx");
|
|
25
|
+
break;
|
|
26
|
+
} catch {
|
|
27
|
+
await sleep(delayMs);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!handle) {
|
|
32
|
+
throw new Error(`could not acquire lock: ${lockPath}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await fn();
|
|
37
|
+
} finally {
|
|
38
|
+
await handle.close().catch(() => {});
|
|
39
|
+
await fs.unlink(lockPath).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ConsolidationLogEntry {
|
|
5
|
+
ts?: string;
|
|
6
|
+
phase: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function appendConsolidationLog(
|
|
11
|
+
logPath: string,
|
|
12
|
+
entry: ConsolidationLogEntry,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
15
|
+
const line = JSON.stringify({
|
|
16
|
+
ts: entry.ts ?? new Date().toISOString(),
|
|
17
|
+
...entry,
|
|
18
|
+
});
|
|
19
|
+
await fs.appendFile(logPath, `${line}\n`, { encoding: "utf8", mode: 0o600 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function readRecentConsolidationLogs(
|
|
23
|
+
logPath: string,
|
|
24
|
+
limit = 5,
|
|
25
|
+
): Promise<ConsolidationLogEntry[]> {
|
|
26
|
+
let text: string;
|
|
27
|
+
try {
|
|
28
|
+
text = await fs.readFile(logPath, "utf8");
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return text
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter((line) => line.trim())
|
|
35
|
+
.slice(-limit)
|
|
36
|
+
.map((line) => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(line) as ConsolidationLogEntry;
|
|
39
|
+
} catch {
|
|
40
|
+
return { phase: "invalid_log_line", raw: line };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsPromises from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
lineMatchesScope,
|
|
6
|
+
type MemoryScope,
|
|
7
|
+
} from "./scope.js";
|
|
8
|
+
|
|
9
|
+
export interface MemoryIndexCapOptions {
|
|
10
|
+
maxLines?: number;
|
|
11
|
+
maxBytes?: number;
|
|
12
|
+
scopes?: readonly MemoryScope[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MemoryIndexStats {
|
|
16
|
+
path: string;
|
|
17
|
+
exists: boolean;
|
|
18
|
+
lines: number;
|
|
19
|
+
bytes: number;
|
|
20
|
+
cappedLines: number;
|
|
21
|
+
cappedBytes: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_MAX_LINES = 200;
|
|
25
|
+
const DEFAULT_MAX_BYTES = 25_600;
|
|
26
|
+
|
|
27
|
+
function capLines(
|
|
28
|
+
text: string,
|
|
29
|
+
opts: Required<Pick<MemoryIndexCapOptions, "maxLines" | "maxBytes">> & {
|
|
30
|
+
scopes?: readonly MemoryScope[];
|
|
31
|
+
},
|
|
32
|
+
): string {
|
|
33
|
+
const out: string[] = [];
|
|
34
|
+
let bytes = 0;
|
|
35
|
+
for (const line of text.split("\n")) {
|
|
36
|
+
if (out.length >= opts.maxLines) break;
|
|
37
|
+
if (!lineMatchesScope(line, opts.scopes)) continue;
|
|
38
|
+
const nextBytes = Buffer.byteLength(line + "\n", "utf8");
|
|
39
|
+
if (bytes + nextBytes > opts.maxBytes) break;
|
|
40
|
+
out.push(line);
|
|
41
|
+
bytes += nextBytes;
|
|
42
|
+
}
|
|
43
|
+
return out.join("\n").trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizedOptions(opts: MemoryIndexCapOptions = {}) {
|
|
47
|
+
return {
|
|
48
|
+
maxLines: opts.maxLines ?? DEFAULT_MAX_LINES,
|
|
49
|
+
maxBytes: opts.maxBytes ?? DEFAULT_MAX_BYTES,
|
|
50
|
+
scopes: opts.scopes,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readMemoryIndexCap(
|
|
55
|
+
paths: string[],
|
|
56
|
+
opts: MemoryIndexCapOptions = {},
|
|
57
|
+
): string {
|
|
58
|
+
const normalized = normalizedOptions(opts);
|
|
59
|
+
for (const p of paths) {
|
|
60
|
+
let text: string;
|
|
61
|
+
try {
|
|
62
|
+
text = fs.readFileSync(p, "utf8");
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const capped = capLines(text, normalized);
|
|
67
|
+
if (capped) return capped;
|
|
68
|
+
}
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function memoryIndexSnippet(
|
|
73
|
+
paths: string[],
|
|
74
|
+
query: string,
|
|
75
|
+
opts: MemoryIndexCapOptions = {},
|
|
76
|
+
): Promise<string> {
|
|
77
|
+
const q = query.trim().toLowerCase();
|
|
78
|
+
if (!q) return "";
|
|
79
|
+
const normalized = normalizedOptions(opts);
|
|
80
|
+
|
|
81
|
+
for (const p of paths) {
|
|
82
|
+
let text: string;
|
|
83
|
+
try {
|
|
84
|
+
text = await fsPromises.readFile(p, "utf8");
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const matches: string[] = [];
|
|
89
|
+
let bytes = 0;
|
|
90
|
+
for (const line of text.split("\n")) {
|
|
91
|
+
if (!lineMatchesScope(line, normalized.scopes)) continue;
|
|
92
|
+
if (!line.toLowerCase().includes(q)) continue;
|
|
93
|
+
const nextBytes = Buffer.byteLength(line + "\n", "utf8");
|
|
94
|
+
if (bytes + nextBytes > normalized.maxBytes) break;
|
|
95
|
+
matches.push(line);
|
|
96
|
+
bytes += nextBytes;
|
|
97
|
+
if (matches.length >= normalized.maxLines) break;
|
|
98
|
+
}
|
|
99
|
+
if (matches.length > 0) return matches.join("\n");
|
|
100
|
+
}
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getMemoryIndexStats(
|
|
105
|
+
paths: string[],
|
|
106
|
+
opts: MemoryIndexCapOptions = {},
|
|
107
|
+
): MemoryIndexStats[] {
|
|
108
|
+
const normalized = normalizedOptions(opts);
|
|
109
|
+
return paths.map((p) => {
|
|
110
|
+
let text = "";
|
|
111
|
+
let exists = true;
|
|
112
|
+
try {
|
|
113
|
+
text = fs.readFileSync(p, "utf8");
|
|
114
|
+
} catch {
|
|
115
|
+
exists = false;
|
|
116
|
+
}
|
|
117
|
+
const capped = exists ? capLines(text, normalized) : "";
|
|
118
|
+
return {
|
|
119
|
+
path: p,
|
|
120
|
+
exists,
|
|
121
|
+
lines: exists ? text.split("\n").length : 0,
|
|
122
|
+
bytes: exists ? Buffer.byteLength(text, "utf8") : 0,
|
|
123
|
+
cappedLines: capped ? capped.split("\n").length : 0,
|
|
124
|
+
cappedBytes: Buffer.byteLength(capped, "utf8"),
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|