memorydetective 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +76 -34
  3. package/USAGE.md +112 -41
  4. package/dist/index.js +43 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/runtime/axe.d.ts +86 -0
  7. package/dist/runtime/axe.js +249 -0
  8. package/dist/runtime/axe.js.map +1 -0
  9. package/dist/runtime/buildSettings.d.ts +27 -0
  10. package/dist/runtime/buildSettings.js +88 -0
  11. package/dist/runtime/buildSettings.js.map +1 -0
  12. package/dist/runtime/exec.d.ts +8 -1
  13. package/dist/runtime/exec.js +8 -2
  14. package/dist/runtime/exec.js.map +1 -1
  15. package/dist/runtime/fixTemplates.d.ts +27 -0
  16. package/dist/runtime/fixTemplates.js +757 -0
  17. package/dist/runtime/fixTemplates.js.map +1 -0
  18. package/dist/runtime/simctl.d.ts +68 -0
  19. package/dist/runtime/simctl.js +194 -0
  20. package/dist/runtime/simctl.js.map +1 -0
  21. package/dist/runtime/staticAnalysisHints.js +8 -0
  22. package/dist/runtime/staticAnalysisHints.js.map +1 -1
  23. package/dist/tools/bootAndLaunchForLeakInvestigation.d.ts +166 -0
  24. package/dist/tools/bootAndLaunchForLeakInvestigation.js +367 -0
  25. package/dist/tools/bootAndLaunchForLeakInvestigation.js.map +1 -0
  26. package/dist/tools/captureMemgraph.d.ts +29 -1
  27. package/dist/tools/captureMemgraph.js +148 -6
  28. package/dist/tools/captureMemgraph.js.map +1 -1
  29. package/dist/tools/captureScenarioState.d.ts +77 -0
  30. package/dist/tools/captureScenarioState.js +159 -0
  31. package/dist/tools/captureScenarioState.js.map +1 -0
  32. package/dist/tools/classifyCycle.d.ts +7 -0
  33. package/dist/tools/classifyCycle.js +31 -0
  34. package/dist/tools/classifyCycle.js.map +1 -1
  35. package/dist/tools/compareTracesByPattern.d.ts +112 -0
  36. package/dist/tools/compareTracesByPattern.js +312 -0
  37. package/dist/tools/compareTracesByPattern.js.map +1 -0
  38. package/dist/tools/detectLeaksInXCUITest.d.ts +2 -2
  39. package/dist/tools/getInvestigationPlaybook.d.ts +15 -0
  40. package/dist/tools/getInvestigationPlaybook.js +24 -1
  41. package/dist/tools/getInvestigationPlaybook.js.map +1 -1
  42. package/dist/tools/replayScenario.d.ts +243 -0
  43. package/dist/tools/replayScenario.js +187 -0
  44. package/dist/tools/replayScenario.js.map +1 -0
  45. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,55 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.8.0] - 2026-05-06
10
+
11
+ `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.
12
+
13
+ ### Added
14
+
15
+ - **`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.
16
+ - **`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.
17
+ - **`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).
18
+ - **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.
19
+ - **`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.
20
+ - **`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.
21
+ - **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.
22
+
23
+ ### Changed
24
+
25
+ - 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`.
26
+ - USAGE.md: documents the 3 new tools with concrete invocation examples plus the new `troubleshooting` field on the memgraph-leak playbook.
27
+ - 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).
28
+
29
+ ### Notes
30
+
31
+ - 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.
32
+ - `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.
33
+ - `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.
34
+ - 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.
35
+
36
+ ## [1.7.0] — 2026-05-03
37
+
38
+ 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.
39
+
40
+ ### Added
41
+
42
+ - **`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.
43
+ - **`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`.
44
+ - **`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]`.
45
+
46
+ ### Changed
47
+
48
+ - 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`.
49
+ - 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).
50
+ - Test count: 183 → 206 (23 new — 4 for the swiftdata pattern + edge cases, 8 for `fixTemplates` coverage and content, 11 for `compareTracesByPattern` verdict logic).
51
+
52
+ ### Notes
53
+
54
+ - No breaking changes — `fixTemplate` is an optional new field on `PatternMatch`. Old callers that ignore it continue to work.
55
+ - 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.
56
+ - 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.
57
+
9
58
  ## [1.6.0] — 2026-05-03
10
59
 
11
60
  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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # memorydetective
2
2
 
3
- > Diagnose iOS retain cycles and performance regressions from your chat window no Xcode required.
3
+ > Diagnose iOS retain cycles and performance regressions from your chat window. No Xcode required.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/memorydetective.svg)](https://www.npmjs.com/package/memorydetective)
6
6
  [![CI](https://github.com/carloshpdoc/memorydetective/actions/workflows/ci.yml/badge.svg)](https://github.com/carloshpdoc/memorydetective/actions/workflows/ci.yml)
@@ -13,11 +13,13 @@
13
13
 
14
14
  ## Highlights
15
15
 
16
- - **CLI-driven leak hunting.** Read `.memgraph` files captured by Xcode (or by `memorydetective` itself on simulators), find ROOT CYCLEs, classify them against known SwiftUI/Combine patterns, and get a one-liner fix hint all from a script or a chat.
16
+ - **CLI-driven leak hunting.** Read `.memgraph` files captured by Xcode (or by `memorydetective` itself on simulators), find ROOT CYCLEs, classify them against known SwiftUI/Combine patterns, and get a one-liner fix hint. All from a script or a chat.
17
17
  - **MCP-native.** Plugs into Claude Code, Claude Desktop, Cursor, Cline, and any other MCP client. The agent drives the full investigate → classify → suggest-fix loop without you opening Instruments.
18
18
  - **Honest about its limits.** No mocked outputs, no over-promises. Hangs analysis works clean from `xctrace`; sample-level Time Profile is parsed when `xctrace` symbolicates the trace and returns a structured workaround notice when it can't (the underlying `xctrace` SIGSEGV on heavy unsymbolicated traces is an Apple-side limitation we surface explicitly). Memory Graph capture works on Mac apps and iOS simulator; physical iOS devices still need Xcode.
19
19
 
20
- > **What's new in v1.6** (2026-05-03): catalog grew from 27 to **33 cycle patterns** (Swift 6 / `@Observable` / SwiftData / NavigationStack / async-sequence-on-self / WKScriptMessageHandler bridge), the server now exposes the catalog as **33 MCP Resources** + ships **5 investigation Prompts** (`/investigate-leak`, `/investigate-hangs`, `/investigate-jank`, `/investigate-launch`, `/verify-cycle-fix`), and every cycle classification now carries a `staticAnalysisHint` pointing at the SwiftLint rule (or explicit gap) that complements the runtime evidence. Full notes in [CHANGELOG](./CHANGELOG.md).
20
+ > **What's new in v1.8** (2026-05-06): `leaks --outputGraph` regressed on macOS 26.x and aborts unless the target was launched with `MallocStackLogging=1`. This release fixes that end to end. `captureMemgraph` now emits a structured `workaroundNotice` when the regression hits, the new **`bootAndLaunchForLeakInvestigation`** tool absorbs build + boot + install + launch with the right env vars so capture works on the first try, and **`replayScenario`** + **`captureScenarioState`** close the verify-fix loop with deterministic before/after snapshots (memgraph + screenshot + UI tree, all labeled). 28 31 MCP tools, 213 → 287 tests.
21
+ >
22
+ > **Also recent**: v1.7 (2026-05-03) added the 34th cycle pattern + `fixTemplate` snippets + `compareTracesByPattern` for trace-side CI gating. v1.6 (same day) added MCP Resources + Prompts. Full notes in [CHANGELOG](./CHANGELOG.md).
21
23
 
22
24
  ## Quickstart
23
25
 
@@ -53,10 +55,10 @@ memorydetective classify ~/Desktop/myapp.memgraph
53
55
 
54
56
  ### Diagnose a leak from a memgraph
55
57
 
56
- > **You:** I just exported a memory graph at `~/Desktop/example.memgraph`. Something's leaking after I close a detail screen what is it?
58
+ > **You:** I just exported a memory graph at `~/Desktop/example.memgraph`. Something's leaking after I close a detail screen. What is it?
57
59
  >
58
60
  > **Claude:** *(calls `analyzeMemgraph` → `classifyCycle`)*
59
- > Found 4 ROOT CYCLE blocks across 60,436 leaks. The dominant one matches **swiftui.tag-index-projection** with high confidence a closure attached via `.tag()` on a photo carousel `ForEach` is capturing `self` strongly through `_viewModel.wrappedValue`, which keeps `DetailViewModel` and the navigation coordinator alive after dismissal.
61
+ > Found 4 ROOT CYCLE blocks across 60,436 leaks. The dominant one matches **swiftui.tag-index-projection** with high confidence. A closure attached via `.tag()` on a photo carousel `ForEach` is capturing `self` strongly through `_viewModel.wrappedValue`, which keeps `DetailViewModel` and the navigation coordinator alive after dismissal.
60
62
  >
61
63
  > **Suggested fix:** replace `[weak self]` capture in the tap handler with a static helper, or weak-capture the view-model via `[weak coord = self.coordinator]`. Want me to find the exact file?
62
64
 
@@ -65,14 +67,36 @@ memorydetective classify ~/Desktop/myapp.memgraph
65
67
  > **You:** I shipped the fix. Compare `~/Desktop/before.memgraph` to `~/Desktop/after.memgraph` and tell me whether `DetailViewModel` count dropped.
66
68
  >
67
69
  > **Claude:** *(calls `diffMemgraphs` → `countAlive`)*
68
- > `DetailViewModel`: **12 → 0** (delta `-12`). The `swiftui.tag-index-projection` cycle is gone. Total leak count fell from 60,436 to 55,576 (−4,860). Three other ROOT CYCLEs persist — they're SwiftUI internals, not your code.
70
+ > `DetailViewModel`: **12 → 0** (delta `-12`). The `swiftui.tag-index-projection` cycle is gone. Total leak count fell from 60,436 to 55,576 (−4,860). Three other ROOT CYCLEs persist. They're SwiftUI internals, not your code.
71
+
72
+ ### Self-contained verify-fix loop (v1.8)
73
+
74
+ When `leaks --outputGraph` fails on macOS 26.x (`Failed to get DYLD info for task`), the cause is usually that the app was not launched with `MallocStackLogging=1`. The new orchestration tools handle this end to end without you wiring up xcodebuild + simctl + axe yourself.
75
+
76
+ > **You:** Reproduce the leak in `MyApp` for me. Build, launch with the right env vars, repeat the photo carousel flow 5 times to amplify it, capture before, then I'll ship the fix and you capture after.
77
+ >
78
+ > **Claude:** *(calls `bootAndLaunchForLeakInvestigation`)*
79
+ > Built + booted iPhone 15 (UDID `ABCD-...`). Launched `MyApp` (PID `49581`) with `MallocStackLogging=1`. Bundle id `com.example.MyApp`.
80
+ >
81
+ > *(calls `replayScenario` with 5 taps on the carousel + back-button cycle, repeat 5)*
82
+ > Executed 25 actions in 12.3s. No failures.
83
+ >
84
+ > *(calls `captureScenarioState({label:"before"})`)*
85
+ > Wrote `before.memgraph` (63,201 leaks), `before.png`, `before.ui.json`. `MallocStackLogging` active, no workaroundNotice.
86
+ >
87
+ > *(you ship the fix and rebuild)*
88
+ >
89
+ > *(calls `bootAndLaunchForLeakInvestigation` + replay + `captureScenarioState({label:"after"})`)*
90
+ >
91
+ > *(calls `diffMemgraphs` → `verifyFix`)*
92
+ > `DetailViewModel`: **15 → 0**. `swiftui.tag-index-projection` PASS. Bytes freed: 28.4 MB.
69
93
 
70
94
  ### Find the slow path on a real device
71
95
 
72
96
  > **You:** Profile DemoApp on my iPhone for 90 seconds and tell me where the hangs are.
73
97
  >
74
98
  > **Claude:** *(calls `listTraceDevices` → `recordTimeProfile` → `analyzeHangs`)*
75
- > Recorded `~/Desktop/run.trace` (90s, attached to DemoApp on iPhone 17 Pro Max). Found **23 user-visible hangs** (>500 ms each), longest 1.16 s, average 769 ms severe load. All on the main thread. Sample-level hotspots aren't symbolicated yet; open the trace in Instruments for that step (`analyzeTimeProfile` returns a structured workaround notice when `xctrace` can't symbolicate see CHANGELOG known limits).
99
+ > Recorded `~/Desktop/run.trace` (90s, attached to DemoApp on iPhone 17 Pro Max). Found **23 user-visible hangs** (>500 ms each), longest 1.16 s, average 769 ms. Severe load. All on the main thread. Sample-level hotspots aren't symbolicated yet; open the trace in Instruments for that step (`analyzeTimeProfile` returns a structured workaround notice when `xctrace` can't symbolicate. See CHANGELOG known limits).
76
100
 
77
101
  ### End-to-end: leak → file → fix suggestion
78
102
 
@@ -81,15 +105,15 @@ The full loop, from artifact to code edit. Combines memory-graph analysis with S
81
105
  > **You:** Tem um leak. Memgraph em `~/Desktop/example-leaks.memgraph`. The cycle points at `DetailViewModel`. Find where it lives in the project `~/Development/myapp/` and suggest a fix.
82
106
  >
83
107
  > **Claude:** *(calls `analyzeMemgraph` → `classifyCycle` → `reachableFromCycle`)*
84
- > Found **4 ROOT CYCLE blocks**. The dominant one matches `swiftui.tag-index-projection` with high confidence. `reachableFromCycle` confirms 4 `DetailViewModel` instances pinning ~1100 `NSURLSessionConfiguration`s each the VM is the culprit, the network stack is collateral.
108
+ > Found **4 ROOT CYCLE blocks**. The dominant one matches `swiftui.tag-index-projection` with high confidence. `reachableFromCycle` confirms 4 `DetailViewModel` instances pinning ~1100 `NSURLSessionConfiguration`s each. The VM is the culprit, the network stack is collateral.
85
109
  >
86
110
  > *(calls `swiftSearchPattern` to locate `.tag(` + `onImageSliderTap` patterns in `~/Development/myapp/Sources/`)*
87
111
  > 1 match in `MyApp/Views/MediaCarousel.swift:142`.
88
112
  >
89
113
  > *(calls `swiftGetSymbolDefinition({ symbolName: "DetailViewModel", projectRoot: "...", candidatePaths: ["MyApp/ViewModels/"] })`)*
90
- > Declared at `MyApp/ViewModels/DetailViewModel.swift:18` `class DetailViewModel: ObservableObject`.
114
+ > Declared at `MyApp/ViewModels/DetailViewModel.swift:18`. `class DetailViewModel: ObservableObject`.
91
115
  >
92
- > *(calls `swiftFindSymbolReferences` to scope the blast radius 7 callsites)*
116
+ > *(calls `swiftFindSymbolReferences` to scope the blast radius. 7 callsites)*
93
117
  >
94
118
  > **Suggested fix at `MediaCarousel.swift:142`:**
95
119
  > ```swift
@@ -99,7 +123,7 @@ The full loop, from artifact to code edit. Combines memory-graph analysis with S
99
123
  > self.viewModel.handlePhotoTap(at: index)
100
124
  > }
101
125
  >
102
- > // after static helper + weak captures
126
+ > // after. Static helper + weak captures
103
127
  > .tag(index)
104
128
  > .onImageSliderTap { [weak vm = _viewModel.wrappedValue,
105
129
  > weak coord = self.coordinator] index in
@@ -116,7 +140,7 @@ The pitch in one sentence: **`memorydetective` turns a 50–500 MB binary memgra
116
140
 
117
141
  ### Tokens (when paired with an AI agent like Claude / Cursor / Cline)
118
142
 
119
- A real-world retain-cycle investigation, run twice once with `memorydetective`, once with the agent reading the raw `leaks(1)` output directly:
143
+ A real-world retain-cycle investigation, run twice. Once with `memorydetective`, once with the agent reading the raw `leaks(1)` output directly:
120
144
 
121
145
  | Step | Without MCP (agent reads raw output) | With `memorydetective` |
122
146
  |---|---|---|
@@ -147,7 +171,7 @@ Numbers are rounded from a single anonymized real investigation (a SwiftUI retai
147
171
 
148
172
  Be honest about where this **doesn't** help much:
149
173
 
150
- - **Tiny memgraphs** (a single cycle, < 50 KB raw): MCP overhead is roughly token-neutral vs. raw read. The dev-time win still holds (no manual cycle parsing) but the token win shrinks.
174
+ - **Tiny memgraphs** (a single cycle, < 50 KB raw): MCP overhead is roughly token-neutral vs. Raw read. The dev-time win still holds (no manual cycle parsing) but the token win shrinks.
151
175
  - **One-shot symbol lookups** without a leak attached: just use `grep`, you don't need this.
152
176
  - **First-time investigations on a new codebase**: the agent still needs orientation turns regardless of MCP. The compounding wins kick in on the *second* and later investigations once the agent has cached the project's shape.
153
177
 
@@ -250,7 +274,7 @@ GitHub Copilot supports MCP servers in Agent mode (VS Code 1.94+). Add to `.vsco
250
274
  }
251
275
  ```
252
276
 
253
- Copilot's MCP integration moves fast if this snippet is stale, see the [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers).
277
+ Copilot's MCP integration moves fast. If this snippet is stale, see the [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers).
254
278
 
255
279
  </details>
256
280
 
@@ -258,11 +282,16 @@ Copilot's MCP integration moves fast — if this snippet is stale, see the [VS C
258
282
 
259
283
  ## API
260
284
 
261
- **27 MCP tools + 33 Resources + 5 Prompts**, grouped by purpose. Tool descriptions are tagged with a category prefix (`[mg.memory]`, `[mg.trace]`, `[mg.code]`, `[mg.log]`, `[mg.render]`, `[mg.ci]`, `[mg.discover]`, `[meta]`) so related tools are visible at a glance.
285
+ **31 MCP tools + 34 Resources + 5 Prompts**, grouped by purpose. Tool descriptions are tagged with a category prefix (`[mg.memory]`, `[mg.trace]`, `[mg.build]`, `[mg.scenario]`, `[mg.code]`, `[mg.log]`, `[mg.render]`, `[mg.ci]`, `[mg.discover]`, `[meta]`) so related tools are visible at a glance.
262
286
 
263
- Many tools include a `suggestedNextCalls` field in their response a typed list of `{ tool, args, why }` entries pre-populated from the current result, so the orchestrating LLM can chain calls without re-reasoning. Start with `getInvestigationPlaybook(kind)` for the canonical sequence or just type `/investigate-leak` (one of the [Prompts](#prompts-5)) in any client that exposes MCP slash commands.
287
+ Many tools include a `suggestedNextCalls` field in their response. A typed list of `{ tool, args, why }` entries pre-populated from the current result, so the orchestrating LLM can chain calls without re-reasoning. Start with `getInvestigationPlaybook(kind)` for the canonical sequence. Or just type `/investigate-leak` (one of the [Prompts](#prompts-5)) in any client that exposes MCP slash commands.
264
288
 
265
- The cycle classifier ships **33 named antipatterns** spanning SwiftUI (including the Swift 6 / `@Observable` / SwiftData / NavigationStack era), Combine, Swift Concurrency (incl. AsyncSequence-on-self and the new `Observations` API), UIKit (Timer/CADisplayLink/UIGestureRecognizer/KVO/URLSession/WebKit/DispatchSource), Core Animation, Core Data, Coordinator pattern, and the popular third-party libs RxSwift + Realm. Each pattern carries a one-line fix hint, a confidence tier, AND a `staticAnalysisHint` field pointing at the SwiftLint rule (`weak_self`, `weak_delegate`) that would have caught it at parse time — or an explicit gap notice when no static rule exists. Reinforces the differentiator: memorydetective sees the runtime evidence linters miss.
289
+ The cycle classifier ships **34 named antipatterns** spanning SwiftUI (including the Swift 6 / `@Observable` / SwiftData / NavigationStack era), Combine, Swift Concurrency (incl. AsyncSequence-on-self and the new `Observations` API), UIKit (Timer/CADisplayLink/UIGestureRecognizer/KVO/URLSession/WebKit/DispatchSource), Core Animation, Core Data, Coordinator pattern, and the popular third-party libs RxSwift + Realm. Each pattern carries:
290
+
291
+ - a textual one-line `fixHint`
292
+ - a confidence tier (`high` / `medium` / `low`)
293
+ - a `staticAnalysisHint` pointing at the SwiftLint rule that complements the runtime evidence (or an explicit gap notice when no rule exists. Reinforces the differentiator: memorydetective sees what linters miss at parse time)
294
+ - a `fixTemplate` with concrete Swift before/after snippets (new in v1.7) the agent can adapt directly to the user's code via the SourceKit-LSP source-bridging tools
266
295
 
267
296
  ### Read & analyze (13)
268
297
 
@@ -270,12 +299,12 @@ The cycle classifier ships **33 named antipatterns** spanning SwiftUI (including
270
299
  |---|---|
271
300
  | `analyzeMemgraph` | Run `leaks` against a `.memgraph` and return summary (totals, ROOT CYCLE blocks, plain-English diagnosis). |
272
301
  | `findCycles` | Extract just the ROOT CYCLE blocks as flattened chains, with optional `className` substring filter. |
273
- | `findRetainers` | "Who is keeping `<class>` alive?" returns retain chain paths from a top-level node down to the match. |
302
+ | `findRetainers` | "Who is keeping `<class>` alive?". Returns retain chain paths from a top-level node down to the match. |
274
303
  | `countAlive` | Count instances by class. Provide `className` for one number, or omit for top-N most-leaked classes. |
275
- | `reachableFromCycle` | Cycle-scoped reachability. "How many `<X>` instances are reachable from the cycle rooted at `<Y>`?" distinguishes the actual culprit from its retained dependencies. |
304
+ | `reachableFromCycle` | Cycle-scoped reachability. "How many `<X>` instances are reachable from the cycle rooted at `<Y>`?". Distinguishes the actual culprit from its retained dependencies. |
276
305
  | `diffMemgraphs` | Compare two `.memgraph` snapshots: total deltas + class-count changes + cycles new/gone/persisted. |
277
306
  | `verifyFix` | Cycle-semantic diff: per-pattern PASS/PARTIAL/FAIL verdict + bytes freed. CI-gateable. |
278
- | `classifyCycle` | Match each ROOT CYCLE against a built-in catalog of **33 named antipatterns** (SwiftUI / Combine / Concurrency / UIKit / Core Animation / Core Data / Coordinator / RxSwift / Realm) with confidence + fix hint + `staticAnalysisHint` (which SwiftLint rule complements this, or explicit gap). |
307
+ | `classifyCycle` | Match each ROOT CYCLE against a built-in catalog of **34 named antipatterns** (SwiftUI / Combine / Concurrency / UIKit / Core Animation / Core Data / Coordinator / RxSwift / Realm) with confidence + textual `fixHint` + `staticAnalysisHint` (which SwiftLint rule complements this, or explicit gap) + `fixTemplate` (Swift before/after snippet). |
279
308
  | `analyzeHangs` | Parse `xctrace` `potential-hangs` schema; return Hang vs Microhang counts + top N longest. |
280
309
  | `analyzeAnimationHitches` | Parse `xctrace` `animation-hitches` schema; report by-type counts and how many hitches crossed Apple's user-perceptible 100ms threshold. |
281
310
  | `analyzeTimeProfile` | Parse `xctrace` `time-profile` schema; return top symbols by sample count. Reports SIGSEGV with workarounds when xctrace can't symbolicate. |
@@ -288,9 +317,19 @@ The cycle classifier ships **33 named antipatterns** spanning SwiftUI (including
288
317
  | Tool | What | Sim | Device |
289
318
  |---|---|---|---|
290
319
  | `recordTimeProfile` | Wrap `xcrun xctrace record --template "Time Profiler" --attach ... --time-limit Ns --output ...`. | ✅ | ✅ |
291
- | `captureMemgraph` | Wrap `leaks --outputGraph <path> <pid>`. Resolves `appName → pid` via `pgrep -x`. | ✅ | use Xcode |
320
+ | `captureMemgraph` | Wrap `leaks --outputGraph <path> <pid>`. Resolves `appName → pid` via `pgrep -x`. Returns a structured `workaroundNotice` on the macOS 26.x `Failed to get DYLD info for task` regression with stable issue ids (`minimal-corpse`, `permission-denied`, `leaks-not-found`, `transient`) and a fallback path to `recordTimeProfile` (Allocations) + `analyzeAllocations`. | ✅ | ❌. Use Xcode |
292
321
  | `logStream` | Wrap `log stream --style compact` for a bounded duration (≤ 60 s). Returns parsed entries collected during the window. | n/a | n/a |
293
322
 
323
+ ### Verify-fix orchestration (3, v1.8)
324
+
325
+ These three tools combine into a single deterministic verify-fix loop: launch the app with `MallocStackLogging=1` so leaks works, drive the UI to amplify the suspected leak, snapshot before, ship the fix, snapshot after, then `diffMemgraphs`.
326
+
327
+ | Tool | What |
328
+ |---|---|
329
+ | `bootAndLaunchForLeakInvestigation` | Single-call build + boot + install + launch with `MallocStackLogging=1` propagated via `SIMCTL_CHILD_*`. Resolves the simulator (udid, name+os, or whichever is booted), discovers `BUILT_PRODUCTS_DIR` / `WRAPPER_NAME` / `EXECUTABLE_NAME` / `PRODUCT_BUNDLE_IDENTIFIER` from `xcodebuild -showBuildSettings -json`, and returns the host PID + UDID + bundle id ready to chain into `captureMemgraph`. Required because `leaks --outputGraph` regressed on macOS 26.x and only works when the target was launched with malloc-stack-logging in its environment. |
330
+ | `replayScenario` | Drive the iOS Simulator through tap / swipe / wait / type actions with a `repeat` count to amplify leaks that only manifest after N iterations. Tap targets accept `label`, `elementId`, or `coords`. Soft dependency on Cameron Cooke's [axe](https://github.com/cameroncooke/AXe) CLI. |
331
+ | `captureScenarioState` | Composite snapshot for verify-fix: writes `.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 + UI tree still complete and the `captureMemgraph` workaroundNotice is surfaced via `memgraphWorkaroundNotice`. |
332
+
294
333
  ### Discover (2)
295
334
 
296
335
  | Tool | What |
@@ -304,11 +343,12 @@ The cycle classifier ships **33 named antipatterns** spanning SwiftUI (including
304
343
  |---|---|
305
344
  | `renderCycleGraph` | Read a `.memgraph`, pick a ROOT CYCLE, and emit a Mermaid graph (markdown-embeddable) or Graphviz DOT. App-level classes highlighted in red; CYCLE BACK terminators amber. |
306
345
 
307
- ### CI / test integration (1)
346
+ ### CI / test integration (2)
308
347
 
309
348
  | Tool | What |
310
349
  |---|---|
311
350
  | `detectLeaksInXCUITest` | **Experimental.** Build the workspace for testing, run the named XCUITest, capture `.memgraph` baseline + after, diff. Returns `passed: false` when new ROOT CYCLEs appear that aren't in the user's allowlist. CI-runnable. |
351
+ | `compareTracesByPattern` | Trace-side counterpart to `verifyFix`. Compares two `.trace` bundles for a perf category (`hangs`, `animation-hitches`, or `app-launch`) and returns PASS/PARTIAL/FAIL with before/after stats and deltas. Apply thresholds: hangs PASS when longest is below `hangsMaxLongestMs`; hitches PASS when longest is below `hitchesMaxLongestMs` (default 100ms. Apple's user-perceptible threshold); app-launch PASS when total is below `appLaunchMaxTotalMs` (default 1000ms). |
312
352
 
313
353
  ### Swift source bridging (5)
314
354
 
@@ -317,16 +357,16 @@ Pair the memory-graph diagnosis with source-code lookups via SourceKit-LSP. Clos
317
357
  | Tool | What |
318
358
  |---|---|
319
359
  | `swiftGetSymbolDefinition` | Locate the file:line where a Swift symbol is declared. Pre-scans `candidatePaths` (or `hint.filePath`) with a fast regex, then asks SourceKit-LSP for jump-to-definition. |
320
- | `swiftFindSymbolReferences` | Find every reference to a Swift symbol via SourceKit-LSP `textDocument/references`. Requires an `IndexStoreDB` for cross-file results the response carries a `needsIndex` hint when the index is missing. |
360
+ | `swiftFindSymbolReferences` | Find every reference to a Swift symbol via SourceKit-LSP `textDocument/references`. Requires an `IndexStoreDB` for cross-file results. The response carries a `needsIndex` hint when the index is missing. |
321
361
  | `swiftGetSymbolsOverview` | List top-level symbols (classes, structs, enums, protocols, free functions) in a Swift file via `documentSymbol`. Cheap orientation when the agent lands in a new file. |
322
362
  | `swiftGetHoverInfo` | Type info / docs at a (line, character) position. Disambiguates `self` captures: a class self in a closure can leak; a struct self can't. |
323
363
  | `swiftSearchPattern` | Pure regex search over a Swift file (no LSP, no index). Catches what LSP misses: closure capture lists, `Task { ... self ... }` blocks, custom patterns from a leak chain. |
324
364
 
325
- These tools require macOS + Xcode (full Xcode, not just Command Line Tools `xcrun sourcekit-lsp` must be available). They start a `sourcekit-lsp` subprocess per project root and reuse it across calls; the subprocess shuts down after a 5-minute idle window.
365
+ These tools require macOS + Xcode (full Xcode, not just Command Line Tools. `xcrun sourcekit-lsp` must be available). They start a `sourcekit-lsp` subprocess per project root and reuse it across calls; the subprocess shuts down after a 5-minute idle window.
326
366
 
327
- > **Why `captureMemgraph` doesn't work on physical iOS devices**: `leaks(1)` only attaches to processes running on the local Mac (which includes iOS simulators). Memory Graph capture from a real device goes through Xcode's debugger over USB/lockdownd different mechanism, no public CLI equivalent.
367
+ > **Why `captureMemgraph` doesn't work on physical iOS devices**: `leaks(1)` only attaches to processes running on the local Mac (which includes iOS simulators). Memory Graph capture from a real device goes through Xcode's debugger over USB/lockdownd. Different mechanism, no public CLI equivalent.
328
368
 
329
- ### Resources (33)
369
+ ### Resources (34)
330
370
 
331
371
  The cycle-pattern catalog is also surfaced as MCP resources, browsable at `memorydetective://patterns/{patternId}`. Each resource is a markdown body with the pattern name, a longer description, and the fix hint. Use this to let an agent (or a human in a UI-aware MCP client) browse the catalog without burning a `classifyCycle` call.
332
372
 
@@ -334,10 +374,11 @@ The cycle-pattern catalog is also surfaced as MCP resources, browsable at `memor
334
374
  memorydetective://patterns/swiftui.tag-index-projection
335
375
  memorydetective://patterns/concurrency.async-sequence-on-self
336
376
  memorydetective://patterns/webkit.wkscriptmessagehandler-bridge
377
+ memorydetective://patterns/swiftdata.modelcontext-actor-cycle
337
378
 
338
379
  ```
339
380
 
340
- `resources/list` returns all 33 entries. `resources/read` resolves any `memorydetective://patterns/{id}` URI to its markdown body.
381
+ `resources/list` returns all 34 entries. `resources/read` resolves any `memorydetective://patterns/{id}` URI to its markdown body.
341
382
 
342
383
  ### Prompts (5)
343
384
 
@@ -351,7 +392,7 @@ Investigation playbooks are exposed as MCP prompts (slash commands in clients th
351
392
  | `/investigate-launch` | Diagnose cold/warm launch slowness from a `.trace`. | `tracePath` |
352
393
  | `/verify-cycle-fix` | Diff a before/after pair of `.memgraph` snapshots to confirm a fix landed. | `before`, `after` |
353
394
 
354
- Each prompt fills the canonical playbook's argument templates with the user-provided values, then hands the agent a ready-to-execute brief. Calls the same tools listed in [Read & analyze](#read--analyze-13) prompts are an orchestration shortcut, not a separate engine.
395
+ Each prompt fills the canonical playbook's argument templates with the user-provided values, then hands the agent a ready-to-execute brief. Calls the same tools listed in [Read & analyze](#read--analyze-13). Prompts are an orchestration shortcut, not a separate engine.
355
396
 
356
397
  ### CLI mode
357
398
 
@@ -387,19 +428,20 @@ npm run dev # tsx, stdio mode (dev mode)
387
428
 
388
429
  ## Contributing
389
430
 
390
- Contributions are welcome bug reports, feature requests, new cycle patterns, all of it.
431
+ Contributions are welcome. Bug reports, feature requests, new cycle patterns, all of it.
391
432
 
392
433
  - **Bugs / feature requests**: [open an issue](https://github.com/carloshpdoc/memorydetective/issues).
393
- - **PRs**: fork → branch → `npm install` → make changes → `npm test` (183 tests must stay green) → open a PR with a concise description of what changed and why.
434
+ - **PRs**: fork → branch → `npm install` → make changes → `npm test` (206 tests must stay green) → open a PR with a concise description of what changed and why.
394
435
 
395
436
  ### Adding a cycle pattern to `classifyCycle`
396
437
 
397
- `classifyCycle` ships with 33 built-in patterns covering SwiftUI (incl. Swift 6 / `@Observable` / SwiftData / NavigationStack), Combine, Swift Concurrency (incl. AsyncSequence-on-self and `Observations`), UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. To add one:
438
+ `classifyCycle` ships with 34 built-in patterns covering SwiftUI (incl. Swift 6 / `@Observable` / SwiftData / NavigationStack), Combine, Swift Concurrency (incl. AsyncSequence-on-self and `Observations`), UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. To add one:
398
439
 
399
- 1. Edit `src/tools/classifyCycle.ts` add an entry to `PATTERNS` with `id`, `name`, `fixHint`, and a `match` function.
440
+ 1. Edit `src/tools/classifyCycle.ts`. Add an entry to `PATTERNS` with `id`, `name`, `fixHint`, and a `match` function.
400
441
  2. Add a test in `src/tools/readTools.test.ts` that asserts the new pattern fires against a representative memgraph fixture.
401
442
  3. Add a `staticAnalysisHint` entry in `src/runtime/staticAnalysisHints.ts` (the test in that file enforces 1:1 coverage with `PATTERNS`).
402
- 4. Open a PR.
443
+ 4. Add a `fixTemplate` entry in `src/runtime/fixTemplates.ts` (same 1:1 coverage guard).
444
+ 5. Open a PR.
403
445
 
404
446
  ## Support this project
405
447
 
@@ -412,7 +454,7 @@ Every contribution helps keep this maintained and documented.
412
454
 
413
455
  ## License
414
456
 
415
- Apache 2.0 see [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
457
+ Apache 2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
416
458
 
417
459
  Permits commercial use, modification, distribution, patent use. Includes attribution clause via the `NOTICE` file.
418
460