@code-fixer-23/pi-session-manager 1.0.4 → 1.0.6

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 CHANGED
@@ -1,3 +1,24 @@
1
+ ## 1.0.6 (2026-07-14)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - **pi-session-manager:** display README asset ([4628004](https://github.com/louiss0/pi-packages/commit/4628004))
6
+ - **pi-session-manager:** respect empty title cancellation ([37729af](https://github.com/louiss0/pi-packages/commit/37729af))
7
+
8
+ ### ❤️ Thank You
9
+
10
+ - louiss0 @louiss0
11
+
12
+ ## 1.0.5 (2026-07-08)
13
+
14
+ ### 🩹 Fixes
15
+
16
+ - prompt parsing and session-series continuation ([#6](https://github.com/louiss0/pi-packages/pull/6))
17
+
18
+ ### ❤️ Thank You
19
+
20
+ - louiss0 @louiss0
21
+
1
22
  ## 1.0.4 (2026-07-04)
2
23
 
3
24
  ### 🩹 Fixes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-session-manager
2
2
 
3
- [!session-manager](https://raw.githubusercontent.com/louiss0/pi-packages/main/pi-session-manager/assets/Pi-Session-Manager-Big.png)
3
+ ![Session Manager](https://raw.githubusercontent.com/louiss0/pi-packages/main/pi-session-manager/assets/Pi-Session_Manager-Big.png)
4
4
 
5
5
 
6
6
  [![npm version](https://img.shields.io/npm/v/@code-fixer-23/pi-session-manager.svg)](https://www.npmjs.com/package/@code-fixer-23/pi-session-manager)
@@ -11,6 +11,7 @@ import {
11
11
  handleSessionCleanOlderThan,
12
12
  handleSessionDeleteLast,
13
13
  handleSessionSeries,
14
+ getSessionEntryWithSeries,
14
15
  getSessionSeriesDataTempPath,
15
16
  persistSessionSeriesData,
16
17
  consumePersistedSessionSeriesData,
@@ -254,6 +255,7 @@ describe("persisted session series data", () => {
254
255
  entry: {
255
256
  customType: sessionSeriesEntrySchema.entries.customType.literal,
256
257
  series: "Implement Auth",
258
+ sessionTitle: "Create JWT Token",
257
259
  createdAt: new Date().toISOString(),
258
260
  },
259
261
  };
@@ -279,6 +281,7 @@ describe("persisted session series data", () => {
279
281
  expect(pi.setSessionName).toHaveBeenCalledWith(sessionData.sessionName);
280
282
  expect(pi.appendEntry).toHaveBeenCalledWith(sessionData.entry.customType, {
281
283
  series: sessionData.entry.series,
284
+ sessionTitle: sessionData.entry.sessionTitle,
282
285
  createdAt: sessionData.entry.createdAt,
283
286
  });
284
287
  expect(ctx.ui.notify).toHaveBeenCalledWith(
@@ -288,6 +291,82 @@ describe("persisted session series data", () => {
288
291
  });
289
292
  });
290
293
 
294
+ describe("getSessionEntryWithSeries", () => {
295
+ it("only returns the series entry that matches the current session name", () => {
296
+ const createdAt = new Date().toISOString();
297
+ const lspCoverageEntry = {
298
+ type: "custom",
299
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
300
+ data: {
301
+ series: "LSP Coverage",
302
+ sessionTitle: "Add diagnostics",
303
+ createdAt,
304
+ },
305
+ } as SessionSeriesEntry;
306
+ const authEntry = {
307
+ type: "custom",
308
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
309
+ data: {
310
+ series: "Auth Cleanup",
311
+ sessionTitle: "Fix token refresh",
312
+ createdAt,
313
+ },
314
+ } as SessionSeriesEntry;
315
+
316
+ expect(
317
+ getSessionEntryWithSeries(
318
+ [lspCoverageEntry, authEntry],
319
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
320
+ ),
321
+ ).toBe(authEntry);
322
+ });
323
+
324
+ it("does not return another session's series entry", () => {
325
+ const createdAt = new Date().toISOString();
326
+ const lspCoverageEntry = {
327
+ type: "custom",
328
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
329
+ data: {
330
+ series: "LSP Coverage",
331
+ sessionTitle: "Add diagnostics",
332
+ createdAt,
333
+ },
334
+ } as SessionSeriesEntry;
335
+
336
+ expect(
337
+ getSessionEntryWithSeries(
338
+ [lspCoverageEntry],
339
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
340
+ ),
341
+ ).toBeUndefined();
342
+ });
343
+
344
+ it("falls back to the session name when legacy series entries are missing sessionTitle", () => {
345
+ const createdAt = new Date().toISOString();
346
+ const legacyEntry = {
347
+ type: "custom",
348
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
349
+ data: {
350
+ series: "Auth Cleanup",
351
+ createdAt,
352
+ },
353
+ } as unknown as SessionSeriesEntry;
354
+
355
+ expect(
356
+ getSessionEntryWithSeries(
357
+ [legacyEntry],
358
+ `Auth Cleanup${SESION_TITLE_SEPARATOR}Fix token refresh`,
359
+ ),
360
+ ).toEqual({
361
+ ...legacyEntry,
362
+ data: {
363
+ ...legacyEntry.data,
364
+ sessionTitle: "Fix token refresh",
365
+ },
366
+ });
367
+ });
368
+ });
369
+
291
370
  describe("handleSessionCleanInactive", () => {
292
371
  test("gets rid of all sessions that haven't been modified in the last three days", ({
293
372
  sessions,
@@ -507,6 +586,75 @@ describe("handleSessionSeries", () => {
507
586
  );
508
587
  });
509
588
 
589
+ it("stops creating a session series when the series prompt is cancelled", async () => {
590
+ const context = {
591
+ cwd: "/pi-packages",
592
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
593
+ ui: {
594
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
595
+ input: vi
596
+ .fn<ExtensionUIContext["input"]>()
597
+ .mockResolvedValue(undefined),
598
+ },
599
+ } satisfies MockExtenstionCommandContext;
600
+
601
+ await handleSessionSeries(
602
+ "create",
603
+ {
604
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock(),
605
+ sessionFilter: new MockSessionFilter(
606
+ [],
607
+ new MockPastTimestampCalculator(),
608
+ ),
609
+ getSessionEntryWithSeries() {
610
+ return undefined;
611
+ },
612
+ removeSessionFiles() {
613
+ return;
614
+ },
615
+ },
616
+ castToExtensionContext(context),
617
+ );
618
+
619
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
620
+ expect(context.newSession).not.toHaveBeenCalled();
621
+ });
622
+
623
+ it("stops creating a session series when the series input is empty", async () => {
624
+ const context = {
625
+ cwd: "/pi-packages",
626
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
627
+ ui: {
628
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
629
+ input: vi
630
+ .fn<ExtensionUIContext["input"]>()
631
+ .mockResolvedValueOnce("")
632
+ .mockResolvedValueOnce("Should not be prompted"),
633
+ },
634
+ } satisfies MockExtenstionCommandContext;
635
+
636
+ await handleSessionSeries(
637
+ "create",
638
+ {
639
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock(),
640
+ sessionFilter: new MockSessionFilter(
641
+ [],
642
+ new MockPastTimestampCalculator(),
643
+ ),
644
+ getSessionEntryWithSeries() {
645
+ return undefined;
646
+ },
647
+ removeSessionFiles() {
648
+ return;
649
+ },
650
+ },
651
+ castToExtensionContext(context),
652
+ );
653
+
654
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
655
+ expect(context.newSession).not.toHaveBeenCalled();
656
+ });
657
+
510
658
  const seriesInput = "Implement Auth";
511
659
  it("keeps asking for a unique trimmed session series when creating", async () => {
512
660
  const sessionCtx = {
@@ -528,6 +676,7 @@ describe("handleSessionSeries", () => {
528
676
  notify: vi.fn<ExtensionUIContext["notify"]>(),
529
677
  input: vi
530
678
  .fn<ExtensionUIContext["input"]>()
679
+ .mockResolvedValueOnce(" ")
531
680
  .mockResolvedValueOnce(" Implement Auth ")
532
681
  .mockResolvedValueOnce(" Implement Billing ")
533
682
  .mockResolvedValueOnce(" Create JWT Token "),
@@ -581,7 +730,7 @@ describe("handleSessionSeries", () => {
581
730
  "warning",
582
731
  );
583
732
 
584
- expect(context.ui.input).toHaveBeenCalledTimes(3);
733
+ expect(context.ui.input).toHaveBeenCalledTimes(4);
585
734
 
586
735
  expect(appendSessionSeriesBasedOnCwdSpy).toHaveBeenCalledWith(
587
736
  sessionCtx.cwd,
@@ -834,6 +983,50 @@ describe("handleSessionSeries", () => {
834
983
  );
835
984
  });
836
985
 
986
+ it("stops creating a new session in a series when the title prompt is cancelled", async () => {
987
+ const selectedSeries = "refactor-auth-middleware";
988
+ const context = {
989
+ cwd: "/user/work/cancel-new-title",
990
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
991
+ ui: {
992
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
993
+ input: vi
994
+ .fn<ExtensionUIContext["input"]>()
995
+ .mockResolvedValue(undefined),
996
+ select: vi
997
+ .fn<ExtensionUIContext["select"]>()
998
+ .mockResolvedValue(selectedSeries),
999
+ },
1000
+ } satisfies MockExtenstionCommandContext;
1001
+
1002
+ await handleSessionSeries(
1003
+ "new",
1004
+ {
1005
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock({
1006
+ seriesRecord: {
1007
+ [context.cwd]: {
1008
+ [selectedSeries]: [],
1009
+ },
1010
+ },
1011
+ }),
1012
+ sessionFilter: new MockSessionFilter(
1013
+ [],
1014
+ new MockPastTimestampCalculator(),
1015
+ ),
1016
+ getSessionEntryWithSeries() {
1017
+ return undefined;
1018
+ },
1019
+ removeSessionFiles() {
1020
+ return;
1021
+ },
1022
+ },
1023
+ castToExtensionContext(context),
1024
+ );
1025
+
1026
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
1027
+ expect(context.newSession).not.toHaveBeenCalled();
1028
+ });
1029
+
837
1030
  it("keeps asking for a unique trimmed title when creating a new session in a series", async () => {
838
1031
  const selectedSeries = "refactor-auth-middleware";
839
1032
 
@@ -922,6 +1115,55 @@ describe("handleSessionSeries", () => {
922
1115
  );
923
1116
  });
924
1117
 
1118
+ it("stops continuing a session in a series when the title prompt is cancelled", async () => {
1119
+ const context = {
1120
+ cwd: "/user/work/cancel-continue-title",
1121
+ sessionManager: {
1122
+ getEntries:
1123
+ vi.fn<ExtensionCommandContext["sessionManager"]["getEntries"]>(),
1124
+ getSessionName: vi
1125
+ .fn<ExtensionCommandContext["sessionManager"]["getSessionName"]>()
1126
+ .mockReturnValue("refactor-auth-middleware--current-task"),
1127
+ },
1128
+ newSession: vi.fn<ExtensionCommandContext["newSession"]>(),
1129
+ ui: {
1130
+ notify: vi.fn<ExtensionUIContext["notify"]>(),
1131
+ input: vi
1132
+ .fn<ExtensionUIContext["input"]>()
1133
+ .mockResolvedValue(undefined),
1134
+ },
1135
+ } satisfies MockExtenstionCommandContext;
1136
+
1137
+ await handleSessionSeries(
1138
+ "continue",
1139
+ {
1140
+ sessionFilter: new MockSessionFilter(
1141
+ [],
1142
+ new MockPastTimestampCalculator(),
1143
+ ),
1144
+ getSessionEntryWithSeries() {
1145
+ return {
1146
+ type: "custom",
1147
+ customType: sessionSeriesEntrySchema.entries.customType.literal,
1148
+ data: {
1149
+ series: "refactor-auth-middleware",
1150
+ sessionTitle: "current-task",
1151
+ createdAt: new Date().toISOString(),
1152
+ },
1153
+ } as SessionSeriesEntry;
1154
+ },
1155
+ sessionManagerConfigurator: new SessionManagerConfiguratorMock(),
1156
+ removeSessionFiles() {
1157
+ return;
1158
+ },
1159
+ },
1160
+ castToExtensionContext(context),
1161
+ );
1162
+
1163
+ expect(context.ui.input).toHaveBeenCalledTimes(1);
1164
+ expect(context.newSession).not.toHaveBeenCalled();
1165
+ });
1166
+
925
1167
  it("continues a session in a series when continue is passed", async () => {
926
1168
  const sessionCtx = {
927
1169
  cwd: "/session/continue",
@@ -935,6 +1177,9 @@ describe("handleSessionSeries", () => {
935
1177
  sessionManager: {
936
1178
  getEntries:
937
1179
  vi.fn<ExtensionCommandContext["sessionManager"]["getEntries"]>(),
1180
+ getSessionName: vi
1181
+ .fn<ExtensionCommandContext["sessionManager"]["getSessionName"]>()
1182
+ .mockReturnValue("refactor-auth-middleware--current-task"),
938
1183
  },
939
1184
  newSession: vi.fn<ExtensionCommandContext["newSession"]>(
940
1185
  async (options) => {
@@ -957,6 +1202,7 @@ describe("handleSessionSeries", () => {
957
1202
  customType: sessionSeriesEntrySchema.entries.customType.literal,
958
1203
  data: {
959
1204
  series: "refactor-auth-middleware",
1205
+ sessionTitle: "current-task",
960
1206
  createdAt: new Date().toISOString(),
961
1207
  },
962
1208
  } as SessionSeriesEntry;
@@ -1010,6 +1256,7 @@ describe("handleSessionSeries", () => {
1010
1256
 
1011
1257
  expect(getSessionEntryWithSeries).toHaveBeenCalledWith(
1012
1258
  context.sessionManager.getEntries.mock.results[0]?.value,
1259
+ context.sessionManager.getSessionName(),
1013
1260
  );
1014
1261
 
1015
1262
  const entry = getSessionEntryWithSeries.mock.results[0]