memorydetective 1.0.0 → 1.1.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 CHANGED
@@ -6,6 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.0] — 2026-05-01
10
+
11
+ Response-size + cycle-scoped queries + license switch + first-run engagement.
12
+
13
+ ### Added
14
+
15
+ - **`reachableFromCycle` tool** — cycle-scoped reachability + per-class counting. Pick a cycle by `cycleIndex` or `rootClassName` substring, get instance counts of every class reachable from that cycle root. Distinguishes the actual culprit (the cycle root) from its retained dependencies. API shape inspired by Meta's `memlab` predicate-based queries. Tool count: 19 → 20.
16
+ - **`verbosity` parameter** on `analyzeMemgraph`, `findCycles`, and `reachableFromCycle`. Three levels: `compact` (default — drops module prefixes, collapses nested SwiftUI `ModifiedContent` into `+N modifiers`, truncates deep generics with a hash placeholder), `normal` (lighter shortening, depth preserved), `full` (Swift demangled names verbatim).
17
+ - **`maxClassesInChain` parameter** on `analyzeMemgraph` (default 10). Caps the per-cycle `classesInChain` array to the top N unique classes ranked by occurrence count, with app-level types prioritized over SwiftUI internals. The full unique-class total is reported in a new `classesInChainTotal` field for context.
18
+ - **Class-name shortener** (`src/parsers/shortenClassName.ts`) — three layers (drop modules, collapse `ModifiedContent` chains, truncate deep generics) with deterministic hash placeholders so the same nested type produces the same short code across runs (useful for diffing two memgraphs).
19
+ - **First-run CLI banner** — first time `memorydetective` runs in non-JSON mode on a machine, prints a one-time message pointing at the GitHub repo, `USAGE.md`, and the sponsor link. Marker stored at `~/.config/memorydetective/seen` so it never re-prints.
20
+ - **Help / output footers** — `--help` and `analyze`/`classify` non-JSON output append a discreet `# ⭐ github.com/carloshpdoc/memorydetective` line. JSON output is untouched (won't break pipes / CI).
21
+
22
+ ### Changed
23
+
24
+ - **License: MIT → Apache 2.0.** Permissions are unchanged for users (commercial use, modification, distribution all allowed). Apache 2.0 adds an explicit patent grant + a `NOTICE` file mechanism; the `NOTICE` file ships in the npm tarball and surfaces project attribution in downstream "About" / acknowledgements screens.
25
+ - `analyzeMemgraph` default response is now ~80% smaller for SwiftUI-heavy memgraphs (the per-cycle `classesInChain` no longer floods with hundreds of demangled SwiftUI generics; class names per node compress from 1000+ chars to ~200). Full-fidelity output is still available via `verbosity: "full"`.
26
+ - README: license badge updated to Apache 2.0; tool count updated 19 → 20.
27
+
28
+ ### Notes
29
+
30
+ - No breaking changes — all new parameters have sensible defaults. Old callers continue to work; they just receive smaller, more readable responses.
31
+
32
+ ## [1.0.1] — 2026-05-01
33
+
34
+ ### Added
35
+
36
+ - `USAGE.md` walkthrough covering the three usage modes (CLI, `--json`, MCP), the 8 cycle patterns and their fix hints, the end-to-end flow of how fixes go from diagnosis to a code edit (memorydetective diagnoses; the LLM agent applies the edit using its own code-editing tools), common follow-up prompts, and troubleshooting. README links to it from the Quickstart pointer line.
37
+ - `USAGE.md` is included in the npm tarball (added to `package.json` `files` whitelist).
38
+
39
+ ### Changed
40
+
41
+ - No code changes from `1.0.0` — this is a documentation bump.
42
+
9
43
  ## [1.0.0] — 2026-05-01
10
44
 
11
45
  First public release. **19 MCP tools** for iOS leak hunting and performance investigation, plus a thin CLI mode for scripting and CI.
@@ -63,5 +97,7 @@ When called with no arguments it starts the MCP server over stdio.
63
97
  - **`captureMemgraph`** does not work on physical iOS devices — `leaks(1)` only attaches to processes on the local Mac (which includes iOS simulators). Memory Graph capture from a physical device still requires Xcode.
64
98
  - **`detectLeaksInXCUITest`** is flagged experimental: orchestration logic is implemented but not yet validated against a wide set of production XCUITest runs.
65
99
 
66
- [Unreleased]: https://github.com/carloshpdoc/memorydetective/compare/v1.0.0...HEAD
100
+ [Unreleased]: https://github.com/carloshpdoc/memorydetective/compare/v1.1.0...HEAD
101
+ [1.1.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.0.1...v1.1.0
102
+ [1.0.1]: https://github.com/carloshpdoc/memorydetective/compare/v1.0.0...v1.0.1
67
103
  [1.0.0]: https://github.com/carloshpdoc/memorydetective/releases/tag/v1.0.0
package/LICENSE CHANGED
@@ -1,21 +1,201 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Carlos Henrique
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for describing the origin of the Work and
141
+ reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Support. While redistributing the Work or
166
+ Derivative Works thereof, You may choose to offer, and charge a
167
+ fee for, acceptance of support, warranty, indemnity, or other
168
+ liability obligations and/or rights consistent with this License.
169
+ However, in accepting such obligations, You may act only on Your
170
+ own behalf and on Your sole responsibility, not on behalf of any
171
+ other Contributor, and only if You agree to indemnify, defend,
172
+ and hold each Contributor harmless for any liability incurred by,
173
+ or claims asserted against, such Contributor by reason of your
174
+ accepting any such warranty or support.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Carlos Henrique
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,19 @@
1
+ memorydetective
2
+ Copyright 2026 Carlos Henrique
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License").
5
+ A copy of the License is included in this distribution as `LICENSE`,
6
+ and is also available at:
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Project home:
11
+ https://github.com/carloshpdoc/memorydetective
12
+
13
+ This product includes software developed by Carlos Henrique
14
+ (https://github.com/carloshpdoc).
15
+
16
+ If `memorydetective` saves you time, please consider:
17
+ • Starring the repository: https://github.com/carloshpdoc/memorydetective
18
+ • Sponsoring development: https://github.com/sponsors/carloshpdoc
19
+ • Buying me a coffee: https://buymeacoffee.com/carloshperc
package/README.md CHANGED
@@ -4,7 +4,8 @@
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)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE)
8
+ [![GitHub stars](https://img.shields.io/github/stars/carloshpdoc/memorydetective?style=flat&logo=github)](https://github.com/carloshpdoc/memorydetective/stargazers)
8
9
  [![macOS](https://img.shields.io/badge/platform-macOS-lightgrey.svg)](#requirements)
9
10
  [![node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen.svg)](#requirements)
10
11
 
@@ -40,7 +41,7 @@ memorydetective analyze ~/Desktop/myapp.memgraph
40
41
  memorydetective classify ~/Desktop/myapp.memgraph
41
42
  ```
42
43
 
43
- → See [Examples](#examples) for chat-driven flows · [API](#api) for the full tool reference · [Configure](#configure) for Claude Desktop / Cursor / Cline.
44
+ → See [Examples](#examples) for chat-driven flows · [API](#api) for the full tool reference · [Configure](#configure) for Claude Desktop / Cursor / Cline · [USAGE.md](./USAGE.md) for the full walkthrough including how fixes flow from diagnosis to your codebase.
44
45
 
45
46
  ---
46
47
 
@@ -176,9 +177,9 @@ Copilot's MCP integration moves fast — if this snippet is stale, see the [VS C
176
177
 
177
178
  ## API
178
179
 
179
- 19 MCP tools, grouped by purpose.
180
+ 20 MCP tools, grouped by purpose.
180
181
 
181
- ### Read & analyze (12)
182
+ ### Read & analyze (13)
182
183
 
183
184
  | Tool | What |
184
185
  |---|---|
@@ -186,6 +187,7 @@ Copilot's MCP integration moves fast — if this snippet is stale, see the [VS C
186
187
  | `findCycles` | Extract just the ROOT CYCLE blocks as flattened chains, with optional `className` substring filter. |
187
188
  | `findRetainers` | "Who is keeping `<class>` alive?" — returns retain chain paths from a top-level node down to the match. |
188
189
  | `countAlive` | Count instances by class. Provide `className` for one number, or omit for top-N most-leaked classes. |
190
+ | `reachableFromCycle` | Cycle-scoped reachability. "How many `<X>` instances are reachable from the cycle rooted at `<Y>`?" — distinguishes the actual culprit from its retained dependencies. |
189
191
  | `diffMemgraphs` | Compare two `.memgraph` snapshots: total deltas + class-count changes + cycles new/gone/persisted. |
190
192
  | `classifyCycle` | Match each ROOT CYCLE against a built-in catalog of 8 known patterns (TagIndexProjection, ForEachState, Combine sink, Task captures, NotificationCenter observer, etc.) with confidence + fix hint. |
191
193
  | `analyzeHangs` | Parse `xctrace` `potential-hangs` schema; return Hang vs Microhang counts + top N longest. |
@@ -293,17 +295,19 @@ If `memorydetective` saves you time, you can support continued development:
293
295
 
294
296
  Every contribution helps keep this maintained and documented.
295
297
 
296
- ## Roadmap
298
+ ## License
297
299
 
298
- - **v0.1.x** (current) 12 MCP tools, 8 cycle patterns, CLI mode.
299
- - **v0.2** — community-contributable cycle catalog, Claude Code plugin (`/plugin install memorydetective`), close the `analyzeTimeProfile` SIGSEGV gap. Design docs in [docs/](./docs/).
300
- - **Phase 3** (gated) — GitHub Action + SaaS dashboard for PR-time leak detection.
300
+ Apache 2.0see [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
301
301
 
302
- See [docs/v0.2-roadmap.md](./docs/v0.2-roadmap.md) for the full plan.
302
+ Permits commercial use, modification, distribution, patent use. Includes attribution clause via the `NOTICE` file.
303
303
 
304
- ## License
304
+ ## Companion MCPs
305
+
306
+ `memorydetective` focuses on memory-graph and trace artifacts. To bridge "found this leak in the cycle" with "find it in your codebase", pair it with a Swift source-aware MCP. Recommended:
307
+
308
+ - [SwiftLens](https://github.com/swiftlens/swiftlens) — wraps SourceKit-LSP for Swift symbol lookup, reference search, hover info. Different license model (non-commercial); read its terms before using in a paid product.
305
309
 
306
- MIT see [LICENSE](./LICENSE).
310
+ A future version of `memorydetective` may include a similar source-bridging surface natively under Apache 2.0; see the v1.2 roadmap notes in the design docs.
307
311
 
308
312
  ## Why "memorydetective"?
309
313
 
package/USAGE.md ADDED
@@ -0,0 +1,259 @@
1
+ # Usage guide
2
+
3
+ Walkthrough of how `memorydetective` actually works in practice — what each tool returns, how fixes flow from diagnosis to your codebase, and the architecture decision behind splitting "diagnose" from "edit".
4
+
5
+ For a quick API reference, see the [`README.md`](./README.md). For the full changelog, see [`CHANGELOG.md`](./CHANGELOG.md).
6
+
7
+ ---
8
+
9
+ ## 1. Three ways to use it
10
+
11
+ ### 1a. CLI mode — quickest way to see it work
12
+
13
+ ```bash
14
+ npm install -g memorydetective
15
+ memorydetective --version # 1.0.0
16
+
17
+ # Run analyze on any .memgraph file
18
+ memorydetective analyze ~/Desktop/myapp.memgraph
19
+ ```
20
+
21
+ What you see (terminal output, ANSI-coloured):
22
+
23
+ ```
24
+ ┌─ memorydetective analyze ──────────────────────────────────────┐
25
+ │ Path: /Users/.../myapp.memgraph
26
+ │ Process: MyApp (pid 12345)
27
+ │ Bundle: com.example.myapp
28
+ └────────────────────────────────────────────────────────────────┘
29
+
30
+ 60,436 leaks (7.89 MB)
31
+ 4 ROOT CYCLE blocks
32
+
33
+ Top cycle: Swift._DictionaryStorage<SwiftUI.AnyHashable2, SwiftUI…
34
+ chain length: 545 nodes
35
+ app-level classes in chain: Closure context, DetailViewModel,
36
+ ItemRepositoryImpl, ItemGraphQLDataSource, GraphQLClient
37
+
38
+ Diagnosis:
39
+ 60436 leaks; 4 ROOT CYCLE blocks. Largest top-level cycle:
40
+ Swift._DictionaryStorage… (chain of 545 nodes). App-level
41
+ classes in chains: Closure context, DetailViewModel, …
42
+ ```
43
+
44
+ Then ask the classifier for fix advice:
45
+
46
+ ```bash
47
+ memorydetective classify ~/Desktop/myapp.memgraph
48
+ ```
49
+
50
+ You see one block per ROOT CYCLE, like:
51
+
52
+ ```
53
+ Root: Swift._DictionaryStorage<SwiftUI.AnyHashable2, SwiftUI…
54
+ Match: swiftui.tag-index-projection (high confidence)
55
+ Fix hint:
56
+ Replace `[weak self]` capture in tap closures with a static
57
+ helper, OR weak-capture the coordinator/view-model directly
58
+ with `[weak coord = self.coordinator]`. The `.tag()` modifier
59
+ on photo carousels is the usual culprit.
60
+ Also matched: swiftui.dictstorage-weakbox-cycle,
61
+ closure.viewmodel-wrapped-strong,
62
+ swiftui.foreach-state-tap
63
+ ```
64
+
65
+ ### 1b. JSON mode — for scripts and CI
66
+
67
+ ```bash
68
+ memorydetective analyze ~/Desktop/myapp.memgraph --json | jq .totals
69
+ memorydetective classify ~/Desktop/myapp.memgraph --json | jq '.classified[0].primaryMatch'
70
+ ```
71
+
72
+ The JSON shape mirrors the MCP tool's response — same fields, no ANSI colours, ready to pipe into anything.
73
+
74
+ ### 1c. MCP mode — the actual product UX
75
+
76
+ This is what we built it for: an LLM agent (Claude Code, Claude Desktop, Cursor, Cline, Kiro, …) drives the investigation by chat.
77
+
78
+ Add to your MCP client config (Claude Code shown):
79
+
80
+ ```jsonc
81
+ // ~/.claude/settings.json
82
+ {
83
+ "mcpServers": {
84
+ "memorydetective": { "command": "memorydetective" }
85
+ }
86
+ }
87
+ ```
88
+
89
+ Open Claude Code in your iOS project and just ask:
90
+
91
+ > Diagnose `~/Desktop/myapp.memgraph` and find where to fix in this codebase.
92
+
93
+ Claude orchestrates the full flow (see [section 3](#3-how-fixes-actually-flow-from-diagnosis-to-edit)).
94
+
95
+ ---
96
+
97
+ ## 2. The 8 cycle patterns and their fix hints
98
+
99
+ `classifyCycle` ships with a built-in catalog of common iOS retain-cycle patterns. Each pattern returns a `fixHint` — a plain-English string describing the fix direction.
100
+
101
+ | Pattern ID | When it matches | Fix hint (summary) |
102
+ |---|---|---|
103
+ | `swiftui.tag-index-projection` | `TagIndexProjection<Int>` appears in chain (`.tag()` modifier capturing self) | Replace `[weak self]` capture with a static helper, or weak-capture the coordinator/view-model directly. |
104
+ | `swiftui.dictstorage-weakbox-cycle` | Root is `_DictionaryStorage<…WeakBox<AnyLocationBase>>` | SwiftUI internal observation graph cycle. Find your app-level types in the chain and break the strong capture there. |
105
+ | `swiftui.foreach-state-tap` | `SwiftUI.ForEachState` in chain | ForEachState held by a tap-gesture closure capturing `self`. Make the tap handler a static function or capture properties weakly. |
106
+ | `closure.viewmodel-wrapped-strong` | `__strong` edge with `_viewModel.wrappedValue` in label | Closure captures `_viewModel.wrappedValue` strongly. Capture the underlying ObservableObject weakly: `[weak vm = _viewModel.wrappedValue]`. |
107
+ | `viewcontroller.uinavigationcontroller-host` | `UINavigationController` + `UIHostingController` both in chain | Clear `viewControllers = []` in `dismantleUIViewController` to break the host->VC->host cycle. |
108
+ | `combine.sink-store-self-capture` | `AnyCancellable` + `Closure context` | `.sink { self.x = … }` keeps self alive through the AnyCancellable that's stored on self. Capture explicitly: `.sink { [weak self] in self?.x = … }`. |
109
+ | `concurrency.task-without-weak-self` | `_Concurrency.Task<…>` + `Closure context` | `Task { }` body strongly captures self for the lifetime of the task. `Task { [weak self] in guard let self else { return }; … }`. |
110
+ | `notificationcenter.observer-strong` | `NotificationCenter` / `NSNotificationCenter` + `Closure context` | Block-form `addObserver(forName:...)` keeps the block alive in the center. Use `[weak self]` in the block, or store the returned `NSObjectProtocol` and call `removeObserver(_:)` in `deinit`. |
111
+
112
+ **Confidence tiers**: each pattern is checked at `high` first, then `medium`. If multiple patterns fire on the same cycle, all matches are returned — the highest-confidence one is `primaryMatch`, the rest are in `allMatches`.
113
+
114
+ **The hints are deliberately textual, not code patches.** That's by design — see the next section.
115
+
116
+ ---
117
+
118
+ ## 3. How fixes actually flow from diagnosis to edit
119
+
120
+ `memorydetective` is the **diagnose** side. It tells you **what** is wrong, **where in the cycle**, and **what type of fix** is needed. It does **not** edit your code.
121
+
122
+ The **edit** side comes from your LLM agent (Claude Code, Cursor, Cline, …) using its native code-editing tools (Read, Grep, Edit, …).
123
+
124
+ This split is intentional:
125
+
126
+ - The fix depends on your real code (file paths, surrounding context, naming). `memorydetective` knows nothing about your codebase.
127
+ - LLM agents already excel at code editing. Letting them keep that role keeps `memorydetective` focused.
128
+ - The catalog of known patterns becomes the knowledge moat; the LLM brings the codebase awareness.
129
+
130
+ ### Concrete end-to-end example
131
+
132
+ Configuration (one-time): `memorydetective` registered as an MCP server in Claude Code, as shown in [section 1c](#1c-mcp-mode--the-actual-product-ux).
133
+
134
+ You open Claude Code in your iOS project and ask:
135
+
136
+ > **You:** I exported `~/Desktop/myapp.memgraph` after closing some detail screens — something is leaking. Diagnose and find where to fix in this codebase.
137
+
138
+ What Claude does, step by step:
139
+
140
+ 1. **Calls `analyzeMemgraph(path: "~/Desktop/myapp.memgraph")`**
141
+ - Receives: 60,436 leaks, 4 ROOT CYCLE blocks, top cycle is a `_DictionaryStorage` chain.
142
+
143
+ 2. **Calls `classifyCycle(path: "~/Desktop/myapp.memgraph")`**
144
+ - Receives: pattern `swiftui.tag-index-projection` (high confidence) + fix hint about `.tag()` and weak capture.
145
+
146
+ 3. **Uses its own code-editing tools to find the offending file:**
147
+ - `Grep` for `.tag(` in your `*.swift` files.
148
+ - `Grep` for `onImageSliderTap` (or whatever closure name surfaced in the chain).
149
+ - `Read` the candidate files to see how the closure is captured.
150
+
151
+ 4. **Presents the proposed fix in chat:**
152
+
153
+ > **Claude:** Found the cycle. `WishlistPlaceDetailsView.swift:142` passes `onImageSliderTap` as a closure that captures `self`. The `.tag(index)` modifier on the carousel `ForEach` is what triggers the SwiftUI internal `TagIndexProjection` observation that pins the cycle.
154
+ >
155
+ > Suggested fix: hoist the handler to a `static` function and capture only what's needed weakly:
156
+ > ```swift
157
+ > // before
158
+ > onImageSliderTap: { index in
159
+ > self.viewModel.handlePhotoTap(at: index)
160
+ > }
161
+ >
162
+ > // after
163
+ > onImageSliderTap: { [weak vm = _viewModel.wrappedValue,
164
+ > weak coord = self.coordinator] index in
165
+ > Self.handlePhotoTap(index: index, viewModel: vm, coordinator: coord)
166
+ > }
167
+ >
168
+ > // plus a static helper:
169
+ > private static func handlePhotoTap(
170
+ > index: Int,
171
+ > viewModel: DetailViewModel?,
172
+ > coordinator: DetailsCoordinator?
173
+ > ) { /* ... */ }
174
+ > ```
175
+ >
176
+ > Want me to apply this?
177
+
178
+ 5. **You accept** → Claude calls its own `Edit` tool → diff appears in your IDE → you review and commit.
179
+
180
+ 6. **(Optional) Verify the fix:** export a fresh `.memgraph` after running the same flow, then ask Claude:
181
+
182
+ > **You:** Did the fix work? Compare `~/Desktop/myapp-before.memgraph` to `~/Desktop/myapp-after.memgraph`.
183
+
184
+ Claude calls `diffMemgraphs` — instance counts dropped, the `swiftui.tag-index-projection` cycle is gone from `cycles.persisted`, present in `cycles.goneFromBefore`.
185
+
186
+ ### Why this is better than "memorydetective generates the diff"
187
+
188
+ If `memorydetective` tried to generate a code patch, it would have to:
189
+ - Parse Swift source
190
+ - Understand the file's import graph
191
+ - Track the actual variable names and types in scope
192
+ - Match surrounding code style
193
+
194
+ That's exactly what an LLM agent already does — and does well. Splitting the responsibility keeps each side simple. `memorydetective` knows **iOS perf**; the agent knows **your codebase**. They compose.
195
+
196
+ ---
197
+
198
+ ## 4. Common follow-up requests
199
+
200
+ Once you have the diagnosis, here are useful follow-up prompts you can paste into Claude:
201
+
202
+ | Prompt | What Claude calls |
203
+ |---|---|
204
+ | "How many `DetailViewModel` instances are leaking?" | `countAlive(path, className: "DetailViewModel")` |
205
+ | "Show the retain chain that keeps `DetailViewModel` alive." | `findRetainers(path, className: "DetailViewModel")` |
206
+ | "Compare `~/Desktop/before.memgraph` to `~/Desktop/after.memgraph` — did the leak go away?" | `diffMemgraphs(before, after)` |
207
+ | "Render the cycle as a Mermaid graph for the PR description." | `renderCycleGraph(path, format: "mermaid")` |
208
+ | "Profile this app on my iPhone for 90 seconds and tell me about hangs." | `listTraceDevices` → `recordTimeProfile` → `analyzeHangs` |
209
+ | "Pull the last 5 minutes of `error`-level logs from `MyApp`." | `logShow(last: "5m", process: "MyApp", level: "default")` |
210
+ | "Run my XCUITest with leak detection." | `detectLeaksInXCUITest(workspace, scheme, testIdentifier, …)` |
211
+
212
+ The agent decides which tool to call based on your prompt — you don't need to remember the tool names.
213
+
214
+ ---
215
+
216
+ ## 5. Troubleshooting
217
+
218
+ ### `memorydetective: command not found`
219
+
220
+ The npm global install isn't on your `$PATH`. Check:
221
+
222
+ ```bash
223
+ which memorydetective
224
+ npm prefix -g
225
+ ```
226
+
227
+ If `npm prefix -g` returns something not in your `$PATH`, add it. Or use the binary directly:
228
+
229
+ ```bash
230
+ $(npm prefix -g)/bin/memorydetective --version
231
+ ```
232
+
233
+ ### `analyzeTimeProfile` returns a SIGSEGV notice
234
+
235
+ Known limit. `xcrun xctrace export` of the `time-profile` schema crashes on heavy unsymbolicated traces. Workarounds (in order of effort):
236
+
237
+ 1. Open the trace once in Instruments.app (forces symbolication), then close it. Re-run `analyzeTimeProfile`.
238
+ 2. Re-record with a shorter `--time-limit` (try 30 s instead of 90 s).
239
+ 3. For hang analysis specifically, use `analyzeHangs` instead — it parses a different (lighter) schema that doesn't crash.
240
+
241
+ ### `captureMemgraph` fails on a physical iOS device
242
+
243
+ By design. `leaks(1)` only attaches to processes on the local Mac (which includes iOS simulators). Memory Graph capture from a physical device goes through Xcode's debugger over USB — different mechanism, no public CLI equivalent. Use Xcode's Memory Graph button + File → Export Memory Graph for physical devices.
244
+
245
+ ### Tests pass locally but fail in CI
246
+
247
+ The stress test has a wallclock budget that's tighter on slower runners. If you see `expected NNNms to be less than 2000`, bump `PARSE_BUDGET_MS` in `src/stress.test.ts`.
248
+
249
+ ### `detectLeaksInXCUITest` says "after-capture failed"
250
+
251
+ The app process exited before `leaks --outputGraph` could attach. Configure your XCUITest to keep the app alive at end-of-test (e.g. `XCTAssertTrue(true); _ = XCTWaiter.wait(for: [...], timeout: 1.0)`), or use a longer simulator boot. This tool is **experimental** in v1.0 — feedback welcome.
252
+
253
+ ---
254
+
255
+ ## 6. Where to go from here
256
+
257
+ - **Add a new cycle pattern**: see the *Adding a cycle pattern to `classifyCycle`* section in [`README.md`](./README.md#contributing).
258
+ - **Run a custom analysis from scratch**: every tool's input schema is documented via the MCP `tools/list` request. Hit the server with `{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}` over stdio.
259
+ - **Open an issue**: https://github.com/carloshpdoc/memorydetective/issues — bug reports, feature requests, and pattern contributions are all welcome.