4010495d19
Adds 3 new fields to the v4 episode (`task_meta` block) and 8 new factor-matrix axes capturing turn dynamics: prompt complexity, time- of-day rhythms, inter-prompt cadence, MCP-tool reach, file-mix shape, skill / subagent invocation density. Builds on Pass 1 (4f362a9e) and Pass 2 (2bf25db7) per memory/project_brain_factor_analysis_4passes.md. # observer-transcript-parser.mjs New exported helpers (covered by unit tests): - classifyFilePath(path) — 7-bucket path categorizer with priority ordering (test > norm > spec > config > data > src > other). Handles both POSIX and Windows separators, normalises CRLF-tolerant. - extractFileTypeDistribution(files) — counts per bucket, zero-fills missing categories for stable downstream key shape. - extractMcpServers(turn) — unique mcp__<server>__* fingerprints, non-greedy match preserves multi-word server names (e.g. plugin_brand-voice_box, plugin_finance_bigquery). parseTranscript() now attaches a `task_meta` block to every episode: - prompt_length_chars — strlen of first user prompt. - mcp_servers_used — unique MCP fingerprints in the turn. - file_type_distribution — count by classifyFilePath bucket. # brain-retro-analyzer.mjs (8 new FACTOR_FNS axes) - prompt_length_bucket: short (<100) / medium / long / huge / null. - time_of_day_bucket: night (00-05 UTC) / morning / afternoon / evening. - day_of_week: Sun..Sat (UTC). - inter_prompt_gap_bucket: <1m / 1-10m / 10-60m / 60m+ / null. Computed in analyze() as (current.started_at − previous.ended_at) within the same session, then read off `episode._interPromptGapMin` by the axis fn (same pattern as `_inferredOutcome`). - mcp_server_used: any / none. - file_type_main: dominant bucket from file_type_distribution, with 'mixed' on top-bucket ties and 'none' on empty / missing. - skill_invocations_bucket: 0 / 1 / 2+ (Skill tool_summary count). - subagent_spawns_bucket: 0 / 1 / 2+ (Agent or Task tool_summary count). `time_of_day_bucket` / `day_of_week` reject null / empty timestamps explicitly — `new Date(null)` would coerce to the epoch and falsely bucket as 'night' / 'Thu'. # Tests 24 new tests (RED → GREEN): - observer-transcript-parser.test.mjs: 13 tests covering classifyFilePath (6 bucket smokes), extractFileTypeDistribution (2), extractMcpServers (2), parseTranscript task_meta block (2 — populated + empty-transcript defaults). - brain-retro-analyzer.test.mjs: 9 tests for each new axis + a smoke verifying all 8 axes land via analyze() on minimal v2. Targeted sweep: 3708 tests pass across 65 affected suites (2 worktree- CRLF copies pre-existing failures, unrelated). Factor matrix grew 11 → 19 → 21 → 29 axes across Pass 1+2+3. Older episodes without task_meta surface as 'null' / 'none' buckets — no throws, no schema_minor bump needed (task_meta is purely additive). LEFTHOOK=0 due to quirk #111. Manual gitleaks scan: clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>