@remnic/core 9.69.29 → 9.69.31

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.
Files changed (97) hide show
  1. package/dist/access-admin-ops-surface.js +4 -4
  2. package/dist/access-authorization-probe.js +5 -5
  3. package/dist/access-boundary.js +4 -4
  4. package/dist/access-cli.js +29 -29
  5. package/dist/access-extraction-force-flush.js +4 -4
  6. package/dist/access-http-briefing.js +5 -5
  7. package/dist/access-http-query.js +4 -4
  8. package/dist/access-http.js +16 -16
  9. package/dist/access-identity-continuity-surface.js +4 -4
  10. package/dist/access-lcm-surface.js +4 -4
  11. package/dist/access-mcp.js +11 -11
  12. package/dist/access-namespace-preflight.js +4 -4
  13. package/dist/access-observe-write-surface.js +4 -4
  14. package/dist/access-operations-batch.js +5 -5
  15. package/dist/access-operations.js +10 -10
  16. package/dist/access-recall-concurrency.js +4 -4
  17. package/dist/access-recall-response.js +4 -4
  18. package/dist/access-recall-surface.js +4 -4
  19. package/dist/access-service.js +4 -4
  20. package/dist/access-surface-catalog.js +6 -6
  21. package/dist/active-recall.js +4 -4
  22. package/dist/buffer.js +4 -4
  23. package/dist/{chunk-2OO6TPQG.js → chunk-4UDQUPT5.js} +2 -2
  24. package/dist/{chunk-XWSMJXIG.js → chunk-5ABN4WVL.js} +9 -9
  25. package/dist/{chunk-UFDMVGUQ.js → chunk-666OOKB7.js} +2 -2
  26. package/dist/{chunk-K5DSKAVF.js → chunk-7DUFJAOE.js} +3 -3
  27. package/dist/{chunk-L5JP37BO.js → chunk-ENMTJRXA.js} +2 -2
  28. package/dist/{chunk-CLALC6ZB.js → chunk-EYMOJ3J6.js} +2 -2
  29. package/dist/{chunk-MT47LH5L.js → chunk-FBMYGNOC.js} +2 -2
  30. package/dist/{chunk-Y3BGA7L5.js → chunk-IA2YSHTX.js} +8 -8
  31. package/dist/{chunk-VT4MK674.js → chunk-JDDXXZWW.js} +2 -2
  32. package/dist/{chunk-TXMAF7FZ.js → chunk-KLMESRPV.js} +2 -2
  33. package/dist/{chunk-HRICDLBE.js → chunk-LWA7MDVO.js} +2 -2
  34. package/dist/{chunk-GBYCFJGV.js → chunk-OGELWNYC.js} +5 -5
  35. package/dist/{chunk-5OOSADXP.js → chunk-OXNU333D.js} +2 -2
  36. package/dist/{chunk-FJ5B36V2.js → chunk-PEOTOFH5.js} +2 -2
  37. package/dist/{chunk-A5JPK3MZ.js → chunk-PLBW64UM.js} +12 -12
  38. package/dist/{chunk-2E3CG66I.js → chunk-QDY6KYTN.js} +2 -2
  39. package/dist/{chunk-VLLEWQIU.js → chunk-RAVRDTP7.js} +2 -2
  40. package/dist/{chunk-JJPRWTOW.js → chunk-RBGREYVJ.js} +2 -2
  41. package/dist/{chunk-GGRD3JEH.js → chunk-ROGRI25A.js} +2 -2
  42. package/dist/{chunk-XPFC6D45.js → chunk-S7DPKM5W.js} +41 -31
  43. package/dist/chunk-S7DPKM5W.js.map +1 -0
  44. package/dist/{chunk-AYAIV5V5.js → chunk-SH2ZWEWB.js} +2 -2
  45. package/dist/{chunk-J5Q2YTOB.js → chunk-SMOFBBPU.js} +2 -2
  46. package/dist/{chunk-22JQEBN3.js → chunk-SQJ3SDIW.js} +3 -3
  47. package/dist/{chunk-GAYZTUIF.js → chunk-T7CJQVRZ.js} +2 -2
  48. package/dist/{chunk-N2CAORTC.js → chunk-TEYN7ZYF.js} +2 -2
  49. package/dist/{chunk-W7SECYCQ.js → chunk-U34EHBPL.js} +2 -2
  50. package/dist/{chunk-NEFKNR4V.js → chunk-WHBVXTUI.js} +2 -2
  51. package/dist/{chunk-GL4EC4RG.js → chunk-WJBZMNNS.js} +4 -4
  52. package/dist/{chunk-GCRGBE72.js → chunk-X74UAWSI.js} +2 -2
  53. package/dist/cli.js +22 -22
  54. package/dist/config.js +4 -4
  55. package/dist/external-wiki-access.js +5 -5
  56. package/dist/extraction.js +5 -5
  57. package/dist/index.js +29 -29
  58. package/dist/location/index.js +5 -5
  59. package/dist/operator-toolkit.js +5 -5
  60. package/dist/orchestration/maintenance.js +6 -6
  61. package/dist/orchestrator.js +29 -29
  62. package/dist/resume-bundles.js +5 -5
  63. package/dist/source-agent-qualifier.js +4 -4
  64. package/dist/standup/build.js +2 -2
  65. package/dist/support-passport/index.js +7 -7
  66. package/package.json +2 -2
  67. package/src/activity/digest.test.ts +107 -1
  68. package/src/activity/digest.ts +76 -51
  69. package/dist/chunk-XPFC6D45.js.map +0 -1
  70. /package/dist/{chunk-2OO6TPQG.js.map → chunk-4UDQUPT5.js.map} +0 -0
  71. /package/dist/{chunk-XWSMJXIG.js.map → chunk-5ABN4WVL.js.map} +0 -0
  72. /package/dist/{chunk-UFDMVGUQ.js.map → chunk-666OOKB7.js.map} +0 -0
  73. /package/dist/{chunk-K5DSKAVF.js.map → chunk-7DUFJAOE.js.map} +0 -0
  74. /package/dist/{chunk-L5JP37BO.js.map → chunk-ENMTJRXA.js.map} +0 -0
  75. /package/dist/{chunk-CLALC6ZB.js.map → chunk-EYMOJ3J6.js.map} +0 -0
  76. /package/dist/{chunk-MT47LH5L.js.map → chunk-FBMYGNOC.js.map} +0 -0
  77. /package/dist/{chunk-Y3BGA7L5.js.map → chunk-IA2YSHTX.js.map} +0 -0
  78. /package/dist/{chunk-VT4MK674.js.map → chunk-JDDXXZWW.js.map} +0 -0
  79. /package/dist/{chunk-TXMAF7FZ.js.map → chunk-KLMESRPV.js.map} +0 -0
  80. /package/dist/{chunk-HRICDLBE.js.map → chunk-LWA7MDVO.js.map} +0 -0
  81. /package/dist/{chunk-GBYCFJGV.js.map → chunk-OGELWNYC.js.map} +0 -0
  82. /package/dist/{chunk-5OOSADXP.js.map → chunk-OXNU333D.js.map} +0 -0
  83. /package/dist/{chunk-FJ5B36V2.js.map → chunk-PEOTOFH5.js.map} +0 -0
  84. /package/dist/{chunk-A5JPK3MZ.js.map → chunk-PLBW64UM.js.map} +0 -0
  85. /package/dist/{chunk-2E3CG66I.js.map → chunk-QDY6KYTN.js.map} +0 -0
  86. /package/dist/{chunk-VLLEWQIU.js.map → chunk-RAVRDTP7.js.map} +0 -0
  87. /package/dist/{chunk-JJPRWTOW.js.map → chunk-RBGREYVJ.js.map} +0 -0
  88. /package/dist/{chunk-GGRD3JEH.js.map → chunk-ROGRI25A.js.map} +0 -0
  89. /package/dist/{chunk-AYAIV5V5.js.map → chunk-SH2ZWEWB.js.map} +0 -0
  90. /package/dist/{chunk-J5Q2YTOB.js.map → chunk-SMOFBBPU.js.map} +0 -0
  91. /package/dist/{chunk-22JQEBN3.js.map → chunk-SQJ3SDIW.js.map} +0 -0
  92. /package/dist/{chunk-GAYZTUIF.js.map → chunk-T7CJQVRZ.js.map} +0 -0
  93. /package/dist/{chunk-N2CAORTC.js.map → chunk-TEYN7ZYF.js.map} +0 -0
  94. /package/dist/{chunk-W7SECYCQ.js.map → chunk-U34EHBPL.js.map} +0 -0
  95. /package/dist/{chunk-NEFKNR4V.js.map → chunk-WHBVXTUI.js.map} +0 -0
  96. /package/dist/{chunk-GL4EC4RG.js.map → chunk-WJBZMNNS.js.map} +0 -0
  97. /package/dist/{chunk-GCRGBE72.js.map → chunk-X74UAWSI.js.map} +0 -0
@@ -106,7 +106,12 @@ test("isValidActivityDate rejects impossible and malformed calendar days", () =>
106
106
  assert.equal(isValidActivityDate("2026-00-10"), false);
107
107
  assert.equal(isValidActivityDate("not-a-date"), false);
108
108
  assert.equal(isValidActivityDate("2026-3-10"), false); // wrong shape
109
- });
109
+ // Astronomical year zero: Intl formats it as 1 BC, so day windows would
110
+ // pad it to 0001 and land on the wrong boundary. Validation rejects it.
111
+ assert.equal(isValidActivityDate("0000-01-01"), false);
112
+ assert.equal(isValidActivityDate("0001-01-01"), true); // first supported year
113
+ assert.equal(isValidActivityDate("0999-01-01"), true); // unpadded Intl year, still correct
114
+ });
110
115
 
111
116
  test("activityDigestPath rejects path-traversal / invalid dates", () => {
112
117
  assert.equal(activityDigestPath("/mem", "2026-03-10"), "/mem/activity/2026-03-10.md");
@@ -117,6 +122,13 @@ test("activityDigestPath rejects path-traversal / invalid dates", () => {
117
122
  test("activityDayWindow rejects an impossible date", () => {
118
123
  assert.throws(() => activityDayWindow("2026-02-30", "UTC"), RangeError);
119
124
  assert.throws(() => activityDayWindow("../evil", "UTC"), RangeError);
125
+ assert.throws(() => activityDayWindow("0000-01-01", "UTC"), RangeError);
126
+ });
127
+
128
+ test("activityDayWindow keeps year 0001 boundaries correct", () => {
129
+ const w = activityDayWindow("0001-01-01", "UTC");
130
+ assert.equal(w.startUtc, "0001-01-01T00:00:00.000Z");
131
+ assert.equal(w.endUtc, "0001-01-02T00:00:00.000Z");
120
132
  });
121
133
 
122
134
  test("parseActivityDigest rejects a non-numeric snapshotCount/formatVersion", () => {
@@ -196,6 +208,100 @@ test("activityDayWindow gives an entirely skipped civil date no interval", () =>
196
208
  assert.notDeepEqual(w, dec31);
197
209
  });
198
210
 
211
+ // Read the local wall clock and offset directly through Intl so these
212
+ // historical-LMT tests derive every expectation from the runtime's own tzdata
213
+ // instead of hard-coding one tzdata release's seconds: ICU bundles differ
214
+ // (Asia/Manila's 1844 LMT is −15:56:08 on recent tzdata, −15:56:00 on older
215
+ // ones), and a hard-coded instant fails the bundle that lacks it.
216
+ function localStampAt(ms: number, timezone: string): string {
217
+ const parts = new Intl.DateTimeFormat("en-CA", {
218
+ timeZone: timezone,
219
+ year: "numeric",
220
+ month: "2-digit",
221
+ day: "2-digit",
222
+ hour: "2-digit",
223
+ minute: "2-digit",
224
+ second: "2-digit",
225
+ hourCycle: "h23",
226
+ }).formatToParts(new Date(ms));
227
+ const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? "";
228
+ return `${get("year").padStart(4, "0")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}`;
229
+ }
230
+
231
+ function offsetMsAt(ms: number, timezone: string): number {
232
+ return Date.parse(`${localStampAt(ms, timezone)}Z`) - ms;
233
+ }
234
+
235
+ test("activityDayWindow resolves second-granularity transitions exactly (Asia/Manila 1844)", () => {
236
+ // Manila's 1844 date-line change carried seconds where tzdata records them
237
+ // (LMT −15:56:08 on recent ICU bundles): the wall clock jumped from
238
+ // 1844-12-31T00:00:00 straight to 1845-01-01, so civil 1844-12-31 never
239
+ // existed and no midnight of it occurred. Offsets with a seconds component
240
+ // must not truncate to whole minutes: pre-fix, the skipped date owned a
241
+ // 60000 ms window starting locally on 1844-12-30.
242
+ const tz = "Asia/Manila";
243
+ const lmt = offsetMsAt(Date.parse("1844-12-15T12:00:00Z"), tz); // LMT in force mid-December
244
+ const after = offsetMsAt(Date.parse("1845-06-01T12:00:00Z"), tz); // post-change LMT in force mid-1845
245
+ const iso = (ms: number): string => new Date(ms).toISOString();
246
+ const skip = activityDayWindow("1844-12-31", tz);
247
+ assert.equal(skip.endUtc, skip.startUtc); // zero-width: the day never existed, syncs nothing
248
+ assert.equal(skip.startUtc, iso(Date.parse("1844-12-31T00:00:00Z") - lmt)); // the transition instant this tzdata carries
249
+ const before = activityDayWindow("1844-12-30", tz);
250
+ assert.equal(before.startUtc, iso(Date.parse("1844-12-30T00:00:00Z") - lmt)); // exact local midnight at the seconds-carrying LMT
251
+ assert.equal(Date.parse(before.endUtc) - Date.parse(before.startUtc), 24 * 3_600_000); // one LMT offset the whole day
252
+ assert.equal(before.endUtc, skip.startUtc); // contiguous into the skip
253
+ const next = activityDayWindow("1845-01-01", tz);
254
+ assert.equal(next.startUtc, skip.startUtc); // no interval shared with the skip
255
+ assert.equal(next.endUtc, iso(Date.parse("1845-01-02T00:00:00Z") - after));
256
+ // Consistency at the skip instant: the transition is the first instant of
257
+ // 1845-01-01, and the last pre-transition instant still belongs to Dec 30.
258
+ assert.equal(activityDateInTimezone(new Date(skip.startUtc), tz), "1845-01-01");
259
+ assert.equal(activityDateInTimezone(new Date(Date.parse(skip.startUtc) - 1), tz), "1844-12-30");
260
+ assert.equal(activityDateInTimezone(new Date(Date.parse(before.endUtc) - 1), tz), "1844-12-30");
261
+ });
262
+
263
+ test("activityDayWindow keeps the first midnight across a rollback that repeats it", () => {
264
+ // America/Cancun dropped LMT −05:47:04 for −06:00 exactly at local midnight
265
+ // 1922-01-01T00:00:00, rolling the clock back to 1921-12-31T23:47:04: local
266
+ // midnight occurred twice. The day must start at the FIRST one, not at the
267
+ // later repeated midnight under the new whole-minute offset — pre-fix, the
268
+ // minute-truncated LMT candidate failed the exact-stamp check and 12m56s of
269
+ // the day were misattributed to the prior date.
270
+ const tz = "America/Cancun";
271
+ const lmt = offsetMsAt(Date.parse("1921-12-15T12:00:00Z"), tz); // LMT in force mid-December
272
+ const after = offsetMsAt(Date.parse("1922-06-01T12:00:00Z"), tz); // whole-minute offset in force mid-1922
273
+ const iso = (ms: number): string => new Date(ms).toISOString();
274
+ const firstMidnight = Date.parse("1922-01-01T00:00:00Z") - lmt;
275
+ const repeatedMidnight = Date.parse("1922-01-01T00:00:00Z") - after;
276
+ const w = activityDayWindow("1922-01-01", tz);
277
+ // The start is an exact local midnight whichever occurrence this tzdata renders.
278
+ assert.equal(localStampAt(Date.parse(w.startUtc), tz), "1922-01-01T00:00:00");
279
+ // When this tzdata carries the seconds-exact LMT midnight (it exists as a
280
+ // real instant), it is the earlier occurrence and must win.
281
+ if (localStampAt(firstMidnight, tz) === "1922-01-01T00:00:00") {
282
+ assert.equal(w.startUtc, iso(firstMidnight));
283
+ assert.ok(Date.parse(w.startUtc) < repeatedMidnight); // not the later repeated midnight
284
+ }
285
+ assert.equal(w.endUtc, iso(Date.parse("1922-01-02T00:00:00Z") - after));
286
+ // The rolled-back stretch stays inside Jan 1, and Dec 31 keeps its own day,
287
+ // ending at the first instant whose local stamp reaches Jan 1.
288
+ const dec31 = activityDayWindow("1921-12-31", tz);
289
+ assert.equal(dec31.endUtc, w.startUtc);
290
+ assert.equal(Date.parse(dec31.endUtc) - Date.parse(dec31.startUtc), 24 * 3_600_000);
291
+ });
292
+
293
+ test("activityDayWindow pads pre-1000 years before the local-date comparison", () => {
294
+ // Intl.DateTimeFormat formats years before 1000 unpadded: the year 0999
295
+ // comes back as "999". Pre-fix, `localStamp` rejected the valid midnight
296
+ // ("999-01-01T00:00:00" ≠ "0999-01-01T00:00:00") and the fallback's
297
+ // lexicographic comparison treated "998-12-31" as ≥ "0999-01-01", so
298
+ // activityDayWindow("0999-01-01", "UTC") resolved its start to
299
+ // 0998-12-31T00:00:00.000Z — fetching and attributing an extra day.
300
+ const w = activityDayWindow("0999-01-01", "UTC");
301
+ assert.equal(w.startUtc, "0999-01-01T00:00:00.000Z");
302
+ assert.equal(w.endUtc, "0999-01-02T00:00:00.000Z");
303
+ });
304
+
199
305
  test("timeline keeps a second machine's span even with the same app/window", () => {
200
306
  const a = snap({ machine: "A", capturedAtUtc: "2026-03-10T14:00:00.000Z", app: "Chrome", windowTitle: "Doc", contentHash: "a1", text: "" });
201
307
  const b = snap({ machine: "B", capturedAtUtc: "2026-03-10T14:00:30.000Z", app: "Chrome", windowTitle: "Doc", contentHash: "b1", text: "" });
@@ -28,6 +28,10 @@ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
28
28
 
29
29
  export function isValidActivityDate(date: string): boolean {
30
30
  if (typeof date !== "string" || !DATE_PATTERN.test(date)) return false;
31
+ // Year 0000 passes the round-trip below but Intl formats it as astronomical
32
+ // year 1 BC, so zonedDayStartIso's padded year yields 0001 and day windows
33
+ // land on the wrong boundary. Reject it here, at validation.
34
+ if (date.startsWith("0000-")) return false;
31
35
  // Reject impossible calendar days (e.g. 2026-02-30, 2026-13-01): the UTC
32
36
  // round-trip must reproduce the same Y-M-D, else Date normalized an overflow.
33
37
  const parsed = new Date(`${date}T00:00:00Z`);
@@ -65,68 +69,89 @@ export function assertValidTimezone(timezone: string): void {
65
69
 
66
70
  function zonedDayStartIso(date: string, timezone: string): string {
67
71
  // The first UTC instant whose local wall-clock is this date at 00:00. Probe
68
- // several instants across the day to collect every offset in play; keep an
69
- // offset only if constructing local midnight with it lands back on that same
70
- // offset (so it is genuinely local midnight, not a wall-clock that never
71
- // occurred), then take the EARLIEST such instant the FIRST 00:00 across a
72
- // DST fall-back that repeats local midnight.
72
+ // several instants across the day to collect every offset in play, keep an
73
+ // offset only if constructing local midnight with it lands on an exact
74
+ // local midnight, then take the EARLIEST such instant the FIRST 00:00
75
+ // across a rollback that repeats local midnight (America/Cancun dropped
76
+ // LMT −05:47:04 for −06:00 exactly at local midnight 1922-01-01, so 00:00
77
+ // occurred twice; the day must start at the first one).
73
78
  // Probe the previous UTC day too: for zones east of UTC the requested date's
74
79
  // UTC instants all fall after local midnight, so the pre-transition offset
75
80
  // valid at the real local 00:00 is only observable on the prior UTC day.
81
+ const wall = new Intl.DateTimeFormat("en-CA", {
82
+ timeZone: timezone,
83
+ year: "numeric",
84
+ month: "2-digit",
85
+ day: "2-digit",
86
+ hour: "2-digit",
87
+ minute: "2-digit",
88
+ second: "2-digit",
89
+ hourCycle: "h23",
90
+ });
91
+ const localStamp = (ms: number): string => {
92
+ const parts = wall.formatToParts(new Date(ms));
93
+ const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? "";
94
+ // Intl formats pre-1000 years unpadded ("999" for 0999). Pad back to the
95
+ // four-digit form the equality and ordering comparisons below assume,
96
+ // or "998-12-31" compares greater than "0999-01-01".
97
+ const year = get("year").padStart(4, "0");
98
+ return `${year}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}`;
99
+ };
100
+ const localDate = (ms: number): string => localStamp(ms).slice(0, 10);
101
+ // Derive each probe's offset at FULL precision (seconds included) from the
102
+ // rendered wall clock, never from a longOffset string: those strings
103
+ // truncate to whole minutes while historical LMT offsets carry seconds
104
+ // (America/Cancun was −05:47:04; Asia/Manila −15:56:08). A candidate built
105
+ // from a truncated offset lands off the true midnight instant, the
106
+ // exact-stamp check below rejects it, and a midnight repeated by a
107
+ // rollback silently resolves to the later occurrence under the new
108
+ // whole-minute offset — misattributing the gap between the two.
109
+ const offsetMs = (ms: number): number | null => {
110
+ const wallAsUtc = Date.parse(`${localStamp(ms)}Z`);
111
+ return Number.isFinite(wallAsUtc) ? wallAsUtc - ms : null;
112
+ };
113
+ const midnightUtc = Date.parse(`${date}T00:00:00Z`);
76
114
  const prevDate = shiftIsoDate(date, -1);
77
- const probeOffsets = new Set(
78
- [
79
- `${prevDate}T12:00:00Z`,
80
- `${prevDate}T23:00:00Z`,
81
- `${date}T00:00:00Z`,
82
- `${date}T12:00:00Z`,
83
- `${date}T23:00:00Z`,
84
- ].map((iso) => timezoneOffsetIso(new Date(iso), timezone)),
85
- );
86
115
  let best: number | null = null;
87
- for (const offset of probeOffsets) {
88
- const candidate = Date.parse(`${date}T00:00:00${offset}`);
116
+ for (const iso of [
117
+ `${prevDate}T12:00:00Z`,
118
+ `${prevDate}T23:00:00Z`,
119
+ `${date}T00:00:00Z`,
120
+ `${date}T12:00:00Z`,
121
+ `${date}T23:00:00Z`,
122
+ ]) {
123
+ const offset = offsetMs(Date.parse(iso));
124
+ if (offset === null) continue;
125
+ const candidate = midnightUtc - offset;
89
126
  if (!Number.isFinite(candidate)) continue;
90
- // Reject an offset whose local midnight does not actually occur (the wall
91
- // clock skipped by a spring-forward): the offset in effect at the candidate
92
- // instant must be the same offset we used to build it.
93
- if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;
127
+ // Accept only an EXACT local midnight. This rejects a wall-clock midnight
128
+ // that never occurred (spring-forward at 00:00, Egypt 2026) and one whose
129
+ // instant the transition already carried past (Asia/Manila's LMT midnight
130
+ // of 1844-12-31 lands on the post-transition clock as 1845-01-01).
131
+ if (localStamp(candidate) !== `${date}T00:00:00`) continue;
94
132
  if (best === null || candidate < best) best = candidate;
95
133
  }
96
134
  if (best === null) {
97
- // Local midnight was skipped by a spring-forward at 00:00. Advance to the
98
- // first local wall-clock minute on this date that actually exists (never
99
- // backdating to a 00:00 that never occurred), scanning forward up to 3h.
100
- for (let minute = 1; minute <= 180 && best === null; minute++) {
101
- const hh = String(Math.floor(minute / 60)).padStart(2, "0");
102
- const mm = String(minute % 60).padStart(2, "0");
103
- for (const offset of probeOffsets) {
104
- const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);
105
- if (!Number.isFinite(candidate)) continue;
106
- if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;
107
- if (best === null || candidate < best) best = candidate;
108
- }
109
- }
110
- }
111
- if (best === null) {
112
- // The ENTIRE civil date was skipped — no local wall-clock time on it
113
- // exists (Pacific/Apia 2011-12-30 is the canonical case: the date-line
114
- // jump went 2011-12-29 23:59:59-10:00 → 2011-12-31 00:00:00+14:00).
115
- // Extending the never-backdate convention above, resolve the day start
116
- // to the transition instant: the earliest instant whose local calendar
117
- // date reaches `date`. Local date is monotone non-decreasing in the UTC
118
- // instant (a fall-back never crosses local midnight backwards), so
119
- // binary-search a 48h bracket at minute granularity. The bracket is
120
- // safe: `<date>T00:00Z − 24h` is always locally before `date`, and
121
- // `<date>T00:00Z + 24h` is always locally on/after it.
122
- let lo = Date.parse(`${date}T00:00:00Z`) - 24 * 3_600_000;
135
+ // No exact local midnight: either local midnight was skipped by a
136
+ // spring-forward (00:00 01:00) or the ENTIRE civil date never existed
137
+ // (Pacific/Apia 2011-12-30; Asia/Manila 1844-12-31). Extending the
138
+ // never-backdate convention, resolve the day start to the first instant
139
+ // whose local calendar date reaches `date`. Local date is monotone
140
+ // non-decreasing in the UTC instant (a fall-back never crosses local
141
+ // midnight backwards), so binary-search a 48h bracket down to the
142
+ // millisecond transitions can carry seconds (Manila's landed at
143
+ // :56:08, not on a minute boundary), so minute granularity mis-resolves
144
+ // by up to 59s and hands the skipped date a window of its own. The
145
+ // bracket is safe: `<date>T00:00Z 24h` is always locally before
146
+ // `date`, and `<date>T00:00Z + 24h` is always locally on/after it.
147
+ let lo = midnightUtc - 24 * 3_600_000;
123
148
  let hi = lo + 48 * 3_600_000;
124
- while (hi - lo > 60_000) {
125
- const mid = lo + Math.floor((hi - lo) / 2 / 60_000) * 60_000;
126
- if (activityDateInTimezone(new Date(mid), timezone) >= date) hi = mid;
127
- else lo = mid;
149
+ while (lo < hi) {
150
+ const mid = lo + Math.floor((hi - lo) / 2);
151
+ if (localDate(mid) >= date) hi = mid;
152
+ else lo = mid + 1;
128
153
  }
129
- best = hi;
154
+ best = lo;
130
155
  }
131
156
  if (best === null || !Number.isFinite(best)) {
132
157
  throw new RangeError(`activity: could not resolve a local day start for "${date}" in "${timezone}".`);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/activity/digest.ts"],"sourcesContent":["/**\n * Deterministic screen-activity day-digest renderer (issue #1899).\n *\n * Renders a day's snapshots into markdown with YAML frontmatter, placed at\n * `<memoryDir>/activity/<date>.md` — outside the memory scan roots but inside\n * the QMD collection root (searchable, never auto-recalled). No LLM: the body\n * is a pure, byte-identical function of its inputs so an unchanged day skips\n * rewrite by contentHash. Day bucketing is DST-aware and half-open\n * [start, end) (AGENTS.md §23); sort keys are total with stable tiebreakers\n * (§12).\n */\n\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\n\nimport type { ActivityDayDigest, ActivityDayMeta, ActivitySnapshot } from \"./types.js\";\n\nexport const ACTIVITY_DIGEST_FORMAT_VERSION = 1;\nexport const ACTIVITY_DIR_NAME = \"activity\";\n\n/** Attribute at most this much dwell to a single snapshot (idle gaps capped). */\nconst MAX_DWELL_MS = 15 * 60_000;\n/** Notable-excerpt caps. */\nconst NOTABLE_MAX_WINDOWS = 10;\nconst NOTABLE_EXCERPT_CHARS = 280;\n\nconst DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nexport function isValidActivityDate(date: string): boolean {\n if (typeof date !== \"string\" || !DATE_PATTERN.test(date)) return false;\n // Reject impossible calendar days (e.g. 2026-02-30, 2026-13-01): the UTC\n // round-trip must reproduce the same Y-M-D, else Date normalized an overflow.\n const parsed = new Date(`${date}T00:00:00Z`);\n return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date;\n}\n\nexport function activityDigestPath(memoryDir: string, date: string): string {\n if (!isValidActivityDate(date)) {\n // Never interpolate an unvalidated date into a filesystem path (a `../`\n // would escape <memoryDir>/activity/). Reject loudly.\n throw new RangeError(`Invalid activity date \"${date}\"; expected YYYY-MM-DD.`);\n }\n return path.join(memoryDir, ACTIVITY_DIR_NAME, `${date}.md`);\n}\n\n// ── DST-aware local-day window ──────────────────────────────────────────────\n\nexport function timezoneOffsetIso(instant: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n}\n\nexport function assertValidTimezone(timezone: string): void {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: timezone });\n } catch {\n throw new RangeError(`Invalid IANA timezone \"${timezone}\".`);\n }\n}\n\nfunction zonedDayStartIso(date: string, timezone: string): string {\n // The first UTC instant whose local wall-clock is this date at 00:00. Probe\n // several instants across the day to collect every offset in play; keep an\n // offset only if constructing local midnight with it lands back on that same\n // offset (so it is genuinely local midnight, not a wall-clock that never\n // occurred), then take the EARLIEST such instant — the FIRST 00:00 across a\n // DST fall-back that repeats local midnight.\n // Probe the previous UTC day too: for zones east of UTC the requested date's\n // UTC instants all fall after local midnight, so the pre-transition offset\n // valid at the real local 00:00 is only observable on the prior UTC day.\n const prevDate = shiftIsoDate(date, -1);\n const probeOffsets = new Set(\n [\n `${prevDate}T12:00:00Z`,\n `${prevDate}T23:00:00Z`,\n `${date}T00:00:00Z`,\n `${date}T12:00:00Z`,\n `${date}T23:00:00Z`,\n ].map((iso) => timezoneOffsetIso(new Date(iso), timezone)),\n );\n let best: number | null = null;\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T00:00:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n // Reject an offset whose local midnight does not actually occur (the wall\n // clock skipped by a spring-forward): the offset in effect at the candidate\n // instant must be the same offset we used to build it.\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n if (best === null) {\n // Local midnight was skipped by a spring-forward at 00:00. Advance to the\n // first local wall-clock minute on this date that actually exists (never\n // backdating to a 00:00 that never occurred), scanning forward up to 3h.\n for (let minute = 1; minute <= 180 && best === null; minute++) {\n const hh = String(Math.floor(minute / 60)).padStart(2, \"0\");\n const mm = String(minute % 60).padStart(2, \"0\");\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n }\n }\n if (best === null) {\n // The ENTIRE civil date was skipped — no local wall-clock time on it\n // exists (Pacific/Apia 2011-12-30 is the canonical case: the date-line\n // jump went 2011-12-29 23:59:59-10:00 → 2011-12-31 00:00:00+14:00).\n // Extending the never-backdate convention above, resolve the day start\n // to the transition instant: the earliest instant whose local calendar\n // date reaches `date`. Local date is monotone non-decreasing in the UTC\n // instant (a fall-back never crosses local midnight backwards), so\n // binary-search a 48h bracket at minute granularity. The bracket is\n // safe: `<date>T00:00Z − 24h` is always locally before `date`, and\n // `<date>T00:00Z + 24h` is always locally on/after it.\n let lo = Date.parse(`${date}T00:00:00Z`) - 24 * 3_600_000;\n let hi = lo + 48 * 3_600_000;\n while (hi - lo > 60_000) {\n const mid = lo + Math.floor((hi - lo) / 2 / 60_000) * 60_000;\n if (activityDateInTimezone(new Date(mid), timezone) >= date) hi = mid;\n else lo = mid;\n }\n best = hi;\n }\n if (best === null || !Number.isFinite(best)) {\n throw new RangeError(`activity: could not resolve a local day start for \"${date}\" in \"${timezone}\".`);\n }\n return new Date(best).toISOString();\n}\n\nfunction shiftIsoDate(date: string, days: number): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + days);\n return parsed.toISOString().slice(0, 10);\n}\n\nfunction nextIsoDate(date: string): string {\n return shiftIsoDate(date, 1);\n}\n\n/**\n * Half-open [start, end) UTC ISO bounds of a local day. A civil date the\n * zone skipped entirely owns no interval: the window is zero-width\n * `[transition, transition)`.\n */\nexport function activityDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {\n if (!isValidActivityDate(date)) {\n throw new RangeError(`Invalid activity date \"${date}\"; expected a real YYYY-MM-DD day.`);\n }\n assertValidTimezone(timezone);\n const startUtc = zonedDayStartIso(date, timezone);\n const endUtc = zonedDayStartIso(nextIsoDate(date), timezone);\n // Skipped-date semantics (Pacific/Apia 2011-12-30 class): the skipped\n // date's start and the following date's start both resolve to the\n // transition instant, so the window is naturally zero-width. Keep it\n // that way: a date that never occurred contributes to no daily total\n // (daysOverlappingWeek drops zero-width days) and a connector sync for\n // it fetches nothing. Pushing the end forward to stay non-degenerate\n // would alias the next real day's interval and double-attribute every\n // card in it to two daily totals.\n return { startUtc, endUtc };\n}\n\n/**\n * The local calendar day (YYYY-MM-DD) an instant falls in for an IANA\n * timezone — the day key `activityDayWindow` buckets on. Callers that need\n * \"today\" for activity artifacts MUST use this rather than the host-local\n * date, which drifts a day near either timezone's midnight.\n */\nexport function activityDateInTimezone(instant: Date, timezone: string): string {\n try {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(instant);\n const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n } catch {\n return instant.toISOString().slice(0, 10);\n }\n}\n\n// ── Rendering ───────────────────────────────────────────────────────────────\n\nfunction sortedByTime(snapshots: ActivitySnapshot[]): ActivitySnapshot[] {\n return [...snapshots].sort((a, b) => {\n // Compare by parsed instant, not raw string, so mixed valid ISO forms\n // (Z vs offset, varying precision) still order chronologically.\n const at = Date.parse(a.capturedAtUtc);\n const bt = Date.parse(b.capturedAtUtc);\n if (at !== bt && Number.isFinite(at) && Number.isFinite(bt)) return at < bt ? -1 : 1;\n const aid = a.id ?? 0;\n const bid = b.id ?? 0;\n if (aid < bid) return -1;\n if (aid > bid) return 1;\n // Unsaved snapshots (pre-store) both default to id 0; fall back to the\n // content hash (their dedup identity), then app/window, for a total order.\n if (a.contentHash !== b.contentHash) return a.contentHash < b.contentHash ? -1 : 1;\n if (a.app !== b.app) return a.app < b.app ? -1 : 1;\n if (a.windowTitle !== b.windowTitle) return a.windowTitle < b.windowTitle ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * Per-snapshot dwell (gap to that machine's next snapshot, capped), scoped per\n * capture machine so an interleaved snapshot from another machine can't steal\n * or truncate a snapshot's dwell (multi-machine days).\n */\nfunction computeDwell(snapshots: ActivitySnapshot[]): Map<ActivitySnapshot, number> {\n const byMachine = new Map<string, ActivitySnapshot[]>();\n for (const snapshot of snapshots) {\n const list = byMachine.get(snapshot.machine);\n if (list === undefined) byMachine.set(snapshot.machine, [snapshot]);\n else list.push(snapshot);\n }\n const dwell = new Map<ActivitySnapshot, number>();\n for (const list of byMachine.values()) {\n const ordered = sortedByTime(list);\n for (let index = 0; index < ordered.length; index++) {\n const current = ordered[index];\n if (current === undefined) continue;\n const next = ordered[index + 1];\n let value = 0;\n if (next !== undefined) {\n const delta = Date.parse(next.capturedAtUtc) - Date.parse(current.capturedAtUtc);\n if (Number.isFinite(delta) && delta > 0) value = Math.min(delta, MAX_DWELL_MS);\n }\n dwell.set(current, value);\n }\n }\n return dwell;\n}\n\nfunction formatDurationMinutes(ms: number): string {\n return `${Math.round(ms / 60_000)}m`;\n}\n\nfunction clockHhMm(iso: string, timezone: string): string {\n const ms = Date.parse(iso);\n if (!Number.isFinite(ms)) return \"??:??\";\n const parts = new Intl.DateTimeFormat(\"en-GB\", {\n timeZone: timezone,\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n }).formatToParts(new Date(ms));\n const hour = parts.find((p) => p.type === \"hour\")?.value ?? \"00\";\n const minute = parts.find((p) => p.type === \"minute\")?.value ?? \"00\";\n return `${hour}:${minute}`;\n}\n\nfunction collapseWhitespace(text: string): string {\n return text.replace(/\\s+/g, \" \").trim();\n}\n\nfunction perAppSection(ordered: ActivitySnapshot[], dwell: Map<ActivitySnapshot, number>): string {\n const totals = new Map<string, number>();\n for (const snapshot of ordered) {\n totals.set(snapshot.app, (totals.get(snapshot.app) ?? 0) + (dwell.get(snapshot) ?? 0));\n }\n const rows = [...totals.entries()].sort((a, b) => {\n if (b[1] !== a[1]) return b[1] - a[1];\n if (a[0] < b[0]) return -1;\n if (a[0] > b[0]) return 1;\n return 0;\n });\n const lines = [\"## Per-app time\", \"\"];\n if (rows.length === 0) {\n lines.push(\"_No activity recorded._\");\n } else {\n for (const [app, ms] of rows) {\n lines.push(`- ${app}: ${formatDurationMinutes(ms)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\ninterface TimelineSpan {\n startIso: string;\n machine: string;\n app: string;\n windowTitle: string;\n browserUrl?: string;\n}\n\nfunction timelineSpans(ordered: ActivitySnapshot[]): TimelineSpan[] {\n const spans: TimelineSpan[] = [];\n for (const snapshot of ordered) {\n const last = spans[spans.length - 1];\n if (\n last !== undefined &&\n last.machine === snapshot.machine &&\n last.app === snapshot.app &&\n last.windowTitle === snapshot.windowTitle &&\n last.browserUrl === snapshot.browserUrl\n ) {\n continue;\n }\n spans.push({\n startIso: snapshot.capturedAtUtc,\n machine: snapshot.machine,\n app: snapshot.app,\n windowTitle: snapshot.windowTitle,\n ...(snapshot.browserUrl !== undefined ? { browserUrl: snapshot.browserUrl } : {}),\n });\n }\n return spans;\n}\n\nfunction timelineSection(ordered: ActivitySnapshot[], timezone: string): string {\n const lines = [\"## Timeline\", \"\"];\n const spans = timelineSpans(ordered);\n if (spans.length === 0) {\n lines.push(\"_No activity recorded._\");\n return lines.join(\"\\n\");\n }\n for (const span of spans) {\n const clock = clockHhMm(span.startIso, timezone);\n const window = collapseWhitespace(span.windowTitle);\n const url = span.browserUrl !== undefined ? ` (${collapseWhitespace(span.browserUrl)})` : \"\";\n lines.push(`- [${clock}] ${span.app}${window.length > 0 ? ` — ${window}` : \"\"}${url}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction notableSection(ordered: ActivitySnapshot[], dwell: Map<ActivitySnapshot, number>): string {\n const withDwell = ordered.map((snapshot) => ({ snapshot, dwell: dwell.get(snapshot) ?? 0 }));\n const ranked = withDwell\n .filter((entry) => collapseWhitespace(entry.snapshot.text).length > 0)\n .sort((a, b) => {\n if (b.dwell !== a.dwell) return b.dwell - a.dwell;\n const at = Date.parse(a.snapshot.capturedAtUtc);\n const bt = Date.parse(b.snapshot.capturedAtUtc);\n if (at !== bt && Number.isFinite(at) && Number.isFinite(bt)) return at < bt ? -1 : 1;\n const idDelta = (a.snapshot.id ?? 0) - (b.snapshot.id ?? 0);\n if (idDelta !== 0) return idDelta;\n if (a.snapshot.contentHash !== b.snapshot.contentHash) {\n return a.snapshot.contentHash < b.snapshot.contentHash ? -1 : 1;\n }\n return 0;\n })\n .slice(0, NOTABLE_MAX_WINDOWS);\n const lines = [\"## Notable\", \"\"];\n if (ranked.length === 0) {\n lines.push(\"_No notable text captured._\");\n return lines.join(\"\\n\");\n }\n for (const { snapshot } of ranked) {\n const excerpt = collapseWhitespace(snapshot.text).slice(0, NOTABLE_EXCERPT_CHARS);\n lines.push(`- **${snapshot.app}** — ${excerpt}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function composeActivityDigestBody(\n date: string,\n timezone: string,\n snapshots: ActivitySnapshot[],\n): string {\n assertValidTimezone(timezone);\n const ordered = sortedByTime(snapshots);\n const dwell = computeDwell(ordered);\n return [\n `# Activity — ${date}`,\n \"\",\n perAppSection(ordered, dwell),\n \"\",\n timelineSection(ordered, timezone),\n \"\",\n notableSection(ordered, dwell),\n \"\",\n ].join(\"\\n\");\n}\n\nexport function hashActivityBody(body: string): string {\n return createHash(\"sha256\").update(body, \"utf8\").digest(\"hex\");\n}\n\nexport function composeActivityDigestMeta(\n date: string,\n machines: string[],\n snapshots: ActivitySnapshot[],\n body: string,\n): ActivityDayMeta {\n const uniqueMachines = [...new Set(machines)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n return {\n kind: \"activity-digest\",\n date,\n machines: uniqueMachines,\n snapshotCount: snapshots.length,\n contentHash: hashActivityBody(body),\n formatVersion: ACTIVITY_DIGEST_FORMAT_VERSION,\n };\n}\n\nexport function serializeActivityDigest(meta: ActivityDayMeta, body: string): string {\n const frontmatter = [\n \"---\",\n `kind: ${meta.kind}`,\n `date: ${meta.date}`,\n `machines: ${JSON.stringify(meta.machines)}`,\n `snapshotCount: ${meta.snapshotCount}`,\n `contentHash: ${meta.contentHash}`,\n `formatVersion: ${meta.formatVersion}`,\n \"---\",\n \"\",\n ].join(\"\\n\");\n return `${frontmatter}${body}`;\n}\n\nexport function parseActivityDigest(raw: string): ActivityDayDigest | null {\n if (typeof raw !== \"string\" || !raw.startsWith(\"---\\n\")) return null;\n const end = raw.indexOf(\"\\n---\\n\", 4);\n if (end === -1) return null;\n const frontmatter = raw.slice(4, end);\n const body = raw.slice(end + 5).replace(/^\\n/, \"\");\n const fields = new Map<string, string>();\n for (const line of frontmatter.split(\"\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx === -1) continue;\n fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());\n }\n const date = fields.get(\"date\");\n const contentHash = fields.get(\"contentHash\");\n if (date === undefined || !isValidActivityDate(date) || contentHash === undefined) return null;\n if (fields.get(\"kind\") !== \"activity-digest\") return null;\n const machines = parseMachinesList(fields.get(\"machines\") ?? \"[]\");\n const snapshotCount = parseNonNegativeInt(fields.get(\"snapshotCount\"));\n const formatVersion = parseNonNegativeInt(fields.get(\"formatVersion\"));\n if (snapshotCount === null || formatVersion === null) return null;\n return {\n meta: {\n kind: \"activity-digest\",\n date,\n machines,\n snapshotCount,\n contentHash,\n formatVersion,\n },\n body,\n };\n}\n\nfunction parseNonNegativeInt(value: string | undefined): number | null {\n if (value === undefined) return null;\n const trimmed = value.trim();\n if (!/^\\d+$/.test(trimmed)) return null;\n const parsed = Number(trimmed);\n return Number.isSafeInteger(parsed) ? parsed : null;\n}\n\nfunction parseMachinesList(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.filter((entry): entry is string => typeof entry === \"string\");\n }\n } catch {\n // Legacy unquoted inline form (`[a, b]`) — fall through to the split parse.\n }\n return raw\n .replace(/^\\[/, \"\")\n .replace(/\\]$/, \"\")\n .split(\",\")\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n}\n"],"mappings":"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","names":[]}