memorydetective 1.7.0 → 1.8.1
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 +41 -0
- package/README.md +61 -29
- package/USAGE.md +87 -41
- package/dist/index.js +34 -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/simctl.d.ts +68 -0
- package/dist/runtime/simctl.js +194 -0
- package/dist/runtime/simctl.js.map +1 -0
- 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/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 +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,47 @@ 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.1] - 2026-05-13
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`mcpName: "io.github.carloshpdoc/memorydetective"`** in `package.json`. Required by the MCP Registry to verify package ownership. Follows the `io.github.<owner>/<repo>` convention mandated for GitHub-based authentication with `mcp-publisher`.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- No code changes, no API changes, no functional changes for existing consumers.
|
|
20
|
+
- Existing v1.8.0 installs keep working unchanged.
|
|
21
|
+
- The plugin's `^1.7` SPM-style range picks this up automatically; no plugin sync needed for this patch.
|
|
22
|
+
|
|
23
|
+
## [1.8.0] - 2026-05-06
|
|
24
|
+
|
|
25
|
+
`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.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **`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.
|
|
30
|
+
- **`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.
|
|
31
|
+
- **`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).
|
|
32
|
+
- **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.
|
|
33
|
+
- **`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.
|
|
34
|
+
- **`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.
|
|
35
|
+
- **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.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- 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`.
|
|
40
|
+
- USAGE.md: documents the 3 new tools with concrete invocation examples plus the new `troubleshooting` field on the memgraph-leak playbook.
|
|
41
|
+
- 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).
|
|
42
|
+
|
|
43
|
+
### Notes
|
|
44
|
+
|
|
45
|
+
- 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.
|
|
46
|
+
- `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.
|
|
47
|
+
- `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.
|
|
48
|
+
- 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.
|
|
49
|
+
|
|
9
50
|
## [1.7.0] — 2026-05-03
|
|
10
51
|
|
|
11
52
|
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.
|
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,13 +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
21
|
>
|
|
22
|
-
> **Also
|
|
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).
|
|
23
23
|
|
|
24
24
|
## Quickstart
|
|
25
25
|
|
|
@@ -55,10 +55,10 @@ memorydetective classify ~/Desktop/myapp.memgraph
|
|
|
55
55
|
|
|
56
56
|
### Diagnose a leak from a memgraph
|
|
57
57
|
|
|
58
|
-
> **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?
|
|
59
59
|
>
|
|
60
60
|
> **Claude:** *(calls `analyzeMemgraph` → `classifyCycle`)*
|
|
61
|
-
> 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.
|
|
62
62
|
>
|
|
63
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?
|
|
64
64
|
|
|
@@ -67,14 +67,36 @@ memorydetective classify ~/Desktop/myapp.memgraph
|
|
|
67
67
|
> **You:** I shipped the fix. Compare `~/Desktop/before.memgraph` to `~/Desktop/after.memgraph` and tell me whether `DetailViewModel` count dropped.
|
|
68
68
|
>
|
|
69
69
|
> **Claude:** *(calls `diffMemgraphs` → `countAlive`)*
|
|
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
|
|
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.
|
|
71
93
|
|
|
72
94
|
### Find the slow path on a real device
|
|
73
95
|
|
|
74
96
|
> **You:** Profile DemoApp on my iPhone for 90 seconds and tell me where the hangs are.
|
|
75
97
|
>
|
|
76
98
|
> **Claude:** *(calls `listTraceDevices` → `recordTimeProfile` → `analyzeHangs`)*
|
|
77
|
-
> 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).
|
|
78
100
|
|
|
79
101
|
### End-to-end: leak → file → fix suggestion
|
|
80
102
|
|
|
@@ -83,15 +105,15 @@ The full loop, from artifact to code edit. Combines memory-graph analysis with S
|
|
|
83
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.
|
|
84
106
|
>
|
|
85
107
|
> **Claude:** *(calls `analyzeMemgraph` → `classifyCycle` → `reachableFromCycle`)*
|
|
86
|
-
> 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.
|
|
87
109
|
>
|
|
88
110
|
> *(calls `swiftSearchPattern` to locate `.tag(` + `onImageSliderTap` patterns in `~/Development/myapp/Sources/`)*
|
|
89
111
|
> 1 match in `MyApp/Views/MediaCarousel.swift:142`.
|
|
90
112
|
>
|
|
91
113
|
> *(calls `swiftGetSymbolDefinition({ symbolName: "DetailViewModel", projectRoot: "...", candidatePaths: ["MyApp/ViewModels/"] })`)*
|
|
92
|
-
> Declared at `MyApp/ViewModels/DetailViewModel.swift:18
|
|
114
|
+
> Declared at `MyApp/ViewModels/DetailViewModel.swift:18`. `class DetailViewModel: ObservableObject`.
|
|
93
115
|
>
|
|
94
|
-
> *(calls `swiftFindSymbolReferences` to scope the blast radius
|
|
116
|
+
> *(calls `swiftFindSymbolReferences` to scope the blast radius. 7 callsites)*
|
|
95
117
|
>
|
|
96
118
|
> **Suggested fix at `MediaCarousel.swift:142`:**
|
|
97
119
|
> ```swift
|
|
@@ -101,7 +123,7 @@ The full loop, from artifact to code edit. Combines memory-graph analysis with S
|
|
|
101
123
|
> self.viewModel.handlePhotoTap(at: index)
|
|
102
124
|
> }
|
|
103
125
|
>
|
|
104
|
-
> // after
|
|
126
|
+
> // after. Static helper + weak captures
|
|
105
127
|
> .tag(index)
|
|
106
128
|
> .onImageSliderTap { [weak vm = _viewModel.wrappedValue,
|
|
107
129
|
> weak coord = self.coordinator] index in
|
|
@@ -118,7 +140,7 @@ The pitch in one sentence: **`memorydetective` turns a 50–500 MB binary memgra
|
|
|
118
140
|
|
|
119
141
|
### Tokens (when paired with an AI agent like Claude / Cursor / Cline)
|
|
120
142
|
|
|
121
|
-
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:
|
|
122
144
|
|
|
123
145
|
| Step | Without MCP (agent reads raw output) | With `memorydetective` |
|
|
124
146
|
|---|---|---|
|
|
@@ -149,7 +171,7 @@ Numbers are rounded from a single anonymized real investigation (a SwiftUI retai
|
|
|
149
171
|
|
|
150
172
|
Be honest about where this **doesn't** help much:
|
|
151
173
|
|
|
152
|
-
- **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.
|
|
153
175
|
- **One-shot symbol lookups** without a leak attached: just use `grep`, you don't need this.
|
|
154
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.
|
|
155
177
|
|
|
@@ -252,7 +274,7 @@ GitHub Copilot supports MCP servers in Agent mode (VS Code 1.94+). Add to `.vsco
|
|
|
252
274
|
}
|
|
253
275
|
```
|
|
254
276
|
|
|
255
|
-
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).
|
|
256
278
|
|
|
257
279
|
</details>
|
|
258
280
|
|
|
@@ -260,15 +282,15 @@ Copilot's MCP integration moves fast — if this snippet is stale, see the [VS C
|
|
|
260
282
|
|
|
261
283
|
## API
|
|
262
284
|
|
|
263
|
-
**
|
|
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.
|
|
264
286
|
|
|
265
|
-
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.
|
|
266
288
|
|
|
267
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:
|
|
268
290
|
|
|
269
291
|
- a textual one-line `fixHint`
|
|
270
292
|
- a confidence tier (`high` / `medium` / `low`)
|
|
271
|
-
- a `staticAnalysisHint` pointing at the SwiftLint rule that complements the runtime evidence (or an explicit gap notice when no rule exists
|
|
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)
|
|
272
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
|
|
273
295
|
|
|
274
296
|
### Read & analyze (13)
|
|
@@ -277,9 +299,9 @@ The cycle classifier ships **34 named antipatterns** spanning SwiftUI (including
|
|
|
277
299
|
|---|---|
|
|
278
300
|
| `analyzeMemgraph` | Run `leaks` against a `.memgraph` and return summary (totals, ROOT CYCLE blocks, plain-English diagnosis). |
|
|
279
301
|
| `findCycles` | Extract just the ROOT CYCLE blocks as flattened chains, with optional `className` substring filter. |
|
|
280
|
-
| `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. |
|
|
281
303
|
| `countAlive` | Count instances by class. Provide `className` for one number, or omit for top-N most-leaked classes. |
|
|
282
|
-
| `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. |
|
|
283
305
|
| `diffMemgraphs` | Compare two `.memgraph` snapshots: total deltas + class-count changes + cycles new/gone/persisted. |
|
|
284
306
|
| `verifyFix` | Cycle-semantic diff: per-pattern PASS/PARTIAL/FAIL verdict + bytes freed. CI-gateable. |
|
|
285
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). |
|
|
@@ -295,9 +317,19 @@ The cycle classifier ships **34 named antipatterns** spanning SwiftUI (including
|
|
|
295
317
|
| Tool | What | Sim | Device |
|
|
296
318
|
|---|---|---|---|
|
|
297
319
|
| `recordTimeProfile` | Wrap `xcrun xctrace record --template "Time Profiler" --attach ... --time-limit Ns --output ...`. | ✅ | ✅ |
|
|
298
|
-
| `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 |
|
|
299
321
|
| `logStream` | Wrap `log stream --style compact` for a bounded duration (≤ 60 s). Returns parsed entries collected during the window. | n/a | n/a |
|
|
300
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
|
+
|
|
301
333
|
### Discover (2)
|
|
302
334
|
|
|
303
335
|
| Tool | What |
|
|
@@ -316,7 +348,7 @@ The cycle classifier ships **34 named antipatterns** spanning SwiftUI (including
|
|
|
316
348
|
| Tool | What |
|
|
317
349
|
|---|---|
|
|
318
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. |
|
|
319
|
-
| `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
|
|
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). |
|
|
320
352
|
|
|
321
353
|
### Swift source bridging (5)
|
|
322
354
|
|
|
@@ -325,14 +357,14 @@ Pair the memory-graph diagnosis with source-code lookups via SourceKit-LSP. Clos
|
|
|
325
357
|
| Tool | What |
|
|
326
358
|
|---|---|
|
|
327
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. |
|
|
328
|
-
| `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. |
|
|
329
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. |
|
|
330
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. |
|
|
331
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. |
|
|
332
364
|
|
|
333
|
-
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.
|
|
334
366
|
|
|
335
|
-
> **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.
|
|
336
368
|
|
|
337
369
|
### Resources (34)
|
|
338
370
|
|
|
@@ -360,7 +392,7 @@ Investigation playbooks are exposed as MCP prompts (slash commands in clients th
|
|
|
360
392
|
| `/investigate-launch` | Diagnose cold/warm launch slowness from a `.trace`. | `tracePath` |
|
|
361
393
|
| `/verify-cycle-fix` | Diff a before/after pair of `.memgraph` snapshots to confirm a fix landed. | `before`, `after` |
|
|
362
394
|
|
|
363
|
-
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.
|
|
364
396
|
|
|
365
397
|
### CLI mode
|
|
366
398
|
|
|
@@ -396,7 +428,7 @@ npm run dev # tsx, stdio mode (dev mode)
|
|
|
396
428
|
|
|
397
429
|
## Contributing
|
|
398
430
|
|
|
399
|
-
Contributions are welcome
|
|
431
|
+
Contributions are welcome. Bug reports, feature requests, new cycle patterns, all of it.
|
|
400
432
|
|
|
401
433
|
- **Bugs / feature requests**: [open an issue](https://github.com/carloshpdoc/memorydetective/issues).
|
|
402
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.
|
|
@@ -405,7 +437,7 @@ Contributions are welcome — bug reports, feature requests, new cycle patterns,
|
|
|
405
437
|
|
|
406
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:
|
|
407
439
|
|
|
408
|
-
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.
|
|
409
441
|
2. Add a test in `src/tools/readTools.test.ts` that asserts the new pattern fires against a representative memgraph fixture.
|
|
410
442
|
3. Add a `staticAnalysisHint` entry in `src/runtime/staticAnalysisHints.ts` (the test in that file enforces 1:1 coverage with `PATTERNS`).
|
|
411
443
|
4. Add a `fixTemplate` entry in `src/runtime/fixTemplates.ts` (same 1:1 coverage guard).
|
|
@@ -422,7 +454,7 @@ Every contribution helps keep this maintained and documented.
|
|
|
422
454
|
|
|
423
455
|
## License
|
|
424
456
|
|
|
425
|
-
Apache 2.0
|
|
457
|
+
Apache 2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|
|
426
458
|
|
|
427
459
|
Permits commercial use, modification, distribution, patent use. Includes attribution clause via the `NOTICE` file.
|
|
428
460
|
|