@rubytech/create-realagent 1.0.794 → 1.0.797
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/package.json +1 -1
- package/payload/platform/neo4j/migrations/003-person-name-eradicate.cypher +24 -0
- package/payload/platform/plugins/admin/PLUGIN.md +4 -0
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-gate.test.sh +166 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-gate.sh +147 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +9 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js +54 -18
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +4 -0
- package/payload/platform/plugins/docs/references/settings.md +6 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -0
- package/payload/platform/plugins/memory/PLUGIN.md +4 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +124 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +41 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +44 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +20 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +2 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +11 -1
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +4 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts +8 -2
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js +66 -15
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +175 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +78 -17
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +2 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +8 -6
- package/payload/platform/scripts/logs-read.sh +17 -12
- package/payload/platform/scripts/seed-neo4j.sh +43 -20
- package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
- package/payload/server/chunk-BURNRCKP.js +3405 -0
- package/payload/server/chunk-JSBRDJBE.js +30 -0
- package/payload/server/chunk-KM23Y7SY.js +9896 -0
- package/payload/server/client-pool-PV45NUTN.js +29 -0
- package/payload/server/maxy-edge.js +3 -2
- package/payload/server/neo4j-migrations-IUSBODOP.js +51 -0
- package/payload/server/public/assets/{admin-jGbRjAxV.js → admin-Cz8hUAqx.js} +60 -60
- package/payload/server/public/assets/data-BvV94XHO.js +1 -0
- package/payload/server/public/assets/graph-CBu0rtrP.js +1 -0
- package/payload/server/public/assets/page-BNM63zsb.js +50 -0
- package/payload/server/public/assets/page-DM19J3ur.js +1 -0
- package/payload/server/public/assets/useAdminFetch-iYCQ9lT0.js +1 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +116 -137
- package/payload/server/public/assets/data-BhrQjgR5.js +0 -1
- package/payload/server/public/assets/graph-Jj7seS-w.js +0 -1
- package/payload/server/public/assets/page-DIG7s5Jp.js +0 -1
- package/payload/server/public/assets/page-sZb3wcOM.js +0 -50
- package/payload/server/public/assets/share-2-BndjMKeG.js +0 -1
|
@@ -3,7 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseExport = parseExport;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
|
-
|
|
6
|
+
// Year capture is `\d{2}|\d{4}` so a single regex covers both 2-digit (legacy)
|
|
7
|
+
// and 4-digit (modern WhatsApp default) prefixes — Task 845. Exactly 2 or 4
|
|
8
|
+
// chars; 3-digit years (truncation typos, hand-edited files) are rejected as
|
|
9
|
+
// not-a-prefix and surface via parse-grammar-miss, not silently coerced into
|
|
10
|
+
// year-202-AD timestamps. Year semantics are resolved per-match in
|
|
11
|
+
// `matchTimestampPrefix` from the captured length, not from the regex shape,
|
|
12
|
+
// so mixed-year files parse natively.
|
|
13
|
+
const TIMESTAMP_PREFIX_DDMMYY = /^\[(\d{2})\/(\d{2})\/(\d{4}|\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
|
|
7
14
|
const TIMESTAMP_PREFIX_MMDDYY = TIMESTAMP_PREFIX_DDMMYY; // shape is identical; ordering differs in interpretation only
|
|
8
15
|
// System-message patterns that appear WITHOUT a `: ` sender/body separator.
|
|
9
16
|
// WhatsApp emits group-event and security-code lines as `<Sender> <verb> ...`
|
|
@@ -40,7 +47,7 @@ const MEDIA_ONLY_PATTERNS = [
|
|
|
40
47
|
/^.+attached:\s*.+$/, // alternative LRM-prefixed format on some platforms
|
|
41
48
|
];
|
|
42
49
|
function parseExport(input) {
|
|
43
|
-
const { filePath, accountId, timezone, dateFormat
|
|
50
|
+
const { filePath, accountId, timezone, dateFormat: explicitDateFormat } = input;
|
|
44
51
|
if (!accountId || !accountId.trim()) {
|
|
45
52
|
throw new Error("parse-export: accountId is required.");
|
|
46
53
|
}
|
|
@@ -56,6 +63,12 @@ function parseExport(input) {
|
|
|
56
63
|
throw new Error(`parse-export: file is empty — not a _chat.txt. file=${filePath}`);
|
|
57
64
|
}
|
|
58
65
|
const lines = text.split("\n");
|
|
66
|
+
// Auto-detect when `dateFormat` is omitted (Task 845): probe the first line
|
|
67
|
+
// that contains a timestamp prefix as DD/MM; lock DD/MM if range-valid,
|
|
68
|
+
// otherwise lock MM/DD. WhatsApp's locale is set per device, so a single
|
|
69
|
+
// file never mixes DD/MM and MM/DD — locking once from line 1 is correct.
|
|
70
|
+
// Concatenated multi-locale exports require an explicit `dateFormat`.
|
|
71
|
+
const ordering = resolveOrdering(explicitDateFormat, lines);
|
|
59
72
|
const counters = {
|
|
60
73
|
parsed: 0,
|
|
61
74
|
systemSkipped: 0,
|
|
@@ -67,7 +80,7 @@ function parseExport(input) {
|
|
|
67
80
|
const line = lines[i];
|
|
68
81
|
if (line.length === 0 && i === lines.length - 1)
|
|
69
82
|
continue; // trailing newline
|
|
70
|
-
const prefixMatch = matchTimestampPrefix(line,
|
|
83
|
+
const prefixMatch = matchTimestampPrefix(line, ordering);
|
|
71
84
|
if (prefixMatch) {
|
|
72
85
|
raw.push({
|
|
73
86
|
rawLineIndex: i + 1,
|
|
@@ -128,7 +141,13 @@ function parseExport(input) {
|
|
|
128
141
|
counters.parsed++;
|
|
129
142
|
}
|
|
130
143
|
if (parsedLines.length === 0 && counters.systemSkipped === 0 && counters.mediaSkipped === 0) {
|
|
131
|
-
|
|
144
|
+
// Task 845: include a sanitised first-line sample so the operator knows
|
|
145
|
+
// WHY the file rejected — closes the diagnostic gap that left conversation
|
|
146
|
+
// 47c6a590-0c2c-4006-9aca-6ee9ec93c95f guessing. Echoed to stderr too so
|
|
147
|
+
// server.log has a grep-able adjunct to the existing parse-failed line.
|
|
148
|
+
const sample = sampleFirstNonBlankLine(lines, 100);
|
|
149
|
+
process.stderr.write(`[whatsapp-import] parse-grammar-miss first-line="${sample}"\n`);
|
|
150
|
+
throw new Error(`parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar. parse-grammar-miss first-line="${sample}"`);
|
|
132
151
|
}
|
|
133
152
|
return {
|
|
134
153
|
conversationId,
|
|
@@ -160,37 +179,69 @@ function decodeAndNormalise(bytes) {
|
|
|
160
179
|
text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
161
180
|
return text;
|
|
162
181
|
}
|
|
163
|
-
function matchTimestampPrefix(line,
|
|
164
|
-
const re =
|
|
182
|
+
function matchTimestampPrefix(line, ordering) {
|
|
183
|
+
const re = ordering === "MMDD" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
|
|
165
184
|
const m = line.match(re);
|
|
166
185
|
if (!m)
|
|
167
186
|
return null;
|
|
168
|
-
const a = parseInt(m[1], 10); // dd or mm depending on
|
|
187
|
+
const a = parseInt(m[1], 10); // dd or mm depending on ordering
|
|
169
188
|
const b = parseInt(m[2], 10); // mm or dd
|
|
170
|
-
const
|
|
189
|
+
const yearRaw = m[3];
|
|
171
190
|
const hour = parseInt(m[4], 10);
|
|
172
191
|
const minute = parseInt(m[5], 10);
|
|
173
192
|
const second = m[6] !== undefined ? parseInt(m[6], 10) : 0;
|
|
174
193
|
const remainder = m[7] ?? "";
|
|
175
|
-
const day =
|
|
176
|
-
const month =
|
|
194
|
+
const day = ordering === "MMDD" ? b : a;
|
|
195
|
+
const month = ordering === "MMDD" ? a : b;
|
|
177
196
|
// Range-check before passing to Date.UTC — that function silently rolls
|
|
178
197
|
// over invalid components (Date.UTC(2026, 13, 1) → 2027-02-01), which
|
|
179
|
-
// would corrupt timestamps when the operator passes the wrong
|
|
198
|
+
// would corrupt timestamps when the operator passes the wrong ordering
|
|
180
199
|
// for a US-locale export. Reject as not-a-prefix; the caller retries the
|
|
181
|
-
// file with the correct
|
|
200
|
+
// file with the correct ordering or LOUD-FAILs when the file isn't a chat.
|
|
182
201
|
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
183
202
|
return null;
|
|
184
203
|
if (hour > 23 || minute > 59 || second > 59)
|
|
185
204
|
return null;
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
|
|
205
|
+
// Task 845: branch year semantics on captured length. WhatsApp's modern
|
|
206
|
+
// exports emit 4-digit years; legacy exports emit 2-digit. Both are
|
|
207
|
+
// accepted by the same regex and disambiguated here so a single file may
|
|
208
|
+
// hold both shapes (mixed-year imports parse natively).
|
|
209
|
+
const year = yearRaw.length === 2 ? 2000 + parseInt(yearRaw, 10) : parseInt(yearRaw, 10);
|
|
189
210
|
return {
|
|
190
211
|
dateParts: { year, month, day, hour, minute, second },
|
|
191
212
|
remainder,
|
|
192
213
|
};
|
|
193
214
|
}
|
|
215
|
+
function resolveOrdering(explicit, lines) {
|
|
216
|
+
if (explicit === "MM/DD/YY" || explicit === "MM/DD/YYYY")
|
|
217
|
+
return "MMDD";
|
|
218
|
+
if (explicit === "DD/MM/YY" || explicit === "DD/MM/YYYY")
|
|
219
|
+
return "DDMM";
|
|
220
|
+
// Auto-detect: probe the first prefix-matching line as DD/MM. If range-valid,
|
|
221
|
+
// lock DD/MM (WhatsApp's global default). Otherwise lock MM/DD (US-locale
|
|
222
|
+
// exports, which are the only meaningful exception). Locked once.
|
|
223
|
+
for (const line of lines) {
|
|
224
|
+
if (matchTimestampPrefix(line, "DDMM"))
|
|
225
|
+
return "DDMM";
|
|
226
|
+
if (matchTimestampPrefix(line, "MMDD"))
|
|
227
|
+
return "MMDD";
|
|
228
|
+
}
|
|
229
|
+
return "DDMM"; // No matching line — caller will throw zero-parsed-lines anyway.
|
|
230
|
+
}
|
|
231
|
+
function sampleFirstNonBlankLine(lines, maxScan) {
|
|
232
|
+
const scanLimit = Math.min(maxScan, lines.length);
|
|
233
|
+
for (let i = 0; i < scanLimit; i++) {
|
|
234
|
+
const trimmed = lines[i].trim();
|
|
235
|
+
if (trimmed.length === 0)
|
|
236
|
+
continue;
|
|
237
|
+
// Strip control characters (including tab, BEL, etc.) so the diagnostic
|
|
238
|
+
// line stays single-line and grep-friendly. Truncate to 80 chars per
|
|
239
|
+
// Task 845 brief — enough to recognise the offending header shape.
|
|
240
|
+
const sanitised = trimmed.replace(/[\x00-\x1F\x7F]/g, "");
|
|
241
|
+
return sanitised.slice(0, 80);
|
|
242
|
+
}
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
194
245
|
function findFirstColonSeparator(remainder) {
|
|
195
246
|
// Split on the FIRST `: ` (colon-space). A sender display name may itself
|
|
196
247
|
// contain a `:` (e.g. "Joel: Work"), so we anchor on the first colon
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-export.js","sourceRoot":"","sources":["../src/parse-export.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-export.js","sourceRoot":"","sources":["../src/parse-export.ts"],"names":[],"mappings":";;AAmHA,kCAuJC;AA1QD,6CAAyC;AACzC,qCAAuC;AAiEvC,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,6EAA6E;AAC7E,mEAAmE;AACnE,6EAA6E;AAC7E,sCAAsC;AACtC,MAAM,uBAAuB,GAC3B,gFAAgF,CAAC;AAEnF,MAAM,uBAAuB,GAAG,uBAAuB,CAAC,CAAC,8DAA8D;AAEvH,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,0EAA0E;AAC1E,WAAW;AACX,MAAM,0BAA0B,GAAa;IAC3C,+CAA+C;IAC/C,+BAA+B;IAC/B,sBAAsB;IACtB,SAAS;IACT,WAAW;IACX,QAAQ;IACR,4BAA4B;IAC5B,4BAA4B;IAC5B,wCAAwC;IACxC,wBAAwB;IACxB,qBAAqB;CACtB,CAAC;AAEF,2EAA2E;AAC3E,2EAA2E;AAC3E,MAAM,0BAA0B,GAAa;IAC3C,+BAA+B;IAC/B,+BAA+B;CAChC,CAAC;AAEF,MAAM,mBAAmB,GAAa;IACpC,mBAAmB;IACnB,6DAA6D;IAC7D,yCAAyC;IACzC,0CAA0C;IAC1C,0CAA0C;IAC1C,0CAA0C;IAC1C,yEAAyE;IACzE,qBAAqB,EAAE,oDAAoD;CAC5E,CAAC;AAEF,SAAgB,WAAW,CAAC,KAAuB;IACjD,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAC;IAEhF,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,sBAAY,EAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,MAAM,iBAAiB,GAAG,mBAAmB,SAAS,EAAE,CAAC;IACzD,MAAM,cAAc,GAAG,mBAAmB,SAAS,IAAI,SAAS,EAAE,CAAC;IAEnE,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,uDAAuD,QAAQ,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,sEAAsE;IACtE,MAAM,QAAQ,GAAG,eAAe,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAwB;QACpC,MAAM,EAAE,CAAC;QACT,aAAa,EAAE,CAAC;QAChB,YAAY,EAAE,CAAC;QACf,WAAW,EAAE,CAAC;KACf,CAAC;IAgBF,MAAM,GAAG,GAAiB,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS,CAAC,mBAAmB;QAC9E,MAAM,WAAW,GAAG,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,WAAW,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,CAAC;gBACP,YAAY,EAAE,CAAC,GAAG,CAAC;gBACnB,GAAG,WAAW,CAAC,SAAS;gBACxB,SAAS,EAAE,WAAW,CAAC,SAAS;aACjC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,iEAAiE;YACjE,uEAAuE;YACvE,wBAAwB;YACxB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG,IAAI,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,4EAA4E;IAC5E,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;QAC9B,MAAM,QAAQ,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;QAEpD,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACpB,qEAAqE;YACrE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,UAAU,CAAC,OAAO,EAAE,0BAA0B,CAAC,EAAE,CAAC;gBACpD,QAAQ,CAAC,aAAa,EAAE,CAAC;gBACzB,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,WAAW,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,kCAAkC,QAAQ,SAAS,CAAC,CAAC,YAAY,6CAA6C,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CACtI,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAE/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,QAAQ,CAAC,aAAa,EAAE,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,EAAE,0BAA0B,CAAC,EAAE,CAAC;YACjD,QAAQ,CAAC,aAAa,EAAE,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,EAAE,mBAAmB,CAAC,EAAE,CAAC;YAC1C,QAAQ,CAAC,YAAY,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAC5B,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,KAAK,EACP,CAAC,CAAC,GAAG,EACL,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,MAAM,EACR,CAAC,CAAC,MAAM,EACR,QAAQ,CACT,CAAC;QAEF,WAAW,CAAC,IAAI,CAAC;YACf,UAAU;YACV,QAAQ;YACR,IAAI;YACJ,aAAa,EAAE,WAAW,CAAC,MAAM;SAClC,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,aAAa,KAAK,CAAC,IAAI,QAAQ,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;QAC5F,wEAAwE;QACxE,2EAA2E;QAC3E,yEAAyE;QACzE,wEAAwE;QACxE,MAAM,MAAM,GAAG,uBAAuB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oDAAoD,MAAM,KAAK,CAChE,CAAC;QACF,MAAM,IAAI,KAAK,CACb,iDAAiD,QAAQ,kFAAkF,MAAM,GAAG,CACrJ,CAAC;IACJ,CAAC;IAED,OAAO;QACL,cAAc;QACd,iBAAiB;QACjB,WAAW;QACX,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,SAAS,kBAAkB,CAAC,KAAa;IACvC,uEAAuE;IACvE,uEAAuE;IACvE,qEAAqE;IACrE,8DAA8D;IAC9D,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,uCAAuC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,yDAAyD,CACjJ,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QAClC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAED,sCAAsC;IACtC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAExD,OAAO,IAAI,CAAC;AACd,CAAC;AAgBD,SAAS,oBAAoB,CAC3B,IAAY,EACZ,QAAkB;IAElB,MAAM,EAAE,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,uBAAuB,CAAC;IACnF,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iCAAiC;IAC/D,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW;IACzC,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,wEAAwE;IACxE,sEAAsE;IACtE,uEAAuE;IACvE,yEAAyE;IACzE,2EAA2E;IAC3E,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAChE,IAAI,IAAI,GAAG,EAAE,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IACzD,wEAAwE;IACxE,oEAAoE;IACpE,yEAAyE;IACzE,wDAAwD;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACzF,OAAO;QACL,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE;QACrD,SAAS;KACV,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,QAAwC,EACxC,KAAwB;IAExB,IAAI,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC;IACxE,IAAI,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC;IACxE,8EAA8E;IAC9E,0EAA0E;IAC1E,kEAAkE;IAClE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QACtD,IAAI,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,iEAAiE;AAClF,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAwB,EAAE,OAAe;IACxE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACnC,wEAAwE;QACxE,qEAAqE;QACrE,mEAAmE;QACnE,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;QAC1D,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,uBAAuB,CAAC,SAAiB;IAChD,0EAA0E;IAC1E,qEAAqE;IACrE,uEAAuE;IACvE,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,QAAkB;IAClD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CACpB,IAAY,EACZ,KAAa,EACb,GAAW,EACX,IAAY,EACZ,MAAc,EACd,MAAc,EACd,QAAgB;IAEhB,yEAAyE;IACzE,wEAAwE;IACxE,0EAA0E;IAC1E,mDAAmD;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxE,IAAI,MAAM,GAAG,eAAe,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;IAClD,MAAM,GAAG,eAAe,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE3D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1C,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,IAAI,KAAK,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,eAAe,CAAC,IAAU,EAAE,QAAgB;IACnD,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QACjD,QAAQ,EAAE,QAAQ;QAClB,YAAY,EAAE,YAAY;KAC3B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC;IAC5D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qDAAqD,QAAQ,IAAI,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAChE,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,+CAA+C,KAAK,oBAAoB,QAAQ,IAAI,CACrF,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9B,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,OAAO,IAAI,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -386,6 +386,181 @@ describe("parseExport — date format toggle", () => {
|
|
|
386
386
|
});
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
describe("parseExport — 4-digit year grammar (Task 845)", () => {
|
|
390
|
+
it("parses DD/MM/YYYY with explicit 4-digit dateFormat", () => {
|
|
391
|
+
// Joel→Adam Mackay export shape: WhatsApp's modern locale emits 4-digit years.
|
|
392
|
+
const filePath = writeChat(
|
|
393
|
+
"_chat.txt",
|
|
394
|
+
"[20/06/2025, 16:35:12] Joel Smalley: Hello\n",
|
|
395
|
+
);
|
|
396
|
+
const result = parseExport({
|
|
397
|
+
filePath,
|
|
398
|
+
accountId: "acct-123",
|
|
399
|
+
timezone: "Europe/London",
|
|
400
|
+
dateFormat: "DD/MM/YYYY",
|
|
401
|
+
});
|
|
402
|
+
expect(result.parsedLines).toHaveLength(1);
|
|
403
|
+
expect(result.parsedLines[0].senderName).toBe("Joel Smalley");
|
|
404
|
+
expect(result.parsedLines[0].body).toBe("Hello");
|
|
405
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("parses MM/DD/YYYY with explicit 4-digit dateFormat", () => {
|
|
409
|
+
const filePath = writeChat(
|
|
410
|
+
"_chat.txt",
|
|
411
|
+
"[06/20/2025, 16:35:12] Joel: Hello\n",
|
|
412
|
+
);
|
|
413
|
+
const result = parseExport({
|
|
414
|
+
filePath,
|
|
415
|
+
accountId: "acct-123",
|
|
416
|
+
timezone: "America/New_York",
|
|
417
|
+
dateFormat: "MM/DD/YYYY",
|
|
418
|
+
});
|
|
419
|
+
expect(result.parsedLines).toHaveLength(1);
|
|
420
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("auto-detects DD/MM/YYYY when dateFormat omitted (4-digit year, default WhatsApp locale)", () => {
|
|
424
|
+
// No explicit dateFormat; first matched line `[20/06/2025, ...]` is range-valid
|
|
425
|
+
// as DD/MM (day=20, month=6) → DD/MM lock.
|
|
426
|
+
const filePath = writeChat(
|
|
427
|
+
"_chat.txt",
|
|
428
|
+
"[20/06/2025, 16:35:12] Joel: Hello\n",
|
|
429
|
+
);
|
|
430
|
+
const result = parseExport({
|
|
431
|
+
filePath,
|
|
432
|
+
accountId: "acct-123",
|
|
433
|
+
timezone: "Europe/London",
|
|
434
|
+
});
|
|
435
|
+
expect(result.parsedLines).toHaveLength(1);
|
|
436
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T/);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("auto-detects MM/DD/YYYY when first line range-fails as DD/MM (US-locale export)", () => {
|
|
440
|
+
// `[06/20/2025, ...]` interpreted DD/MM → day=6, month=20 → range-fail.
|
|
441
|
+
// Auto-detect probes DD/MM first; falls back to MM/DD lock.
|
|
442
|
+
const filePath = writeChat(
|
|
443
|
+
"_chat.txt",
|
|
444
|
+
"[06/20/2025, 16:35:12] Joel: Hello\n",
|
|
445
|
+
);
|
|
446
|
+
const result = parseExport({
|
|
447
|
+
filePath,
|
|
448
|
+
accountId: "acct-123",
|
|
449
|
+
timezone: "America/New_York",
|
|
450
|
+
});
|
|
451
|
+
expect(result.parsedLines).toHaveLength(1);
|
|
452
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T/);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("accepts mixed 2-digit and 4-digit years in the same file (regex \\d{2,4})", () => {
|
|
456
|
+
const filePath = writeChat(
|
|
457
|
+
"_chat.txt",
|
|
458
|
+
[
|
|
459
|
+
"[20/06/25, 16:35:12] Joel: Two-digit year",
|
|
460
|
+
"[21/06/2025, 09:00:00] Joel: Four-digit year",
|
|
461
|
+
"",
|
|
462
|
+
].join("\n"),
|
|
463
|
+
);
|
|
464
|
+
const result = parseExport({
|
|
465
|
+
filePath,
|
|
466
|
+
accountId: "acct-123",
|
|
467
|
+
timezone: "Europe/London",
|
|
468
|
+
});
|
|
469
|
+
expect(result.parsedLines).toHaveLength(2);
|
|
470
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2025-06-20T16:35:12/);
|
|
471
|
+
expect(result.parsedLines[1].dateSent).toMatch(/^2025-06-21T09:00:00/);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("2-digit year still maps to 2000+yy after grammar widening (regression)", () => {
|
|
475
|
+
const filePath = writeChat(
|
|
476
|
+
"_chat.txt",
|
|
477
|
+
"[14/03/26, 10:15:23] Joel: Hello\n",
|
|
478
|
+
);
|
|
479
|
+
const result = parseExport({
|
|
480
|
+
filePath,
|
|
481
|
+
accountId: "acct-123",
|
|
482
|
+
timezone: "Europe/London",
|
|
483
|
+
});
|
|
484
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2026-03-14T/);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("4-digit year passes through unchanged (no 2000+ shift)", () => {
|
|
488
|
+
const filePath = writeChat(
|
|
489
|
+
"_chat.txt",
|
|
490
|
+
"[14/03/2099, 10:15:23] Joel: Hello\n",
|
|
491
|
+
);
|
|
492
|
+
const result = parseExport({
|
|
493
|
+
filePath,
|
|
494
|
+
accountId: "acct-123",
|
|
495
|
+
timezone: "Europe/London",
|
|
496
|
+
});
|
|
497
|
+
expect(result.parsedLines[0].dateSent).toMatch(/^2099-03-14T/);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("rejects 3-digit years (truncation typos, hand-edited files) — silent year-202-AD coercion is the failure mode this guards against", () => {
|
|
501
|
+
// Without the explicit (\d{4}|\d{2}) alternation, the greedy `\d{2,4}`
|
|
502
|
+
// would match "202" and the year-length branch would emit year 202 AD
|
|
503
|
+
// timestamps that pass downstream validation but are clearly wrong.
|
|
504
|
+
// Reject as not-a-prefix; the file falls into the parse-grammar-miss
|
|
505
|
+
// diagnostic path so the operator sees the offending header shape.
|
|
506
|
+
const filePath = writeChat(
|
|
507
|
+
"_chat.txt",
|
|
508
|
+
"[14/03/202, 10:15:23] Joel: Hello\n",
|
|
509
|
+
);
|
|
510
|
+
expect(() =>
|
|
511
|
+
parseExport({
|
|
512
|
+
filePath,
|
|
513
|
+
accountId: "acct-123",
|
|
514
|
+
timezone: "Europe/London",
|
|
515
|
+
}),
|
|
516
|
+
).toThrow(/parse-grammar-miss first-line="\[14\/03\/202, 10:15:23\] Joel: Hello"/);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
describe("parseExport — parse-grammar-miss diagnostic (Task 845)", () => {
|
|
521
|
+
it("includes a sanitised first-line sample in the thrown error when no prefix matches", () => {
|
|
522
|
+
// Junk file with no timestamp prefixes → zero-parsed-lines path.
|
|
523
|
+
// The thrown error must include the first non-blank line so the operator
|
|
524
|
+
// knows WHY the file rejected, instead of guessing as in conversation
|
|
525
|
+
// 47c6a590-0c2c-4006-9aca-6ee9ec93c95f.
|
|
526
|
+
const filePath = writeChat(
|
|
527
|
+
"_chat.txt",
|
|
528
|
+
"this is some random text\nwith no timestamp prefixes\nat all.\n",
|
|
529
|
+
);
|
|
530
|
+
expect(() =>
|
|
531
|
+
parseExport({
|
|
532
|
+
filePath,
|
|
533
|
+
accountId: "acct-123",
|
|
534
|
+
timezone: "Europe/London",
|
|
535
|
+
}),
|
|
536
|
+
).toThrow(/parse-grammar-miss first-line="this is some random text"/);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("truncates the first-line sample to 80 characters", () => {
|
|
540
|
+
const longLine = "x".repeat(200);
|
|
541
|
+
const filePath = writeChat("_chat.txt", longLine + "\n");
|
|
542
|
+
expect(() =>
|
|
543
|
+
parseExport({
|
|
544
|
+
filePath,
|
|
545
|
+
accountId: "acct-123",
|
|
546
|
+
timezone: "Europe/London",
|
|
547
|
+
}),
|
|
548
|
+
).toThrow(/parse-grammar-miss first-line="x{80}"/);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("strips control characters from the first-line sample", () => {
|
|
552
|
+
// Tab, bell, and other control chars should not leak into the diagnostic.
|
|
553
|
+
const filePath = writeChat("_chat.txt", "junk\twith\x07control\x01chars\n");
|
|
554
|
+
expect(() =>
|
|
555
|
+
parseExport({
|
|
556
|
+
filePath,
|
|
557
|
+
accountId: "acct-123",
|
|
558
|
+
timezone: "Europe/London",
|
|
559
|
+
}),
|
|
560
|
+
).toThrow(/parse-grammar-miss first-line="junkwithcontrolchars"/);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
389
564
|
describe("parseExport — LOUD-FAIL scenarios", () => {
|
|
390
565
|
it("throws when the file is empty", () => {
|
|
391
566
|
const filePath = writeChat("_chat.txt", "");
|
|
@@ -30,8 +30,14 @@ export interface ParseExportInput {
|
|
|
30
30
|
accountId: string;
|
|
31
31
|
/** IANA timezone the operator confirmed (e.g. `Europe/London`). */
|
|
32
32
|
timezone: string;
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Date ordering and year shape. Omit for auto-detect (Task 845): the parser
|
|
35
|
+
* probes the first matched line as DD/MM and locks that ordering if range-valid;
|
|
36
|
+
* otherwise locks MM/DD. Year shape is independent — `\d{2,4}` accepts 2-digit
|
|
37
|
+
* (mapped `2000+yy`) and 4-digit (passed through) years per-line, including
|
|
38
|
+
* mixed-year files.
|
|
39
|
+
*/
|
|
40
|
+
dateFormat?: "DD/MM/YY" | "MM/DD/YY" | "DD/MM/YYYY" | "MM/DD/YYYY";
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export interface ParsedLine {
|
|
@@ -58,8 +64,15 @@ export interface ParseExportResult {
|
|
|
58
64
|
counters: ParseExportCounters;
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
// Year capture is `\d{2}|\d{4}` so a single regex covers both 2-digit (legacy)
|
|
68
|
+
// and 4-digit (modern WhatsApp default) prefixes — Task 845. Exactly 2 or 4
|
|
69
|
+
// chars; 3-digit years (truncation typos, hand-edited files) are rejected as
|
|
70
|
+
// not-a-prefix and surface via parse-grammar-miss, not silently coerced into
|
|
71
|
+
// year-202-AD timestamps. Year semantics are resolved per-match in
|
|
72
|
+
// `matchTimestampPrefix` from the captured length, not from the regex shape,
|
|
73
|
+
// so mixed-year files parse natively.
|
|
61
74
|
const TIMESTAMP_PREFIX_DDMMYY =
|
|
62
|
-
/^\[(\d{2})\/(\d{2})\/(\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
|
|
75
|
+
/^\[(\d{2})\/(\d{2})\/(\d{4}|\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
|
|
63
76
|
|
|
64
77
|
const TIMESTAMP_PREFIX_MMDDYY = TIMESTAMP_PREFIX_DDMMYY; // shape is identical; ordering differs in interpretation only
|
|
65
78
|
|
|
@@ -101,7 +114,7 @@ const MEDIA_ONLY_PATTERNS: RegExp[] = [
|
|
|
101
114
|
];
|
|
102
115
|
|
|
103
116
|
export function parseExport(input: ParseExportInput): ParseExportResult {
|
|
104
|
-
const { filePath, accountId, timezone, dateFormat
|
|
117
|
+
const { filePath, accountId, timezone, dateFormat: explicitDateFormat } = input;
|
|
105
118
|
|
|
106
119
|
if (!accountId || !accountId.trim()) {
|
|
107
120
|
throw new Error("parse-export: accountId is required.");
|
|
@@ -123,6 +136,12 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
|
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
const lines = text.split("\n");
|
|
139
|
+
// Auto-detect when `dateFormat` is omitted (Task 845): probe the first line
|
|
140
|
+
// that contains a timestamp prefix as DD/MM; lock DD/MM if range-valid,
|
|
141
|
+
// otherwise lock MM/DD. WhatsApp's locale is set per device, so a single
|
|
142
|
+
// file never mixes DD/MM and MM/DD — locking once from line 1 is correct.
|
|
143
|
+
// Concatenated multi-locale exports require an explicit `dateFormat`.
|
|
144
|
+
const ordering = resolveOrdering(explicitDateFormat, lines);
|
|
126
145
|
const counters: ParseExportCounters = {
|
|
127
146
|
parsed: 0,
|
|
128
147
|
systemSkipped: 0,
|
|
@@ -149,7 +168,7 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
|
|
|
149
168
|
for (let i = 0; i < lines.length; i++) {
|
|
150
169
|
const line = lines[i];
|
|
151
170
|
if (line.length === 0 && i === lines.length - 1) continue; // trailing newline
|
|
152
|
-
const prefixMatch = matchTimestampPrefix(line,
|
|
171
|
+
const prefixMatch = matchTimestampPrefix(line, ordering);
|
|
153
172
|
if (prefixMatch) {
|
|
154
173
|
raw.push({
|
|
155
174
|
rawLineIndex: i + 1,
|
|
@@ -226,8 +245,16 @@ export function parseExport(input: ParseExportInput): ParseExportResult {
|
|
|
226
245
|
}
|
|
227
246
|
|
|
228
247
|
if (parsedLines.length === 0 && counters.systemSkipped === 0 && counters.mediaSkipped === 0) {
|
|
248
|
+
// Task 845: include a sanitised first-line sample so the operator knows
|
|
249
|
+
// WHY the file rejected — closes the diagnostic gap that left conversation
|
|
250
|
+
// 47c6a590-0c2c-4006-9aca-6ee9ec93c95f guessing. Echoed to stderr too so
|
|
251
|
+
// server.log has a grep-able adjunct to the existing parse-failed line.
|
|
252
|
+
const sample = sampleFirstNonBlankLine(lines, 100);
|
|
253
|
+
process.stderr.write(
|
|
254
|
+
`[whatsapp-import] parse-grammar-miss first-line="${sample}"\n`,
|
|
255
|
+
);
|
|
229
256
|
throw new Error(
|
|
230
|
-
`parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar
|
|
257
|
+
`parse-export: zero parsed lines after walking ${filePath} — not a _chat.txt or all lines failed grammar. parse-grammar-miss first-line="${sample}"`,
|
|
231
258
|
);
|
|
232
259
|
}
|
|
233
260
|
|
|
@@ -280,38 +307,72 @@ interface TimestampMatch {
|
|
|
280
307
|
remainder: string;
|
|
281
308
|
}
|
|
282
309
|
|
|
310
|
+
type Ordering = "DDMM" | "MMDD";
|
|
311
|
+
|
|
283
312
|
function matchTimestampPrefix(
|
|
284
313
|
line: string,
|
|
285
|
-
|
|
314
|
+
ordering: Ordering,
|
|
286
315
|
): TimestampMatch | null {
|
|
287
|
-
const re =
|
|
316
|
+
const re = ordering === "MMDD" ? TIMESTAMP_PREFIX_MMDDYY : TIMESTAMP_PREFIX_DDMMYY;
|
|
288
317
|
const m = line.match(re);
|
|
289
318
|
if (!m) return null;
|
|
290
|
-
const a = parseInt(m[1], 10); // dd or mm depending on
|
|
319
|
+
const a = parseInt(m[1], 10); // dd or mm depending on ordering
|
|
291
320
|
const b = parseInt(m[2], 10); // mm or dd
|
|
292
|
-
const
|
|
321
|
+
const yearRaw = m[3];
|
|
293
322
|
const hour = parseInt(m[4], 10);
|
|
294
323
|
const minute = parseInt(m[5], 10);
|
|
295
324
|
const second = m[6] !== undefined ? parseInt(m[6], 10) : 0;
|
|
296
325
|
const remainder = m[7] ?? "";
|
|
297
|
-
const day =
|
|
298
|
-
const month =
|
|
326
|
+
const day = ordering === "MMDD" ? b : a;
|
|
327
|
+
const month = ordering === "MMDD" ? a : b;
|
|
299
328
|
// Range-check before passing to Date.UTC — that function silently rolls
|
|
300
329
|
// over invalid components (Date.UTC(2026, 13, 1) → 2027-02-01), which
|
|
301
|
-
// would corrupt timestamps when the operator passes the wrong
|
|
330
|
+
// would corrupt timestamps when the operator passes the wrong ordering
|
|
302
331
|
// for a US-locale export. Reject as not-a-prefix; the caller retries the
|
|
303
|
-
// file with the correct
|
|
332
|
+
// file with the correct ordering or LOUD-FAILs when the file isn't a chat.
|
|
304
333
|
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
305
334
|
if (hour > 23 || minute > 59 || second > 59) return null;
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
335
|
+
// Task 845: branch year semantics on captured length. WhatsApp's modern
|
|
336
|
+
// exports emit 4-digit years; legacy exports emit 2-digit. Both are
|
|
337
|
+
// accepted by the same regex and disambiguated here so a single file may
|
|
338
|
+
// hold both shapes (mixed-year imports parse natively).
|
|
339
|
+
const year = yearRaw.length === 2 ? 2000 + parseInt(yearRaw, 10) : parseInt(yearRaw, 10);
|
|
309
340
|
return {
|
|
310
341
|
dateParts: { year, month, day, hour, minute, second },
|
|
311
342
|
remainder,
|
|
312
343
|
};
|
|
313
344
|
}
|
|
314
345
|
|
|
346
|
+
function resolveOrdering(
|
|
347
|
+
explicit: ParseExportInput["dateFormat"],
|
|
348
|
+
lines: readonly string[],
|
|
349
|
+
): Ordering {
|
|
350
|
+
if (explicit === "MM/DD/YY" || explicit === "MM/DD/YYYY") return "MMDD";
|
|
351
|
+
if (explicit === "DD/MM/YY" || explicit === "DD/MM/YYYY") return "DDMM";
|
|
352
|
+
// Auto-detect: probe the first prefix-matching line as DD/MM. If range-valid,
|
|
353
|
+
// lock DD/MM (WhatsApp's global default). Otherwise lock MM/DD (US-locale
|
|
354
|
+
// exports, which are the only meaningful exception). Locked once.
|
|
355
|
+
for (const line of lines) {
|
|
356
|
+
if (matchTimestampPrefix(line, "DDMM")) return "DDMM";
|
|
357
|
+
if (matchTimestampPrefix(line, "MMDD")) return "MMDD";
|
|
358
|
+
}
|
|
359
|
+
return "DDMM"; // No matching line — caller will throw zero-parsed-lines anyway.
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function sampleFirstNonBlankLine(lines: readonly string[], maxScan: number): string {
|
|
363
|
+
const scanLimit = Math.min(maxScan, lines.length);
|
|
364
|
+
for (let i = 0; i < scanLimit; i++) {
|
|
365
|
+
const trimmed = lines[i].trim();
|
|
366
|
+
if (trimmed.length === 0) continue;
|
|
367
|
+
// Strip control characters (including tab, BEL, etc.) so the diagnostic
|
|
368
|
+
// line stays single-line and grep-friendly. Truncate to 80 chars per
|
|
369
|
+
// Task 845 brief — enough to recognise the offending header shape.
|
|
370
|
+
const sanitised = trimmed.replace(/[\x00-\x1F\x7F]/g, "");
|
|
371
|
+
return sanitised.slice(0, 80);
|
|
372
|
+
}
|
|
373
|
+
return "";
|
|
374
|
+
}
|
|
375
|
+
|
|
315
376
|
function findFirstColonSeparator(remainder: string): number {
|
|
316
377
|
// Split on the FIRST `: ` (colon-space). A sender display name may itself
|
|
317
378
|
// contain a `:` (e.g. "Joel: Work"), so we anchor on the first colon
|
|
@@ -107,6 +107,8 @@ All writes route through `mcp__memory__memory-archive-write` (bulk Conversation+
|
|
|
107
107
|
|
|
108
108
|
The deterministic parser also LOUD-FAILs on UTF-8 decode failure (`reason=encoding-error`), zero parsed lines (`reason=not-a-_chat.txt`), and missing required arguments (`reason=accountId|timezone`). All of these surface through the same tool error path; the agent does not need to detect them itself.
|
|
109
109
|
|
|
110
|
+
**Structurally enforced (Task 846).** The harness-level `platform/plugins/admin/hooks/archive-ingest-gate.sh` watches every `mcp__memory__whatsapp-export-parse` PostToolUse event; on `isError: true` it sets a parse-error flag and blocks every subsequent tool call this turn until the operator submits the next prompt. Editing parser source under `platform/plugins/*/lib/*` and running `vitest`/`bun test`/`npm test`/`npx jest` is denied unconditionally. The skill's behavioural rule above is the contract; the hook is the enforcement. See [database-operator's LOUD-FAIL prerogative](../../../../templates/specialists/agents/database-operator.md#prerogatives) and [.docs/hooks.md](../../../../../.docs/hooks.md) for the gate's full surface.
|
|
111
|
+
|
|
110
112
|
## Idempotency contract
|
|
111
113
|
|
|
112
114
|
Re-importing the same `_chat.txt` is a no-op (`createdMessages=0`, `mergedMessages=N`, NEXT chain unchanged). Re-importing a re-exported file with appended messages adds only the delta and extends the NEXT chain to cover the new tail. Both paths are server-enforced via MERGE on `messageId` and the finalize hook's idempotent NEXT-MERGE.
|
package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md
CHANGED
|
@@ -15,13 +15,15 @@ WhatsApp's "Export Chat" produces a UTF-8 text file with a deterministic line gr
|
|
|
15
15
|
Every message line begins with a square-bracketed timestamp prefix followed by `<Sender>: <body>`:
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
[DD/MM/
|
|
18
|
+
[DD/MM/YYYY, HH:MM:SS] <Sender>: <body> ← modern WhatsApp default (4-digit year)
|
|
19
|
+
[DD/MM/YY, HH:MM:SS] <Sender>: <body> ← legacy exports (2-digit year)
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
- `DD/MM
|
|
22
|
-
- `
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
- **Day/month ordering.** `DD/MM` is the WhatsApp default everywhere except US iOS, which emits `MM/DD`. The parser auto-detects from the first prefix-matching line when `dateFormat` is omitted: probe DD/MM first; if range-valid, lock DD/MM; otherwise lock MM/DD. The lock is per-file — a single export never mixes orderings (the locale is set by the device that generated the export). Manually concatenated multi-locale archives are an explicit out-of-scope: pass `dateFormat` to override.
|
|
23
|
+
- **Year shape.** Both 2-digit (`\d{2}` → `2000+yy`) and 4-digit (`\d{4}` → as-is) years are accepted by the same regex (`\d{2,4}`). A single file may hold both shapes; year semantics are resolved per-line from the captured length, not per-file.
|
|
24
|
+
- **Time.** `HH:MM:SS` 24-hour; older exports may emit `HH:MM` (no seconds — treat as `:00`).
|
|
25
|
+
- **Sender.** Saved contact name, phone number with country code (`+44 7700 900123`), or `You` for legacy operator-sent messages.
|
|
26
|
+
- **Body.** Message text, possibly multi-line.
|
|
25
27
|
|
|
26
28
|
Trim trailing whitespace from each line before parsing.
|
|
27
29
|
|
|
@@ -100,7 +102,7 @@ The skill consumes this directly. The `messageId` is computed by the skill (not
|
|
|
100
102
|
The parser throws (and `whatsapp-export-parse` returns `isError: true`) on:
|
|
101
103
|
|
|
102
104
|
- Encoding error at file open (UTF-8 decode fails — the parser uses `TextDecoder` with `fatal: true`, so any invalid byte sequence aborts loudly rather than silently substituting U+FFFD).
|
|
103
|
-
- Empty file or zero parsed lines after walking the file (the file isn't a `_chat.txt`).
|
|
105
|
+
- Empty file or zero parsed lines after walking the file (the file isn't a `_chat.txt`). The thrown error and the `[whatsapp-import] parse-grammar-miss first-line="<sample>"` stderr line both carry a sanitised first-line sample (control chars stripped, truncated to 80 chars) so the operator can recognise the offending header shape without re-running with a debugger (Task 845).
|
|
104
106
|
- A timestamp prefix matches but the body parse fails (no `: ` separator after the closing `]` AND no system-pattern match) — emits `parse-error file=<...> line=<n> reason=no-sender-body-separator content="<...>"`.
|
|
105
107
|
- Missing required input (`accountId`, `timezone`).
|
|
106
108
|
|