memorydetective 1.4.0 → 1.6.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 +57 -0
- package/README.md +84 -11
- package/USAGE.md +105 -4
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +58 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/prompts.d.ts +26 -0
- package/dist/runtime/prompts.js +143 -0
- package/dist/runtime/prompts.js.map +1 -0
- package/dist/runtime/resources.d.ts +26 -0
- package/dist/runtime/resources.js +71 -0
- package/dist/runtime/resources.js.map +1 -0
- package/dist/runtime/staticAnalysisHints.d.ts +29 -0
- package/dist/runtime/staticAnalysisHints.js +209 -0
- package/dist/runtime/staticAnalysisHints.js.map +1 -0
- package/dist/tools/classifyCycle.d.ts +7 -0
- package/dist/tools/classifyCycle.js +173 -0
- package/dist/tools/classifyCycle.js.map +1 -1
- package/dist/tools/getInvestigationPlaybook.d.ts +4 -1
- package/dist/tools/getInvestigationPlaybook.js +3 -1
- package/dist/tools/getInvestigationPlaybook.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Prompts surface — exposes the canonical investigation playbooks as
|
|
3
|
+
* named prompts (slash commands in clients that support them).
|
|
4
|
+
*
|
|
5
|
+
* Each prompt corresponds to a `Playbook` from `getInvestigationPlaybook`,
|
|
6
|
+
* but takes user-provided args (e.g. memgraph path, before/after pair) and
|
|
7
|
+
* fills the placeholders so the agent receives a ready-to-execute brief.
|
|
8
|
+
*
|
|
9
|
+
* Prompt names follow `investigate-*` / `verify-*` convention; surfaces in
|
|
10
|
+
* Claude Code as `/investigate-leak`, `/investigate-hangs`, etc.
|
|
11
|
+
*/
|
|
12
|
+
import { PLAYBOOKS } from "../tools/getInvestigationPlaybook.js";
|
|
13
|
+
export const PROMPTS = [
|
|
14
|
+
{
|
|
15
|
+
name: "investigate-leak",
|
|
16
|
+
title: "Investigate a memgraph leak",
|
|
17
|
+
description: "Run the canonical 6-step memgraph-leak investigation: analyzeMemgraph → classifyCycle → reachableFromCycle → swiftSearchPattern → swiftGetSymbolDefinition → swiftFindSymbolReferences.",
|
|
18
|
+
arguments: [
|
|
19
|
+
{
|
|
20
|
+
name: "memgraphPath",
|
|
21
|
+
description: "Absolute path to the .memgraph file",
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
render: ({ memgraphPath }) => renderPlaybookPrompt(PLAYBOOKS["memgraph-leak"], {
|
|
26
|
+
path: memgraphPath,
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "investigate-hangs",
|
|
31
|
+
title: "Investigate user-visible hangs",
|
|
32
|
+
description: "Diagnose main-thread hangs from a `.trace` recorded with the Time Profiler or Hangs template.",
|
|
33
|
+
arguments: [
|
|
34
|
+
{
|
|
35
|
+
name: "tracePath",
|
|
36
|
+
description: "Absolute path to the .trace bundle",
|
|
37
|
+
required: true,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
render: ({ tracePath }) => renderPlaybookPrompt(PLAYBOOKS["perf-hangs"], { tracePath }),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "investigate-jank",
|
|
44
|
+
title: "Investigate UI jank / animation hitches",
|
|
45
|
+
description: "Diagnose dropped frames from a `.trace` recorded with the Animation Hitches template.",
|
|
46
|
+
arguments: [
|
|
47
|
+
{
|
|
48
|
+
name: "tracePath",
|
|
49
|
+
description: "Absolute path to the .trace bundle",
|
|
50
|
+
required: true,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
render: ({ tracePath }) => renderPlaybookPrompt(PLAYBOOKS["ui-jank"], { tracePath }),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "investigate-launch",
|
|
57
|
+
title: "Investigate slow app launch",
|
|
58
|
+
description: "Diagnose cold/warm launch slowness from a `.trace` recorded with the App Launch template.",
|
|
59
|
+
arguments: [
|
|
60
|
+
{
|
|
61
|
+
name: "tracePath",
|
|
62
|
+
description: "Absolute path to the .trace bundle",
|
|
63
|
+
required: true,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
render: ({ tracePath }) => renderPlaybookPrompt(PLAYBOOKS["app-launch-slow"], { tracePath }),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "verify-cycle-fix",
|
|
70
|
+
title: "Verify a fix closed a known cycle",
|
|
71
|
+
description: "Diff a before/after pair of `.memgraph` snapshots to confirm a fix actually closed the originally-classified cycle.",
|
|
72
|
+
arguments: [
|
|
73
|
+
{
|
|
74
|
+
name: "before",
|
|
75
|
+
description: "Path to the before-fix .memgraph",
|
|
76
|
+
required: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "after",
|
|
80
|
+
description: "Path to the after-fix .memgraph",
|
|
81
|
+
required: true,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
render: ({ before, after }) => renderPlaybookPrompt(PLAYBOOKS["verify-fix"], { before, after }),
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
export function findPrompt(name) {
|
|
88
|
+
return PROMPTS.find((p) => p.name === name);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Substitutes user-provided values into a playbook's argsTemplate slots and
|
|
92
|
+
* renders a markdown brief the agent can act on directly.
|
|
93
|
+
*/
|
|
94
|
+
function renderPlaybookPrompt(playbook, values) {
|
|
95
|
+
const lines = [];
|
|
96
|
+
lines.push(`Run the **${playbook.kind}** investigation playbook.`);
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push(playbook.summary);
|
|
99
|
+
lines.push("");
|
|
100
|
+
lines.push("## User-provided values");
|
|
101
|
+
for (const [key, value] of Object.entries(values)) {
|
|
102
|
+
lines.push(`- \`${key}\`: \`${value}\``);
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
lines.push("## Steps");
|
|
106
|
+
lines.push("");
|
|
107
|
+
for (const step of playbook.steps) {
|
|
108
|
+
const argsRendered = renderArgsTemplate(step.argsTemplate, values);
|
|
109
|
+
lines.push(`### ${step.step}. \`${step.tool}\``);
|
|
110
|
+
lines.push("");
|
|
111
|
+
lines.push(step.purpose);
|
|
112
|
+
lines.push("");
|
|
113
|
+
lines.push("```json");
|
|
114
|
+
lines.push(argsRendered);
|
|
115
|
+
lines.push("```");
|
|
116
|
+
if (step.resultGuidance) {
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(`> ${step.resultGuidance}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push("");
|
|
121
|
+
}
|
|
122
|
+
lines.push("Execute the steps in order. After each tool call, follow any");
|
|
123
|
+
lines.push("`suggestedNextCalls` returned by the tool — they're the canonical");
|
|
124
|
+
lines.push("chain. Do not propose architectural changes before evidence.");
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
function renderArgsTemplate(template, values) {
|
|
128
|
+
// Substitute values where keys match — e.g. {path: "<absolute path...>"}
|
|
129
|
+
// becomes {path: "<actual user value>"} when values has matching key.
|
|
130
|
+
// For slots we don't have, leave the template placeholder so the agent
|
|
131
|
+
// can fill it from result chaining (e.g. step 2's class name).
|
|
132
|
+
const filled = {};
|
|
133
|
+
for (const [key, slot] of Object.entries(template)) {
|
|
134
|
+
if (typeof slot === "string" && values[key]) {
|
|
135
|
+
filled[key] = values[key];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
filled[key] = slot;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return JSON.stringify(filled, null, 2);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/runtime/prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources surface — exposes the cycle-pattern catalog as browsable
|
|
3
|
+
* read-only resources. Clients can list / read patterns without burning a
|
|
4
|
+
* tool call (cheaper than `classifyCycle`'s incidental output).
|
|
5
|
+
*
|
|
6
|
+
* URI shape: `memorydetective://patterns/{patternId}`
|
|
7
|
+
*
|
|
8
|
+
* Each resource body is markdown — clients render it directly in the UI.
|
|
9
|
+
*/
|
|
10
|
+
export interface PatternResource {
|
|
11
|
+
uri: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
mimeType: "text/markdown";
|
|
15
|
+
}
|
|
16
|
+
export interface PatternResourceBody {
|
|
17
|
+
uri: string;
|
|
18
|
+
mimeType: "text/markdown";
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
/** Build the resource list shown to clients via `resources/list`. */
|
|
22
|
+
export declare function listPatternResources(): PatternResource[];
|
|
23
|
+
/** Resolve a pattern URI to a markdown body, or return null if unknown. */
|
|
24
|
+
export declare function readPatternResource(uri: string): PatternResourceBody | null;
|
|
25
|
+
export declare function patternUri(patternId: string): string;
|
|
26
|
+
export declare function patternIdFromUri(uri: string): string | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources surface — exposes the cycle-pattern catalog as browsable
|
|
3
|
+
* read-only resources. Clients can list / read patterns without burning a
|
|
4
|
+
* tool call (cheaper than `classifyCycle`'s incidental output).
|
|
5
|
+
*
|
|
6
|
+
* URI shape: `memorydetective://patterns/{patternId}`
|
|
7
|
+
*
|
|
8
|
+
* Each resource body is markdown — clients render it directly in the UI.
|
|
9
|
+
*/
|
|
10
|
+
import { PATTERNS } from "../tools/classifyCycle.js";
|
|
11
|
+
const URI_SCHEME = "memorydetective";
|
|
12
|
+
const PATTERNS_HOST = "patterns";
|
|
13
|
+
/** Build the resource list shown to clients via `resources/list`. */
|
|
14
|
+
export function listPatternResources() {
|
|
15
|
+
return PATTERNS.map((p) => ({
|
|
16
|
+
uri: patternUri(p.id),
|
|
17
|
+
name: p.name,
|
|
18
|
+
description: oneLineDescription(p),
|
|
19
|
+
mimeType: "text/markdown",
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/** Resolve a pattern URI to a markdown body, or return null if unknown. */
|
|
23
|
+
export function readPatternResource(uri) {
|
|
24
|
+
const id = patternIdFromUri(uri);
|
|
25
|
+
if (!id)
|
|
26
|
+
return null;
|
|
27
|
+
const pattern = PATTERNS.find((p) => p.id === id);
|
|
28
|
+
if (!pattern)
|
|
29
|
+
return null;
|
|
30
|
+
return {
|
|
31
|
+
uri,
|
|
32
|
+
mimeType: "text/markdown",
|
|
33
|
+
text: renderPatternMarkdown(pattern),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function patternUri(patternId) {
|
|
37
|
+
return `${URI_SCHEME}://${PATTERNS_HOST}/${patternId}`;
|
|
38
|
+
}
|
|
39
|
+
export function patternIdFromUri(uri) {
|
|
40
|
+
const prefix = `${URI_SCHEME}://${PATTERNS_HOST}/`;
|
|
41
|
+
if (!uri.startsWith(prefix))
|
|
42
|
+
return null;
|
|
43
|
+
const id = uri.slice(prefix.length);
|
|
44
|
+
return id.length > 0 ? id : null;
|
|
45
|
+
}
|
|
46
|
+
/** Single-line summary used as the resource list description. */
|
|
47
|
+
function oneLineDescription(p) {
|
|
48
|
+
// The full fixHint can be long; the list view should be scannable.
|
|
49
|
+
return p.name;
|
|
50
|
+
}
|
|
51
|
+
/** Markdown body for `resources/read` responses. */
|
|
52
|
+
function renderPatternMarkdown(p) {
|
|
53
|
+
return [
|
|
54
|
+
`# ${p.name}`,
|
|
55
|
+
"",
|
|
56
|
+
`**Pattern ID:** \`${p.id}\``,
|
|
57
|
+
"",
|
|
58
|
+
"## Fix hint",
|
|
59
|
+
"",
|
|
60
|
+
p.fixHint,
|
|
61
|
+
"",
|
|
62
|
+
"---",
|
|
63
|
+
"",
|
|
64
|
+
"*This is a built-in pattern from the `memorydetective` cycle classifier. " +
|
|
65
|
+
"When a `.memgraph` cycle matches this pattern, `classifyCycle` returns this fix hint " +
|
|
66
|
+
"in its `primaryMatch.fixHint` field, and `verifyFix` can gate a cycle-semantic diff " +
|
|
67
|
+
`against \`expectedPatternId: "${p.id}"\`.*`,
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/runtime/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,MAAM,UAAU,GAAG,iBAAiB,CAAC;AACrC,MAAM,aAAa,GAAG,UAAU,CAAC;AAejC,qEAAqE;AACrE,MAAM,UAAU,oBAAoB;IAClC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAClC,QAAQ,EAAE,eAAwB;KACnC,CAAC,CAAC,CAAC;AACN,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO;QACL,GAAG;QACH,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,OAAO,GAAG,UAAU,MAAM,aAAa,IAAI,SAAS,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,MAAM,GAAG,GAAG,UAAU,MAAM,aAAa,GAAG,CAAC;IACnD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC;AAED,iEAAiE;AACjE,SAAS,kBAAkB,CAAC,CAA+B;IACzD,mEAAmE;IACnE,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC;AAED,oDAAoD;AACpD,SAAS,qBAAqB,CAAC,CAI9B;IACC,OAAO;QACL,KAAK,CAAC,CAAC,IAAI,EAAE;QACb,EAAE;QACF,qBAAqB,CAAC,CAAC,EAAE,IAAI;QAC7B,EAAE;QACF,aAAa;QACb,EAAE;QACF,CAAC,CAAC,OAAO;QACT,EAAE;QACF,KAAK;QACL,EAAE;QACF,2EAA2E;YACzE,uFAAuF;YACvF,sFAAsF;YACtF,iCAAiC,CAAC,CAAC,EAAE,OAAO;QAC9C,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static-analysis bridge: maps each cycle pattern in the catalog to the
|
|
3
|
+
* SwiftLint rule (or other static analyzer) that *would* have caught it
|
|
4
|
+
* at parse time, OR explicitly notes when no static rule exists yet.
|
|
5
|
+
*
|
|
6
|
+
* Reinforces the differentiator: memorydetective sees the *runtime evidence*
|
|
7
|
+
* (the actual cycle in a memgraph) where compilers and linters miss it
|
|
8
|
+
* because the cycle's existence requires runtime conditions (closure
|
|
9
|
+
* actually outlives owner, AsyncSequence actually never terminates, etc.).
|
|
10
|
+
*
|
|
11
|
+
* Sources:
|
|
12
|
+
* - SwiftLint rules: https://realm.github.io/SwiftLint/rule-directory.html
|
|
13
|
+
* - SwiftLint open-but-unshipped @escaping rule (8 years old):
|
|
14
|
+
* https://github.com/realm/SwiftLint/issues/776
|
|
15
|
+
* - Swift compiler nested-closure [weak self] warning (partial fix):
|
|
16
|
+
* https://github.com/swiftlang/swift/issues/72391 — PR #77063
|
|
17
|
+
*/
|
|
18
|
+
export interface StaticAnalysisHint {
|
|
19
|
+
/** SwiftLint rule identifier, or null when no rule exists. */
|
|
20
|
+
rule: string | null;
|
|
21
|
+
/** URL to the rule docs OR the open issue tracking the gap. */
|
|
22
|
+
url: string | null;
|
|
23
|
+
/** Plain-English explanation of the relationship. */
|
|
24
|
+
explanation: string;
|
|
25
|
+
}
|
|
26
|
+
/** Returns the static-analysis hint for a given pattern, or null if unknown. */
|
|
27
|
+
export declare function getStaticAnalysisHint(patternId: string): StaticAnalysisHint | null;
|
|
28
|
+
/** All known pattern ids that have hints. Used in tests for coverage assertion. */
|
|
29
|
+
export declare function knownHintPatternIds(): string[];
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static-analysis bridge: maps each cycle pattern in the catalog to the
|
|
3
|
+
* SwiftLint rule (or other static analyzer) that *would* have caught it
|
|
4
|
+
* at parse time, OR explicitly notes when no static rule exists yet.
|
|
5
|
+
*
|
|
6
|
+
* Reinforces the differentiator: memorydetective sees the *runtime evidence*
|
|
7
|
+
* (the actual cycle in a memgraph) where compilers and linters miss it
|
|
8
|
+
* because the cycle's existence requires runtime conditions (closure
|
|
9
|
+
* actually outlives owner, AsyncSequence actually never terminates, etc.).
|
|
10
|
+
*
|
|
11
|
+
* Sources:
|
|
12
|
+
* - SwiftLint rules: https://realm.github.io/SwiftLint/rule-directory.html
|
|
13
|
+
* - SwiftLint open-but-unshipped @escaping rule (8 years old):
|
|
14
|
+
* https://github.com/realm/SwiftLint/issues/776
|
|
15
|
+
* - Swift compiler nested-closure [weak self] warning (partial fix):
|
|
16
|
+
* https://github.com/swiftlang/swift/issues/72391 — PR #77063
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Pattern-id → static-analysis hint. Every pattern in `classifyCycle.PATTERNS`
|
|
20
|
+
* gets an entry — either a real rule or an explicit `null` rule with reasoning.
|
|
21
|
+
*/
|
|
22
|
+
const HINTS = {
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// v1.0 core
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
26
|
+
"swiftui.tag-index-projection": {
|
|
27
|
+
rule: null,
|
|
28
|
+
url: null,
|
|
29
|
+
explanation: "No static rule exists. The cycle is in SwiftUI's internal observation graph (TagIndexProjection), not in the user's closure. Static analyzers don't model SwiftUI internals.",
|
|
30
|
+
},
|
|
31
|
+
"swiftui.dictstorage-weakbox-cycle": {
|
|
32
|
+
rule: null,
|
|
33
|
+
url: null,
|
|
34
|
+
explanation: "No static rule exists. SwiftUI's `_DictionaryStorage<...WeakBox<AnyLocationBase>>` is a private observation type; user code never references it directly.",
|
|
35
|
+
},
|
|
36
|
+
"swiftui.foreach-state-tap": {
|
|
37
|
+
rule: null,
|
|
38
|
+
url: null,
|
|
39
|
+
explanation: "No static rule exists. The leak appears when a tap-gesture closure captures self and ForEachState outlives the closure — neither side is detectable in isolation.",
|
|
40
|
+
},
|
|
41
|
+
"closure.viewmodel-wrapped-strong": {
|
|
42
|
+
rule: null,
|
|
43
|
+
url: "https://github.com/realm/SwiftLint/issues/776",
|
|
44
|
+
explanation: "SwiftLint has an open-but-unshipped rule for `@escaping` closure capture cycles (issue #776, open since 2017). Until that ships, this can only be caught at runtime via the memgraph.",
|
|
45
|
+
},
|
|
46
|
+
"viewcontroller.uinavigationcontroller-host": {
|
|
47
|
+
rule: null,
|
|
48
|
+
url: null,
|
|
49
|
+
explanation: "No static rule exists. The cycle requires `UIViewControllerRepresentable` plus a hosting controller plus a `dismantleUIViewController` that doesn't clear the view-controller stack — too contextual for static analysis.",
|
|
50
|
+
},
|
|
51
|
+
"combine.sink-store-self-capture": {
|
|
52
|
+
rule: "weak_self",
|
|
53
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
54
|
+
explanation: "SwiftLint's `weak_self` rule warns on closure bodies that reference `self` without `[weak self]`. Catches the most obvious cases. Doesn't catch nested closures where only the inner one has `[weak self]` (Swift compiler issue #72391, partially fixed in PR #77063).",
|
|
55
|
+
},
|
|
56
|
+
"concurrency.task-without-weak-self": {
|
|
57
|
+
rule: "weak_self",
|
|
58
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
59
|
+
explanation: "SwiftLint `weak_self` flags `Task { self.foo() }` without `[weak self]`. The runtime evidence is still useful when the warning is suppressed or the closure is nested.",
|
|
60
|
+
},
|
|
61
|
+
"notificationcenter.observer-strong": {
|
|
62
|
+
rule: "weak_self",
|
|
63
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
64
|
+
explanation: "Same as `combine.sink-store-self-capture` — `weak_self` catches the closure form. Doesn't help when the observer is stored as `NSObjectProtocol` and never explicitly removed in `deinit`.",
|
|
65
|
+
},
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// v1.4 expansion
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
69
|
+
"timer.scheduled-target-strong": {
|
|
70
|
+
rule: null,
|
|
71
|
+
url: null,
|
|
72
|
+
explanation: "No static rule. The leak is in the *target/selector* form of `Timer.scheduledTimer` — selector-based APIs aren't analyzable for retain semantics by SwiftLint.",
|
|
73
|
+
},
|
|
74
|
+
"displaylink.target-strong": {
|
|
75
|
+
rule: null,
|
|
76
|
+
url: null,
|
|
77
|
+
explanation: "Same as `timer.scheduled-target-strong` — selector-based target retention is opaque to static analysis.",
|
|
78
|
+
},
|
|
79
|
+
"gesture.target-strong": {
|
|
80
|
+
rule: null,
|
|
81
|
+
url: null,
|
|
82
|
+
explanation: "No static rule for `addTarget(_:action:)` retention. The closure-style `UIAction` API (iOS 14+) avoids this; promoting it would prevent the leak by construction.",
|
|
83
|
+
},
|
|
84
|
+
"kvo.observation-not-invalidated": {
|
|
85
|
+
rule: "weak_self",
|
|
86
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
87
|
+
explanation: "`weak_self` partially helps (the change-handler closure should `[weak self]`). Doesn't catch the `token.invalidate()` omission — that requires lifecycle analysis.",
|
|
88
|
+
},
|
|
89
|
+
"urlsession.delegate-strong": {
|
|
90
|
+
rule: null,
|
|
91
|
+
url: null,
|
|
92
|
+
explanation: "No static rule. `URLSession.init(configuration:delegate:delegateQueue:)`'s strong-delegate semantics is documented Apple behavior, not a closure-capture issue.",
|
|
93
|
+
},
|
|
94
|
+
"dispatch.source-event-handler-self": {
|
|
95
|
+
rule: "weak_self",
|
|
96
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
97
|
+
explanation: "`weak_self` catches `setEventHandler { self.foo() }`. Doesn't catch the missing `setEventHandler {}` clear-out in `deinit`.",
|
|
98
|
+
},
|
|
99
|
+
"notificationcenter.observer-not-removed": {
|
|
100
|
+
rule: null,
|
|
101
|
+
url: null,
|
|
102
|
+
explanation: "No static rule. The leak is the *omission* of `removeObserver(_:)` in `deinit` — static analyzers don't reason about lifecycle balance.",
|
|
103
|
+
},
|
|
104
|
+
"delegate.strong-reference": {
|
|
105
|
+
rule: "weak_delegate",
|
|
106
|
+
url: "https://realm.github.io/SwiftLint/weak_delegate.html",
|
|
107
|
+
explanation: "SwiftLint's `weak_delegate` rule warns on `var delegate: SomeProtocol?` without `weak`. Catches the canonical case directly.",
|
|
108
|
+
},
|
|
109
|
+
"swiftui.envobject-back-reference": {
|
|
110
|
+
rule: null,
|
|
111
|
+
url: null,
|
|
112
|
+
explanation: "No static rule. `@EnvironmentObject` strong back-references to UIKit interop classes require dependency-graph analysis SwiftLint doesn't perform.",
|
|
113
|
+
},
|
|
114
|
+
"combine.assign-to-self": {
|
|
115
|
+
rule: null,
|
|
116
|
+
url: null,
|
|
117
|
+
explanation: "No static rule. `.assign(to: \\.x, on: self)` is syntactically valid; the retention is in the Combine internals.",
|
|
118
|
+
},
|
|
119
|
+
"concurrency.task-mainactor-view": {
|
|
120
|
+
rule: "weak_self",
|
|
121
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
122
|
+
explanation: "`weak_self` catches the closure body. Doesn't catch the SwiftUI-view-storage lifetime issue — `.task { ... }` modifier vs `Task { ... }` block can't be told apart by syntax alone.",
|
|
123
|
+
},
|
|
124
|
+
"concurrency.asyncstream-continuation-self": {
|
|
125
|
+
rule: null,
|
|
126
|
+
url: null,
|
|
127
|
+
explanation: "No static rule. `AsyncStream` continuation retention is a runtime property — the consuming Task either terminates or it doesn't.",
|
|
128
|
+
},
|
|
129
|
+
"webkit.scriptmessage-handler-strong": {
|
|
130
|
+
rule: null,
|
|
131
|
+
url: null,
|
|
132
|
+
explanation: "No static rule. `WKUserContentController.add(_:name:)` has documented strong-retain semantics; the cycle requires the handler to also own the WebView.",
|
|
133
|
+
},
|
|
134
|
+
"coordinator.parent-strong-back-reference": {
|
|
135
|
+
rule: null,
|
|
136
|
+
url: null,
|
|
137
|
+
explanation: "No static rule. The Coordinator pattern's `parentCoordinator` property usually isn't named `delegate`, so `weak_delegate` doesn't fire. A custom rule could match `parent*Coordinator` properties — would be a useful SwiftLint contribution.",
|
|
138
|
+
},
|
|
139
|
+
"rxswift.disposebag-self-cycle": {
|
|
140
|
+
rule: "weak_self",
|
|
141
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
142
|
+
explanation: "`weak_self` partially helps for explicit closures. Doesn't catch the unbound-method-reference form: `subscribe(onNext: self.handle)` — Swift auto-captures the instance strongly with no syntax cue.",
|
|
143
|
+
},
|
|
144
|
+
"realm.notificationtoken-retained": {
|
|
145
|
+
rule: "weak_self",
|
|
146
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
147
|
+
explanation: "`weak_self` catches the observe closure. Doesn't catch the `token?.invalidate()` omission in `deinit`.",
|
|
148
|
+
},
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
150
|
+
// v1.5 catalog completion
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
152
|
+
"coreanimation.animation-delegate-strong": {
|
|
153
|
+
rule: null,
|
|
154
|
+
url: null,
|
|
155
|
+
explanation: "No static rule. `CAAnimation.delegate`'s strong-retain is Apple-documented behavior, not detectable from source. A targeted SwiftLint custom rule could flag `anim.delegate = self` patterns near `layer.add(anim, ...)` — has not been written.",
|
|
156
|
+
},
|
|
157
|
+
"coreanimation.layer-delegate-cycle": {
|
|
158
|
+
rule: null,
|
|
159
|
+
url: null,
|
|
160
|
+
explanation: "No static rule. The leak depends on whether the layer's delegate is a UIView (auto-paired, safe) or another type (leaks). Runtime-only.",
|
|
161
|
+
},
|
|
162
|
+
"coredata.fetchedresultscontroller-delegate": {
|
|
163
|
+
rule: null,
|
|
164
|
+
url: null,
|
|
165
|
+
explanation: "No static rule. `NSFetchedResultsController.delegate` is declared `weak` in modern bridging headers, so `weak_delegate` doesn't fire — but the change-tracker still retains via the ObjC contract. Runtime-only.",
|
|
166
|
+
},
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
168
|
+
// v1.6 catalog expansion
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
170
|
+
"swiftui.observable-state-modal-leak": {
|
|
171
|
+
rule: null,
|
|
172
|
+
url: null,
|
|
173
|
+
explanation: "No static rule. The leak depends on `@Observable` + modal presentation lifecycle interaction — too contextual for SwiftLint.",
|
|
174
|
+
},
|
|
175
|
+
"swiftui.navigationpath-stored-in-viewmodel": {
|
|
176
|
+
rule: null,
|
|
177
|
+
url: null,
|
|
178
|
+
explanation: "No static rule. `NavigationPath` is a value type held on a class — perfectly normal syntactically. The leak is in the lifecycle of pushed elements, observable only at runtime.",
|
|
179
|
+
},
|
|
180
|
+
"concurrency.async-sequence-on-self": {
|
|
181
|
+
rule: null,
|
|
182
|
+
url: "https://forums.swift.org/t/memory-leak-issue-while-asynchronously-iterating-over-async-sequence/64584",
|
|
183
|
+
explanation: "No static rule, and `[weak self]` does NOT help here — the iteration itself holds self. Documented on Swift Forums (#64584). A future swift-format/SwiftLint rule could warn on `for await ... in <storedSeq> { /* uses self */ }` patterns.",
|
|
184
|
+
},
|
|
185
|
+
"concurrency.notificationcenter-async-observer-task": {
|
|
186
|
+
rule: null,
|
|
187
|
+
url: "https://forums.swift.org/t/asyncsequence-version-of-notifications-is-causing-memory-leaks/77257",
|
|
188
|
+
explanation: "Same as `concurrency.async-sequence-on-self` — `[weak self]` is insufficient because the iteration holds the actor isolation context. Specifically called out on Swift Forums (#77257).",
|
|
189
|
+
},
|
|
190
|
+
"swiftui.observations-closure-strong-self": {
|
|
191
|
+
rule: "weak_self",
|
|
192
|
+
url: "https://realm.github.io/SwiftLint/weak_self.html",
|
|
193
|
+
explanation: "`weak_self` catches the closure body — same shape as `Combine.sink`. The `Observations { }` API is brand-new (Swift 6.2 / Xcode 26), so most projects haven't enabled the rule for it yet.",
|
|
194
|
+
},
|
|
195
|
+
"webkit.wkscriptmessagehandler-bridge": {
|
|
196
|
+
rule: null,
|
|
197
|
+
url: null,
|
|
198
|
+
explanation: "No static rule. The 3-link cycle requires a class to both own a `WKWebView` AND conform to `WKScriptMessageHandler` — detectable at runtime via the memgraph but not via SwiftLint conformance analysis.",
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
/** Returns the static-analysis hint for a given pattern, or null if unknown. */
|
|
202
|
+
export function getStaticAnalysisHint(patternId) {
|
|
203
|
+
return HINTS[patternId] ?? null;
|
|
204
|
+
}
|
|
205
|
+
/** All known pattern ids that have hints. Used in tests for coverage assertion. */
|
|
206
|
+
export function knownHintPatternIds() {
|
|
207
|
+
return Object.keys(HINTS);
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=staticAnalysisHints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staticAnalysisHints.js","sourceRoot":"","sources":["../../src/runtime/staticAnalysisHints.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type StaticAnalysisHint } from "../runtime/staticAnalysisHints.js";
|
|
2
3
|
import type { CycleNode, LeaksReport, NextCallSuggestion } from "../types.js";
|
|
3
4
|
export declare const classifyCycleSchema: z.ZodObject<{
|
|
4
5
|
path: z.ZodString;
|
|
@@ -22,6 +23,12 @@ export interface PatternMatch {
|
|
|
22
23
|
reason: string;
|
|
23
24
|
/** Suggested fix direction (one-liner). */
|
|
24
25
|
fixHint: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional bridge to static analysis: which SwiftLint rule (if any) would
|
|
28
|
+
* have caught this cycle at parse time, or an explicit gap notice when no
|
|
29
|
+
* rule exists. Populated for every catalog pattern.
|
|
30
|
+
*/
|
|
31
|
+
staticAnalysisHint?: StaticAnalysisHint;
|
|
25
32
|
}
|
|
26
33
|
export interface CycleClassification {
|
|
27
34
|
rootClass: string;
|