# Changelog All notable changes to `memorydetective` are recorded here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.18.2] - 2026-05-23 Post-v1.18.1 follow-up. Adds the `/investigate-metrickit` MCP prompt (the 7th, completing the prompt surface for the v1.18 MetricKit lane) and syncs the README prompt count which had drifted (stale at 5, actual 7 including `/summarize-trace` and the new MetricKit prompt). No tool API changes. 757 -> 758 tests (+1). ### Added - **`/investigate-metrickit` MCP prompt (7th prompt).** Bespoke render (not playbook-backed). Takes a `payloadPath` argument and produces a ready-to-execute brief: 4 read-priority sections (crashCluster, hangHotspots, cpuExceptions, diskWriteExceptions), `suggestedNextCalls` chain hints into `findCycles` / `analyzeHangs`, and explicit constraints (no symbolication, no direct fix from raw `.mxdiagnostic`, `payloadCount: 0` is a valid state). Surfaces in Claude Code as `/investigate-metrickit`. Prompt count goes from 6 to 7. Asserted via `bespoke = ["summarize-trace", "investigate-metrickit"]` filter in the playbook-backed coverage test. ### Changed - **README prompt count synced.** The README listed 5 prompts (stale snapshot from v1.16) when actual count is 7. Updated to enumerate all 7 prompts, including `/summarize-trace` (v1.17) and the new `/investigate-metrickit`. ## [1.18.1] - 2026-05-17 Docs-only patch. Syncs the npm README + USAGE with the v1.18 surface notes that landed on GitHub right after v1.18.0 was published. No code changes. ### Changed - README "What's new" block leads with v1.18 (MetricKit 42nd tool + audit-close trio). New Examples block "Analyze MetricKit payloads from real-user crashes" with a concrete TestFlight directory aggregation example. New "Production diagnostics (1, v1.18)" API section with full `analyzeMetricKitPayload` surface description. API "Read & analyze" prefaced with the v1.18 D-02 `AnalyzeTraceOptions` hint. `summarizeTrace` row gains the D-02 wall-clock-win sentence. - USAGE follow-up requests table adds a "Production diagnostics" sub-section with 4 prompt examples covering `analyzeMetricKitPayload` (single payload, directory aggregation, `groupBy` variants, hang ranking). New end-to-end example "MetricKit production post-mortem" walks through a 47-payload TestFlight investigation mirroring the shape of the existing memgraph example, so the agent flow is consistent across lanes. ## [1.18.0] - 2026-05-17 Audit-close + MetricKit bet. v1.17 was reliability tightening on the existing surface. v1.18 ships the **42nd MCP tool** — `analyzeMetricKitPayload`, the post-mortem production-diagnostics lane — alongside three audit-close items deferred from v1.17 (open-enum SupportStatusKind, schemaDiscovery cache, real-Apple integration tests). The MCP ecosystem has zero coverage for MetricKit today; this release opens that lane. 701 → 757 tests (+56). 41 → 42 MCP tools. ### Added - **`analyzeMetricKitPayload` MCP tool (42nd, `[mg.production]`).** Ingests Apple MetricKit `.mxdiagnostic` JSON payloads from real-device TestFlight / App Store builds. Three input forms (single file, directory glob with cross-payload aggregation, raw JSON string). Three actionable outputs: `crashCluster` (groups by exception type / binary / top frame with configurable `groupBy`), `hangHotspots` (sorted by `hangDurationMs` with localized-string handling for `"5.4 sec"` / `"20秒"` / etc. — Apple ships localized values), `cpuExceptions` + `diskWriteExceptions` (long tail). Each entry includes raw `binaryUUID + offsetIntoBinaryTextSegment + binaryName` for downstream dSYM lookup. Cross-tool chain hints: `_objc_release`-style top frame surfaces a `findCycles` suggestion, `libsqlite3.dylib` top frame surfaces an `analyzeHangs` chain hint with the main-thread-violation classifier. **No symbolication in v1** — `analyzeMemgraph` evolved through the same staging pattern; dSYM lookup is its own future tool. **Simulator does not generate MetricKit payloads** (Apple-side limitation); framed as post-mortem analyzer, not live capture. Adds 4 new `SupportStatusKind` values (`crash-diagnostics`, `hang-diagnostics`, `cpu-exception-diagnostics`, `disk-write-exception-diagnostics`). - **`AnalyzeTraceOptions` interface** in `src/types.ts`. New optional second argument shared by every trace-side analyzer carrying `discoveredSchemas?: Partial>`. Direct callers leave it unset and pay the one-shot TOC fetch as before; orchestrators (`summarizeTrace`) populate it once up front so the 6 fan-out analyzers skip their own TOC calls. - **`resolveSchemasForAnalyzer` helper** in `src/parsers/schemaDiscovery.ts`. Cache-aware schema name resolution: reads the optional `cached` map when provided (no runner call), falls back to the cold path (`fetchDiscoveredSchemas`) otherwise, fills gaps with canonical names per family. Same shape as the existing single-family `fetchDiscoveredSchemas`, drop-in for the 9 trace analyzers that adopted it. - **Local-only integration test suite against real Apple `.trace` bundles** (`src/tools/realApple.integration.test.ts`). Reads `MEMORYDETECTIVE_INTEGRATION_TRACES` env var; tests skip silently when unset or fixture missing. 12 tests cover `inspectTrace` (TOC + metadata + suggestedNextCalls), `analyzeHangs` (pre + post fix verdicts), `analyzeTimeProfile` (v1.14 item O regression guard — symbols must be real function names, not the weight column), `compareTracesByPattern`, and `summarizeTrace` end-to-end (also exercises the D-02 cache). Documentation in `tests/INTEGRATION.md`. Fixtures live locally on the maintainer's machine; never committed to git, never shipped via npm. ### Changed - **`SupportStatusKind` is now an open enum.** Pre-v1.18 it was a closed TypeScript literal union (10 kinds). Adding a kind forced a breaking type change for downstream consumers branching exhaustively. v1.18 switches to the `KnownSupportStatusKind | (string & {})` pattern: inline literals still get autocomplete + typo detection internally (via `KnownSupportStatusKind`), external strings (e.g. the 4 new MetricKit kinds) now type-check without us shipping a new union. Exported runtime constant `SUPPORT_STATUS_KINDS` (readonly tuple of the 10 strings) so callers can iterate / validate. Existing call sites unchanged. Unblocks `analyzeMetricKitPayload`. - **`summarizeTrace` runs schema discovery once up front.** Pre-v1.18 each of the 6 analyzers it fans out to ran its own `xctrace export --toc`. Wall-clock penalty: +600-3000ms on real Apple traces, dominated by xctrace cold-start. v1.18 runs `fetchDiscoveredSchemasWithStatus` once at the top with all 7 families needed and passes the cache to every analyzer call via `{ discoveredSchemas }`. Measured win against the wishlist-tti-device.trace (37 MB Time Profiler) in integration tests: end-to-end summarizeTrace went from ~28s to ~15s per call. - **All 9 trace analyzers** (`analyzeHangs`, `analyzeAnimationHitches`, `analyzeAllocations`, `analyzeAppLaunch`, `analyzeTimeProfile`, `analyzeNetworkActivity`, `analyzeMemoryFootprint`, `analyzeEnergyImpact`, `analyzeLeakTimeline`) accept the new optional `AnalyzeTraceOptions` second argument. Backwards-compatible default: when omitted, behavior is identical to v1.17 (cold-path TOC fetch). No breaking change for any direct caller. ### Notes - The integration test suite is local-only and never runs in CI. CI on Linux continues to skip the 12 tests entirely. To activate locally: `MEMORYDETECTIVE_INTEGRATION_TRACES=~/Desktop npm test`. Expected fixtures and the workflow are documented in `tests/INTEGRATION.md`. - The 4 v1.15+ trace analyzers (`analyzeNetworkActivity`, `analyzeMemoryFootprint`, `analyzeEnergyImpact`, `analyzeLeakTimeline`) are still synthetic-only at integration level. Add coverage when matching template recordings land in the integration directory. - MetricKit delivery on iOS 18+ has a documented 24-48h delay and a "new bundle id probation" window (Apple Forums thread 766016). The tool surfaces `payloadCount: 0` honestly when the directory is empty; do not oversell coverage to users. - The deferred items (D-01 / D-02 / D-03) from `~/Desktop/internal/v1.17-bugs-and-debt.md` are all closed in v1.18. Remaining backlog (Phase 8 v1.9 dSYM symbolication, cross-payload trend analysis on MetricKit, `/investigate-metrickit` MCP prompt) tracked in `~/Desktop/internal/v1.18-roadmap.md` § "Out of scope". ## [1.17.1] - 2026-05-16 Docs-only patch. Syncs the npm README + USAGE with the v1.17 surface notes shipped to GitHub right after v1.17.0 was published. No code changes. ### Changed - README leads with the v1.17 reliability headlines; env-var table prefaced with the strtobool truthy parsing rule; macOS 26.x callout cites the new `bundleStatus` field on `recordTimeProfile`, the `savedOutsideWatchDir` field on `recordViaInstrumentsApp`, and the fault-tolerant `inspectTrace` fallback. Capture / record section renumbered from (3) to (4) with the previously-missing `recordViaInstrumentsApp` row. `verifyFix` + `countAlive` + `inspectTrace` + `recordTimeProfile` rows annotated with their v1.17 surface additions. - USAGE.md follow-up prompts table gained a `verifyFix` example using `{ pattern, mode: "exact" }`. Troubleshooting recovery list for the macOS 26.x recording wedge documents the `savedOutsideWatchDir` AppleScript fallback. ## [1.17.0] - 2026-05-16 Reliability pass. v1.16 closed the macOS 26.x recording gap with `recordViaInstrumentsApp`. v1.17 sweeps the audit punch list that surfaced after dogfooding: 14 bugs across three tiers, 3 known limitations documented, 3 tech-debt items deferred. The headlines are env-var truthy parsing (every `MEMORYDETECTIVE_*` boolean now accepts `1 / true / yes / on`, was `1`-only), a `verifyFix` whitelist that supports exact / substring / regex modes (was substring-only), the `recordViaInstrumentsApp` watcher catching saves outside the watch dir, and the `inspectTrace` fault-tolerant fallback so a wedged 52K bundle no longer throws. 41 MCP tools, 701 tests (+24 vs v1.16). ### Fixed #### Tier 1 (user-visible) - **`recordViaInstrumentsApp` watcher misses saves outside `watchDir` (B-01).** Pre-v1.17 the tool only watched the filesystem path; users who hit Save in Instruments.app and accepted the default Desktop location would time out even though the trace was on disk. Now queries the running Instruments.app via AppleScript every poll for any `document` whose file path was set after the tool started; on first match outside `watchDir` returns the path with `savedOutsideWatchDir: true`. Uses the documented `Instruments.sdef` `file of document` accessor only (no unsupported verbs). - **`verifyFix.expectedAliveClasses` supports per-entry mode (B-02).** Pre-v1.17 every whitelist entry was matched as a case-insensitive substring. Caller wanting "match exactly `UIRemoteKeyboardWindow`, do not match `MyUIRemoteKeyboardWindowWrapper`" had no way to express it. Schema now accepts `string` (defaults to substring for backwards compat) or `{ pattern: string, mode: "exact" | "substring" | "regex" }`. ESLint and Jest both use single-mode matching, but their users have asked for the inverse in tracked issues; we picked the opposite trade-off. - **`MEMORYDETECTIVE_*` env booleans accept the strtobool truthy set (B-03).** Pre-v1.17 only the literal string `1` turned a gate on. Users exporting `MEMORYDETECTIVE_ALLOW_LAUNCH=true` or `=yes` saw silent no-op behavior. v1.17 normalizes the five booleans through `parseBooleanEnv`, which accepts `1 / true / t / yes / y / on` (truthy) and `0 / false / f / no / n / off` (falsy), case-insensitive. Unrecognized values emit a one-time stderr warning per var so the operator knows the setting was ignored. Mirrors `envalid`'s `bool()` semantics without taking the dependency. - **`maybeOpenInInstruments` checks bundle viability before opening (B-04).** Pre-v1.17 the auto-open path would launch a wedged 52K macOS 26.x stub bundle in Instruments.app, where it presents a Document Missing Template Error dialog the operator has to dismiss. Now probes `Trace1.run/MANIFEST.plist` before calling `open`; on missing manifest, skips the open and surfaces the bundle status to the caller. The exported `classifyBundleOnDisk(tracePath)` helper returns `"unknown" | "salvageable" | "wedged"` for reuse. #### Tier 2 (silently sub-optimal) - **`inspectTrace` fault-tolerant fallback when `xctrace export --toc` fails (B-05).** Pre-v1.17 a failed TOC export threw, which broke the entire MCP call. Now returns `ok: true` with `schemas: []`, `rowCounts: {}`, `suggestedNextCalls: []`, and a diagnosis string naming the 52K macOS 26.x stub pattern so callers can self-diagnose. Throwing is reserved for missing trace paths. - **`schemaDiscovery.fetchDiscoveredSchemasWithStatus` surfaces failures (B-06).** New sibling of `fetchDiscoveredSchemas` returns `{ schemas, status: "ok" | "failed", reason? }` so trace analyzers can include a discovery-failure entry in `supportStatus[]` instead of silently using canonical schema names against a renamed-schema trace. Emits a one-time stderr warning per `tracePath:reason`, gated on `MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY`. Legacy `fetchDiscoveredSchemas` keeps its silent-fallback contract. - **`countByClassWithBytes` reports min/max/median for variable-size classes (B-07).** Pre-v1.17 we returned the first observed `instanceSize` as canonical for every class. Misled callers inspecting `NSData`, `NSString`, `CFData` whose per-instance size is payload-dependent. Now: fixed-size classes return a single `instanceSizeBytes` value as before; variable-size classes return `{ instanceSizeBytes: median, instanceSizeBytesMin, instanceSizeBytesMax, instanceSizeBytesMedian }`. `totalBytes` unchanged. `CountAliveEntry` propagates the spread through per-class and topN paths. - **`analyzeHangs.supportStatus[]` always includes both schema entries (B-08).** Pre-v1.17 a caller without `hangRisksXml` saw a single entry, a caller with it saw two. Agent code branching on `supportStatus.find(s => s.kind === "hang-risks")` could not distinguish "I did not ask" from "schema absent". Both entries are now always present; the missing-XML path returns `status: "not_present"` with reason `"caller did not provide hangRisksXml"`. - **`recordTimeProfile.bundleStatus` reflects on-disk reality (B-09).** New `bundleStatus: "unknown" | "salvageable" | "wedged"` field on the response. The timeout path now probes `MANIFEST.plist` via `classifyBundleOnDisk` so callers branching on `tracePath ? "have trace" : "no trace"` get an honest signal. - **`countAlive` framework-noise filter is configurable (B-10).** New inputs: `excludeFrameworkNoise: boolean` (default true), `additionalNoisePatterns?: string[]`, `unsuppressClassPatterns?: string[]`, `noiseAuditMode: boolean`. The curated noise list (calibrated for the v1.5 notelet investigation) remains the default. Audit mode returns a `noiseAudit[]` array listing every filtered class with reason `'default-list'`, `'additional-pattern'`, or `'kept-by-unsuppress'`. User-supplied regex strings compile with a literal-escape fallback on syntax errors. #### Tier 3 (edge case / cosmetic) - **`extractHost` parses IPv6 bracket form (B-11).** `analyzeNetworkActivity` was misreporting hosts like `[::1]:443` as the empty string. Now strips brackets and splits the port correctly. - **`normalizeBucket` priority fix (B-12).** `analyzeEnergyImpact` was matching `"high"` before `"foreground"` in xctrace's varying-case bucket strings. Reordered to check the named buckets first. - **Redundant `t.schema === "memory-footprint"` equality removed (B-13).** `analyzeMemoryFootprint` was double-checking after `discoverSchemas` already resolved the family name. - **`analyzeLeakTimeline` handles column-drift gracefully (B-14).** When every row is skipped due to missing `className`, returns `status: "partial"` with a reason naming the column drift instead of silently emitting an empty timeline. ### Behavior changes (informational) - The `MEMORYDETECTIVE_*` boolean env vars are now case-insensitively recognized for the strtobool set. Setting `=yes` or `=true` now turns gates on; pre-v1.17 it did not. Operators who deliberately set unrecognized values will see a stderr warning but no behavioral change (unrecognized falls back to default). - `verifyFix.expectedAliveClasses` string entries continue to behave as case-insensitive substring matches for backwards compat. The new object form `{ pattern, mode }` is opt-in. - The deprecated `notice` and `status` aliases on trace analyzers stay (will be removed in a future major bump). ## [1.16.0] - 2026-05-15 macOS 26.x recording-unblock release. The single feature that v1.14 punted on. `xcrun xctrace record --time-limit Ns` is broken on macOS 26.x simulator targets (documented in v1.14). Instruments.app GUI still produces valid `.trace` bundles. v1.16 ships the wrapper that automates the surrounding choreography: open the app, prompt the user with step-by-step instructions, poll for the saved trace, chain into `inspectTrace` on success. 41 MCP tools, 677 tests. ### Added - **`recordViaInstrumentsApp` MCP tool (41st, `[mg.build]`).** The macOS 26.x escape hatch. Opens Instruments.app via `open -a Instruments`, returns an `instructions[]` array telling the user which template to pick + when to hit Record / Stop / Save, then polls a `watchDir` every 5s for new `.trace` bundles. A bundle is treated as "saved" when its mtime is stable for 10s. On detection, chains into `inspectTrace` and returns the trace path + schemas + diagnosis. Times out after `timeoutSec` (default 600s, max 3600s). Why user-in-loop? Instruments.app's AppleScript surface only exposes queries on the `document` class (`name`, `modified`, `file`), no verbs for start/stop/template-select - documented in `Xcode.app/.../Instruments.sdef`. Full GUI automation is impossible until Apple expands the dictionary. README workaround callout + USAGE.md Troubleshooting recovery list updated to point at this tool as the v1.16 escape hatch (formerly: "wait for Apple"). 11 new tests against the watcher helpers (snapshot, new-trace detection, mtime stability, instruction text). ## [1.15.0] - 2026-05-15 Schema coverage + verify-fix UX release. v1.14 fixed the trace-side pipeline and shipped network coverage. v1.15 closes the remaining trace schemas the analyzer pipeline didn't cover (memory-footprint, energy-impact, xctrace leaks-as-time-series), folds network into summarizeTrace's synthesis chain, and gives replayScenario the screenshot-per-step capability DebugSwift inspired. 5 items landed across the same day. Suite 626 -> 666 (+40). 40 MCP tools. ### Added - **`analyzeMemoryFootprint` MCP tool (38th, `[mg.trace]`).** Parses the `memory-footprint` schema. Returns peak resident bytes (RAM in use), peak dirty bytes (the OOM-kill discriminator on iOS), peak virtual bytes, per-sample timeline. Distinct from `analyzeAllocations` (cumulative malloc bytes by category): this is process-level VM state. The "why is my app getting jetsam-killed?" investigation. Flags peak dirty above 200 MB as approaching jetsam territory on smaller devices. Resilient to column-name drift (resident / resident-bytes / phys / phys-footprint, dirty / private-dirty, etc.). `formatBytes` helper exported. - **`analyzeEnergyImpact` MCP tool (39th, `[mg.trace]`).** Parses the `energy-impact` schema. Returns per-sample bucket classification (idle / passive / active / high), aggregate wakeup count, active-state ratio, top-N by energy cost. Distinct from `analyzeTimeProfile` (CPU sampling): this reads the OS power-management subsystem directly. The "why is my app draining battery?" investigation. `normalizeBucket` helper handles xctrace's varying string conventions (idle / Passive / active / HIGH / foreground / background) plus an `unknown` fallback. - **`analyzeLeakTimeline` MCP tool (40th, `[mg.trace]`).** Parses the xctrace `leaks` schema (the instrument, distinct from `leaks(1)` CLI snapshot). Returns per-class first-seen-at timestamp, peak instance count, peak bytes, event count. Time-series view that answers "when in the recording did the leak first appear?" which a snapshot cannot. Flags multiple leaking classes as "suggests a shared cause" to nudge the agent toward investigating shared retainers (notification center, KVO global, etc.). - **`summarizeTrace` now chains `analyzeNetworkActivity`.** Sixth `buildAreaSummary` runs the new network analyzer in parallel. Markdown card gains a Network section between App launch and Cross-correlations, with a duration / method / status / bytes / URL table plus a top-hosts-by-request-count bullet list. Section is omitted when schema-absent. `buildHeadline` surfaces a slow network request (>= 3000ms) as the headline only when hangs, launch, and hitches are quiet. `focus: "network"` is a new valid enum value on the input. - **`replayScenario` captures simulator screenshots per step.** New `screenshotDir?: string` input. When provided, each scenario step writes a PNG to `/iteration-{N}_step-{M}.png` via `xcrun simctl io screenshot`. No `axe` dependency for screenshots. DebugSwift-inspired: their leak detector shows the leaked view, this gives the same context to verify-fix loops. `captureSimulatorScreenshot` helper exported. Failures non-fatal (surfaced on `failures[]`, scenario continues). - **`SupportStatusKind` enum extended** with `memory-footprint`, `energy-impact`, `leak-events` to back the three new analyzers. ## [1.14.0] - 2026-05-15 Trace-side reliability + breadth release. v1.13 shipped `summarizeTrace`, the trace-to-summary-card-in-one-call play. v1.14 fixes two parser bugs that were causing it to return empty results against real Apple-produced traces, expands schema coverage to include hang risks + network activity, robustens the analyzer pipeline against future xctrace schema renames, and adds three opt-in UX paths around the macOS 26.x xctrace wedge regression (pre-flight probe, auto-open Instruments, README callout). Plus FLEX-inspired memgraph size view on `countAlive` and an MLeaksFinder + DebugSwift-inspired whitelist on `verifyFix`. 11 changes landed across one day. Suite 546 -> 626 (+80 tests). 37 MCP tools. ### Fixed - **`inspectTrace` + `summarizeTrace` now work against real Apple-produced `.trace` bundles.** The previous implementation ran `xctrace export --xpath '/trace-toc/run'`, which returns "This node has no content to export." against bundles produced by `xcrun xctrace record` (and by Instruments.app GUI saves). Cascaded into `summarizeTrace` reporting "No user-perceptible perf events detected" even when the trace had 35 hangs and 44k time-profile samples. Fix switches discovery to `xctrace export --toc` and adds async parallel xpath row-count queries for the 5 known analyzer schemas. `parseTraceToc` also now accepts self-closing `` (Apple's TOC shape) in addition to the open-close form (test fixtures), extracts device/OS from `` attributes, and reads `` for the recording timestamp. Validated end-to-end against a Time Profiler trace from a physical iPhone 17 Pro Max: 40 schemas detected, summarizeTrace headline becomes "1164ms hang at t=89.01s. Likely user-visible freeze." 8 new tests against the real Apple TOC fixture. - **`analyzeTimeProfile` now returns real symbol names instead of the weight column.** Two cascading bugs: (1) the parser keyed cells by schema mnemonic, so the backtrace cell was at `row.stack`, not `row.backtrace`; (2) `XctraceValue` only captured `@_fmt`, dropping `@_name` from `` and `` elements where the symbol identity lives. Added an `XctraceValue.name` field and rewrote `analyzeTimeProfile` to walk `row.stack.nested.frame.name` with fallback to the leaf frame's binary name for unsymbolicated hex addresses. Validated end-to-end against the same Time Profiler trace: top symbol becomes `CFStringHashCString` (2429 samples), followed by `-[CoreTelephonyClientMux _computeNotificationSet_sync:completion:]` (2214). Unsymbolicated frames cluster by binary as `libsystem_kernel.dylib (0x24c0aacd5)` etc. 2 new tests. ### Added - **`analyzeNetworkActivity` MCP tool (37th, `[mg.trace]`).** Parses the `network-connections` schema from a `.trace` recorded with a Network template. Returns per-request URL / host / method / status code / response time / bytes in / bytes out, plus aggregates: total bytes in/out, longest + average response time, status-code buckets (2xx / 3xx / 4xx / 5xx / n/a), top-N by duration (which calls blocked the user), top-N by bytes (which calls bloated the budget), and per-host aggregates (which SDKs are chatty). The pure helper `analyzeNetworkActivityFromXml` accepts multiple plausible mnemonic names per field (time / event-time / start / connect-time, url / host / endpoint, bytes-in / response-bytes / received-bytes, etc.) so it survives xctrace column-name drift across iOS versions. Registered in `inspectTrace.SCHEMA_TO_ANALYZER` so the discovery path auto-suggests it when the trace has a Network schema. 15 new tests on the parser plus 4 on the host-extraction helper. Closes the "network is slow" / "chatty SDK" / "slow launch from one API call" coverage gap in the trace family. ### `supportStatus[]` unified status surface (v1.14 item I) - **New `SupportStatus` type in `src/types.ts`** with `kind` (one of `potential-hangs`, `hang-risks`, `animation-hitches`, `time-profile`, `allocations`, `app-launch`, `network-connections`), `status` (`available` / `partial` / `not_exportable` / `not_present` / `failed`), optional `reason`, optional `sourceSchemas`. Mirrors XcodeTraceMCP's surface shape so cross-tool LLM drivers can branch consistently. All 6 trace-side analyzers (`analyzeHangs`, `analyzeAnimationHitches`, `analyzeTimeProfile`, `analyzeAllocations`, `analyzeAppLaunch`, `analyzeNetworkActivity`) now return a populated `supportStatus[]` array. `analyzeHangs` returns 1 entry by default and 2 when hang-risks XML is also provided (one per schema). `analyzeTimeProfile`'s SIGSEGV path surfaces the workaround tip in `supportStatus[0].reason`. Old `status: DataStatus` and `notice?: string` aliases stay on each result with `@deprecated` JSDoc for backwards compatibility with v1.13 callers; both alias and new field always agree on the status value. 10 new cross-analyzer tests verifying the kind / status / reason / sourceSchemas surfacing per analyzer. ### Pattern-matching schema discovery refactor (v1.14 item B) - **New `src/parsers/schemaDiscovery.ts` module** with SCHEMA_FAMILIES (11 families: hangs, hang-risks, animation-hitches, time-profile, time-sample, allocations, app-launch, memory, network, energy, leaks), CANONICAL_SCHEMA_NAME fallbacks, and `discoverSchema` / `discoverSchemas` / `fetchDiscoveredSchemas` helpers. Each family maps to a list of case-insensitive RegExp patterns deliberately conservative so they don't false-positive on unrelated schemas. The async `fetchDiscoveredSchemas` runs `xctrace export --toc` once and bulk-resolves multiple families in one pass. Inspired by XcodeTraceMCP's pattern-matching approach. - **All 5 trace-side analyzers refactored** (`analyzeAllocations`, `analyzeAnimationHitches`, `analyzeAppLaunch`, `analyzeTimeProfile`, `analyzeHangs`) to call `fetchDiscoveredSchemas` before their xpath query. Survives future Apple renames: when the trace uses a renamed schema name, discovery picks it up; when nothing matches, falls back to the canonical pre-v1.14 hardcoded name (preserves backwards compat). `analyzeHangs` bulk-discovers all three schemas it consumes (hangs + hang-risks + time-profile) in a single TOC pass. - Cost: one extra `xctrace --toc` invocation per analyze call (~100-500ms on real traces). - **`recordTimeProfile` auto-opens Instruments.app on timeout (opt-in).** Set `MEMORYDETECTIVE_AUTO_OPEN_INSTRUMENTS=1` to make a timed-out recording fire-and-forget invoke `open -a Instruments ` so the partial `.trace` opens in the GUI. Instruments.app on macOS 26.x can still symbolicate and display traces the `xctrace export` CLI path rejects, giving a manual escape hatch. The response gains an `openedInInstrumentsApp: boolean` field reporting whether the open was invoked. Off by default to avoid spamming agent / CI runs. XcodeTraceMCP-inspired UX pattern. 4 new tests on the env-gated helper. - **`recordTimeProfile` pre-flights the xctrace wedge on macOS 26.x simulator attach.** New 2-second probe runs before the user's actual recording. When the probe times out, recordTimeProfile bails fast with the same `workaroundNotice` instead of waiting the full `durationSec + 30s grace` on a wedge. Cuts user-visible failure latency from `~70s` (a 30s recording with the timeout wrapper firing) to `~8s` (probe wrapper window). Gated to the known-broken combo by default: macOS 26.x host + simulator target + attach mode. `--launch` mode is excluded so we never double-launch the app. New `MEMORYDETECTIVE_PREFLIGHT_XCTRACE=1` forces on; `0` forces off. 7 new tests covering the env-flag overrides, platform detection, target/mode gating, and `MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1` propagation. - **`countAlive` returns per-class `instanceSizeBytes` + `totalBytes` and accepts `sortBy: "count" | "totalBytes"`.** Inspired by FLEX's Live Objects "Size" sort column. The `.memgraph` format already carried per-class size data (cycle-side `[N]` annotations + reference-tree per-class totals); we just were not surfacing it. New `countByClassWithBytes` pure helper aggregates both. `sortBy: "totalBytes"` is FLEX-equivalent memory-budget rank; default `"count"` preserves v1.13 ranking. Per-instance size is the first non-null observed (cycle-side) or `round(totalBytes / count)` (reference-tree fallback). Validated against a real abandoned-memory memgraph: top by totalBytes surfaces 2 MB NSMutableDictionary, 1.3 MB CFString; `actionableCounts[]` (framework-noise filtered) leads with 800 KB Kernel Pointers, 460 KB CGDataProvider. 5 new tests. - **`verifyFix` accepts `expectedAliveClasses[]` whitelist + curated default list.** Singletons, framework registrars, and persistent caches legitimately stay alive across before/after snapshots. Pre-v1.14 they landed in `regressionClasses[]` and could flip the verdict to FAIL on otherwise-clean fixes. New `expectedAliveClasses?: string[]` schema input (substring patterns, case-insensitive) + `disableDefaultWhitelist: boolean` (default false). Exported `DEFAULT_EXPECTED_ALIVE_CLASSES` constant: 11 entries sourced from DebugSwift's `Performance.LeakDetector.swift` curation (system VCs like `UICompatibilityInputViewController` + `UIPredictionViewController`, internal views like `PLTileContainerView`, OS-retained windows like `UIRemoteKeyboardWindow`). Whitelist hits are moved out of `regressionClasses[]` into a new `expectedAlive: string[]` field on the response for transparency, without affecting magnitude / verdict computation. MLeaksFinder + DebugSwift-inspired UX pattern. Validated against real notelet memgraphs: user whitelist of `[pthread_mutex_t, SwiftUI.ObjectCache, NSCache]` filtered 5 entries (including substring matches for both Font and Color ObjectCaches). 5 new tests. - **`analyzeHangs` now reads the `hang-risks` schema alongside `potential-hangs`.** xctrace emits two complementary hang-related schemas in Time Profiler / Hangs templates: `potential-hangs` (measured durations) and `hang-risks` (runtime risk annotations like "Hang Risk" / "Severe Hang Risk" narrative events with backtraces). Pre-v1.14 we only read the former. The result schema gains optional `risks: HangRiskEntry[]` and `risksTotals: { rows, bySeverity }` populated when the second schema is exported, absent otherwise. New pure `analyzeHangRisksFromXml` helper. The async wrapper fires both exports in parallel via Promise.all; hang-risks failure is non-fatal. Diagnosis appends "N hang risk annotations from the iOS runtime" with severe count when the bySeverity bucket has any "Severe ..." entries. XcodeTraceMCP-inspired schema discovery extension. 9 new tests. ### Documentation - **README + USAGE.md gained a `macOS 26.x xctrace record` callout.** `xcrun xctrace record --time-limit Ns` against iOS simulator targets is broken on macOS 26.x. The recording wedges past the requested time limit, exits when SIGKILL fires, and produces a `.trace` bundle that fails template export. Apple-side regression; survives Xcode 26.5 (build 17F42, xctrace 16.0) per the 2026-05-15 re-validation. Hits every `xctrace`-based tool the same way. README highlights the issue near the top with `task_for_pid` callout; USAGE.md Troubleshooting gains a ranked recovery list (older macOS host, Instruments.app GUI, physical device, wait for Apple). Trace-side analyzers (`inspectTrace`, `summarizeTrace`, `analyzeHangs`, `analyzeTimeProfile`, `analyzeAnimationHitches`, `compareTracesByPattern`) all work normally against Instruments-recorded `.trace` bundles, so the workaround path stays automatable end-to-end after the recording step. ## [1.13.0] - 2026-05-14 Trace-depth synthesis differentiator. v1.11 added `inspectTrace` (discovery), v1.12 added cross-schema correlation inside `analyzeHangs`. v1.13 ties everything together: one call returns a cross-schema summary card tuned for direct presentation to the user, instead of chaining 5-6 analyzers manually and reasoning over the JSON. ### Added - **`summarizeTrace` MCP tool (36th tool, `[mg.synthesize]`).** Single call that: 1. Inspects the TOC via the existing inspectTrace path. 2. Chains `analyzeHangs` (with `includeStackClassification: true` to auto-classify main-thread violations), `analyzeAnimationHitches` (with Apple's 100ms perceptible threshold), `analyzeTimeProfile`, `analyzeAllocations`, `analyzeAppLaunch` in parallel via Promise.all guards. 3. Builds cross-area correlations (Phase 2: hangs+hitches timestamp overlap detection with HIGH/MEDIUM/LOW confidence tiers). 4. Returns BOTH a structured per-area result AND a pre-rendered compact markdown card (< 10 KB at default settings, validated by a card-size guard in the unit tests). The card layout: H1 title, device/OS/template metadata, headline (1-2 sentences naming the biggest user-impact finding ranked by hang>=250ms > launch>1s > perceptible hitches > short hang), per-area sub-sections (suppressed when empty to reduce noise), cross-correlations section with confidence badges, suggested next calls from inspectTrace. Optional inputs: `focus: "hangs" | "hitches" | "allocations" | "launch" | "all"` to bias the summary toward one area; `verbose: true` to expand each section's top-N from 5 to 15+ and surface low-confidence correlations inline. Failed analyzers (e.g. xctrace SIGSEGV on time-profile) surface their workaround notice inline; other sections unaffected. Empty schemas don't fail the call. - **`correlateHangsAndHitches()` pure cross-correlation helper.** Detects hangs whose time windows overlap with animation hitches. When a user sees a hang AND a hitch in the same window, they almost certainly perceived the impact (main-thread block delayed render commits). Confidence tiers: `high` when both events >= 250ms AND overlap span >= 100ms; `medium` when at least one event >= 250ms; `low` when both sub-250ms but touching. Results sorted by `atSec` ascending so the markdown card lists correlations in trace order. Allocation-based correlations (hangs+allocations, hitches+allocations) are deferred to v1.14+ because the existing `analyzeAllocations` doesn't expose per-timestamp allocation rows. - **`/summarize-trace` MCP prompt (6th prompt).** Counterpart to the `summarizeTrace` tool. Unlike the existing 5 prompts (which wrap multi-step playbooks), `/summarize-trace` wraps a single tool call. The brief tells the agent to (1) call `summarizeTrace`, (2) present `result.markdown` verbatim instead of paraphrasing, (3) offer drill-ins via `suggestedNextCalls`. Surfaces the "present the result; don't re-summarize" pattern that agents often get wrong with structured data. ### Changed - `package.json` + `server.json`: 1.12.0 → 1.13.0. ### Test count 523 → 546 (+23): 14 buildHeadline/buildMarkdownCard tests in Phase 1, 9 correlation tests in Phase 2, +0 in Phase 3 (the prompts test was updated, not extended). ## [1.12.0] - 2026-05-14 Four-phase patch completing the v1.10 retro §7.2 propagation (`countAlive`, `findRetainers`, `verifyFix` reference-tree integrations) and internalizing the v1.9 cross-schema correlation (`analyzeHangs` auto-classification). The agent chain `analyzeAbandonedMemory` → `findRetainers` → `countAlive` → `verifyFix` no longer falls off a cliff after the first call. ### Added - **`countAlive` reference-tree integration.** New `includeReferenceTree: boolean` input flag (default false preserves v1.11 behavior). When true, spawns `leaks --referenceTree --groupByType --noContent` in parallel with the existing cycle pass, merges counts by class name, and surfaces a new `actionableCounts[]` field filtered via `isFrameworkNoise`. For `countAlive({ className: "AVPlayerItem", includeReferenceTree: true })` on the notelet pre-fix memgraph, returns `instanceCount: 685` (substring match captures both AVPlayerItem 342 + AVPlayerItemInternal 343); for the topN path, surfaces AVCMNotificationDispatcher / __NSObserver / AVPlayerItem instead of NSMutableDictionary / CFString noise. New per-class `byCycle` + `byReferenceTree` breakdown fields when the flag is on. 3 new schema validation tests. - **`verifyFix` abandoned-memory verdict fallback.** v1.11 returned `overallVerdict: "PASS"` with empty `patternResolution[]` on the notelet pair (both `leakCount: 0`); v1.12 chains internally into `analyzeAbandonedMemory` when no cycle patterns fire on either side. New `verdictSource: "cycle-pattern" | "abandoned-memory"`, `freedClasses[]`, `regressionClasses[]` fields. Magnitude-dominance heuristic resolves verdict to PASS / PARTIAL / FAIL by comparing the sum of `|freed delta|` to `|growth delta|` at a 2x ratio. Notelet pair now returns `verdict: PASS` with diagnosis "Fix verified via abandoned-memory shrinkage: 10,386 instances freed dominates the residual 4,531-instance growth (typically Swift runtime / font cache / ObjC class table)." `isFrameworkNoise` extended to flag `N bytes into [size]` heap-offset entries so the magnitude check doesn't get fooled by partial-allocation noise. 1 new reference-tree filter test. - **`findRetainers` reference-tree chains.** New `includeReferenceTree: boolean` input flag (default false). When true, spawns `leaks --debug=stacks --debug='$'` and parses the per-instance allocation stack output via a new `src/parsers/leaksDebugStacks.ts` parser. Instances sharing the same call-stack fingerprint aggregate into one chain with `instanceCount`. Each chain exposes `callStack[]` (ordered frames from dyld root to allocation site), `retainers[]` (unique retainer classes with aggregate counts), `exampleAddress` (one representative instance), and `userFrame` (the deepest non-system frame, surfacing the actual line a developer would inspect). New 11-test parser file. leaks's `--debug=` predicate restriction documented in the schema description: `^` is rejected by leaks, only trailing `$` works. MallocStackLogging caveat documented: Xcode-exported memgraphs may surface fewer chains than the total instance count because `leaks --debug=stacks` only emits blocks for instances whose allocation stack was recorded. - **`analyzeHangs` cross-schema correlation.** v1.9 added `mainThreadViolations[]` enrichment via the caller-supplied `topFramesByHangStartNs` map; v1.12 internalizes it. New `includeStackClassification: boolean` input flag (default false preserves v1.9 behavior). When true, exports the `time-profile` schema in parallel with `potential-hangs`, correlates samples to hang windows by timestamp (sample in `[hang.startNs, hang.startNs + hang.durationNs]`), picks the dominant top frame per hang by aggregate weight, and runs `classifyHangFrame()` on it. New pure `correlateTimeProfileToHangs()` helper. Caller-supplied maps still take precedence over the auto-correlation. 8 new pure correlation tests covering empty inputs both sides, in/out-of-window, weight tiebreaker, backtrace fallback, multi-hang independence, default-weight handling. ### Changed - `package.json` + `server.json`: 1.11.0 → 1.12.0. - `compareTracesByPattern` call sites of `analyzeHangs` now pass `includeStackClassification: false` explicitly to satisfy the inferred `analyzeHangs` input type (cosmetic; no behavioral change). ## [1.11.0] - 2026-05-14 Three-fix patch driven by the v1.10 validation pass on the global npm binary. The v1.10 retro §7 surfaced gaps that v1.11 closes: CLI human-output ignored the new abandoned-memory data, `diffMemgraphs` had the same reference-tree blind spot v1.10 fixed only in `analyzeMemgraph` + `analyzeAbandonedMemory`, and there was no orientation tool for `.trace` bundles. ### Added - **`inspectTrace(tracePath)` MCP tool (35th tool, `[mg.discover]`).** Orientation tool for `.trace` bundles: single-call `xcrun xctrace export --xpath '/trace-toc/run'` invocation returns the schemas present, their row counts, the device model, OS version, template name, recording timestamp, file size, and a `suggestedNextCalls[]` array mapping each populated known schema (`potential-hangs`, `animation-hitches`, `time-profile`, `allocations`, `app-launch`) to its analyzer with pre-populated args. Closes the discovery gap vs XcodeTraceMCP without surrendering the synthesis differentiator: the analyzer mappings carry one-sentence rationales explaining what each call would return. Fallback path: when `/trace-toc/run` returns non-zero (older xctrace versions, malformed TOC), retries with the broader `/trace-toc` xpath. Empty traces return `schemas: []` with a diagnosis pointing at Instruments.app for manual triage. 9 new unit tests cover full-TOC parse, row counts + sort order, all run-level metadata fields, analyzer-suggestion mapping for known + custom + empty schemas, empty TOC, malformed XML graceful degradation. - **`diffMemgraphs` reference-tree integration.** v1.10 fixed `analyzeMemgraph` and `analyzeAbandonedMemory` to read the reference-tree output but `diffMemgraphs` still operated on cycle data only. On the notelet pair (`leakCount: 0` both sides) the response came back with empty `classCountChanges`, zero delta, and the AVPlayerItem 342 to 0 finding completely invisible. v1.11 adds: a pure `diffReferenceTrees()` helper, a `captureReferenceTree()` wrapper that spawns `leaks --referenceTree --groupByType --noContent` against each `.memgraph` in parallel with the existing cycle pass (4 spawns total, parallelized via Promise.all so wall-clock unchanged), and three new response fields: `referenceTreeChanges.{increased,decreased}` (raw view), `actionableReferenceTreeChanges.{increased,decreased}` (filtered via `isFrameworkNoise` to surface AV / KVO / app-level classes), and `totals.referenceTreeInstanceDelta` + `referenceTreeBytesDelta` (heap-wide single-number branching). Validation on the notelet pair: `actionableReferenceTreeChanges.decreased` leads with AVCMNotificationDispatcher (1802 to 4, -1798), __NSObserver (665 to 7, -658), AVCMNotificationDispatcherListenerKey (603 to 0, -603), AVPlayerItem (342 to 0, -342), and the full AV plumbing tree the notelet fix freed. 4 new unit tests for the pure `diffReferenceTrees` helper. - **CLI human-output abandoned-memory surface.** `memorydetective analyze leak.memgraph` (no `--json` flag) now renders a top-5 suspects mini-table below the Diagnosis line when `abandonedMemorySuspects[]` is populated. 3 columns (Class, Instances, Bytes), locale-formatted counts for skimmability, name padded to actual width (capped at 56 chars). Footer hint pointing to `--json` for the full top-20 plus the raw `abandonedMemoryTop` view. Suppressed when the suspects field is empty (cycle-shape memgraphs unchanged). `memorydetective classify` adds a one-line tip at the bottom when `result.classified` is empty, pointing the user at `analyze` for the abandoned-memory view. Closes the v1.10 retro §7.1 gap where terminal users saw "No leaks detected" and walked away from real bugs. ### Changed - `package.json` + `server.json`: 1.10.0 → 1.11.0. ### Known structural gaps deferred to v1.12+ The v1.10 retro §7.2 listed 4 tools (`countAlive`, `findRetainers`, `diffMemgraphs`, `verifyFix`) with the same reference-tree blind spot as v1.9-era `analyzeMemgraph`. v1.11 closes `diffMemgraphs` (the cheapest of the four); the remaining 3 are deferred to v1.12. ## [1.10.0] - 2026-05-14 Notelet retro feedback loop: re-running the v1.9 abandoned-memory tools against the same memgraphs that originally drove the v1.9.0 release surfaced three concrete gaps. This minor patches all of them so the notelet investigation's headline finding (AVPlayerItem went 342 to 0, KVO observer never invalidated) is reproducible end-to-end via JSON instead of requiring a manual `leaks --debug=stacks` grep. ### Added - **`analyzeMemgraph.abandonedMemorySuspects[]`** parallel to `abandonedMemoryTop[]`. The raw view kept its semantics (top-N classes by live instance count, including framework collections + ObjC runtime metadata) for cache-bloat-shaped investigations. The new "suspects" view applies a `isFrameworkNoise()` filter on the same data, so AV* / KVO* / app-level classes surface even when ranked below NSMutableDictionary / CFString / `__DATA __bss` in the raw view. Internally captures a 10x pool (min 200 entries) so post-filter ranking still has enough headroom to surface classes that v1.9 left invisible at default `referenceTreeTopN: 20`. Validated on the notelet pre-fix memgraph: AVPlayerItem at raw rank 82 surfaces at suspect rank 13 of 20. - **`analyzeAbandonedMemory.actionableGrowth[]` + `actionableShrinkage[]`** parallel to the existing `growthByClass[]` + `shrinkageByClass[]`. Same noise filter applied. The verify-fix loop becomes "did `actionableShrinkage` lead with the suspect class?" instead of "scroll past 25 framework noise rows to find AVPlayerItem". Validated on the notelet pre-fix vs post-fix diff: `actionableShrinkage[]` leads with AVCMNotificationDispatcher (1802 to 4), AVCMNotificationDispatcherListenerKey (603 to 0), AVPlayerItem (342 to 0), AVPlayerInternal (297 to 0), AVPlayerPlaybackCoordinator (290 to 0), all the AVFoundation classes the fix actually freed. - **`outputFormat: "verify-fix-table"`** value on the shared `outputFormatField`. When set on `analyzeAbandonedMemory`, the response is a focused 4-column markdown table (Class | Before | After | Delta) of the actionable rows (|delta| >= 10), split into "What the fix freed" (shrinkage) and "Classes that grew (regressions or unrelated)" (growth), plus a trailing diagnosis blockquote. Other tools fall back to standard markdown. The hand-built comparison table in the notelet follow-up post is reproducible from a single call now. 7 new unit tests in `src/runtime/responseFormatter.test.ts` cover the renderer + threshold filter + the formatMcpResponse routing. ### Changed - **`extractClassName` parser fix.** The allocator filter (`malloc | calloc | realloc`) now runs AFTER the arrow split, so entries like `unaligned --> calloc in quic_stream_allocate` are correctly dropped (the v1.9 filter only ran against the original label, missing allocator names on the RHS of arrows). 1 new unit test covering this exact case. - **`extractClassName` normalizes bracketed instance forms.** ` [size]` and `` patterns now resolve to `ClassName`. Without this normalization, each address became its own aggregation key and the same logical class appeared as N separate rows in the top-N list. Arrow-targeted entries (e.g. `_object --> `) now aggregate correctly with the root-level form. 5 new unit tests cover the new patterns (with size, without size, after arrow, namespaced names like `SwiftUI.ViewGraph`, allocator-in-brackets filtering). - **Classifier escalation tightened (`analyzeAbandonedMemory.classifyGrowth`).** Three guards added to the KVO co-occurrence path so framework noise stops getting tagged `kvo-observer-orphaned high` whenever `NSKeyValueObservance` grows: (1) `isFrameworkNoise()` filter rejects allocator stacks, memory zones, `__DATA` sections, summary rows, and Foundation collection types from escalation, (2) non-object-shaped candidates (bracketed anonymous forms ` [size]`, byte-offset prefixes like `8600 bytes into ...`) are rejected, (3) proportional thresholds replace the flat `delta >= 5` rule (medium requires `delta >= max(5, kvoObservanceDelta * 0.5)`; high requires `delta >= max(50, kvoObservanceDelta * 5)`). On the notelet swapped-direction analysis the false-positive count dropped from 25 high-confidence-tagged classes to ~1 (AVPlayerItem alone, correctly). 7 new unit tests cover the three guards plus the existing classifier paths. ### Fixed - The notelet-followup post draft's claim that `analyzeAbandonedMemory` "tags AVPlayerItem with kvo-observer-orphaned at high confidence in one call" is now reproducible on v1.10 (was technically incorrect on v1.9 against the cited memgraphs; v1.9's parser left AVPlayerItem at rank ~82 and the over-broad classifier obscured it under 25 false positives). ## [1.9.0] - 2026-05-14 ### Added - **Security env flags: `MEMORYDETECTIVE_ALLOW_LAUNCH`, `MEMORYDETECTIVE_MAX_RECORDING_SECONDS`, `MEMORYDETECTIVE_TRACE_ROOT`.** `ALLOW_LAUNCH` gates `bootAndLaunchForLeakInvestigation`. The tool executes `xcodebuild` + `xcrun simctl launch` against caller-supplied paths and bundle ids; without the env var set to literally `"1"`, the tool returns `ok: false` with `state: "launchNotAllowed"` and a clear explanation rather than running. `MAX_RECORDING_SECONDS` caps `recordTimeProfile.durationSec` at the default 300s (configurable, bounded to 3600s hard ceiling) so an unattended agent cannot pile up multi-GB traces; over-cap requests throw with an actionable message. `TRACE_ROOT` becomes the default directory for `.trace` bundles when `recordTimeProfile.output` is a relative path (absolute paths bypass it, preserving v1.8 behavior); the directory is auto-created on first write. The same root will be the default scan path for the upcoming `cleanup_traces` tool. The `launchNotAllowed` state is a new value on the `LaunchState` union: agents that branch on `state` should add a case. 13 new unit tests in `src/runtime/securityFlags.test.ts` cover env-var parsing (defaults, strict literal-`1` for ALLOW_LAUNCH, bounds clamping for MAX_RECORDING_SECONDS, fallback on empty/invalid values for all three) plus the error-message helpers. - **`MEMORYDETECTIVE_REDACTION` env var: `balanced` / `strict` / `off`.** Output-scrubbing layer applied to every tool response at the formatter boundary. `balanced` (default) collapses home-directory absolute paths to `~/...` and masks token-shaped secrets (AWS access keys, GitHub classic + fine-grained PATs, Stripe live/test secrets, Slack tokens, Bearer auth headers). `strict` additionally masks hostnames, IPv4 addresses, and bundle identifiers (`com.example.app`). `off` disables redaction for local-only debugging. The active mode is logged once on server startup to stderr so an operator running `off` knows responses are unfiltered. Redaction is structural: object keys and non-string scalars (numbers, booleans, null) pass through; only string VALUES and string array items are rewritten, so the response schema is preserved. The host/IP regexes deliberately skip filename-with-extension patterns (`leak.memgraph`, `run.trace`) to avoid false positives on paths. 28 new unit tests cover the mode parsing, the three modes, recursion through objects/arrays, key preservation, scalar pass-through, and the once-per-instance log advisory. - **`outputFormat: "markdown" | "json" | "both"` on every analyzer.** Optional input field (omitted/`json` preserves v1.8 behavior). When `markdown`, the response is a human-readable view of the same data (H1 title, H2 per top-level field, markdown tables for arrays of uniform objects, bullet lists for scalars, inline JSON for deeply nested values). When `both`, the response carries TWO content items: markdown first (so a UI that picks `content[0]` gets the readable view), then JSON (so an agent looking for the structured data finds it). Useful when the agent wants to display markdown to the user AND parse JSON for the next call without a second round-trip. Applied to: analyzeMemgraph, analyzeTimeProfile, analyzeAllocations, analyzeAnimationHitches, analyzeHangs, analyzeAppLaunch, diffMemgraphs, analyzeAbandonedMemory. Shared formatter at `src/runtime/responseFormatter.ts` (generic JSON-to-markdown renderer with table detection, cell truncation, deep-nesting collapse) keeps the wiring DRY. 14 new unit tests cover all three modes + edge cases (empty arrays, null, deeply nested, long strings, table detection). - **`timeRangeMs: { startMs, endMs }` scoping on `analyzeHangs` and `analyzeAnimationHitches`.** Optional time-window filter that drops samples whose `startNs` falls outside the window. Lets the agent answer "what hangs happened during this 5-second user-visible jank?" without re-recording. Filter is applied post-parse, reusing the existing parsers. When the window matches zero rows, the response carries `status: "available"` and empty arrays (different from `not_present`, which means the table itself was missing from the trace). 2 new unit tests cover the windowed filter narrowing the baseline result set and the empty-window-still-available case. `analyzeTimeProfile` and `analyzeAllocations` deferred to a follow-up because the existing parsers don't expose per-row timestamps; the window would need a parser extension. - **`status` field (DataStatus taxonomy) on trace analyzers.** `analyzeTimeProfile`, `analyzeAllocations`, `analyzeAnimationHitches`, and `analyzeHangs` now return a `status` field disambiguating empty arrays into four cases: `available` (data was exported and parsed; empty arrays mean the trace genuinely had no rows for the section), `partial` (export started but did not finish), `not_exportable` (a GUI track exists in Instruments.app but `xcrun xctrace export` has no exportable schema for it; Apple-side limitation), and `not_present` (the requested table schema is not in the trace bundle at all). Agents should branch on this rather than collapsing all empty arrays into a generic "no data" signal. analyzeTimeProfile's existing SIGSEGV-on-xctrace-export path is now tagged `not_exportable`. Shared `DataStatus` type added to `src/types.ts`. Backwards-compatible: existing callers see a new required field on the response, TypeScript consumers that destructure or check the field continue to work; ad-hoc consumers that ignored the response shape are unaffected. 2 new unit tests cover the available + not_present cases. - **`analyzeAbandonedMemory(beforePath, afterPath)` tool.** New top-level MCP tool that diffs two `.memgraph` snapshots on heap reference-tree class counts (NOT cycle list) and classifies each grown class against a catalog of abandoned-memory shapes: `kvo-observer-orphaned`, `notificationcenter-observer-leaked`, `cache-too-aggressive`, `singleton-retains-payload`, `unknown-growth`. Each entry carries a `confidence` tier (`high` / `medium` / `low`) and a contextual `hint` pointing at the fix. The classifier escalates large co-occurrence growth: when `NSKeyValueObservance` grew, other classes with delta >= 5 are reclassified as `kvo-observer-orphaned` (medium for delta < 50, high for delta >= 50). The natural pair for the v1.8 verify-fix loop: `captureScenarioState({label:'before'})` -> ship fix -> `captureScenarioState({label:'after'})` -> `analyzeAbandonedMemory(before, after)`. Validated end-to-end on the notelet investigation 2026-05-12 where AVPlayerItem went 342 to 0 across a fix invisible in standard `leaks` output (`leakCount: 0` on both sides). 17 new unit tests cover the classifier catalog, co-occurrence escalation, topN slicing, classFilter substring, totals computation, and the empty/identity edge cases. Registered as the 32nd MCP tool (`[mg.memory]`). - **`analyzeMemgraph` surfaces abandoned-memory top classes when `leakCount` is 0.** Previously a clean leaks count gave `cycles: [], "No leaks detected."` and no further signal, even when the heap was retaining hundreds of orphaned objects via KVO observers, NotificationCenter handlers, or runaway caches. Now `analyzeMemgraph` invokes a second `leaks --referenceTree --groupByType --noContent` pass on the leakCount-0 path and populates a new `abandonedMemoryTop[]` field with the top N classes by live instance count. New optional input parameter `referenceTreeTopN` (default 20, set 0 to skip the second leaks invocation). Reference-tree parser at `src/parsers/referenceTree.ts` aggregates instance counts across the entire tree by class name, drops c-runtime allocator entries (malloc/calloc/realloc), and resolves `--> ClassName` arrows to the value type. 14 new unit tests cover size parsing, class-name extraction, aggregation, sort stability, topN slicing, malloc filtering, and empty-input cases. Surfaced from the notelet investigation 2026-05-12 where the pre-fix memgraph had `leakCount: 0` but 342 alive AVPlayerItem instances visible in the reference tree. Backwards-compatible: existing callers without `referenceTreeTopN` get the default and a new optional field; consumers that ignore the field continue to work unchanged. - **Catalog: `uikit.viewcontroller-retained-after-pop` pattern.** New cycle-catalog entry covering the case where a UIViewController subclass is alive in the heap but no `_parentViewController` / `_presentingViewController` edge appears in the cycle. The VC was popped from its navigation stack but a closure, Combine sink, NotificationCenter block, or KVO observation is still retaining it. DebugSwift surfaces the same shape via `dealloc` swizzle on-device; the catalog-side equivalent matches the heap residue. Confidence tiers: `high` when the root is a `*ViewController` AND a `Closure context` co-occurs (the classic shape), `medium` when only one of the two signals is present, `low` otherwise. Suppressed when explicit `_parentViewController` / `_presentingViewController` edges appear in the chain (the VC is still owned, the leak is elsewhere). 4 unit tests cover the HIGH match with closure, the MEDIUM match without closure, the parent-edge suppression, and the negative case (non-VC root). Fix-hint chains audit closures captured in `viewDidLoad`, `Task { }` blocks that outlive the screen, KVO observations that never `invalidate()`, and delegate properties declared without `weak`. - **Catalog: `swiftui.observable-write-on-every-render` pattern.** New cycle-catalog entry for the SwiftUI antipattern of mutating an `@Observable` (or `ObservableObject`) inside `body`, which triggers infinite re-render. Beyond the perf cost, the closure that mutates the observable usually captures the view's `self` (or an enclosing model), pinning a render-graph closure in the heap. DebugSwift detects the perf side via render-frequency analysis; the cycle catalog catches the heap shape it leaves behind. Match signals: `ObservationRegistrar` / `Observation._` / `ObservableObject` co-occurs with a SwiftUI view-graph class (`SwiftUI.ViewGraph`, `SwiftUI._GraphValue`, `SwiftUI._ViewList`, `ViewBodyAccessor`, `DynamicViewProperty`) AND a `Closure context`. Confidence tiers: `high` when all three coexist, `medium` for Observable + ViewGraph, `low` for Observable + Closure without an explicit view-graph signal. 4 unit tests cover each tier and the negative case (Observable alone). Fix-hint chains the canonical "compute in computed property, not in body" or "move side effects to .onChange/.task/.onAppear" guidance. - **`analyzeHangs`: optional `mainThreadViolations` enrichment.** Each top hang now carries an optional `mainThreadViolations: Array<{ kind: "sync-io" | "db-lock" | "network" | "lock-contention"; topFrame: string; samples: number }>` field populated when the caller supplies a supplemental `topFramesByHangStartNs: Record` map. Stringified `startNs` values are the keys so the map survives JSON round-trips. The pure classifier `classifyHangFrame(topFrame)` is exported for programmatic use and matches a four-category catalog inspired by DebugSwift's Thread Checker: `sync-io` (read/write/fsync, NSData blocking initializers, FileManager mutators), `db-lock` (SQLite mutex acquisition, NSPersistentStoreCoordinator lock, NSManagedObjectContext save), `network` (NSURLConnection sendSynchronousRequest, CFReadStreamRead, nw_connection_start/wait), and `lock-contention` (pthread / os_unfair_lock / dispatch_semaphore_wait / dispatch_sync / NSLock). When the frame is supplied but matches no signature, `mainThreadViolations` is set to `[]` (deliberate, "we looked and found nothing actionable"); when no frame is supplied for a given hang, the field stays `undefined`. The typical pipeline: call `analyzeTimeProfile` on the same trace, correlate samples to hang windows by timestamp, then re-call `analyzeHangs` with the resulting map. 11 new unit tests cover the four categories, the no-match null path, the samples threading, the map-key convention, and the three enrichment branches (matched, unmatched-frame, missing-frame). - **`detectLeaksInXCTest` tool: per-test leak gate for XCTest unit-test schemes.** Sibling to `detectLeaksInXCUITest`. Builds for testing, launches the test bundle with an optional `-only-testing:/[/]` filter, polls for the runner process (`xctest` by default, overridable via `processName` for app-hosted unit bundles), captures `.memgraph` baseline + after, diffs. Returns `passed: false` when new ROOT CYCLEs appear that are not in the `allowlistPatterns` list. Per-test granularity is achieved by calling the tool once per test method with different `testCaseFilter` values; aggregation stays on the caller side, keeping every response tied to a single well-defined before/after pair. When the runner exits before the after-capture window (common for fast unit tests with no host), the response carries an explicit `failureReason` pointing at the `tearDown` workaround. Registered as the 34th MCP tool (`[mg.ci]`). 15 unit tests cover schema validation (mutually-exclusive workspace/project, default `processName`, custom processName), the allowlist substring check, the descendant counter, and the new-cycles diff (no-change, new-leak, allowlist-matched, mixed, anonymous-root by address, chainLength inclusion). - **HTML report output for both `detectLeaks*` tools.** Added optional `outputHtmlPath` parameter. When set, the tool writes a self-contained HTML report (inline CSS, no external assets) with verdict pills (PASS/FAIL), baseline/after/delta stat blocks, a new-cycles table tagging each entry as allowlisted or failing, the run-log inside a collapsed `
` block, and full HTML-entity escaping so leaked class names cannot inject markup. The response gains an `htmlReportPath` field pointing at the same file. Template lives at `src/templates/leak-report.html` and is copied to `dist/templates/` by the build script. Designed for CI artifact upload + PR-comment bots that link directly to the artifact. 12 unit tests cover the renderer (PASS/FAIL pills, failureReason rendering, allowlist badges, HTML escaping against script/img injection, memgraph path embedding, steps block, multi-section ordering, subtitle, version/timestamp substitution, signed delta formatting) plus the file-write helper. - **CI recipe: "Add memorydetective to your CI in 5 minutes".** New README subsection under "CI / test integration" with a working `.github/workflows/leaks.yml` template that runs `detectLeaksInXCTest` and uploads the HTML report as an artifact. The same workflow is also available verbatim at `examples/ci/github-actions-leaks.yml`. Walkthrough covers Xcode pinning, simulator boot, allowlist pattern guidance (incl. `_TtC` Swift mangled prefixes), the iOS 18 runtime choice for the macOS 26.x regression, and `actions/cache` + `--skipBuild` for build reuse across chained invocations. - **`cleanupTraces` tool: preview and delete `.trace` bundles under `MEMORYDETECTIVE_TRACE_ROOT`.** New top-level MCP tool that walks the trace root, finds `.trace` directories produced by `recordTimeProfile`, and returns a sorted (oldest-first) list of candidates with `path`, `sizeMB`, and `ageDays`. `dryRun: true` by default so an accidental call previews instead of destroying. Pass `dryRun: false` to actually delete. Optional `olderThanDays: N` filters to bundles older than the threshold (useful for "delete anything older than a week" workflows); omitted, all bundles are considered regardless of age. **Scope is restricted to `MEMORYDETECTIVE_TRACE_ROOT` by default.** To clean up an arbitrary directory, pass `root: ` AND set `MEMORYDETECTIVE_ALLOW_EXTERNAL_CLEANUP=1` in the env; without it, the tool returns `ok: false` with the failure reason and deletes nothing (default-deny on destructive disk operations outside the configured boundary). The walker stops at the `.trace` directory boundary (does NOT descend INTO bundles) so xctrace's `Run1`, `Form1.template`, etc. inside a bundle are not misread as nested bundles. Solves the "trace root fills up after a few profiling sessions" problem that v1.8 left to manual `rm -rf`. 15 new unit tests cover the boundary check (sibling-prefix string false positives), age filter, dryRun preserve-on-disk, dryRun=false deletes, external-root guard, missing-root tolerance, and file/non-`.trace`-directory skipping. Registered as the 33rd MCP tool (`[ops]`). - **`recordTimeProfile` external timeout wrapper for the macOS 26.x `xctrace --time-limit` regression.** On some macOS 26.x simulator builds, `xctrace record` ignores `--time-limit` and runs indefinitely past its declared deadline. `recordTimeProfile` now wraps the invocation with a soft timeout at `durationSec + 30s` that sends `SIGINT` (so xctrace flushes the trace cleanly), waits up to 10s for graceful exit, then escalates to `SIGKILL` only if necessary. When the wrapper fires, the response gains `recordingTimedOut: true` and a structured `workaroundNotice` with `issue: "xctrace-time-limit-ignored"` listing concrete mitigations (iOS 18 sim runtime, shorter durations, simulator restart for partial-trace recovery). Previously the only outcomes were "xctrace exits cleanly" or "user kills the agent loop manually". Critically, `SIGTERM` (the previous default in `runCommand`) corrupts xctrace traces; this path explicitly uses `SIGINT` so the partial output remains parseable. `runCommand` in `src/runtime/exec.ts` gains two new options to support this: `timeoutSignal: NodeJS.Signals` (default `SIGTERM`, opt in to `SIGINT`) and `gracefulKillAfterMs: number` (default `0`, opt in to "resolve with `timedOut: true` instead of reject"). Default behavior preserved for all existing callers. 6 new unit tests in `src/runtime/exec.test.ts` cover the new paths. ### Changed - **Docs: macOS 26.x regression and the iOS 18 escape hatch documented prominently.** README gains a "Heads up for macOS 26.x users" callout in the Highlights section, naming the regression and the iOS 18 sim runtime workaround. USAGE.md Troubleshooting section now distinguishes `minimal-corpse` (relaunch with MallocStackLogging fixes) from `macos-26-task-for-pid-broken` (iOS 18 sim is the only reliable path), each with their own recovery checklist. Plus a note on the scheme-level Malloc Stack Logging toggle needed for Xcode's "View Memory Graph Hierarchy" on macOS 26.x. ### Added - **New `macos-26-task-for-pid-broken` workaround issue.** When `captureMemgraph` detects a `minimal-corpse` failure pattern AND the host is macOS 26.x (Darwin kernel 25.x), the workaround notice now upgrades the `issue` field from `minimal-corpse` to `macos-26-task-for-pid-broken`, swaps in a platform-aware message that names the Apple-side kernel regression as the root cause, and reorders the `fallbacks[]` to put the iOS 18 simulator runtime first. Adjusts `suggestedNextCalls` to chain into `recordTimeProfile` + `analyzeAllocations` on the new issue id the same way it does for `minimal-corpse`. Agents that branch on the issue id should add a case for the new value; the existing `minimal-corpse` branch continues to fire on non-macOS-26 hosts (older macOS, future macOS releases pending verification). 2 new unit tests cover the upgrade path and confirm `permission-denied` / `transient` are unaffected by the platform context. `classifyLeaksFailure` gains an optional `isMacOS26: boolean` parameter (defaults to `false`, so existing callers compile unchanged). - **Proactive macOS 26.x platform advisory.** `captureMemgraph`, `captureScenarioState`, and `bootAndLaunchForLeakInvestigation` now emit a one-time stderr banner and a structured `platformAdvisory` field on their response when running on macOS 26.x (Darwin kernel 25.x). The advisory documents Apple's `task_for_pid` kernel regression that blocks `leaks --outputGraph`, `heap`, and `xctrace --template Allocations` against simulator processes regardless of `MallocStackLogging=1`, and recommends an iOS 18 simulator runtime as the most reliable workaround. Set `MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1` to silence. New `src/runtime/platformCheck.ts` with 10 unit tests covering the helper. Reduces wasted time for users hitting the regression for the first time. Surfaced during the notelet investigation 2026-05-12 where three independent CLI memory-introspection paths failed before iOS 18 was identified as the working escape hatch. ### Fixed - **`replayScenario` and `captureScenarioState`: tap targets by `elementId` now resolve SwiftUI's `accessibilityIdentifier(_:)`.** The internal `normalizeAxeNode` in `src/runtime/axe.ts` previously only read the `AXIdentifier` key when populating `UIElement.identifier`, but `axe describe-ui` (and Apple's accessibility tree) emit the SwiftUI `accessibilityIdentifier` value under `AXUniqueId`. Result: every `tap` targeted by `elementId` failed with "Could not locate element matching ..." even when the element was present in the tree. Now reads both keys in order (`AXIdentifier` first, then `AXUniqueId`). Surfaced from the notelet investigation 2026-05-12 where 20 replay iterations all failed to find a SwiftUI Button identified by `.accessibilityIdentifier("present-button")`. 2 new unit tests cover the AXUniqueId path and the precedence case. ## [1.8.1] - 2026-05-13 Metadata-only release to enable submission to the official MCP Registry (`registry.modelcontextprotocol.io`). Adds the `mcpName` property to `package.json` so the registry can verify that the published npm package matches the registry submission metadata. ### Added - **`mcpName: "io.github.carloshpdoc/memorydetective"`** in `package.json`. Required by the MCP Registry to verify package ownership. Follows the `io.github./` convention mandated for GitHub-based authentication with `mcp-publisher`. ### Notes - No code changes, no API changes, no functional changes for existing consumers. - Existing v1.8.0 installs keep working unchanged. - The plugin's `^1.7` SPM-style range picks this up automatically; no plugin sync needed for this patch. ## [1.8.0] - 2026-05-06 `leaks --outputGraph` regressed on macOS 26.x and aborts with `Failed to get DYLD info for task` when the target was not launched with malloc-stack-logging. This release fixes that end to end. `captureMemgraph` detects the regression and emits a structured `workaroundNotice`, the new `bootAndLaunchForLeakInvestigation` tool absorbs build + boot + install + launch with `MallocStackLogging=1` so capture works on the first try, and `replayScenario` + `captureScenarioState` close the verify-fix loop with deterministic before/after snapshots. 28 -> 31 MCP tools, 213 -> 287 tests. ### Added - **`bootAndLaunchForLeakInvestigation` tool** (`[mg.build]`). Single-call orchestration: resolves a simulator (udid, name+os, or whichever is booted), runs `xcodebuild -showBuildSettings -json` to discover BUILT_PRODUCTS_DIR / WRAPPER_NAME / EXECUTABLE_NAME / PRODUCT_BUNDLE_IDENTIFIER, runs `xcodebuild build` (skippable), boots the simulator with `bootstatus -b` waiting for SpringBoard, installs the .app, and launches with `MallocStackLogging=1` propagated via the `SIMCTL_CHILD_*` prefix simctl honors. Returns the host PID + simulator UDID + bundle id ready to chain into `captureMemgraph`. Multi-simulator disambiguation via filtering `ps -Ao pid,command` by the target UDID's CoreSimulator path; long executable names that would silently miss `pgrep -x` (15-char comm truncation) work natively. - **`replayScenario` tool** (`[mg.scenario]`). Drives the iOS Simulator through tap / swipe / wait / type actions with a `repeat` count, useful for amplifying leaks that only manifest after N iterations of a navigation flow. Tap targets accept `label`, `elementId`, or explicit `coords`. Optional `finalUITreePath` writes the post-replay accessibility tree as JSON for the agent to verify the app ended where expected. Soft dependency on Cameron Cooke's [axe](https://github.com/cameroncooke/AXe) CLI: when missing, returns `ok:false` with a structured workaroundNotice pointing at `brew install cameroncooke/axe/axe` instead of throwing. - **`captureScenarioState` tool** (`[mg.scenario]`). Composite snapshot for verify-fix: writes a `.memgraph` + `.png` screenshot + `.ui.json` accessibility tree into `outputDir`, all prefixed by `label` (typically `before` / `after`). Sub-captures are best-effort: if leaks fails on macOS 26.x, the screenshot and UI tree still complete and the captureMemgraph workaroundNotice is surfaced via `memgraphWorkaroundNotice` so the agent can fall back to xctrace Allocations or Xcode manual export. `include` parameter lets the caller skip pieces (e.g. `["memgraph", "screenshot"]` when no UI tree is needed). - **Structured `workaroundNotice` on `captureMemgraph`**. New shape `{ issue, message, fallbacks[] }` with stable issue ids: `minimal-corpse` (the macOS 26.x DYLD info regression), `permission-denied` (task_for_pid failures), `leaks-not-found` (binary missing from PATH), `transient` (unrecognized non-zero exit). Single retry on transient only; deterministic issues skip the retry. New `warnings` field surfaces non-fatal observations (e.g. MallocStackLogging not active on the target). New `suggestedNextCalls` field points at `recordTimeProfile` (Allocations) + `analyzeAllocations` as a structured fallback when leaks cannot capture a memgraph. - **`troubleshooting` field on Playbook**. The `memgraph-leak` playbook documents the macOS 26.x minimal-corpse and permission-denied recovery paths inline with structured `{ tool, issueId, trigger, recovery[] }` entries the agent can branch on. Includes the Xcode manual export fallback for cases where every automated path fails. - **`runCommand` env support**. `src/runtime/exec.ts` now accepts an optional `env` parameter that merges on top of `process.env` (preserves PATH, DEVELOPER_DIR, HOME). Required to propagate `SIMCTL_CHILD_*` keys through to the simctl child. - **Internal infrastructure modules** (not exposed as MCP tools, supports the leak/perf workflow only). `src/runtime/buildSettings.ts` parses `xcodebuild -showBuildSettings -json` defensively (slicing between first `[` and last `]`, filtering targets by `WRAPPER_EXTENSION=app`). `src/runtime/simctl.ts` wraps `xcrun simctl` boot / bootstatus / install / launch / list / io screenshot with idempotent error handling. `src/runtime/axe.ts` wraps the axe CLI for UI tree introspection and tap/swipe/type, with normalized UIElement parsing across CGRect-string and AppKit-dictionary frame formats. ### Changed - README: new "What's new in v1.8" callout. Tool count `28 -> 31`. New macOS 26.x troubleshooting note. Examples gain a verify-fix loop combining `bootAndLaunchForLeakInvestigation` -> `captureScenarioState({label:"before"})` -> ship fix -> `captureScenarioState({label:"after"})` -> `diffMemgraphs`. - USAGE.md: documents the 3 new tools with concrete invocation examples plus the new `troubleshooting` field on the memgraph-leak playbook. - Test count: 213 -> 287 (74 new). 6 buildSettings parser, 12 simctl parsers, 16 boot-and-launch (schema + pickHostPidFromPs across multi-sim and long-name cases), 16 axe parsers (parseAxeDescribeUI, parseAxFrame, findElementByLabel, centerOf), 11 replayScenario (schema + resolveTapTarget), 13 captureScenarioState (schema + sanitizeLabel). ### Notes - No breaking changes for existing callers. `captureMemgraph` now returns `ok:false` with structured workaroundNotice on known issues instead of throwing, but consumers that read `result.ok` before `result.output` continue to work. - `output` field on `CaptureMemgraphResult` is now optional (present on success, absent on failure paths). Old code that destructured it without checking `ok` first will see `undefined` instead of a path, which surfaces the failure rather than silently using a broken value. - `axe` is a soft dependency. The plugin installs and runs without it. Only `replayScenario` and the `uiTree` sub-capture of `captureScenarioState` require it; both return structured install hints when axe is missing instead of failing hard. - The plugin's public surface stays scoped to leak/perf debug. UI primitives (`describeUI`, `tap`, `swipe`, `typeText`) live in `src/runtime/axe.ts` and are not registered as MCP tools, only `replayScenario` and `captureScenarioState` are exposed, both tied to the verify-fix workflow. ## [1.7.0] — 2026-05-03 Catalog grows from 33 to **34 patterns** (SwiftData `@Actor` cycle), every classification now ships a **`fixTemplate` field** with Swift before/after snippets the agent can adapt directly, and a new **`compareTracesByPattern` tool** does for `.trace` bundles what `verifyFix` does for memgraphs. 27 → 28 MCP tools. ### Added - **`swiftdata.modelcontext-actor-cycle`** cycle pattern. Fires when a `ModelContext` + `DefaultSerialModelExecutor` (or `ModelExecutor`) + `Actor` appear together in the chain. Apple-documented quirk (FB13844786) — fixed at the framework level in iOS 18 beta 1, but the user-code shape persists on older targets and on hand-rolled executors. Sourced from [Apple Developer Forums #748042](https://developer.apple.com/forums/thread/748042). Confidence-tiered: `high` when all three signals coexist, `medium` for ModelContext + Executor without an Actor in chain, `low` when only ModelContext is visible. - **`fixTemplate` field** on every `PatternMatch`. Each pattern now carries a Swift code snippet showing the typical before/after. The agent reads the template and adapts type/method names to the user's codebase via the SourceKit-LSP source-bridging tools. Implemented in `src/runtime/fixTemplates.ts` with a 1:1-coverage test guard against `PATTERNS`. Where `staticAnalysisHint` (v1.6) says *which* linter rule would catch a pattern, `fixTemplate` shows *what* the fix looks like in code. Both ride alongside the original textual `fixHint`. - **`compareTracesByPattern` tool** — trace-side counterpart to `verifyFix`. Takes a before/after pair of `.trace` bundles + a category (`hangs`, `animation-hitches`, or `app-launch`) + optional thresholds, and returns a PASS/PARTIAL/FAIL verdict plus before/after stats and deltas. Threshold semantics: hangs PASS when longest is below `hangsMaxLongestMs` (default 0); hitches PASS when longest is below `hitchesMaxLongestMs` (default 100ms — Apple's user-perceptible threshold); app-launch PASS when total is below `appLaunchMaxTotalMs` (default 1000ms). Designed for CI gating: a hangs-fix PR's before/after traces gate the merge. Tagged `[mg.trace][mg.ci]`. ### Changed - README: new "What's new in v1.7" callout. Pattern catalog count `33 → 34`. Tool count `27 → 28`. CI / test integration subsection grows from 1 to 2 tools. Resources section now lists 34 entries. The "Adding a cycle pattern" workflow gains a 4th step: add a `fixTemplate` entry alongside the `staticAnalysisHint`. - USAGE.md section 2 gains a v1.7 sub-table with the new SwiftData+Actor pattern + a paragraph explaining the new `fixTemplate` field with a concrete JSON example. Updated header note describes the per-pattern triple now returned: `fixHint` (prose), `staticAnalysisHint` (linter rule or gap), and `fixTemplate` (code). - Test count: 183 → 206 (23 new — 4 for the swiftdata pattern + edge cases, 8 for `fixTemplates` coverage and content, 11 for `compareTracesByPattern` verdict logic). ### Notes - No breaking changes — `fixTemplate` is an optional new field on `PatternMatch`. Old callers that ignore it continue to work. - Catalog now covers 34 distinct cycle shapes; the `fixTemplate` content is the most user-visible upgrade. Each template is intentionally minimal (just enough to demonstrate the shape of the fix); the agent fills in real type/method names from the surrounding code. - The `@ModelActor` recommendation in the SwiftData fix template only applies on iOS 17+ where the macro is available. The fallback (custom executor with weak ModelContext) is provided for older targets. ## [1.6.0] — 2026-05-03 Catalog grows from 27 to **33 patterns** (Swift 6 / Observation / SwiftData / NavigationStack era), the server adopts MCP **Resources** + **Prompts** beyond raw Tools, every classification now carries a `staticAnalysisHint` bridging to SwiftLint, and the `--version` drift bug from earlier is fixed. ### Added - **6 new cycle patterns** in `classifyCycle`, sourced from Apple Developer Forums (#736110, #716804, #748042, #22795), Swift Forums (#64584, #77257), Donny Wals on the Swift 6.2 `Observations` API, and the Embrace WKWebView memory-leak writeup: - `swiftui.observable-state-modal-leak` — `@Observable` model held as `@State` across modal presentation - `swiftui.navigationpath-stored-in-viewmodel` — `NavigationPath` retains every element ever pushed (FB11643551, unfixed) - `concurrency.async-sequence-on-self` — `for await ... in seq` pins self via the consuming Task; `[weak self]` does NOT help - `concurrency.notificationcenter-async-observer-task` — special case of the above for `NotificationCenter.notifications(named:)` - `swiftui.observations-closure-strong-self` — Swift 6.2 `Observations { }` closure retains self like `Combine.sink` - `webkit.wkscriptmessagehandler-bridge` — handler ↔ webview ↔ contentController 3-link bridge cycle - **MCP Resources surface** — all 33 catalog patterns are now browsable as MCP resources at `memorydetective://patterns/{patternId}`. Each resource is a markdown body. Implemented in `src/runtime/resources.ts`. `resources/list` returns all 33; `resources/read` resolves any pattern URI to its markdown body. - **MCP Prompts surface** — 5 investigation playbooks exposed as MCP prompts (slash commands in clients that surface them, e.g. Claude Code): `/investigate-leak`, `/investigate-hangs`, `/investigate-jank`, `/investigate-launch`, `/verify-cycle-fix`. Each prompt fills the canonical playbook's argument templates with user-provided values and hands the agent a ready-to-execute brief. Implemented in `src/runtime/prompts.ts`. - **`staticAnalysisHint` field** on every `PatternMatch` — bridges runtime evidence to static analysis. Per-pattern entries point at the SwiftLint rule that would catch this at parse time (`weak_self`, `weak_delegate`) OR explicitly note the gap (with a link to e.g. SwiftLint #776 for `@escaping` retain cycles, or Swift Forums #64584 for AsyncSequence). The `swiftui.tag-index-projection` original-investigation pattern explicitly notes "no rule exists; this is a SwiftUI-internal observation issue, not a closure-capture issue." Implemented in `src/runtime/staticAnalysisHints.ts` with a 1:1-coverage test guard against `PATTERNS`. ### Fixed - `memorydetective --version` now reports the actually-installed version. Previously the CLI string was hardcoded (last bumped to `"1.4.0"` in v1.4.0; never bumped for `1.5.0`). The MCP server's `SERVER_VERSION` was even staler — it had been `"0.1.0-dev"` since the v1.0.0 release. Both surfaces now read from `package.json` at runtime via `src/version.ts`, so they can never drift again. (Originally caught while dogfooding the new release script — bundled into 1.6.0 since 1.5.x didn't ship.) ### Changed - README: new "What's new in v1.6" callout. New "Resources (33)" and "Prompts (5)" subsections in the API section. The opening API line now reads "27 MCP tools + 33 Resources + 5 Prompts" instead of "27 MCP tools". Pattern-count and tool-description cells updated. - USAGE.md section 2 gains a v1.6 sub-table with the 6 new patterns + a paragraph explaining the new `staticAnalysisHint` field. New section 7 ("MCP Resources + Prompts") documents the catalog-as-resources and slash-command surfaces; the old section 7 is renumbered to 8. - Release process is now automated: `scripts/release.sh` orchestrates preflight → build/test → tag → npm publish → GitHub Release in one command. `.github/workflows/release.yml` re-validates on every `vX.Y.Z` tag push (build, tests, version match, CLI smoke). See the maintainer-facing checklist for the full process. - Test count: 152 → 183 (31 new — 14 for resources + prompts, 10 for the 6 new patterns + edge cases, 7 for the static-analysis-hint coverage guard). ### Notes - No breaking changes — all additions are catalog entries or new optional fields. Old callers that ignore Resources/Prompts continue to work. - The `webkit.wkscriptmessagehandler-bridge` pattern is intentionally additive: it fires *alongside* the broader v1.4 `webkit.scriptmessage-handler-strong` pattern when all three signals (WKWebView + WKUserContentController + handler/bridge class) coexist. Different fix templates apply to each — the new specific one tells you to wrap in a `WeakScriptMessageHandler` proxy; the old broad one just notes that `WKUserContentController.add(_:name:)` retains strongly. - Catalog now covers 33 distinct cycle shapes across SwiftUI (incl. Swift 6 / `@Observable` / SwiftData / NavigationStack), Combine, Swift Concurrency (incl. AsyncSequence), UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. ## [1.5.0] — 2026-05-02 Catalog completion + cost transparency. **24 → 27 patterns** (Core Animation animation/layer delegate quirks, Core Data `NSFetchedResultsController`), and the README now documents what `memorydetective` saves you in tokens and developer time, including the cases where the win is marginal. ### Added - **3 new cycle patterns** in `classifyCycle`, completing the catalog triage from the v1.4 research review: - `coreanimation.animation-delegate-strong` — `CAAnimation.delegate` is **strong** (Apple-documented quirk). Catches `CABasicAnimation`, `CAKeyframeAnimation`, `CASpringAnimation`, `CAAnimationGroup`, `CATransition`. Fix hint: use a `WeakProxy` delegate or set `anim.delegate = nil` in `deinit`. - `coreanimation.layer-delegate-cycle` — Custom `CALayer` subclass (`CAShapeLayer`, `CAGradientLayer`, `CAEmitterLayer`, `CAMetalLayer`, etc.) wired to a non-UIView delegate. UIKit's auto-weak pairing only protects `UIView`-owned layers. Confidence is `high` when the cycle has no `UIView`, `medium` otherwise; plain `CALayer + UIView` is treated as normal pairing and skipped to avoid false positives. - `coredata.fetchedresultscontroller-delegate` — `NSFetchedResultsController` (and the private `_PFFetchedResultsController`) historically retained its delegate via the change-tracking machinery. Fix hint: clear `frc.delegate = nil` in `viewWillDisappear`/`deinit` or store behind a `WeakFRCDelegate` proxy. - **README "What it saves you" section** — concrete token-cost and developer-time comparison for a real-world retain-cycle investigation, with explicit acknowledgement of when the win is marginal (tiny memgraphs, one-shot lookups, first-time investigations on a new codebase). Anonymized numbers from a real investigation, not synthetic. ### Changed - `USAGE.md` section 2 now lists all 27 patterns, grouped by release wave (v1.0 core / v1.4 expansion / v1.5 completion). Previously stale at "8 cycle patterns" since v1.4 — fixed in this release. - README "Adding a cycle pattern" section updated for the current catalog shape and dropped the outdated "v0.2 catalog repo" plan (the catalog stayed in-process via `PATTERNS`, that aspirational split never happened). - Test count: 144 → 152 (8 new tests covering the 3 v1.5 patterns and the catalog count assertion). ### Notes - No breaking changes — all additions are catalog entries plus documentation. - The 27-pattern catalog now covers SwiftUI, Combine, Swift Concurrency, UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm — broadly the leak families that account for ~95% of real-world iOS retain cycles per the FBRetainCycleDetector + SwiftLint + Apple-docs research review. ## [1.4.0] — 2026-05-01 Deeper diagnostics. Catalog **triples** in size (8 → 24 patterns), cycles report transitive impact, and a new `verifyFix` tool gates fixes in CI by classifier-aware diff. 26 → 27 tools. ### Added - **16 new cycle patterns** in `classifyCycle`. Sourced from Apple developer docs, FBRetainCycleDetector heuristics, SwiftLint rules, and well-known community references. Each pattern carries a fix hint and a confidence tier. **UIKit / Foundation:** - `timer.scheduled-target-strong` — `Timer.scheduledTimer(target:selector:)` retains its target - `displaylink.target-strong` — `CADisplayLink` retains its target - `gesture.target-strong` — `UIGestureRecognizer` / `UIControl` `addTarget` retains - `kvo.observation-not-invalidated` — `NSKeyValueObservation` retains its change handler - `urlsession.delegate-strong` — `URLSession` strongly retains its delegate (Apple-documented) - `dispatch.source-event-handler-self` — `DispatchSource.setEventHandler` retains the closure - `notificationcenter.observer-not-removed` — block-form observer never deregistered - `delegate.strong-reference` — `var delegate: Foo?` declared without `weak` **SwiftUI / Combine / Concurrency:** - `swiftui.envobject-back-reference` — `@EnvironmentObject` with back-reference to UIView/UIViewController - `combine.assign-to-self` — `.assign(to: \\.x, on: self)` retains self - `concurrency.task-mainactor-view` — `Task { await self.foo() }` inside a SwiftUI View - `concurrency.asyncstream-continuation-self` — `AsyncStream` consumer never cancelled **WebKit / Architecture / Third-party:** - `webkit.scriptmessage-handler-strong` — `WKUserContentController` retains the message handler - `coordinator.parent-strong-back-reference` — Coordinator pattern: child holds parent strongly - `rxswift.disposebag-self-cycle` — RxSwift DisposeBag + method reference armadilha - `realm.notificationtoken-retained` — Realm `NotificationToken` retains the change closure - **`verifyFix` tool** — cycle-semantic diff. Classifies both before/after `.memgraph` snapshots and emits a per-pattern `PASS` / `PARTIAL` / `FAIL` verdict plus bytes freed and instances released. Use as a CI gate: pass `expectedPatternId` and check `expectedPatternVerdict === "PASS"`. - **Transitive bytes per cycle.** `analyzeMemgraph`'s `CycleSummary` now includes `transitiveBytes` (sum of `instanceSize` across reachable nodes) and `transitiveInstanceCount`. Useful for prioritization: "breaking this one frees 8.2 MB" vs "this one frees 200 bytes". ### Changed - `classifyReport` (pure function) now also returns `classNamesByIndex` so callers can build typed suggestions without re-walking the cycle forest. (Internal — already used by v1.3.0's `suggestedNextCalls` plumbing.) - README + USAGE.md updated to reflect the 24-pattern catalog and new `verifyFix` tool. - Tool count badge: 26 → 27. ### Notes - No breaking changes — new fields are additive, all new patterns are classifier additions. - The catalog is now broad enough to cover the Foundation/UIKit/Combine/Concurrency/SwiftUI/WebKit/RxSwift/Realm leak families that account for ~95% of real-world iOS retain cycles per the research review (FBRetainCycleDetector + SwiftLint + Apple docs). ## [1.3.1] — 2026-05-01 ### Added - USAGE.md: new section 6 "Pipeline awareness" documenting `suggestedNextCalls` (with a full JSON example), `getInvestigationPlaybook` (with the five playbook kinds), and the tool-description tag taxonomy. Establishes the workflow norm: every release ships with USAGE updates. - USAGE.md section 4: row for `getInvestigationPlaybook` added at the top of the follow-up requests table. - README API section: opening paragraph documents the namespace tags + `suggestedNextCalls` mechanism. ### Changed - No code changes from `1.3.0` — this is a documentation catch-up release. ## [1.3.0] — 2026-05-01 Pipeline-aware release. Addresses real-user feedback that the Swift tools were "an attachment" rather than part of the investigation chain. **Discovery is now data, not inference.** 25 → 26 tools. ### Added - **`suggestedNextCalls` field** on `analyzeMemgraph`, `classifyCycle`, `findRetainers`, and `reachableFromCycle` results. Each entry is a typed `{ tool, args, why }` triple with pre-populated arguments based on the current result. The orchestrating LLM can chain calls without re-reasoning over the response. - `analyzeMemgraph` → suggests `classifyCycle` + `reachableFromCycle`. - `classifyCycle` → suggests `swiftSearchPattern` (with a regex pre-translated from the matched pattern) + `swiftGetSymbolDefinition` (with the cycle's app-level class name extracted). - `findRetainers` → suggests `swiftGetSymbolDefinition` for the class. - `reachableFromCycle` → suggests `swiftGetSymbolDefinition` + `swiftFindSymbolReferences` for the dominant app-level class. - **`getInvestigationPlaybook` meta-tool** — returns a versioned, declarative pipeline for a known investigation kind. Five playbooks shipped: `memgraph-leak`, `perf-hangs`, `ui-jank`, `app-launch-slow`, `verify-fix`. Use this once at the start of an investigation to give a fresh agent the canonical sequence without rediscovering it. - **Tool-name namespaces in descriptions**: every tool description now opens with a category tag (`[mg.memory]`, `[mg.trace]`, `[mg.code]`, `[mg.log]`, `[mg.discover]`, `[mg.render]`, `[mg.ci]`, `[meta]`). Makes related tools visible as a group at a glance, especially when the agent is browsing the deferred-tools list. - **Pipeline lines in key tool descriptions** (`analyzeMemgraph` and `classifyCycle`): each description ends with a "Pipeline: → X (purpose) → Y (purpose)" sentence. Even when the agent only reads the description (not the result), the chain is visible. - New `src/runtime/suggestions.ts` helper module so multiple tools agree on the same heuristics for "which class is most actionable" and "which followup is most useful". ### Changed - No breaking changes. All `suggestedNextCalls` fields are optional; old callers that ignore them continue to work. - `classifyReport` (pure function) now also returns `classNamesByIndex` so the caller can build typed suggestions without re-walking the cycle forest. ### Notes - Inspired by the HATEOAS pattern (Hypermedia as the Engine of Application State) — each response telegraphs the next valid actions. Keeps tool boundaries clean while making the workflow self-documenting. ## [1.2.1] — 2026-05-01 ### Added - README: new fourth example "End-to-end: leak → file → fix suggestion" walking through the complete chat-driven workflow with v1.2's Swift source-bridging tools. - USAGE.md: section 4 ("Common follow-up requests") expanded with prompts that exercise `swiftGetSymbolDefinition`, `swiftFindSymbolReferences`, `swiftGetSymbolsOverview`, `swiftGetHoverInfo`, `swiftSearchPattern`. New `reachableFromCycle` row added. - USAGE.md: section 3 ("How fixes flow") rewritten to reflect the new responsibility split — `memorydetective` now covers diagnose **and** source bridging; the agent owns "decide and apply the edit". ### Changed - USAGE.md concrete end-to-end example replaced with a richer 9-step flow that exercises memgraph analysis + `reachableFromCycle` + Swift LSP tools end-to-end. ### Notes - Doc-only release. No code changes from `1.2.0`. ## [1.2.0] — 2026-05-01 Swift source-bridging. The agent can now go from "found a leak in the cycle" to "find the file/line in this project" without leaving chat. 20 → 25 tools. ### Added - **5 Swift source-bridging tools** backed by a `sourcekit-lsp` subprocess pool: - `swiftGetSymbolDefinition` — locate a class/struct/enum/etc. declaration. Pre-scans candidate paths with a fast regex, then asks SourceKit-LSP for jump-to-definition. - `swiftFindSymbolReferences` — every reference to a Swift symbol via `textDocument/references`. Includes a `needsIndex` hint when the IndexStoreDB is missing. - `swiftGetSymbolsOverview` — top-level symbols in a file (cheap orientation when landing in a new file). - `swiftGetHoverInfo` — type info / docs at a position. Useful to disambiguate class vs struct `self` captures. - `swiftSearchPattern` — pure regex search over a Swift file (no LSP, no index). Catches closure capture lists and other patterns LSP can't see. - **`src/runtime/sourcekit/` infrastructure**: `client.ts` (LSP subprocess + JSON-RPC stdio via `vscode-jsonrpc`), `pool.ts` (per-project-root client pool with 5-minute idle shutdown), `protocol.ts` (typed wrappers for the LSP methods we use, using `vscode-languageserver-protocol` types). - New deps: `vscode-jsonrpc`, `vscode-languageserver-protocol`. Both MIT. - 13 new unit tests for the Swift tools (mostly `searchPattern` + helper coverage; LSP-backed tools require a live SourceKit-LSP and are smoke-tested out-of-band). ### Notes - The Swift tools require macOS + a full Xcode install (`xcrun sourcekit-lsp` must be available). Command Line Tools alone is not enough. - For cross-file references, the project needs an `IndexStoreDB` at `/.build/index/store`. Build it with `swift build -Xswiftc -index-store-path -Xswiftc /.build/index/store`. - `sourcekit-lsp` cold start is ~2s; the pool amortizes that across calls within a project. ## [1.1.0] — 2026-05-01 Response-size + cycle-scoped queries + license switch + first-run engagement. ### Added - **`reachableFromCycle` tool** — cycle-scoped reachability + per-class counting. Pick a cycle by `cycleIndex` or `rootClassName` substring, get instance counts of every class reachable from that cycle root. Distinguishes the actual culprit (the cycle root) from its retained dependencies. API shape inspired by Meta's `memlab` predicate-based queries. Tool count: 19 → 20. - **`verbosity` parameter** on `analyzeMemgraph`, `findCycles`, and `reachableFromCycle`. Three levels: `compact` (default — drops module prefixes, collapses nested SwiftUI `ModifiedContent` into `+N modifiers`, truncates deep generics with a hash placeholder), `normal` (lighter shortening, depth preserved), `full` (Swift demangled names verbatim). - **`maxClassesInChain` parameter** on `analyzeMemgraph` (default 10). Caps the per-cycle `classesInChain` array to the top N unique classes ranked by occurrence count, with app-level types prioritized over SwiftUI internals. The full unique-class total is reported in a new `classesInChainTotal` field for context. - **Class-name shortener** (`src/parsers/shortenClassName.ts`) — three layers (drop modules, collapse `ModifiedContent` chains, truncate deep generics) with deterministic hash placeholders so the same nested type produces the same short code across runs (useful for diffing two memgraphs). - **First-run CLI banner** — first time `memorydetective` runs in non-JSON mode on a machine, prints a one-time message pointing at the GitHub repo, `USAGE.md`, and the sponsor link. Marker stored at `~/.config/memorydetective/seen` so it never re-prints. - **Help / output footers** — `--help` and `analyze`/`classify` non-JSON output append a discreet `# ⭐ github.com/carloshpdoc/memorydetective` line. JSON output is untouched (won't break pipes / CI). ### Changed - **License: MIT → Apache 2.0.** Permissions are unchanged for users (commercial use, modification, distribution all allowed). Apache 2.0 adds an explicit patent grant + a `NOTICE` file mechanism; the `NOTICE` file ships in the npm tarball and surfaces project attribution in downstream "About" / acknowledgements screens. - `analyzeMemgraph` default response is now ~80% smaller for SwiftUI-heavy memgraphs (the per-cycle `classesInChain` no longer floods with hundreds of demangled SwiftUI generics; class names per node compress from 1000+ chars to ~200). Full-fidelity output is still available via `verbosity: "full"`. - README: license badge updated to Apache 2.0; tool count updated 19 → 20. ### Notes - No breaking changes — all new parameters have sensible defaults. Old callers continue to work; they just receive smaller, more readable responses. ## [1.0.1] — 2026-05-01 ### Added - `USAGE.md` walkthrough covering the three usage modes (CLI, `--json`, MCP), the 8 cycle patterns and their fix hints, the end-to-end flow of how fixes go from diagnosis to a code edit (memorydetective diagnoses; the LLM agent applies the edit using its own code-editing tools), common follow-up prompts, and troubleshooting. README links to it from the Quickstart pointer line. - `USAGE.md` is included in the npm tarball (added to `package.json` `files` whitelist). ### Changed - No code changes from `1.0.0` — this is a documentation bump. ## [1.0.0] — 2026-05-01 First public release. **19 MCP tools** for iOS leak hunting and performance investigation, plus a thin CLI mode for scripting and CI. ### Tools **Read & analyze (12)** — `analyzeMemgraph`, `findCycles`, `findRetainers`, `countAlive`, `diffMemgraphs`, `classifyCycle`, `analyzeHangs`, `analyzeAnimationHitches`, `analyzeTimeProfile`, `analyzeAllocations`, `analyzeAppLaunch`, `logShow`. **Capture / record (3)** — `recordTimeProfile`, `captureMemgraph`, `logStream`. **Discover (2)** — `listTraceDevices`, `listTraceTemplates`. **Render (1)** — `renderCycleGraph` (Mermaid + Graphviz DOT). **CI / test integration (1, experimental)** — `detectLeaksInXCUITest`. ### Cycle classifier `classifyCycle` ships with an in-process catalog of 8 cycle patterns: - `swiftui.tag-index-projection` - `swiftui.dictstorage-weakbox-cycle` - `swiftui.foreach-state-tap` - `closure.viewmodel-wrapped-strong` - `viewcontroller.uinavigationcontroller-host` - `combine.sink-store-self-capture` - `concurrency.task-without-weak-self` - `notificationcenter.observer-strong` Each pattern carries a one-line fix hint; matches are surfaced with high/medium confidence. ### CLI mode The `memorydetective` binary doubles as a thin CLI: ```bash memorydetective analyze [--json] memorydetective classify [--json] memorydetective --help memorydetective --version ``` When called with no arguments it starts the MCP server over stdio. ### Quality - **89 unit tests** across parsers and tools. - Stress test guards against accidental O(n²) regressions on large memgraphs. - CI runs Ubuntu Node 20+22 plus macOS smoke on every push. - Strict zod input validation across every tool. ### Known limits - **`analyzeTimeProfile`** is fragile: `xcrun xctrace export` of the `time-profile` schema crashes (SIGSEGV) on heavy unsymbolicated traces. The tool returns a structured workaround notice. Hangs analysis (`analyzeHangs`) is unaffected. - **`captureMemgraph`** does not work on physical iOS devices — `leaks(1)` only attaches to processes on the local Mac (which includes iOS simulators). Memory Graph capture from a physical device still requires Xcode. - **`detectLeaksInXCUITest`** is flagged experimental: orchestration logic is implemented but not yet validated against a wide set of production XCUITest runs. [Unreleased]: https://github.com/carloshpdoc/memorydetective/compare/v1.13.0...HEAD [1.13.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.12.0...v1.13.0 [1.12.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.11.0...v1.12.0 [1.11.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.10.0...v1.11.0 [1.10.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.9.0...v1.10.0 [1.9.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.8.1...v1.9.0 [1.4.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.3.1...v1.4.0 [1.3.1]: https://github.com/carloshpdoc/memorydetective/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/carloshpdoc/memorydetective/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/carloshpdoc/memorydetective/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/carloshpdoc/memorydetective/releases/tag/v1.0.0