@rubytech/create-maxy 1.0.794 → 1.0.795

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 (38) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-gate.test.sh +166 -0
  3. package/payload/platform/plugins/admin/hooks/archive-ingest-gate.sh +147 -0
  4. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -0
  5. package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
  6. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +2 -1
  8. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +1 -1
  9. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +1 -1
  10. package/payload/platform/plugins/whatsapp-import/PLUGIN.md +4 -0
  11. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts +8 -2
  12. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts.map +1 -1
  13. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js +66 -15
  14. package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js.map +1 -1
  15. package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +175 -0
  16. package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +78 -17
  17. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +2 -0
  18. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +8 -6
  19. package/payload/platform/scripts/seed-neo4j.sh +43 -20
  20. package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
  21. package/payload/server/public/assets/{Checkbox-DHsoNPeM.js → Checkbox-BruL6MSR.js} +1 -1
  22. package/payload/server/public/assets/{admin-DEhQ1wNO.js → admin-D8wbpnrW.js} +7 -7
  23. package/payload/server/public/assets/data-BhrQjgR5.js +1 -0
  24. package/payload/server/public/assets/graph-Jj7seS-w.js +1 -0
  25. package/payload/server/public/assets/{jsx-runtime-lOmSwjvd.css → jsx-runtime-foO6ZMix.css} +1 -1
  26. package/payload/server/public/assets/{page-DU8F3OGU.js → page-DIG7s5Jp.js} +1 -1
  27. package/payload/server/public/assets/{page-BuoQU1c6.js → page-sZb3wcOM.js} +1 -1
  28. package/payload/server/public/assets/{public-Bn-gEWOv.js → public-CfjzDdUe.js} +1 -1
  29. package/payload/server/public/assets/{share-2-0IDKUUq9.js → share-2-BndjMKeG.js} +1 -1
  30. package/payload/server/public/assets/{useVoiceRecorder-B1S_t3Hq.js → useVoiceRecorder-D_8P7xJU.js} +1 -1
  31. package/payload/server/public/data.html +5 -5
  32. package/payload/server/public/graph.html +6 -6
  33. package/payload/server/public/index.html +8 -8
  34. package/payload/server/public/public.html +5 -5
  35. package/payload/server/server.js +77 -119
  36. package/payload/server/public/assets/data-bIkywng-.js +0 -1
  37. package/payload/server/public/assets/graph-DwzwJvlu.js +0 -1
  38. /package/payload/server/public/assets/{jsx-runtime-Br2bU3EJ.js → jsx-runtime-DJER3a7U.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.794",
3
+ "version": "1.0.795",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # Regression test for archive-ingest-gate.sh (Task 846).
3
+ #
4
+ # Six cases cover the contract:
5
+ # 1. Edit on /platform/plugins/<x>/lib/* is BLOCKED (exit 2).
6
+ # 2. Edit on a benign path is ALLOWED (exit 0).
7
+ # 3. Bash with `npx vitest` is BLOCKED.
8
+ # 4. PostToolUse on whatsapp-export-parse with isError:true sets the flag.
9
+ # 5. Subsequent PreToolUse on ANY tool is BLOCKED (post-parse-error gate).
10
+ # 6. UserPromptSubmit clears the flag, restoring normal allow behavior.
11
+ #
12
+ # Tests use ARCHIVE_INGEST_GATE_STATE_DIR to point at a tmp dir so they run
13
+ # without a real account layout.
14
+
15
+ set -u
16
+
17
+ HOOK="$(cd "$(dirname "$0")/.." && pwd)/archive-ingest-gate.sh"
18
+ if [[ ! -x "$HOOK" ]]; then
19
+ echo "FAIL: $HOOK not executable" >&2
20
+ exit 1
21
+ fi
22
+
23
+ # Per-run isolated state dir
24
+ STATE_DIR=$(mktemp -d)
25
+ export ARCHIVE_INGEST_GATE_STATE_DIR="$STATE_DIR"
26
+ FLAG_FILE="$STATE_DIR/archive-ingest-parse-error.flag"
27
+
28
+ cleanup() { rm -rf "$STATE_DIR"; }
29
+ trap cleanup EXIT
30
+
31
+ PASS=0
32
+ FAIL=0
33
+
34
+ run_case() {
35
+ local name="$1" stdin="$2" expected_exit="$3"
36
+ local actual_exit
37
+ printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>/dev/null
38
+ actual_exit=$?
39
+ if [[ "$actual_exit" -eq "$expected_exit" ]]; then
40
+ echo "PASS: $name (exit=$actual_exit)"
41
+ PASS=$((PASS + 1))
42
+ else
43
+ echo "FAIL: $name (expected exit=$expected_exit, got=$actual_exit)" >&2
44
+ FAIL=$((FAIL + 1))
45
+ fi
46
+ }
47
+
48
+ # Case 1 — Edit on plugin lib path: BLOCKED
49
+ run_case "Edit on platform/plugins/whatsapp-import/lib/src/parse-export.ts → BLOCKED" \
50
+ '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/platform/plugins/whatsapp-import/lib/src/parse-export.ts","old_string":"a","new_string":"b"}}' \
51
+ 2
52
+
53
+ # Case 2 — Edit on a benign path: ALLOWED
54
+ run_case "Edit on README.md → ALLOWED" \
55
+ '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/README.md","old_string":"a","new_string":"b"}}' \
56
+ 0
57
+
58
+ # Case 3 — Bash with `npx vitest`: BLOCKED
59
+ run_case "Bash 'npx vitest run parse-export.test.ts' → BLOCKED" \
60
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npx vitest run parse-export.test.ts"}}' \
61
+ 2
62
+
63
+ # Case 3b — Bash with benign command: ALLOWED
64
+ run_case "Bash 'ls -la' → ALLOWED" \
65
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
66
+ 0
67
+
68
+ # Case 3c — Bash with `bun test`: BLOCKED
69
+ run_case "Bash 'bun test' → BLOCKED" \
70
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"bun test"}}' \
71
+ 2
72
+
73
+ # Case 3d — Bash with `npm test`: BLOCKED
74
+ run_case "Bash 'npm test' → BLOCKED" \
75
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npm test"}}' \
76
+ 2
77
+
78
+ # Make sure flag is absent before parse-error simulation
79
+ rm -f "$FLAG_FILE"
80
+
81
+ # Case 4 — PostToolUse on whatsapp-export-parse with isError:true sets flag
82
+ run_case "PostToolUse parse-error sets flag (exit 0, flag side-effect)" \
83
+ '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":true,"content":[{"type":"text","text":"parse-error file=_chat.txt line=1 reason=not-a-_chat.txt"}]}}' \
84
+ 0
85
+
86
+ if [[ -f "$FLAG_FILE" ]]; then
87
+ echo "PASS: parse-error flag created at $FLAG_FILE"
88
+ PASS=$((PASS + 1))
89
+ else
90
+ echo "FAIL: parse-error flag NOT created at $FLAG_FILE" >&2
91
+ FAIL=$((FAIL + 1))
92
+ fi
93
+
94
+ # Case 5 — Subsequent PreToolUse on ANY tool BLOCKED while flag is fresh
95
+ run_case "PreToolUse Read after parse-error → BLOCKED" \
96
+ '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
97
+ 2
98
+
99
+ run_case "PreToolUse Bash after parse-error → BLOCKED" \
100
+ '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo hi"}}' \
101
+ 2
102
+
103
+ # Case 6 — UserPromptSubmit clears flag
104
+ run_case "UserPromptSubmit clears flag (exit 0)" \
105
+ '{"hook_event_name":"UserPromptSubmit","prompt":"retry"}' \
106
+ 0
107
+
108
+ if [[ ! -f "$FLAG_FILE" ]]; then
109
+ echo "PASS: UserPromptSubmit cleared flag"
110
+ PASS=$((PASS + 1))
111
+ else
112
+ echo "FAIL: UserPromptSubmit did NOT clear flag" >&2
113
+ FAIL=$((FAIL + 1))
114
+ fi
115
+
116
+ # Case 7 — After clearance, normal allow resumes
117
+ run_case "PreToolUse Read after clearance → ALLOWED" \
118
+ '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
119
+ 0
120
+
121
+ # Case 8 — PostToolUse with isError:false does NOT set flag
122
+ rm -f "$FLAG_FILE"
123
+ run_case "PostToolUse parse-success (isError:false) does NOT set flag" \
124
+ '{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":false,"content":[{"type":"text","text":"{\"parsedLines\":[]}"}]}}' \
125
+ 0
126
+
127
+ if [[ ! -f "$FLAG_FILE" ]]; then
128
+ echo "PASS: parse-success leaves flag absent"
129
+ PASS=$((PASS + 1))
130
+ else
131
+ echo "FAIL: parse-success incorrectly created flag" >&2
132
+ FAIL=$((FAIL + 1))
133
+ fi
134
+
135
+ # Case 9 — Stale flag (>600s) auto-clears + allows
136
+ PAST=$(( $(date -u +%s) - 700 ))
137
+ echo "$PAST" > "$FLAG_FILE"
138
+ run_case "Stale flag auto-clears, PreToolUse Read → ALLOWED" \
139
+ '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
140
+ 0
141
+
142
+ # Case 10 — No stdin (terminal) fails closed
143
+ echo "Probing fail-closed behaviour (no stdin)..."
144
+ bash "$HOOK" </dev/null >/dev/null 2>/dev/null
145
+ ACTUAL=$?
146
+ # /dev/null IS a stdin — the `[ -t 0 ]` check tests for terminal, not file.
147
+ # A file/pipe stdin reads as empty, which produces empty hook_event_name and
148
+ # falls through to default `exit 0` (allow). The terminal-only fail-closed
149
+ # branch can't be tested non-interactively; verify the script reads `[ -t 0 ]`.
150
+ if grep -q '\[ -t 0 \]' "$HOOK"; then
151
+ echo "PASS: fail-closed terminal check is present"
152
+ PASS=$((PASS + 1))
153
+ else
154
+ echo "FAIL: fail-closed terminal check missing" >&2
155
+ FAIL=$((FAIL + 1))
156
+ fi
157
+
158
+ echo
159
+ echo "──────── archive-ingest-gate test summary ────────"
160
+ echo "PASS: $PASS"
161
+ echo "FAIL: $FAIL"
162
+
163
+ if [[ "$FAIL" -gt 0 ]]; then
164
+ exit 1
165
+ fi
166
+ exit 0
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bash
2
+ # Archive-ingest gate (Task 846).
3
+ #
4
+ # Three enforcements, one script — phase decided by `hook_event_name` on stdin:
5
+ #
6
+ # 1. PreToolUse Edit/Write/NotebookEdit: deny writes under
7
+ # `*platform/plugins/*/lib/*` (parser/CSV-shape source for any *-import or
8
+ # *-export plugin). The database-operator subagent has Read+Bash but not
9
+ # Edit/Write per its `tools:` frontmatter — yet it can still mutate files
10
+ # via Bash heredoc. The path block applies to every tool that takes a
11
+ # `file_path` and has been observed as the improvisation surface.
12
+ #
13
+ # 2. PreToolUse Bash: deny commands invoking JavaScript test runners
14
+ # (vitest|bun test|npm test|npx jest|node .*vitest). The reproducer
15
+ # incident (conv 47c6a590) saw the operator run all four variants in
16
+ # sequence after parse-export returned isError.
17
+ #
18
+ # 3. Parse-error gate: PostToolUse on any `mcp__*__*-export-parse` /
19
+ # `mcp__*__*-import-parse` tool whose `tool_response.isError == true`
20
+ # writes a flag file. Subsequent PreToolUse on ANY tool blocks until
21
+ # UserPromptSubmit clears the flag (semantics: "subagent's next action
22
+ # must be a user-facing message; further tool calls blocked"). A 600s
23
+ # TTL is the cross-session safety net.
24
+ #
25
+ # Exit codes follow Claude Code hook protocol: 0 = allow, 2 = block (stderr
26
+ # message shown to the agent). Fail-closed on stdin read failure to match
27
+ # pre-tool-use.sh.
28
+
29
+ set -uo pipefail
30
+
31
+ # Read stdin — fail closed if unavailable
32
+ if [ -t 0 ]; then
33
+ echo "Blocked: archive-ingest-gate received no stdin (cannot inspect tool call). Failing closed." >&2
34
+ exit 2
35
+ fi
36
+ INPUT=$(cat)
37
+
38
+ # ----- Resolve account dir for state file ----------------------------------
39
+ # Mirrors pre-tool-use.sh lines 100-107: walk from this hook's location to
40
+ # platform/, then `../data/accounts/<single-account>/`. Phase 0 has exactly
41
+ # one account directory.
42
+ HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
43
+ PLATFORM_ROOT_RESOLVED="${HOOK_DIR}/../../.."
44
+ ACCOUNTS_DIR="${PLATFORM_ROOT_RESOLVED}/../data/accounts"
45
+ ACCOUNT_DIR=""
46
+ if [ -d "$ACCOUNTS_DIR" ]; then
47
+ ACCOUNT_DIR=$(find "$ACCOUNTS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
48
+ fi
49
+ # Test harness override — tests pass `ARCHIVE_INGEST_GATE_STATE_DIR` to point
50
+ # at a tmp dir without needing a real account layout.
51
+ STATE_DIR="${ARCHIVE_INGEST_GATE_STATE_DIR:-${ACCOUNT_DIR}/state}"
52
+ FLAG_FILE="${STATE_DIR}/archive-ingest-parse-error.flag"
53
+ TTL_SECONDS=600
54
+
55
+ # ----- Parse fields from stdin ---------------------------------------------
56
+ # Hook protocol: every event includes `hook_event_name`. PreToolUse +
57
+ # PostToolUse include `tool_name`. PostToolUse adds `tool_response`.
58
+ # UserPromptSubmit has neither. Use grep/sed against the JSON envelope —
59
+ # no jq dependency to match the rest of the hook fleet.
60
+ HOOK_EVENT=$(printf '%s' "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
61
+ TOOL_NAME=$(printf '%s' "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
62
+
63
+ # ============================================================================
64
+ # UserPromptSubmit — clear the parse-error flag.
65
+ # ============================================================================
66
+ if [ "$HOOK_EVENT" = "UserPromptSubmit" ]; then
67
+ if [ -f "$FLAG_FILE" ]; then
68
+ rm -f "$FLAG_FILE" 2>/dev/null
69
+ echo "[archive-ingest-gate] cleared reason=user-prompt-submit" >&2
70
+ fi
71
+ exit 0
72
+ fi
73
+
74
+ # ============================================================================
75
+ # PostToolUse — record parse-error from any *-export-parse / *-import-parse.
76
+ # ============================================================================
77
+ if [ "$HOOK_EVENT" = "PostToolUse" ]; then
78
+ case "$TOOL_NAME" in
79
+ mcp__*__*-export-parse|mcp__*__*-import-parse)
80
+ # Inspect tool_response for isError true. Body is a JSON object; match
81
+ # `"isError":true` with optional whitespace.
82
+ if printf '%s' "$INPUT" | grep -Eq '"isError"[[:space:]]*:[[:space:]]*true'; then
83
+ mkdir -p "$STATE_DIR" 2>/dev/null
84
+ date -u +%s > "$FLAG_FILE" 2>/dev/null
85
+ echo "[archive-ingest-gate] surfaced reason=parse-error tool=${TOOL_NAME}" >&2
86
+ fi
87
+ ;;
88
+ esac
89
+ # PostToolUse must always allow the tool result through.
90
+ exit 0
91
+ fi
92
+
93
+ # ============================================================================
94
+ # PreToolUse — three independent blocks.
95
+ # ============================================================================
96
+ if [ "$HOOK_EVENT" != "PreToolUse" ]; then
97
+ # Unknown event, or hook fired in a context without an event name. Allow.
98
+ exit 0
99
+ fi
100
+
101
+ # --- Block 1: post-parse-error gate (applies to ALL tools) -----------------
102
+ if [ -f "$FLAG_FILE" ]; then
103
+ FLAG_TS=$(cat "$FLAG_FILE" 2>/dev/null | head -1)
104
+ NOW=$(date -u +%s)
105
+ if [ -n "$FLAG_TS" ] && [ "$FLAG_TS" -gt 0 ] 2>/dev/null; then
106
+ AGE=$(( NOW - FLAG_TS ))
107
+ if [ "$AGE" -lt "$TTL_SECONDS" ]; then
108
+ echo "[archive-ingest-gate] block tool=${TOOL_NAME} reason=post-parse-error age_s=${AGE}" >&2
109
+ echo "Blocked: an archive-parser MCP tool returned isError=true earlier in this turn. The subagent's next action must be a user-facing message naming the parse-error and yielding back to the operator. Further tool calls are blocked until the operator submits a new prompt." >&2
110
+ exit 2
111
+ fi
112
+ # Stale flag — clean up so we don't keep blocking on a corpse.
113
+ rm -f "$FLAG_FILE" 2>/dev/null
114
+ else
115
+ # Unparseable flag — remove rather than block on garbage.
116
+ rm -f "$FLAG_FILE" 2>/dev/null
117
+ fi
118
+ fi
119
+
120
+ # --- Block 2: plugin-source path block (Edit/Write/NotebookEdit) -----------
121
+ case "$TOOL_NAME" in
122
+ Edit|Write|NotebookEdit)
123
+ FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
124
+ case "$FILE_PATH" in
125
+ */platform/plugins/*/lib/*|platform/plugins/*/lib/*)
126
+ echo "[archive-ingest-gate] block tool=${TOOL_NAME} reason=plugin-source-edit path=${FILE_PATH}" >&2
127
+ echo "Blocked: ${TOOL_NAME} on ${FILE_PATH} is a platform plugin lib/ path. The database-operator subagent does not own plugin source; if a parser is broken, surface the parse-error to the operator and let them dispatch a code-edit task instead." >&2
128
+ exit 2
129
+ ;;
130
+ esac
131
+ ;;
132
+ esac
133
+
134
+ # --- Block 3: shell test-runner block (Bash) -------------------------------
135
+ if [ "$TOOL_NAME" = "Bash" ]; then
136
+ COMMAND=$(printf '%s' "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
137
+ # Match any of: `vitest`, `bun test`, `npm test`, `npx jest`, `node ... vitest`.
138
+ # Word-boundary checks via grep -E with explicit token boundaries — POSIX
139
+ # `[[:space:]]` covers leading-token edge cases, end-of-string covers tail.
140
+ if printf '%s' "$COMMAND" | grep -Eq '(^|[[:space:]/])vitest($|[[:space:]])|(^|[[:space:]])bun[[:space:]]+test($|[[:space:]])|(^|[[:space:]])npm[[:space:]]+test($|[[:space:]])|(^|[[:space:]])npx[[:space:]]+jest($|[[:space:]])|node[[:space:]].*vitest'; then
141
+ echo "[archive-ingest-gate] block tool=Bash reason=test-runner command=${COMMAND}" >&2
142
+ echo "Blocked: Bash command invokes a JavaScript test runner (vitest/bun test/npm test/npx jest). The database-operator subagent does not run plugin tests; surface the parse-error to the operator." >&2
143
+ exit 2
144
+ fi
145
+ fi
146
+
147
+ exit 0
@@ -68,6 +68,8 @@ When the owner is an external Person (non-operator archive), the anchor is the c
68
68
 
69
69
  **Doctrine:** raw Cypher and `cypher-shell` invocations are forbidden in this skill and its references. Writes route through `mcp__memory__memory-archive-write` (bulk archives) or `mcp__memory__memory-write` / `mcp__memory__memory-update` (single-node enrichments like `profile.md`). If a CSV needs a write shape no current MCP tool supports, file a task to extend `memory-archive-write` with a new `archiveType` handler — never improvise via Bash. See [database-operator's LOUD-FAIL prerogative](../../../../templates/specialists/agents/database-operator.md#prerogatives).
70
70
 
71
+ **LOUD-FAIL on parse errors (structurally enforced, Task 846).** When LinkedIn parser tools land that follow the `mcp__*__*-import-parse` naming convention, the harness-level `platform/plugins/admin/hooks/archive-ingest-gate.sh` will record an `isError: true` response and block every subsequent tool call this turn until the operator submits the next prompt. The hook also denies edits to `platform/plugins/*/lib/*` and JavaScript test runners (`vitest`, `bun test`, `npm test`, `npx jest`) unconditionally. The skill's "no Bash improvisation" doctrine above is the contract; the hook is the enforcement. See [.docs/hooks.md](../../../../../.docs/hooks.md) for the full gate surface.
72
+
71
73
  ## Selective-ingest threshold (bulk archives)
72
74
 
73
75
  A LinkedIn export typically contains 3,000–10,000 connections. Writing all of them in one shot defeats compression-on-write — most rows will never be queried, and the noise compounds with every subsequent ingest. The skill compresses by interrogating the operator before bulk writes.
@@ -860,9 +860,9 @@ if (!readOnly) {
860
860
  .min(1)
861
861
  .describe("IANA timezone the operator confirmed (e.g. 'Europe/London', 'America/New_York', 'UTC'). Each parsed timestamp is emitted as ISO 8601 with the offset that this zone holds for the wall-clock instant — DST is handled correctly."),
862
862
  dateFormat: z
863
- .enum(["DD/MM/YY", "MM/DD/YY"])
863
+ .enum(["DD/MM/YY", "MM/DD/YY", "DD/MM/YYYY", "MM/DD/YYYY"])
864
864
  .optional()
865
- .describe("Date ordering the export uses. Defaults to DD/MM/YY (WhatsApp's default in most locales). Pass MM/DD/YY for US-locale exports confirm with the operator before passing this."),
865
+ .describe("Date ordering and year shape. Omit for auto-detect (Task 845): the parser probes the first matched line as DD/MM and locks that ordering if range-valid; otherwise locks MM/DD. Year shape is independent — both 2-digit (legacy) and 4-digit (modern) years are accepted within the same file. Pass an explicit value only when the operator confirms a US-locale export or when auto-detect would mis-lock on a manually concatenated multi-locale archive."),
866
866
  }, async ({ filePath, timezone, dateFormat }) => {
867
867
  try {
868
868
  const result = await whatsappExportParse({