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.
- package/CHANGELOG.md +49 -0
- package/README.md +76 -34
- package/USAGE.md +112 -41
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/axe.d.ts +86 -0
- package/dist/runtime/axe.js +249 -0
- package/dist/runtime/axe.js.map +1 -0
- package/dist/runtime/buildSettings.d.ts +27 -0
- package/dist/runtime/buildSettings.js +88 -0
- package/dist/runtime/buildSettings.js.map +1 -0
- package/dist/runtime/exec.d.ts +8 -1
- package/dist/runtime/exec.js +8 -2
- package/dist/runtime/exec.js.map +1 -1
- package/dist/runtime/fixTemplates.d.ts +27 -0
- package/dist/runtime/fixTemplates.js +757 -0
- package/dist/runtime/fixTemplates.js.map +1 -0
- package/dist/runtime/simctl.d.ts +68 -0
- package/dist/runtime/simctl.js +194 -0
- package/dist/runtime/simctl.js.map +1 -0
- package/dist/runtime/staticAnalysisHints.js +8 -0
- package/dist/runtime/staticAnalysisHints.js.map +1 -1
- package/dist/tools/bootAndLaunchForLeakInvestigation.d.ts +166 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.js +367 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.js.map +1 -0
- package/dist/tools/captureMemgraph.d.ts +29 -1
- package/dist/tools/captureMemgraph.js +148 -6
- package/dist/tools/captureMemgraph.js.map +1 -1
- package/dist/tools/captureScenarioState.d.ts +77 -0
- package/dist/tools/captureScenarioState.js +159 -0
- package/dist/tools/captureScenarioState.js.map +1 -0
- package/dist/tools/classifyCycle.d.ts +7 -0
- package/dist/tools/classifyCycle.js +31 -0
- package/dist/tools/classifyCycle.js.map +1 -1
- package/dist/tools/compareTracesByPattern.d.ts +112 -0
- package/dist/tools/compareTracesByPattern.js +312 -0
- package/dist/tools/compareTracesByPattern.js.map +1 -0
- package/dist/tools/detectLeaksInXCUITest.d.ts +2 -2
- package/dist/tools/getInvestigationPlaybook.d.ts +15 -0
- package/dist/tools/getInvestigationPlaybook.js +24 -1
- package/dist/tools/getInvestigationPlaybook.js.map +1 -1
- package/dist/tools/replayScenario.d.ts +243 -0
- package/dist/tools/replayScenario.js +187 -0
- package/dist/tools/replayScenario.js.map +1 -0
- 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
|
|
3
|
+
> Diagnose iOS retain cycles and performance regressions from your chat window. No Xcode required.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/memorydetective)
|
|
6
6
|
[](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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
114
|
+
> Declared at `MyApp/ViewModels/DetailViewModel.swift:18`. `class DetailViewModel: ObservableObject`.
|
|
91
115
|
>
|
|
92
|
-
> *(calls `swiftFindSymbolReferences` to scope the blast radius
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
**
|
|
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
|
|
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 **
|
|
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?"
|
|
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>`?"
|
|
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 **
|
|
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`. | ✅ |
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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)
|
|
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
|
|
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` (
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|