engrm 0.4.21 → 0.4.22
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/dist/cli.js +23 -2
- package/dist/hooks/elicitation-result.js +26 -1
- package/dist/hooks/post-tool-use.js +80 -3
- package/dist/hooks/pre-compact.js +26 -1
- package/dist/hooks/sentinel.js +26 -1
- package/dist/hooks/session-start.js +36 -3
- package/dist/hooks/stop.js +74 -47
- package/dist/hooks/user-prompt-submit.js +79 -2
- package/dist/server.js +68 -45
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -599,7 +599,7 @@ var MIGRATIONS = [
|
|
|
599
599
|
`
|
|
600
600
|
},
|
|
601
601
|
{
|
|
602
|
-
version:
|
|
602
|
+
version: 12,
|
|
603
603
|
description: "Add synced handoff metadata to session summaries",
|
|
604
604
|
sql: `
|
|
605
605
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -674,6 +674,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
674
674
|
version = Math.max(version, 10);
|
|
675
675
|
if (columnExists(db, "observations", "source_tool"))
|
|
676
676
|
version = Math.max(version, 11);
|
|
677
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
678
|
+
version = Math.max(version, 12);
|
|
679
|
+
}
|
|
677
680
|
return version;
|
|
678
681
|
}
|
|
679
682
|
function runMigrations(db) {
|
|
@@ -752,6 +755,23 @@ function ensureObservationTypes(db) {
|
|
|
752
755
|
}
|
|
753
756
|
}
|
|
754
757
|
}
|
|
758
|
+
function ensureSessionSummaryColumns(db) {
|
|
759
|
+
const required = [
|
|
760
|
+
"capture_state",
|
|
761
|
+
"recent_tool_names",
|
|
762
|
+
"hot_files",
|
|
763
|
+
"recent_outcomes"
|
|
764
|
+
];
|
|
765
|
+
for (const column of required) {
|
|
766
|
+
if (columnExists(db, "session_summaries", column))
|
|
767
|
+
continue;
|
|
768
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
769
|
+
}
|
|
770
|
+
const current = getSchemaVersion(db);
|
|
771
|
+
if (current < 12) {
|
|
772
|
+
db.exec("PRAGMA user_version = 12");
|
|
773
|
+
}
|
|
774
|
+
}
|
|
755
775
|
function getSchemaVersion(db) {
|
|
756
776
|
const result = db.query("PRAGMA user_version").get();
|
|
757
777
|
return result.user_version;
|
|
@@ -910,6 +930,7 @@ class MemDatabase {
|
|
|
910
930
|
this.vecAvailable = this.loadVecExtension();
|
|
911
931
|
runMigrations(this.db);
|
|
912
932
|
ensureObservationTypes(this.db);
|
|
933
|
+
ensureSessionSummaryColumns(this.db);
|
|
913
934
|
}
|
|
914
935
|
loadVecExtension() {
|
|
915
936
|
try {
|
|
@@ -1873,7 +1894,7 @@ var MIGRATIONS2 = [
|
|
|
1873
1894
|
`
|
|
1874
1895
|
},
|
|
1875
1896
|
{
|
|
1876
|
-
version:
|
|
1897
|
+
version: 12,
|
|
1877
1898
|
description: "Add synced handoff metadata to session summaries",
|
|
1878
1899
|
sql: `
|
|
1879
1900
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -1432,7 +1432,7 @@ var MIGRATIONS = [
|
|
|
1432
1432
|
`
|
|
1433
1433
|
},
|
|
1434
1434
|
{
|
|
1435
|
-
version:
|
|
1435
|
+
version: 12,
|
|
1436
1436
|
description: "Add synced handoff metadata to session summaries",
|
|
1437
1437
|
sql: `
|
|
1438
1438
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -1507,6 +1507,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
1507
1507
|
version = Math.max(version, 10);
|
|
1508
1508
|
if (columnExists(db, "observations", "source_tool"))
|
|
1509
1509
|
version = Math.max(version, 11);
|
|
1510
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
1511
|
+
version = Math.max(version, 12);
|
|
1512
|
+
}
|
|
1510
1513
|
return version;
|
|
1511
1514
|
}
|
|
1512
1515
|
function runMigrations(db) {
|
|
@@ -1585,6 +1588,27 @@ function ensureObservationTypes(db) {
|
|
|
1585
1588
|
}
|
|
1586
1589
|
}
|
|
1587
1590
|
}
|
|
1591
|
+
function ensureSessionSummaryColumns(db) {
|
|
1592
|
+
const required = [
|
|
1593
|
+
"capture_state",
|
|
1594
|
+
"recent_tool_names",
|
|
1595
|
+
"hot_files",
|
|
1596
|
+
"recent_outcomes"
|
|
1597
|
+
];
|
|
1598
|
+
for (const column of required) {
|
|
1599
|
+
if (columnExists(db, "session_summaries", column))
|
|
1600
|
+
continue;
|
|
1601
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
1602
|
+
}
|
|
1603
|
+
const current = getSchemaVersion(db);
|
|
1604
|
+
if (current < 12) {
|
|
1605
|
+
db.exec("PRAGMA user_version = 12");
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function getSchemaVersion(db) {
|
|
1609
|
+
const result = db.query("PRAGMA user_version").get();
|
|
1610
|
+
return result.user_version;
|
|
1611
|
+
}
|
|
1588
1612
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
1589
1613
|
|
|
1590
1614
|
// src/storage/sqlite.ts
|
|
@@ -1739,6 +1763,7 @@ class MemDatabase {
|
|
|
1739
1763
|
this.vecAvailable = this.loadVecExtension();
|
|
1740
1764
|
runMigrations(this.db);
|
|
1741
1765
|
ensureObservationTypes(this.db);
|
|
1766
|
+
ensureSessionSummaryColumns(this.db);
|
|
1742
1767
|
}
|
|
1743
1768
|
loadVecExtension() {
|
|
1744
1769
|
try {
|
|
@@ -777,7 +777,7 @@ var MIGRATIONS = [
|
|
|
777
777
|
`
|
|
778
778
|
},
|
|
779
779
|
{
|
|
780
|
-
version:
|
|
780
|
+
version: 12,
|
|
781
781
|
description: "Add synced handoff metadata to session summaries",
|
|
782
782
|
sql: `
|
|
783
783
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -852,6 +852,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
852
852
|
version = Math.max(version, 10);
|
|
853
853
|
if (columnExists(db, "observations", "source_tool"))
|
|
854
854
|
version = Math.max(version, 11);
|
|
855
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
856
|
+
version = Math.max(version, 12);
|
|
857
|
+
}
|
|
855
858
|
return version;
|
|
856
859
|
}
|
|
857
860
|
function runMigrations(db) {
|
|
@@ -930,6 +933,27 @@ function ensureObservationTypes(db) {
|
|
|
930
933
|
}
|
|
931
934
|
}
|
|
932
935
|
}
|
|
936
|
+
function ensureSessionSummaryColumns(db) {
|
|
937
|
+
const required = [
|
|
938
|
+
"capture_state",
|
|
939
|
+
"recent_tool_names",
|
|
940
|
+
"hot_files",
|
|
941
|
+
"recent_outcomes"
|
|
942
|
+
];
|
|
943
|
+
for (const column of required) {
|
|
944
|
+
if (columnExists(db, "session_summaries", column))
|
|
945
|
+
continue;
|
|
946
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
947
|
+
}
|
|
948
|
+
const current = getSchemaVersion(db);
|
|
949
|
+
if (current < 12) {
|
|
950
|
+
db.exec("PRAGMA user_version = 12");
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function getSchemaVersion(db) {
|
|
954
|
+
const result = db.query("PRAGMA user_version").get();
|
|
955
|
+
return result.user_version;
|
|
956
|
+
}
|
|
933
957
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
934
958
|
|
|
935
959
|
// src/storage/sqlite.ts
|
|
@@ -1084,6 +1108,7 @@ class MemDatabase {
|
|
|
1084
1108
|
this.vecAvailable = this.loadVecExtension();
|
|
1085
1109
|
runMigrations(this.db);
|
|
1086
1110
|
ensureObservationTypes(this.db);
|
|
1111
|
+
ensureSessionSummaryColumns(this.db);
|
|
1087
1112
|
}
|
|
1088
1113
|
loadVecExtension() {
|
|
1089
1114
|
try {
|
|
@@ -3252,6 +3277,50 @@ function mergeSectionItem(existing, item) {
|
|
|
3252
3277
|
- ${normalizedItem}`;
|
|
3253
3278
|
}
|
|
3254
3279
|
|
|
3280
|
+
// src/capture/session-handoff.ts
|
|
3281
|
+
function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
3282
|
+
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
3283
|
+
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
3284
|
+
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
3285
|
+
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
3286
|
+
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
3287
|
+
...parseJsonArray(obs.files_modified),
|
|
3288
|
+
...parseJsonArray(obs.files_read)
|
|
3289
|
+
]).filter(Boolean))].slice(0, 6);
|
|
3290
|
+
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
3291
|
+
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
3292
|
+
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
3293
|
+
if (!obs.source_tool)
|
|
3294
|
+
return acc;
|
|
3295
|
+
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
3296
|
+
return acc;
|
|
3297
|
+
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
3298
|
+
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
3299
|
+
return {
|
|
3300
|
+
prompt_count: prompts.length,
|
|
3301
|
+
tool_event_count: toolEvents.length,
|
|
3302
|
+
recent_request_prompts: recentRequestPrompts,
|
|
3303
|
+
latest_request: latestRequest,
|
|
3304
|
+
recent_tool_names: recentToolNames,
|
|
3305
|
+
recent_tool_commands: recentToolCommands,
|
|
3306
|
+
capture_state: captureState,
|
|
3307
|
+
hot_files: hotFiles,
|
|
3308
|
+
recent_outcomes: recentOutcomes,
|
|
3309
|
+
observation_source_tools: observationSourceTools,
|
|
3310
|
+
latest_observation_prompt_number: latestObservationPromptNumber
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
function parseJsonArray(value) {
|
|
3314
|
+
if (!value)
|
|
3315
|
+
return [];
|
|
3316
|
+
try {
|
|
3317
|
+
const parsed = JSON.parse(value);
|
|
3318
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
3319
|
+
} catch {
|
|
3320
|
+
return [];
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3255
3324
|
// hooks/post-tool-use.ts
|
|
3256
3325
|
async function main() {
|
|
3257
3326
|
const raw = await readStdin();
|
|
@@ -3450,8 +3519,12 @@ function updateRollingSummaryFromObservation(db, observationId, event, userId) {
|
|
|
3450
3519
|
if (!update)
|
|
3451
3520
|
return;
|
|
3452
3521
|
const existing = db.getSessionSummary(event.session_id);
|
|
3522
|
+
const sessionPrompts = db.getSessionUserPrompts(event.session_id, 20);
|
|
3523
|
+
const sessionToolEvents = db.getSessionToolEvents(event.session_id, 20);
|
|
3524
|
+
const sessionObservations = db.getObservationsBySession(event.session_id);
|
|
3453
3525
|
const merged = mergeLiveSummarySections(existing, update);
|
|
3454
|
-
const
|
|
3526
|
+
const handoff = buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, sessionObservations);
|
|
3527
|
+
const currentRequest = existing?.request ?? handoff.latest_request ?? null;
|
|
3455
3528
|
const summary = db.upsertSessionSummary({
|
|
3456
3529
|
session_id: event.session_id,
|
|
3457
3530
|
project_id: observation.project_id,
|
|
@@ -3460,7 +3533,11 @@ function updateRollingSummaryFromObservation(db, observationId, event, userId) {
|
|
|
3460
3533
|
investigated: merged.investigated,
|
|
3461
3534
|
learned: merged.learned,
|
|
3462
3535
|
completed: merged.completed,
|
|
3463
|
-
next_steps: existing?.next_steps ?? null
|
|
3536
|
+
next_steps: existing?.next_steps ?? null,
|
|
3537
|
+
capture_state: handoff.capture_state,
|
|
3538
|
+
recent_tool_names: JSON.stringify(handoff.recent_tool_names),
|
|
3539
|
+
hot_files: JSON.stringify(handoff.hot_files),
|
|
3540
|
+
recent_outcomes: JSON.stringify(handoff.recent_outcomes)
|
|
3464
3541
|
});
|
|
3465
3542
|
db.addToOutbox("summary", summary.id);
|
|
3466
3543
|
}
|
|
@@ -571,7 +571,7 @@ var MIGRATIONS = [
|
|
|
571
571
|
`
|
|
572
572
|
},
|
|
573
573
|
{
|
|
574
|
-
version:
|
|
574
|
+
version: 12,
|
|
575
575
|
description: "Add synced handoff metadata to session summaries",
|
|
576
576
|
sql: `
|
|
577
577
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -646,6 +646,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
646
646
|
version = Math.max(version, 10);
|
|
647
647
|
if (columnExists(db, "observations", "source_tool"))
|
|
648
648
|
version = Math.max(version, 11);
|
|
649
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
650
|
+
version = Math.max(version, 12);
|
|
651
|
+
}
|
|
649
652
|
return version;
|
|
650
653
|
}
|
|
651
654
|
function runMigrations(db) {
|
|
@@ -724,6 +727,27 @@ function ensureObservationTypes(db) {
|
|
|
724
727
|
}
|
|
725
728
|
}
|
|
726
729
|
}
|
|
730
|
+
function ensureSessionSummaryColumns(db) {
|
|
731
|
+
const required = [
|
|
732
|
+
"capture_state",
|
|
733
|
+
"recent_tool_names",
|
|
734
|
+
"hot_files",
|
|
735
|
+
"recent_outcomes"
|
|
736
|
+
];
|
|
737
|
+
for (const column of required) {
|
|
738
|
+
if (columnExists(db, "session_summaries", column))
|
|
739
|
+
continue;
|
|
740
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
741
|
+
}
|
|
742
|
+
const current = getSchemaVersion(db);
|
|
743
|
+
if (current < 12) {
|
|
744
|
+
db.exec("PRAGMA user_version = 12");
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function getSchemaVersion(db) {
|
|
748
|
+
const result = db.query("PRAGMA user_version").get();
|
|
749
|
+
return result.user_version;
|
|
750
|
+
}
|
|
727
751
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
728
752
|
|
|
729
753
|
// src/storage/sqlite.ts
|
|
@@ -878,6 +902,7 @@ class MemDatabase {
|
|
|
878
902
|
this.vecAvailable = this.loadVecExtension();
|
|
879
903
|
runMigrations(this.db);
|
|
880
904
|
ensureObservationTypes(this.db);
|
|
905
|
+
ensureSessionSummaryColumns(this.db);
|
|
881
906
|
}
|
|
882
907
|
loadVecExtension() {
|
|
883
908
|
try {
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -647,7 +647,7 @@ var MIGRATIONS = [
|
|
|
647
647
|
`
|
|
648
648
|
},
|
|
649
649
|
{
|
|
650
|
-
version:
|
|
650
|
+
version: 12,
|
|
651
651
|
description: "Add synced handoff metadata to session summaries",
|
|
652
652
|
sql: `
|
|
653
653
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -722,6 +722,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
722
722
|
version = Math.max(version, 10);
|
|
723
723
|
if (columnExists(db, "observations", "source_tool"))
|
|
724
724
|
version = Math.max(version, 11);
|
|
725
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
726
|
+
version = Math.max(version, 12);
|
|
727
|
+
}
|
|
725
728
|
return version;
|
|
726
729
|
}
|
|
727
730
|
function runMigrations(db) {
|
|
@@ -800,6 +803,27 @@ function ensureObservationTypes(db) {
|
|
|
800
803
|
}
|
|
801
804
|
}
|
|
802
805
|
}
|
|
806
|
+
function ensureSessionSummaryColumns(db) {
|
|
807
|
+
const required = [
|
|
808
|
+
"capture_state",
|
|
809
|
+
"recent_tool_names",
|
|
810
|
+
"hot_files",
|
|
811
|
+
"recent_outcomes"
|
|
812
|
+
];
|
|
813
|
+
for (const column of required) {
|
|
814
|
+
if (columnExists(db, "session_summaries", column))
|
|
815
|
+
continue;
|
|
816
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
817
|
+
}
|
|
818
|
+
const current = getSchemaVersion(db);
|
|
819
|
+
if (current < 12) {
|
|
820
|
+
db.exec("PRAGMA user_version = 12");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function getSchemaVersion(db) {
|
|
824
|
+
const result = db.query("PRAGMA user_version").get();
|
|
825
|
+
return result.user_version;
|
|
826
|
+
}
|
|
803
827
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
804
828
|
|
|
805
829
|
// src/storage/sqlite.ts
|
|
@@ -954,6 +978,7 @@ class MemDatabase {
|
|
|
954
978
|
this.vecAvailable = this.loadVecExtension();
|
|
955
979
|
runMigrations(this.db);
|
|
956
980
|
ensureObservationTypes(this.db);
|
|
981
|
+
ensureSessionSummaryColumns(this.db);
|
|
957
982
|
}
|
|
958
983
|
loadVecExtension() {
|
|
959
984
|
try {
|
|
@@ -1184,7 +1184,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
1184
1184
|
import { join as join3 } from "node:path";
|
|
1185
1185
|
import { homedir } from "node:os";
|
|
1186
1186
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
1187
|
-
var CLIENT_VERSION = "0.4.
|
|
1187
|
+
var CLIENT_VERSION = "0.4.22";
|
|
1188
1188
|
function hashFile(filePath) {
|
|
1189
1189
|
try {
|
|
1190
1190
|
if (!existsSync3(filePath))
|
|
@@ -2287,7 +2287,7 @@ var MIGRATIONS = [
|
|
|
2287
2287
|
`
|
|
2288
2288
|
},
|
|
2289
2289
|
{
|
|
2290
|
-
version:
|
|
2290
|
+
version: 12,
|
|
2291
2291
|
description: "Add synced handoff metadata to session summaries",
|
|
2292
2292
|
sql: `
|
|
2293
2293
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -2362,6 +2362,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
2362
2362
|
version = Math.max(version, 10);
|
|
2363
2363
|
if (columnExists(db, "observations", "source_tool"))
|
|
2364
2364
|
version = Math.max(version, 11);
|
|
2365
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
2366
|
+
version = Math.max(version, 12);
|
|
2367
|
+
}
|
|
2365
2368
|
return version;
|
|
2366
2369
|
}
|
|
2367
2370
|
function runMigrations(db) {
|
|
@@ -2440,6 +2443,27 @@ function ensureObservationTypes(db) {
|
|
|
2440
2443
|
}
|
|
2441
2444
|
}
|
|
2442
2445
|
}
|
|
2446
|
+
function ensureSessionSummaryColumns(db) {
|
|
2447
|
+
const required = [
|
|
2448
|
+
"capture_state",
|
|
2449
|
+
"recent_tool_names",
|
|
2450
|
+
"hot_files",
|
|
2451
|
+
"recent_outcomes"
|
|
2452
|
+
];
|
|
2453
|
+
for (const column of required) {
|
|
2454
|
+
if (columnExists(db, "session_summaries", column))
|
|
2455
|
+
continue;
|
|
2456
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
2457
|
+
}
|
|
2458
|
+
const current = getSchemaVersion(db);
|
|
2459
|
+
if (current < 12) {
|
|
2460
|
+
db.exec("PRAGMA user_version = 12");
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function getSchemaVersion(db) {
|
|
2464
|
+
const result = db.query("PRAGMA user_version").get();
|
|
2465
|
+
return result.user_version;
|
|
2466
|
+
}
|
|
2443
2467
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
2444
2468
|
|
|
2445
2469
|
// src/storage/sqlite.ts
|
|
@@ -2514,6 +2538,7 @@ class MemDatabase {
|
|
|
2514
2538
|
this.vecAvailable = this.loadVecExtension();
|
|
2515
2539
|
runMigrations(this.db);
|
|
2516
2540
|
ensureObservationTypes(this.db);
|
|
2541
|
+
ensureSessionSummaryColumns(this.db);
|
|
2517
2542
|
}
|
|
2518
2543
|
loadVecExtension() {
|
|
2519
2544
|
try {
|
|
@@ -3833,7 +3858,15 @@ function isWeakCompletedSection(value) {
|
|
|
3833
3858
|
return weakCount === items.length;
|
|
3834
3859
|
}
|
|
3835
3860
|
function looksLikeFileOperationTitle2(value) {
|
|
3836
|
-
|
|
3861
|
+
const trimmed = value.trim();
|
|
3862
|
+
if (/^(modified|updated|edited|touched|changed|extended|refactored|redesigned)\s+[A-Za-z0-9_.\-\/]+(?:\s*\([^)]*\))?$/i.test(trimmed)) {
|
|
3863
|
+
return true;
|
|
3864
|
+
}
|
|
3865
|
+
return looksLikeGenericSummaryWrapper(trimmed);
|
|
3866
|
+
}
|
|
3867
|
+
function looksLikeGenericSummaryWrapper(value) {
|
|
3868
|
+
const normalized = value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3869
|
+
return normalized.startsWith("all clean. here's a summary of what was fixed") || normalized.startsWith("all clean, here's a summary of what was fixed") || normalized.startsWith("now i have enough to give a clear, accurate assessment") || normalized.startsWith("here's the real picture") || normalized === "event log \u2192 existing events feed" || normalized.startsWith("event log -> existing events feed") || normalized.startsWith("tl;dr:");
|
|
3837
3870
|
}
|
|
3838
3871
|
function scoreSplashLine(value) {
|
|
3839
3872
|
let score = 0;
|
package/dist/hooks/stop.js
CHANGED
|
@@ -884,7 +884,7 @@ var MIGRATIONS = [
|
|
|
884
884
|
`
|
|
885
885
|
},
|
|
886
886
|
{
|
|
887
|
-
version:
|
|
887
|
+
version: 12,
|
|
888
888
|
description: "Add synced handoff metadata to session summaries",
|
|
889
889
|
sql: `
|
|
890
890
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -959,6 +959,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
959
959
|
version = Math.max(version, 10);
|
|
960
960
|
if (columnExists(db, "observations", "source_tool"))
|
|
961
961
|
version = Math.max(version, 11);
|
|
962
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
963
|
+
version = Math.max(version, 12);
|
|
964
|
+
}
|
|
962
965
|
return version;
|
|
963
966
|
}
|
|
964
967
|
function runMigrations(db) {
|
|
@@ -1037,6 +1040,27 @@ function ensureObservationTypes(db) {
|
|
|
1037
1040
|
}
|
|
1038
1041
|
}
|
|
1039
1042
|
}
|
|
1043
|
+
function ensureSessionSummaryColumns(db) {
|
|
1044
|
+
const required = [
|
|
1045
|
+
"capture_state",
|
|
1046
|
+
"recent_tool_names",
|
|
1047
|
+
"hot_files",
|
|
1048
|
+
"recent_outcomes"
|
|
1049
|
+
];
|
|
1050
|
+
for (const column of required) {
|
|
1051
|
+
if (columnExists(db, "session_summaries", column))
|
|
1052
|
+
continue;
|
|
1053
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
1054
|
+
}
|
|
1055
|
+
const current = getSchemaVersion(db);
|
|
1056
|
+
if (current < 12) {
|
|
1057
|
+
db.exec("PRAGMA user_version = 12");
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function getSchemaVersion(db) {
|
|
1061
|
+
const result = db.query("PRAGMA user_version").get();
|
|
1062
|
+
return result.user_version;
|
|
1063
|
+
}
|
|
1040
1064
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
1041
1065
|
|
|
1042
1066
|
// src/storage/sqlite.ts
|
|
@@ -1111,6 +1135,7 @@ class MemDatabase {
|
|
|
1111
1135
|
this.vecAvailable = this.loadVecExtension();
|
|
1112
1136
|
runMigrations(this.db);
|
|
1113
1137
|
ensureObservationTypes(this.db);
|
|
1138
|
+
ensureSessionSummaryColumns(this.db);
|
|
1114
1139
|
}
|
|
1115
1140
|
loadVecExtension() {
|
|
1116
1141
|
try {
|
|
@@ -2001,6 +2026,50 @@ function computeSessionValueSignals(observations, securityFindings = []) {
|
|
|
2001
2026
|
};
|
|
2002
2027
|
}
|
|
2003
2028
|
|
|
2029
|
+
// src/capture/session-handoff.ts
|
|
2030
|
+
function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
2031
|
+
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
2032
|
+
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
2033
|
+
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
2034
|
+
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
2035
|
+
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
2036
|
+
...parseJsonArray2(obs.files_modified),
|
|
2037
|
+
...parseJsonArray2(obs.files_read)
|
|
2038
|
+
]).filter(Boolean))].slice(0, 6);
|
|
2039
|
+
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
2040
|
+
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
2041
|
+
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
2042
|
+
if (!obs.source_tool)
|
|
2043
|
+
return acc;
|
|
2044
|
+
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
2045
|
+
return acc;
|
|
2046
|
+
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
2047
|
+
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
2048
|
+
return {
|
|
2049
|
+
prompt_count: prompts.length,
|
|
2050
|
+
tool_event_count: toolEvents.length,
|
|
2051
|
+
recent_request_prompts: recentRequestPrompts,
|
|
2052
|
+
latest_request: latestRequest,
|
|
2053
|
+
recent_tool_names: recentToolNames,
|
|
2054
|
+
recent_tool_commands: recentToolCommands,
|
|
2055
|
+
capture_state: captureState,
|
|
2056
|
+
hot_files: hotFiles,
|
|
2057
|
+
recent_outcomes: recentOutcomes,
|
|
2058
|
+
observation_source_tools: observationSourceTools,
|
|
2059
|
+
latest_observation_prompt_number: latestObservationPromptNumber
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function parseJsonArray2(value) {
|
|
2063
|
+
if (!value)
|
|
2064
|
+
return [];
|
|
2065
|
+
try {
|
|
2066
|
+
const parsed = JSON.parse(value);
|
|
2067
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
2068
|
+
} catch {
|
|
2069
|
+
return [];
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2004
2073
|
// src/sync/push.ts
|
|
2005
2074
|
function buildVectorDocument(obs, config, project) {
|
|
2006
2075
|
const parts = [obs.title];
|
|
@@ -2141,7 +2210,7 @@ async function pushOutbox(db, client, config, batchSize = 50) {
|
|
|
2141
2210
|
const doc2 = buildSummaryVectorDocument(summary, config, {
|
|
2142
2211
|
canonical_id: project2.canonical_id,
|
|
2143
2212
|
name: project2.name
|
|
2144
|
-
}, summaryObservations,
|
|
2213
|
+
}, summaryObservations, buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, summaryObservations));
|
|
2145
2214
|
batch.push({ entryId: entry.id, doc: doc2 });
|
|
2146
2215
|
continue;
|
|
2147
2216
|
}
|
|
@@ -2212,48 +2281,6 @@ function countPresentSections(summary) {
|
|
|
2212
2281
|
function extractSectionItems(section) {
|
|
2213
2282
|
return extractSummaryItems(section, 4);
|
|
2214
2283
|
}
|
|
2215
|
-
function buildSummaryCaptureContext(prompts, toolEvents, observations) {
|
|
2216
|
-
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
2217
|
-
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
2218
|
-
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
2219
|
-
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
2220
|
-
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
2221
|
-
...parseJsonArray2(obs.files_modified),
|
|
2222
|
-
...parseJsonArray2(obs.files_read)
|
|
2223
|
-
]).filter(Boolean))].slice(0, 6);
|
|
2224
|
-
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
2225
|
-
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
2226
|
-
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
2227
|
-
if (!obs.source_tool)
|
|
2228
|
-
return acc;
|
|
2229
|
-
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
2230
|
-
return acc;
|
|
2231
|
-
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
2232
|
-
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
2233
|
-
return {
|
|
2234
|
-
prompt_count: prompts.length,
|
|
2235
|
-
tool_event_count: toolEvents.length,
|
|
2236
|
-
recent_request_prompts: recentRequestPrompts,
|
|
2237
|
-
latest_request: latestRequest,
|
|
2238
|
-
recent_tool_names: recentToolNames,
|
|
2239
|
-
recent_tool_commands: recentToolCommands,
|
|
2240
|
-
capture_state: captureState,
|
|
2241
|
-
hot_files: hotFiles,
|
|
2242
|
-
recent_outcomes: recentOutcomes,
|
|
2243
|
-
observation_source_tools: observationSourceTools,
|
|
2244
|
-
latest_observation_prompt_number: latestObservationPromptNumber
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
function parseJsonArray2(value) {
|
|
2248
|
-
if (!value)
|
|
2249
|
-
return [];
|
|
2250
|
-
try {
|
|
2251
|
-
const parsed = JSON.parse(value);
|
|
2252
|
-
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
2253
|
-
} catch {
|
|
2254
|
-
return [];
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
2284
|
|
|
2258
2285
|
// src/embeddings/embedder.ts
|
|
2259
2286
|
var _available = null;
|
|
@@ -2587,7 +2614,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
2587
2614
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
2588
2615
|
risk_score: riskScore,
|
|
2589
2616
|
stacks_detected: stacks,
|
|
2590
|
-
client_version: "0.4.
|
|
2617
|
+
client_version: "0.4.22",
|
|
2591
2618
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
2592
2619
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
2593
2620
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -3623,7 +3650,7 @@ async function main() {
|
|
|
3623
3650
|
const assistantSections = extractAssistantSummarySections(event.last_assistant_message);
|
|
3624
3651
|
const summary = mergeSessionSummary(retrospective, assistantSections, event.session_id, session?.project_id ?? null, config.user_id) ?? mergeSessionSummary(buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message), assistantSections, event.session_id, session?.project_id ?? null, config.user_id) ?? buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message);
|
|
3625
3652
|
if (summary) {
|
|
3626
|
-
const row = db.
|
|
3653
|
+
const row = db.upsertSessionSummary(summary);
|
|
3627
3654
|
db.addToOutbox("summary", row.id);
|
|
3628
3655
|
let securityFindings = [];
|
|
3629
3656
|
try {
|
|
@@ -3969,7 +3996,7 @@ function pickAssistantCheckpointTitle(substantiveLines, bulletLines) {
|
|
|
3969
3996
|
}
|
|
3970
3997
|
function isGenericCheckpointLine(value) {
|
|
3971
3998
|
const normalized = value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3972
|
-
return normalized === "here's where things stand:" || normalized === "here's where things stand" || normalized === "where things stand:" || normalized === "where things stand" || normalized === "current status:" || normalized === "current status" || normalized === "status update:" || normalized === "status update";
|
|
3999
|
+
return normalized === "here's where things stand:" || normalized === "here's where things stand" || normalized === "where things stand:" || normalized === "where things stand" || normalized === "current status:" || normalized === "current status" || normalized === "status update:" || normalized === "status update" || normalized.startsWith("all clean. here's a summary of what was fixed") || normalized.startsWith("all clean, here's a summary of what was fixed") || normalized.startsWith("now i have enough to give a clear, accurate assessment") || normalized.startsWith("here's the real picture") || normalized === "tl;dr:" || normalized.startsWith("tl;dr:");
|
|
3973
4000
|
}
|
|
3974
4001
|
function detectUnsavedPlans(message) {
|
|
3975
4002
|
const hints = [];
|
|
@@ -715,7 +715,7 @@ var MIGRATIONS = [
|
|
|
715
715
|
`
|
|
716
716
|
},
|
|
717
717
|
{
|
|
718
|
-
version:
|
|
718
|
+
version: 12,
|
|
719
719
|
description: "Add synced handoff metadata to session summaries",
|
|
720
720
|
sql: `
|
|
721
721
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -790,6 +790,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
790
790
|
version = Math.max(version, 10);
|
|
791
791
|
if (columnExists(db, "observations", "source_tool"))
|
|
792
792
|
version = Math.max(version, 11);
|
|
793
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
794
|
+
version = Math.max(version, 12);
|
|
795
|
+
}
|
|
793
796
|
return version;
|
|
794
797
|
}
|
|
795
798
|
function runMigrations(db) {
|
|
@@ -868,6 +871,27 @@ function ensureObservationTypes(db) {
|
|
|
868
871
|
}
|
|
869
872
|
}
|
|
870
873
|
}
|
|
874
|
+
function ensureSessionSummaryColumns(db) {
|
|
875
|
+
const required = [
|
|
876
|
+
"capture_state",
|
|
877
|
+
"recent_tool_names",
|
|
878
|
+
"hot_files",
|
|
879
|
+
"recent_outcomes"
|
|
880
|
+
];
|
|
881
|
+
for (const column of required) {
|
|
882
|
+
if (columnExists(db, "session_summaries", column))
|
|
883
|
+
continue;
|
|
884
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
885
|
+
}
|
|
886
|
+
const current = getSchemaVersion(db);
|
|
887
|
+
if (current < 12) {
|
|
888
|
+
db.exec("PRAGMA user_version = 12");
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function getSchemaVersion(db) {
|
|
892
|
+
const result = db.query("PRAGMA user_version").get();
|
|
893
|
+
return result.user_version;
|
|
894
|
+
}
|
|
871
895
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
872
896
|
|
|
873
897
|
// src/storage/sqlite.ts
|
|
@@ -1022,6 +1046,7 @@ class MemDatabase {
|
|
|
1022
1046
|
this.vecAvailable = this.loadVecExtension();
|
|
1023
1047
|
runMigrations(this.db);
|
|
1024
1048
|
ensureObservationTypes(this.db);
|
|
1049
|
+
ensureSessionSummaryColumns(this.db);
|
|
1025
1050
|
}
|
|
1026
1051
|
loadVecExtension() {
|
|
1027
1052
|
try {
|
|
@@ -1626,6 +1651,50 @@ function runHook(hookName, fn) {
|
|
|
1626
1651
|
});
|
|
1627
1652
|
}
|
|
1628
1653
|
|
|
1654
|
+
// src/capture/session-handoff.ts
|
|
1655
|
+
function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
1656
|
+
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
1657
|
+
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
1658
|
+
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
1659
|
+
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
1660
|
+
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
1661
|
+
...parseJsonArray(obs.files_modified),
|
|
1662
|
+
...parseJsonArray(obs.files_read)
|
|
1663
|
+
]).filter(Boolean))].slice(0, 6);
|
|
1664
|
+
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
1665
|
+
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
1666
|
+
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
1667
|
+
if (!obs.source_tool)
|
|
1668
|
+
return acc;
|
|
1669
|
+
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
1670
|
+
return acc;
|
|
1671
|
+
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
1672
|
+
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
1673
|
+
return {
|
|
1674
|
+
prompt_count: prompts.length,
|
|
1675
|
+
tool_event_count: toolEvents.length,
|
|
1676
|
+
recent_request_prompts: recentRequestPrompts,
|
|
1677
|
+
latest_request: latestRequest,
|
|
1678
|
+
recent_tool_names: recentToolNames,
|
|
1679
|
+
recent_tool_commands: recentToolCommands,
|
|
1680
|
+
capture_state: captureState,
|
|
1681
|
+
hot_files: hotFiles,
|
|
1682
|
+
recent_outcomes: recentOutcomes,
|
|
1683
|
+
observation_source_tools: observationSourceTools,
|
|
1684
|
+
latest_observation_prompt_number: latestObservationPromptNumber
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function parseJsonArray(value) {
|
|
1688
|
+
if (!value)
|
|
1689
|
+
return [];
|
|
1690
|
+
try {
|
|
1691
|
+
const parsed = JSON.parse(value);
|
|
1692
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1693
|
+
} catch {
|
|
1694
|
+
return [];
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1629
1698
|
// hooks/user-prompt-submit.ts
|
|
1630
1699
|
async function main() {
|
|
1631
1700
|
const event = await parseStdinJson();
|
|
@@ -1655,6 +1724,10 @@ async function main() {
|
|
|
1655
1724
|
});
|
|
1656
1725
|
const compactPrompt = event.prompt.replace(/\s+/g, " ").trim();
|
|
1657
1726
|
if (compactPrompt.length >= 8) {
|
|
1727
|
+
const sessionPrompts = db.getSessionUserPrompts(event.session_id, 20);
|
|
1728
|
+
const sessionToolEvents = db.getSessionToolEvents(event.session_id, 20);
|
|
1729
|
+
const sessionObservations = db.getObservationsBySession(event.session_id);
|
|
1730
|
+
const handoff = buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, sessionObservations);
|
|
1658
1731
|
const summary = db.upsertSessionSummary({
|
|
1659
1732
|
session_id: event.session_id,
|
|
1660
1733
|
project_id: project.id,
|
|
@@ -1663,7 +1736,11 @@ async function main() {
|
|
|
1663
1736
|
investigated: null,
|
|
1664
1737
|
learned: null,
|
|
1665
1738
|
completed: null,
|
|
1666
|
-
next_steps: null
|
|
1739
|
+
next_steps: null,
|
|
1740
|
+
capture_state: handoff.capture_state,
|
|
1741
|
+
recent_tool_names: JSON.stringify(handoff.recent_tool_names),
|
|
1742
|
+
hot_files: JSON.stringify(handoff.hot_files),
|
|
1743
|
+
recent_outcomes: JSON.stringify(handoff.recent_outcomes)
|
|
1667
1744
|
});
|
|
1668
1745
|
db.addToOutbox("summary", summary.id);
|
|
1669
1746
|
}
|
package/dist/server.js
CHANGED
|
@@ -14121,7 +14121,7 @@ var MIGRATIONS = [
|
|
|
14121
14121
|
`
|
|
14122
14122
|
},
|
|
14123
14123
|
{
|
|
14124
|
-
version:
|
|
14124
|
+
version: 12,
|
|
14125
14125
|
description: "Add synced handoff metadata to session summaries",
|
|
14126
14126
|
sql: `
|
|
14127
14127
|
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
@@ -14196,6 +14196,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
14196
14196
|
version2 = Math.max(version2, 10);
|
|
14197
14197
|
if (columnExists(db, "observations", "source_tool"))
|
|
14198
14198
|
version2 = Math.max(version2, 11);
|
|
14199
|
+
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
14200
|
+
version2 = Math.max(version2, 12);
|
|
14201
|
+
}
|
|
14199
14202
|
return version2;
|
|
14200
14203
|
}
|
|
14201
14204
|
function runMigrations(db) {
|
|
@@ -14274,6 +14277,23 @@ function ensureObservationTypes(db) {
|
|
|
14274
14277
|
}
|
|
14275
14278
|
}
|
|
14276
14279
|
}
|
|
14280
|
+
function ensureSessionSummaryColumns(db) {
|
|
14281
|
+
const required2 = [
|
|
14282
|
+
"capture_state",
|
|
14283
|
+
"recent_tool_names",
|
|
14284
|
+
"hot_files",
|
|
14285
|
+
"recent_outcomes"
|
|
14286
|
+
];
|
|
14287
|
+
for (const column of required2) {
|
|
14288
|
+
if (columnExists(db, "session_summaries", column))
|
|
14289
|
+
continue;
|
|
14290
|
+
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
14291
|
+
}
|
|
14292
|
+
const current = getSchemaVersion(db);
|
|
14293
|
+
if (current < 12) {
|
|
14294
|
+
db.exec("PRAGMA user_version = 12");
|
|
14295
|
+
}
|
|
14296
|
+
}
|
|
14277
14297
|
function getSchemaVersion(db) {
|
|
14278
14298
|
const result = db.query("PRAGMA user_version").get();
|
|
14279
14299
|
return result.user_version;
|
|
@@ -14432,6 +14452,7 @@ class MemDatabase {
|
|
|
14432
14452
|
this.vecAvailable = this.loadVecExtension();
|
|
14433
14453
|
runMigrations(this.db);
|
|
14434
14454
|
ensureObservationTypes(this.db);
|
|
14455
|
+
ensureSessionSummaryColumns(this.db);
|
|
14435
14456
|
}
|
|
14436
14457
|
loadVecExtension() {
|
|
14437
14458
|
try {
|
|
@@ -18712,6 +18733,50 @@ class VectorApiError extends Error {
|
|
|
18712
18733
|
}
|
|
18713
18734
|
}
|
|
18714
18735
|
|
|
18736
|
+
// src/capture/session-handoff.ts
|
|
18737
|
+
function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
18738
|
+
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
18739
|
+
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
18740
|
+
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
18741
|
+
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
18742
|
+
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
18743
|
+
...parseJsonArray3(obs.files_modified),
|
|
18744
|
+
...parseJsonArray3(obs.files_read)
|
|
18745
|
+
]).filter(Boolean))].slice(0, 6);
|
|
18746
|
+
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
18747
|
+
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
18748
|
+
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
18749
|
+
if (!obs.source_tool)
|
|
18750
|
+
return acc;
|
|
18751
|
+
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
18752
|
+
return acc;
|
|
18753
|
+
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
18754
|
+
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
18755
|
+
return {
|
|
18756
|
+
prompt_count: prompts.length,
|
|
18757
|
+
tool_event_count: toolEvents.length,
|
|
18758
|
+
recent_request_prompts: recentRequestPrompts,
|
|
18759
|
+
latest_request: latestRequest,
|
|
18760
|
+
recent_tool_names: recentToolNames,
|
|
18761
|
+
recent_tool_commands: recentToolCommands,
|
|
18762
|
+
capture_state: captureState,
|
|
18763
|
+
hot_files: hotFiles,
|
|
18764
|
+
recent_outcomes: recentOutcomes,
|
|
18765
|
+
observation_source_tools: observationSourceTools,
|
|
18766
|
+
latest_observation_prompt_number: latestObservationPromptNumber
|
|
18767
|
+
};
|
|
18768
|
+
}
|
|
18769
|
+
function parseJsonArray3(value) {
|
|
18770
|
+
if (!value)
|
|
18771
|
+
return [];
|
|
18772
|
+
try {
|
|
18773
|
+
const parsed = JSON.parse(value);
|
|
18774
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
18775
|
+
} catch {
|
|
18776
|
+
return [];
|
|
18777
|
+
}
|
|
18778
|
+
}
|
|
18779
|
+
|
|
18715
18780
|
// src/sync/push.ts
|
|
18716
18781
|
function buildVectorDocument(obs, config2, project) {
|
|
18717
18782
|
const parts = [obs.title];
|
|
@@ -18852,7 +18917,7 @@ async function pushOutbox(db, client, config2, batchSize = 50) {
|
|
|
18852
18917
|
const doc3 = buildSummaryVectorDocument(summary, config2, {
|
|
18853
18918
|
canonical_id: project2.canonical_id,
|
|
18854
18919
|
name: project2.name
|
|
18855
|
-
}, summaryObservations,
|
|
18920
|
+
}, summaryObservations, buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, summaryObservations));
|
|
18856
18921
|
batch.push({ entryId: entry.id, doc: doc3 });
|
|
18857
18922
|
continue;
|
|
18858
18923
|
}
|
|
@@ -18923,48 +18988,6 @@ function countPresentSections2(summary) {
|
|
|
18923
18988
|
function extractSectionItems2(section) {
|
|
18924
18989
|
return extractSummaryItems(section, 4);
|
|
18925
18990
|
}
|
|
18926
|
-
function buildSummaryCaptureContext(prompts, toolEvents, observations) {
|
|
18927
|
-
const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
|
|
18928
|
-
const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
|
|
18929
|
-
const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
|
|
18930
|
-
const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
|
|
18931
|
-
const hotFiles = [...new Set(observations.flatMap((obs) => [
|
|
18932
|
-
...parseJsonArray3(obs.files_modified),
|
|
18933
|
-
...parseJsonArray3(obs.files_read)
|
|
18934
|
-
]).filter(Boolean))].slice(0, 6);
|
|
18935
|
-
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
|
|
18936
|
-
const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
|
|
18937
|
-
const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
|
|
18938
|
-
if (!obs.source_tool)
|
|
18939
|
-
return acc;
|
|
18940
|
-
acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
|
|
18941
|
-
return acc;
|
|
18942
|
-
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
18943
|
-
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
18944
|
-
return {
|
|
18945
|
-
prompt_count: prompts.length,
|
|
18946
|
-
tool_event_count: toolEvents.length,
|
|
18947
|
-
recent_request_prompts: recentRequestPrompts,
|
|
18948
|
-
latest_request: latestRequest,
|
|
18949
|
-
recent_tool_names: recentToolNames,
|
|
18950
|
-
recent_tool_commands: recentToolCommands,
|
|
18951
|
-
capture_state: captureState,
|
|
18952
|
-
hot_files: hotFiles,
|
|
18953
|
-
recent_outcomes: recentOutcomes,
|
|
18954
|
-
observation_source_tools: observationSourceTools,
|
|
18955
|
-
latest_observation_prompt_number: latestObservationPromptNumber
|
|
18956
|
-
};
|
|
18957
|
-
}
|
|
18958
|
-
function parseJsonArray3(value) {
|
|
18959
|
-
if (!value)
|
|
18960
|
-
return [];
|
|
18961
|
-
try {
|
|
18962
|
-
const parsed = JSON.parse(value);
|
|
18963
|
-
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
18964
|
-
} catch {
|
|
18965
|
-
return [];
|
|
18966
|
-
}
|
|
18967
|
-
}
|
|
18968
18991
|
|
|
18969
18992
|
// src/sync/pull.ts
|
|
18970
18993
|
var PULL_CURSOR_KEY = "pull_cursor";
|
|
@@ -19886,7 +19909,7 @@ process.on("SIGTERM", () => {
|
|
|
19886
19909
|
});
|
|
19887
19910
|
var server = new McpServer({
|
|
19888
19911
|
name: "engrm",
|
|
19889
|
-
version: "0.4.
|
|
19912
|
+
version: "0.4.22"
|
|
19890
19913
|
});
|
|
19891
19914
|
server.tool("save_observation", "Save an observation to memory", {
|
|
19892
19915
|
type: exports_external.enum([
|