memorydetective 1.8.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/README.md +84 -15
  3. package/USAGE.md +27 -4
  4. package/dist/cli.js +106 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.js +42 -22
  7. package/dist/index.js.map +1 -1
  8. package/dist/parsers/referenceTree.d.ts +111 -0
  9. package/dist/parsers/referenceTree.js +328 -0
  10. package/dist/parsers/referenceTree.js.map +1 -0
  11. package/dist/runtime/axe.js +6 -1
  12. package/dist/runtime/axe.js.map +1 -1
  13. package/dist/runtime/exec.d.ts +30 -0
  14. package/dist/runtime/exec.js +30 -3
  15. package/dist/runtime/exec.js.map +1 -1
  16. package/dist/runtime/fixTemplates.js +67 -0
  17. package/dist/runtime/fixTemplates.js.map +1 -1
  18. package/dist/runtime/leakReport.d.ts +62 -0
  19. package/dist/runtime/leakReport.js +138 -0
  20. package/dist/runtime/leakReport.js.map +1 -0
  21. package/dist/runtime/platformCheck.d.ts +54 -0
  22. package/dist/runtime/platformCheck.js +94 -0
  23. package/dist/runtime/platformCheck.js.map +1 -0
  24. package/dist/runtime/redact.d.ts +66 -0
  25. package/dist/runtime/redact.js +146 -0
  26. package/dist/runtime/redact.js.map +1 -0
  27. package/dist/runtime/responseFormatter.d.ts +78 -0
  28. package/dist/runtime/responseFormatter.js +307 -0
  29. package/dist/runtime/responseFormatter.js.map +1 -0
  30. package/dist/runtime/securityFlags.d.ts +74 -0
  31. package/dist/runtime/securityFlags.js +90 -0
  32. package/dist/runtime/securityFlags.js.map +1 -0
  33. package/dist/runtime/staticAnalysisHints.js +14 -1
  34. package/dist/runtime/staticAnalysisHints.js.map +1 -1
  35. package/dist/templates/leak-report.html +39 -0
  36. package/dist/templates/templates/leak-report.html +39 -0
  37. package/dist/tools/analyzeAbandonedMemory.d.ts +162 -0
  38. package/dist/tools/analyzeAbandonedMemory.js +325 -0
  39. package/dist/tools/analyzeAbandonedMemory.js.map +1 -0
  40. package/dist/tools/analyzeAllocations.d.ts +11 -2
  41. package/dist/tools/analyzeAllocations.js +4 -0
  42. package/dist/tools/analyzeAllocations.js.map +1 -1
  43. package/dist/tools/analyzeAnimationHitches.d.ts +32 -2
  44. package/dist/tools/analyzeAnimationHitches.js +25 -4
  45. package/dist/tools/analyzeAnimationHitches.js.map +1 -1
  46. package/dist/tools/analyzeAppLaunch.d.ts +3 -0
  47. package/dist/tools/analyzeAppLaunch.js +2 -0
  48. package/dist/tools/analyzeAppLaunch.js.map +1 -1
  49. package/dist/tools/analyzeHangs.d.ts +78 -2
  50. package/dist/tools/analyzeHangs.js +117 -4
  51. package/dist/tools/analyzeHangs.js.map +1 -1
  52. package/dist/tools/analyzeMemgraph.d.ts +40 -1
  53. package/dist/tools/analyzeMemgraph.js +66 -2
  54. package/dist/tools/analyzeMemgraph.js.map +1 -1
  55. package/dist/tools/analyzeTimeProfile.d.ts +11 -1
  56. package/dist/tools/analyzeTimeProfile.js +5 -0
  57. package/dist/tools/analyzeTimeProfile.js.map +1 -1
  58. package/dist/tools/bootAndLaunchForLeakInvestigation.d.ts +18 -9
  59. package/dist/tools/bootAndLaunchForLeakInvestigation.js +27 -0
  60. package/dist/tools/bootAndLaunchForLeakInvestigation.js.map +1 -1
  61. package/dist/tools/captureMemgraph.d.ts +22 -4
  62. package/dist/tools/captureMemgraph.js +42 -9
  63. package/dist/tools/captureMemgraph.js.map +1 -1
  64. package/dist/tools/captureScenarioState.d.ts +12 -4
  65. package/dist/tools/captureScenarioState.js +4 -0
  66. package/dist/tools/captureScenarioState.js.map +1 -1
  67. package/dist/tools/classifyCycle.js +77 -0
  68. package/dist/tools/classifyCycle.js.map +1 -1
  69. package/dist/tools/cleanupTraces.d.ts +87 -0
  70. package/dist/tools/cleanupTraces.js +232 -0
  71. package/dist/tools/cleanupTraces.js.map +1 -0
  72. package/dist/tools/compareTracesByPattern.d.ts +2 -2
  73. package/dist/tools/detectLeaksInXCTest.d.ts +116 -0
  74. package/dist/tools/detectLeaksInXCTest.js +311 -0
  75. package/dist/tools/detectLeaksInXCTest.js.map +1 -0
  76. package/dist/tools/detectLeaksInXCUITest.d.ts +8 -3
  77. package/dist/tools/detectLeaksInXCUITest.js +30 -4
  78. package/dist/tools/detectLeaksInXCUITest.js.map +1 -1
  79. package/dist/tools/diffMemgraphs.d.ts +5 -2
  80. package/dist/tools/diffMemgraphs.js +2 -0
  81. package/dist/tools/diffMemgraphs.js.map +1 -1
  82. package/dist/tools/findCycles.d.ts +1 -1
  83. package/dist/tools/recordTimeProfile.d.ts +29 -9
  84. package/dist/tools/recordTimeProfile.js +70 -7
  85. package/dist/tools/recordTimeProfile.js.map +1 -1
  86. package/dist/tools/renderCycleGraph.d.ts +1 -1
  87. package/dist/tools/verifyFix.d.ts +2 -2
  88. package/dist/types.d.ts +24 -1
  89. package/package.json +3 -3
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Shared response formatter for MCP tool outputs.
3
+ *
4
+ * Tools can declare `outputFormat?: "markdown" | "json" | "both"` (defaults to
5
+ * `"json"`, preserving v1.8 behavior). When the caller asks for a different
6
+ * shape, the registration wrapper in `src/index.ts` calls
7
+ * {@link formatMcpResponse} to produce the actual response content.
8
+ *
9
+ * Two audiences are served at once:
10
+ * - **AI agents** (the typical caller) want raw JSON they can parse and chain
11
+ * into the next call without re-reasoning.
12
+ * - **Humans reading the same response** (the typical second audience: the
13
+ * dev pasting the result into a PR comment, a Slack thread, or a Jira
14
+ * ticket) want a markdown view that highlights the key fields.
15
+ *
16
+ * `outputFormat: "both"` returns BOTH content items in a single response, so a
17
+ * client can display the markdown to the user AND parse the JSON for the
18
+ * agent loop without an extra round-trip.
19
+ */
20
+ import { z } from "zod";
21
+ import { getRedactionMode, redact } from "./redact.js";
22
+ export const outputFormatField = z
23
+ .enum(["markdown", "json", "both", "verify-fix-table"])
24
+ .optional()
25
+ .describe("Response format. Omitted or `json` (default, preserves v1.8 behavior) returns JSON.stringify of the result. `markdown` renders a human-readable view of the same data. `both` returns both content items in one response, so a client can display markdown to the user and parse JSON for the agent loop without a second call. `verify-fix-table` (v1.10, applies to `analyzeAbandonedMemory` and `diffMemgraphs`) emits a focused 4-column markdown comparison table (Class | Before | After | Delta) of the actionable rows; other tools fall back to `markdown` for this value.");
26
+ /**
27
+ * Pure: shape the MCP response based on the caller's `outputFormat`.
28
+ *
29
+ * For `json` and `both`, the JSON is `JSON.stringify(result, null, 2)`. For
30
+ * `markdown` and `both`, the markdown is rendered via {@link renderAsMarkdown}.
31
+ */
32
+ export function formatMcpResponse(result, toolName, format) {
33
+ const mode = format ?? "json";
34
+ // Redaction happens at the structured-value level so both the JSON
35
+ // and the markdown views are scrubbed consistently. `off` short-circuits
36
+ // and returns the input unchanged; `balanced` (default) masks home-dir
37
+ // paths and common secret-shaped tokens; `strict` also masks hostnames,
38
+ // IPs, and bundle identifiers. See src/runtime/redact.ts.
39
+ const redacted = redact(result, getRedactionMode());
40
+ const json = JSON.stringify(redacted, null, 2);
41
+ if (mode === "json") {
42
+ return { content: [{ type: "text", text: json }] };
43
+ }
44
+ // `verify-fix-table` is a focused renderer that takes precedence over
45
+ // the generic markdown one. Falls back to standard markdown when the
46
+ // tool does not implement a verify-fix view.
47
+ if (mode === "verify-fix-table") {
48
+ const focused = renderVerifyFixTable(redacted, toolName);
49
+ if (focused != null) {
50
+ return { content: [{ type: "text", text: focused }] };
51
+ }
52
+ // Fall through to markdown for tools that don't implement it.
53
+ const fallback = renderAsMarkdown(redacted, toolName);
54
+ return { content: [{ type: "text", text: fallback }] };
55
+ }
56
+ const markdown = renderAsMarkdown(redacted, toolName);
57
+ if (mode === "markdown") {
58
+ return { content: [{ type: "text", text: markdown }] };
59
+ }
60
+ // "both": markdown first so a UI that picks content[0] gets the readable
61
+ // view, then JSON so an agent looking for the structured data finds it
62
+ // without having to parse the markdown.
63
+ return {
64
+ content: [
65
+ { type: "text", text: markdown },
66
+ { type: "text", text: json },
67
+ ],
68
+ };
69
+ }
70
+ /**
71
+ * Pure: render a verify-fix focused markdown table for tools that support
72
+ * it. Returns `null` if the tool's result does not match the expected
73
+ * verify-fix shape, signaling the caller to fall back to standard markdown.
74
+ *
75
+ * Supported tools:
76
+ *
77
+ * - `analyzeAbandonedMemory`: reads `actionableShrinkage[]` (the v1.10
78
+ * verify-fix-default direction: classes that the fix freed) and
79
+ * `actionableGrowth[]` (regressions the fix didn't address). Emits one
80
+ * table for shrinkage and, when non-empty, a second smaller table for
81
+ * growth. Threshold: |delta| >= 10 by default to filter cosmetic noise.
82
+ *
83
+ * - `diffMemgraphs`: reads `classCountChanges[]` (positive + negative).
84
+ * Future expansion; for now returns null and falls back to standard
85
+ * markdown.
86
+ *
87
+ * The 4-column layout is deliberately compact (Class | Before | After |
88
+ * Delta) so it renders cleanly in GitHub's markdown preview, dev.to, and
89
+ * agent chat contexts. A trailing `> Diagnosis: ...` blockquote carries
90
+ * the structured `diagnosis` field when present.
91
+ */
92
+ export function renderVerifyFixTable(result, toolName) {
93
+ if (toolName !== "analyzeAbandonedMemory") {
94
+ return null;
95
+ }
96
+ if (result == null || typeof result !== "object") {
97
+ return null;
98
+ }
99
+ const obj = result;
100
+ const shrinkage = extractVerifyFixRows(obj["actionableShrinkage"]);
101
+ const growth = extractVerifyFixRows(obj["actionableGrowth"]);
102
+ const diagnosis = typeof obj["diagnosis"] === "string" ? obj["diagnosis"] : null;
103
+ // Threshold: filter cosmetic noise.
104
+ const DELTA_THRESHOLD = 10;
105
+ const filteredShrinkage = shrinkage.filter((r) => Math.abs(r.delta) >= DELTA_THRESHOLD);
106
+ const filteredGrowth = growth.filter((r) => Math.abs(r.delta) >= DELTA_THRESHOLD);
107
+ if (filteredShrinkage.length === 0 && filteredGrowth.length === 0) {
108
+ return [
109
+ "# analyzeAbandonedMemory: verify-fix",
110
+ "",
111
+ "_No class counts crossed the actionable threshold (|delta| >= 10)._",
112
+ diagnosis ? `\n> ${diagnosis}` : "",
113
+ ]
114
+ .join("\n")
115
+ .trim();
116
+ }
117
+ const sections = ["# analyzeAbandonedMemory: verify-fix", ""];
118
+ if (filteredShrinkage.length > 0) {
119
+ sections.push("## What the fix freed");
120
+ sections.push("");
121
+ sections.push("| Class | Before | After | Delta |");
122
+ sections.push("|---|---:|---:|---:|");
123
+ for (const row of filteredShrinkage) {
124
+ sections.push(`| \`${row.className}\` | ${row.beforeCount} | ${row.afterCount} | ${row.delta} |`);
125
+ }
126
+ sections.push("");
127
+ }
128
+ if (filteredGrowth.length > 0) {
129
+ sections.push("## Classes that grew (regressions or unrelated)");
130
+ sections.push("");
131
+ sections.push("| Class | Before | After | Delta |");
132
+ sections.push("|---|---:|---:|---:|");
133
+ for (const row of filteredGrowth) {
134
+ sections.push(`| \`${row.className}\` | ${row.beforeCount} | ${row.afterCount} | +${row.delta} |`);
135
+ }
136
+ sections.push("");
137
+ }
138
+ if (diagnosis) {
139
+ sections.push(`> ${diagnosis}`);
140
+ }
141
+ return sections.join("\n").trim();
142
+ }
143
+ function extractVerifyFixRows(value) {
144
+ if (!Array.isArray(value))
145
+ return [];
146
+ const rows = [];
147
+ for (const item of value) {
148
+ if (item == null || typeof item !== "object")
149
+ continue;
150
+ const r = item;
151
+ if (typeof r["className"] === "string" &&
152
+ typeof r["beforeCount"] === "number" &&
153
+ typeof r["afterCount"] === "number" &&
154
+ typeof r["delta"] === "number") {
155
+ rows.push({
156
+ className: r["className"],
157
+ beforeCount: r["beforeCount"],
158
+ afterCount: r["afterCount"],
159
+ delta: r["delta"],
160
+ });
161
+ }
162
+ }
163
+ return rows;
164
+ }
165
+ /**
166
+ * Pure: render an arbitrary JSON-shaped value as markdown.
167
+ *
168
+ * The rendering is intentionally generic: it does not have per-tool
169
+ * templates. A `# Tool name` header, a `## Key` for each top-level field, and
170
+ * smart formatting for arrays of objects (tables when the rows share a
171
+ * schema) and scalars. Per-tool overrides can land in v1.9.1+ if any
172
+ * specific tool's output deserves a more curated view.
173
+ *
174
+ * Exposed for tests.
175
+ */
176
+ export function renderAsMarkdown(value, toolName) {
177
+ const lines = [`# ${toolName}`, ""];
178
+ if (value == null || typeof value !== "object") {
179
+ lines.push(formatScalar(value));
180
+ return lines.join("\n");
181
+ }
182
+ const obj = value;
183
+ for (const [key, val] of Object.entries(obj)) {
184
+ lines.push(`## ${key}`);
185
+ lines.push("");
186
+ lines.push(formatValue(val, 0));
187
+ lines.push("");
188
+ }
189
+ return lines.join("\n").trim() + "\n";
190
+ }
191
+ function formatValue(value, depth) {
192
+ if (value == null)
193
+ return "_(null)_";
194
+ if (typeof value === "string")
195
+ return value || "_(empty)_";
196
+ if (typeof value === "number" || typeof value === "boolean")
197
+ return formatScalar(value);
198
+ if (Array.isArray(value))
199
+ return formatArray(value, depth);
200
+ if (typeof value === "object")
201
+ return formatObject(value, depth);
202
+ return String(value);
203
+ }
204
+ function formatArray(arr, depth) {
205
+ if (arr.length === 0)
206
+ return "_(empty array)_";
207
+ // Table if all entries are objects with a shared key set.
208
+ if (arr.length > 0 &&
209
+ arr.every((e) => e != null && typeof e === "object" && !Array.isArray(e))) {
210
+ const objects = arr;
211
+ const cols = collectCommonKeys(objects);
212
+ if (cols.length > 0 && cols.length <= 8) {
213
+ const header = `| ${cols.join(" | ")} |`;
214
+ const sep = `| ${cols.map(() => "---").join(" | ")} |`;
215
+ const rows = objects.slice(0, 50).map((o) => {
216
+ const cells = cols.map((c) => formatCell(o[c]));
217
+ return `| ${cells.join(" | ")} |`;
218
+ });
219
+ const tail = objects.length > 50 ? `\n_(${objects.length - 50} more rows omitted)_` : "";
220
+ return [header, sep, ...rows].join("\n") + tail;
221
+ }
222
+ }
223
+ // Otherwise bullet list of scalars / mixed.
224
+ return arr
225
+ .slice(0, 50)
226
+ .map((e) => `- ${formatCell(e)}`)
227
+ .join("\n");
228
+ }
229
+ function formatObject(obj, depth) {
230
+ const entries = Object.entries(obj);
231
+ if (entries.length === 0)
232
+ return "_(empty object)_";
233
+ if (depth >= 2) {
234
+ // Deeply nested: collapse to inline JSON to keep the markdown tidy.
235
+ return "```json\n" + JSON.stringify(obj, null, 2) + "\n```";
236
+ }
237
+ return entries
238
+ .map(([k, v]) => `- **${k}**: ${formatInline(v, depth + 1)}`)
239
+ .join("\n");
240
+ }
241
+ function formatInline(value, depth) {
242
+ if (value == null)
243
+ return "_(null)_";
244
+ if (typeof value === "string")
245
+ return value || "_(empty)_";
246
+ if (typeof value === "number" || typeof value === "boolean")
247
+ return formatScalar(value);
248
+ if (Array.isArray(value)) {
249
+ if (value.length === 0)
250
+ return "_(empty array)_";
251
+ if (value.length <= 5 && value.every((e) => typeof e !== "object" || e == null)) {
252
+ return `[${value.map((e) => formatCell(e)).join(", ")}]`;
253
+ }
254
+ return `\n${formatArray(value, depth)}`;
255
+ }
256
+ if (typeof value === "object") {
257
+ return `\n${formatObject(value, depth)}`;
258
+ }
259
+ return String(value);
260
+ }
261
+ function formatScalar(value) {
262
+ if (value == null)
263
+ return "_(null)_";
264
+ if (typeof value === "boolean")
265
+ return value ? "`true`" : "`false`";
266
+ if (typeof value === "number")
267
+ return `\`${value}\``;
268
+ return String(value);
269
+ }
270
+ function formatCell(value) {
271
+ if (value == null)
272
+ return "_";
273
+ if (typeof value === "string") {
274
+ // Escape pipes for table safety, truncate long strings.
275
+ const escaped = value.replace(/\|/g, "\\|");
276
+ return escaped.length > 80 ? escaped.slice(0, 77) + "..." : escaped;
277
+ }
278
+ if (typeof value === "number" || typeof value === "boolean") {
279
+ return formatScalar(value);
280
+ }
281
+ if (Array.isArray(value))
282
+ return `[${value.length}]`;
283
+ if (typeof value === "object") {
284
+ // Compact inline JSON, truncated.
285
+ const s = JSON.stringify(value);
286
+ return s.length > 60 ? s.slice(0, 57) + "..." : s;
287
+ }
288
+ return String(value);
289
+ }
290
+ function collectCommonKeys(objects) {
291
+ // Use the keys of the FIRST object as the column set, filtered to those
292
+ // present in at least half of the rows. Keeps the table compact when rows
293
+ // have optional fields.
294
+ if (objects.length === 0)
295
+ return [];
296
+ const firstKeys = Object.keys(objects[0]);
297
+ const threshold = Math.ceil(objects.length / 2);
298
+ return firstKeys.filter((k) => {
299
+ let hits = 0;
300
+ for (const o of objects) {
301
+ if (Object.prototype.hasOwnProperty.call(o, k))
302
+ hits += 1;
303
+ }
304
+ return hits >= threshold;
305
+ });
306
+ }
307
+ //# sourceMappingURL=responseFormatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseFormatter.js","sourceRoot":"","sources":["../../src/runtime/responseFormatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Security and resource-limit flags read from the environment.
3
+ *
4
+ * These flags exist to make memorydetective safer to install in shared
5
+ * setups (CI runners, teammate's machine, automation pipelines) by
6
+ * gating operations that can execute arbitrary local programs, bound
7
+ * recording durations so an unattended agent does not pile up
8
+ * multi-GB traces, and centralize where `.trace` bundles get written
9
+ * by default so cleanup is predictable.
10
+ *
11
+ * The flags are read from `process.env` lazily (each tool call calls
12
+ * `getSecurityFlags()` fresh). Defaults preserve the v1.8 behavior
13
+ * for every existing caller; the new behavior is opt-in via the env
14
+ * var.
15
+ *
16
+ * - `MEMORYDETECTIVE_ALLOW_LAUNCH=1`: gates
17
+ * `bootAndLaunchForLeakInvestigation`. Without it, the tool returns
18
+ * `ok: false` with an explanation. With it, the tool runs normally.
19
+ * Default is OFF because that tool executes `xcodebuild` and
20
+ * `xcrun simctl launch`, which are "run arbitrary local program"
21
+ * in a trusted-input sense.
22
+ *
23
+ * - `MEMORYDETECTIVE_MAX_RECORDING_SECONDS=300` (default 300): caps
24
+ * `durationSec` for `recordTimeProfile`. A caller asking for a
25
+ * recording longer than this gets a clear error rather than the
26
+ * tool silently agreeing to a 10-minute trace.
27
+ *
28
+ * - `MEMORYDETECTIVE_TRACE_ROOT=<path>` (default
29
+ * `~/Library/Application Support/memorydetective/traces`):
30
+ * directory where `.trace` bundles are written when the caller
31
+ * provides a relative `output` path. Absolute `output` paths
32
+ * bypass this default, preserving existing behavior. Also used by
33
+ * the upcoming `cleanup_traces` tool as the default scan path.
34
+ */
35
+ export interface SecurityFlags {
36
+ allowLaunch: boolean;
37
+ maxRecordingSeconds: number;
38
+ traceRoot: string;
39
+ }
40
+ export declare const DEFAULT_MAX_RECORDING_SECONDS = 300;
41
+ export declare function defaultTraceRoot(homeDir?: string): string;
42
+ /**
43
+ * Pure: read the security flags from an env-like object. Threaded as
44
+ * a parameter for testability; production callers omit it and get
45
+ * `process.env`.
46
+ *
47
+ * Parse rules:
48
+ *
49
+ * - `MEMORYDETECTIVE_ALLOW_LAUNCH` is truthy only when the value is
50
+ * literally `"1"`. Any other value (including `"true"` and `"yes"`)
51
+ * leaves it off, to keep the gate explicit.
52
+ *
53
+ * - `MEMORYDETECTIVE_MAX_RECORDING_SECONDS` accepts a positive
54
+ * integer string; anything else (missing, zero, negative, NaN)
55
+ * falls back to the default 300. The cap is bounded at 3600s (1h)
56
+ * to prevent obviously-bad configs from disabling the gate via
57
+ * absurd values.
58
+ *
59
+ * - `MEMORYDETECTIVE_TRACE_ROOT` accepts any non-empty string; empty
60
+ * or missing values fall back to the default location.
61
+ */
62
+ export declare function getSecurityFlags(env?: Readonly<Record<string, string | undefined>>, homeDir?: string): SecurityFlags;
63
+ /**
64
+ * Build the error message returned when `bootAndLaunchForLeakInvestigation`
65
+ * is invoked without `MEMORYDETECTIVE_ALLOW_LAUNCH=1`. Exposed as a
66
+ * named export so the same wording is used everywhere (and so the
67
+ * unit tests can assert on it).
68
+ */
69
+ export declare const ALLOW_LAUNCH_REQUIRED_MESSAGE = "bootAndLaunchForLeakInvestigation requires MEMORYDETECTIVE_ALLOW_LAUNCH=1 in the environment, because this tool executes xcodebuild + xcrun simctl launch against the host. Set the env var only when you trust the inputs (workspace/project paths and bundle ids) the agent will pass.";
70
+ /**
71
+ * Build the error message returned when `recordTimeProfile` is asked
72
+ * for a duration above the cap.
73
+ */
74
+ export declare function maxRecordingExceededMessage(requested: number, cap: number): string;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Security and resource-limit flags read from the environment.
3
+ *
4
+ * These flags exist to make memorydetective safer to install in shared
5
+ * setups (CI runners, teammate's machine, automation pipelines) by
6
+ * gating operations that can execute arbitrary local programs, bound
7
+ * recording durations so an unattended agent does not pile up
8
+ * multi-GB traces, and centralize where `.trace` bundles get written
9
+ * by default so cleanup is predictable.
10
+ *
11
+ * The flags are read from `process.env` lazily (each tool call calls
12
+ * `getSecurityFlags()` fresh). Defaults preserve the v1.8 behavior
13
+ * for every existing caller; the new behavior is opt-in via the env
14
+ * var.
15
+ *
16
+ * - `MEMORYDETECTIVE_ALLOW_LAUNCH=1`: gates
17
+ * `bootAndLaunchForLeakInvestigation`. Without it, the tool returns
18
+ * `ok: false` with an explanation. With it, the tool runs normally.
19
+ * Default is OFF because that tool executes `xcodebuild` and
20
+ * `xcrun simctl launch`, which are "run arbitrary local program"
21
+ * in a trusted-input sense.
22
+ *
23
+ * - `MEMORYDETECTIVE_MAX_RECORDING_SECONDS=300` (default 300): caps
24
+ * `durationSec` for `recordTimeProfile`. A caller asking for a
25
+ * recording longer than this gets a clear error rather than the
26
+ * tool silently agreeing to a 10-minute trace.
27
+ *
28
+ * - `MEMORYDETECTIVE_TRACE_ROOT=<path>` (default
29
+ * `~/Library/Application Support/memorydetective/traces`):
30
+ * directory where `.trace` bundles are written when the caller
31
+ * provides a relative `output` path. Absolute `output` paths
32
+ * bypass this default, preserving existing behavior. Also used by
33
+ * the upcoming `cleanup_traces` tool as the default scan path.
34
+ */
35
+ import os from "node:os";
36
+ import { join as joinPath } from "node:path";
37
+ export const DEFAULT_MAX_RECORDING_SECONDS = 300;
38
+ export function defaultTraceRoot(homeDir = os.homedir()) {
39
+ return joinPath(homeDir, "Library", "Application Support", "memorydetective", "traces");
40
+ }
41
+ /**
42
+ * Pure: read the security flags from an env-like object. Threaded as
43
+ * a parameter for testability; production callers omit it and get
44
+ * `process.env`.
45
+ *
46
+ * Parse rules:
47
+ *
48
+ * - `MEMORYDETECTIVE_ALLOW_LAUNCH` is truthy only when the value is
49
+ * literally `"1"`. Any other value (including `"true"` and `"yes"`)
50
+ * leaves it off, to keep the gate explicit.
51
+ *
52
+ * - `MEMORYDETECTIVE_MAX_RECORDING_SECONDS` accepts a positive
53
+ * integer string; anything else (missing, zero, negative, NaN)
54
+ * falls back to the default 300. The cap is bounded at 3600s (1h)
55
+ * to prevent obviously-bad configs from disabling the gate via
56
+ * absurd values.
57
+ *
58
+ * - `MEMORYDETECTIVE_TRACE_ROOT` accepts any non-empty string; empty
59
+ * or missing values fall back to the default location.
60
+ */
61
+ export function getSecurityFlags(env = process.env, homeDir = os.homedir()) {
62
+ const allowLaunch = env.MEMORYDETECTIVE_ALLOW_LAUNCH === "1";
63
+ const rawMax = env.MEMORYDETECTIVE_MAX_RECORDING_SECONDS;
64
+ let maxRecordingSeconds = DEFAULT_MAX_RECORDING_SECONDS;
65
+ if (rawMax != null && rawMax !== "") {
66
+ const parsed = Number.parseInt(rawMax, 10);
67
+ if (Number.isFinite(parsed) && parsed > 0) {
68
+ maxRecordingSeconds = Math.min(parsed, 3600);
69
+ }
70
+ }
71
+ const rawRoot = env.MEMORYDETECTIVE_TRACE_ROOT;
72
+ const traceRoot = rawRoot != null && rawRoot.length > 0 ? rawRoot : defaultTraceRoot(homeDir);
73
+ return { allowLaunch, maxRecordingSeconds, traceRoot };
74
+ }
75
+ /**
76
+ * Build the error message returned when `bootAndLaunchForLeakInvestigation`
77
+ * is invoked without `MEMORYDETECTIVE_ALLOW_LAUNCH=1`. Exposed as a
78
+ * named export so the same wording is used everywhere (and so the
79
+ * unit tests can assert on it).
80
+ */
81
+ export const ALLOW_LAUNCH_REQUIRED_MESSAGE = "bootAndLaunchForLeakInvestigation requires MEMORYDETECTIVE_ALLOW_LAUNCH=1 in the environment, because this tool executes xcodebuild + xcrun simctl launch against the host. Set the env var only when you trust the inputs (workspace/project paths and bundle ids) the agent will pass.";
82
+ /**
83
+ * Build the error message returned when `recordTimeProfile` is asked
84
+ * for a duration above the cap.
85
+ */
86
+ export function maxRecordingExceededMessage(requested, cap) {
87
+ return (`recordTimeProfile durationSec=${requested} exceeds the configured cap of ${cap}s ` +
88
+ `(MEMORYDETECTIVE_MAX_RECORDING_SECONDS). Lower durationSec, or raise the cap with the env var.`);
89
+ }
90
+ //# sourceMappingURL=securityFlags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"securityFlags.js","sourceRoot":"","sources":["../../src/runtime/securityFlags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAC;AAQ7C,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,CAAC;AAEjD,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,CAAC,OAAO,EAAE;IAC7D,OAAO,QAAQ,CACb,OAAO,EACP,SAAS,EACT,qBAAqB,EACrB,iBAAiB,EACjB,QAAQ,CACT,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAoD,OAAO,CAAC,GAAG,EAC/D,UAAkB,EAAE,CAAC,OAAO,EAAE;IAE9B,MAAM,WAAW,GAAG,GAAG,CAAC,4BAA4B,KAAK,GAAG,CAAC;IAE7D,MAAM,MAAM,GAAG,GAAG,CAAC,qCAAqC,CAAC;IACzD,IAAI,mBAAmB,GAAG,6BAA6B,CAAC;IACxD,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC;IAC/C,MAAM,SAAS,GACb,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE9E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GACxC,0RAA0R,CAAC;AAE7R;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CACzC,SAAiB,EACjB,GAAW;IAEX,OAAO,CACL,iCAAiC,SAAS,kCAAkC,GAAG,IAAI;QACnF,gGAAgG,CACjG,CAAC;AACJ,CAAC"}
@@ -203,7 +203,20 @@ const HINTS = {
203
203
  "swiftdata.modelcontext-actor-cycle": {
204
204
  rule: null,
205
205
  url: "https://developer.apple.com/forums/thread/748042",
206
- explanation: "No static rule. The cycle is between Apple-provided types (`Actor` / `DefaultSerialModelExecutor` / `ModelContext`) SwiftLint can't reason about Apple-framework retain semantics. Apple fixed the framework-level shape in iOS 18 beta 1 (FB13844786). Until your minimum target is iOS 18+, the user-code shape persists.",
206
+ explanation: "No static rule. The cycle is between Apple-provided types (`Actor` / `DefaultSerialModelExecutor` / `ModelContext`); SwiftLint can't reason about Apple-framework retain semantics. Apple fixed the framework-level shape in iOS 18 beta 1 (FB13844786). Until your minimum target is iOS 18+, the user-code shape persists.",
207
+ },
208
+ // ─────────────────────────────────────────────────────────────────────────
209
+ // v1.9 catalog (DebugSwift borrow)
210
+ // ─────────────────────────────────────────────────────────────────────────
211
+ "uikit.viewcontroller-retained-after-pop": {
212
+ rule: "weak_delegate",
213
+ url: "https://realm.github.io/SwiftLint/weak_delegate.html",
214
+ explanation: "`weak_delegate` catches the most common shape (delegate property without `weak`), and the project-wide `weak_self` rule covers closure-captured self in `viewDidLoad`/`viewWillAppear`. Neither catches Combine sinks stored on the VC nor KVO observations that never `invalidate()`; for those you need the offline catalog match.",
215
+ },
216
+ "swiftui.observable-write-on-every-render": {
217
+ rule: null,
218
+ url: "https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app",
219
+ explanation: "No static rule. SwiftLint cannot prove that an `@Observable` mutation happens inside a `View.body` chain (it would need to reason about View body resolution, which is compiler-internal). The cycle catalog catches the heap shape it leaves behind; the trace-side perf signal (excessive body re-evaluations on the same view) is a parallel detector.",
207
220
  },
208
221
  };
209
222
  /** Returns the static-analysis hint for a given pattern, or null if unknown. */
@@ -1 +1 @@
1
- {"version":3,"file":"staticAnalysisHints.js","sourceRoot":"","sources":["../../src/runtime/staticAnalysisHints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH;;;GAGG;AACH,MAAM,KAAK,GAAuC;IAChD,4EAA4E;IAC5E,YAAY;IACZ,4EAA4E;IAE5E,8BAA8B,EAAE;QAC9B,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,8KAA8K;KACjL;IACD,mCAAmC,EAAE;QACnC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,2JAA2J;KAC9J;IACD,2BAA2B,EAAE;QAC3B,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,mKAAmK;KACtK;IACD,kCAAkC,EAAE;QAClC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,+CAA+C;QACpD,WAAW,EACT,uLAAuL;KAC1L;IACD,4CAA4C,EAAE;QAC5C,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,2NAA2N;KAC9N;IACD,iCAAiC,EAAE;QACjC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,yQAAyQ;KAC5Q;IACD,oCAAoC,EAAE;QACpC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,wKAAwK;KAC3K;IACD,oCAAoC,EAAE;QACpC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,4LAA4L;KAC/L;IAED,4EAA4E;IAC5E,iBAAiB;IACjB,4EAA4E;IAE5E,+BAA+B,EAAE;QAC/B,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,gKAAgK;KACnK;IACD,2BAA2B,EAAE;QAC3B,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,yGAAyG;KAC5G;IACD,uBAAuB,EAAE;QACvB,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,mKAAmK;KACtK;IACD,iCAAiC,EAAE;QACjC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,oKAAoK;KACvK;IACD,4BAA4B,EAAE;QAC5B,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,iKAAiK;KACpK;IACD,oCAAoC,EAAE;QACpC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,6HAA6H;KAChI;IACD,yCAAyC,EAAE;QACzC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,yIAAyI;KAC5I;IACD,2BAA2B,EAAE;QAC3B,IAAI,EAAE,eAAe;QACrB,GAAG,EAAE,sDAAsD;QAC3D,WAAW,EACT,8HAA8H;KACjI;IACD,kCAAkC,EAAE;QAClC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,mJAAmJ;KACtJ;IACD,wBAAwB,EAAE;QACxB,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,kHAAkH;KACrH;IACD,iCAAiC,EAAE;QACjC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,qLAAqL;KACxL;IACD,2CAA2C,EAAE;QAC3C,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,kIAAkI;KACrI;IACD,qCAAqC,EAAE;QACrC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,wJAAwJ;KAC3J;IACD,0CAA0C,EAAE;QAC1C,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,+OAA+O;KAClP;IACD,+BAA+B,EAAE;QAC/B,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,sMAAsM;KACzM;IACD,kCAAkC,EAAE;QAClC,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,wGAAwG;KAC3G;IAED,4EAA4E;IAC5E,0BAA0B;IAC1B,4EAA4E;IAE5E,yCAAyC,EAAE;QACzC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,kPAAkP;KACrP;IACD,oCAAoC,EAAE;QACpC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,yIAAyI;KAC5I;IACD,4CAA4C,EAAE;QAC5C,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,kNAAkN;KACrN;IAED,4EAA4E;IAC5E,yBAAyB;IACzB,4EAA4E;IAE5E,qCAAqC,EAAE;QACrC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,8HAA8H;KACjI;IACD,4CAA4C,EAAE;QAC5C,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,iLAAiL;KACpL;IACD,oCAAoC,EAAE;QACpC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,uGAAuG;QAC5G,WAAW,EACT,8OAA8O;KACjP;IACD,oDAAoD,EAAE;QACpD,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,iGAAiG;QACtG,WAAW,EACT,yLAAyL;KAC5L;IACD,0CAA0C,EAAE;QAC1C,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,4LAA4L;KAC/L;IACD,sCAAsC,EAAE;QACtC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,WAAW,EACT,0MAA0M;KAC7M;IAED,4EAA4E;IAC5E,eAAe;IACf,4EAA4E;IAE5E,oCAAoC,EAAE;QACpC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,kDAAkD;QACvD,WAAW,EACT,+TAA+T;KAClU;CACF,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CACnC,SAAiB;IAEjB,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;AAClC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,mBAAmB;IACjC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"staticAnalysisHints.js","sourceRoot":"","sources":["../../src/runtime/staticAnalysisHints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="memorydetective">
6
+ <title>{{TITLE}}</title>
7
+ <style>
8
+ :root { color-scheme: light dark; }
9
+ body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; margin: 0; padding: 2rem; max-width: 960px; background: #fafafa; color: #1c1c1e; }
10
+ @media (prefers-color-scheme: dark) { body { background: #1c1c1e; color: #f2f2f7; } .card { background: #2c2c2e; } th { background: #3a3a3c; } code { background: #3a3a3c; } }
11
+ h1 { font-size: 1.6rem; margin: 0 0 .5rem; }
12
+ h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
13
+ .meta { font-size: .8rem; opacity: .65; margin-bottom: 1.5rem; }
14
+ .card { background: #fff; border: 1px solid rgba(127,127,127,.2); border-radius: 8px; padding: 1rem 1.25rem; margin-bottom: 1rem; }
15
+ .verdict { display: inline-block; padding: .25rem .75rem; border-radius: 999px; font-weight: 600; font-size: .8rem; letter-spacing: .02em; }
16
+ .verdict.pass { background: #34c759; color: #fff; }
17
+ .verdict.fail { background: #ff3b30; color: #fff; }
18
+ .verdict.skip { background: #8e8e93; color: #fff; }
19
+ table { border-collapse: collapse; width: 100%; margin: .5rem 0; font-size: .85rem; }
20
+ th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid rgba(127,127,127,.2); }
21
+ th { background: #f2f2f7; font-weight: 600; }
22
+ code { font: .85rem/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; background: #f2f2f7; padding: .15rem .35rem; border-radius: 4px; }
23
+ .badge { font-size: .7rem; padding: .1rem .4rem; border-radius: 4px; }
24
+ .badge.allow { background: rgba(142,142,147,.2); }
25
+ .badge.fail { background: rgba(255,59,48,.15); color: #ff3b30; }
26
+ .stat { display: inline-block; margin-right: 1.5rem; }
27
+ .stat .n { font-size: 1.4rem; font-weight: 600; }
28
+ .stat .label { font-size: .75rem; opacity: .65; text-transform: uppercase; letter-spacing: .05em; }
29
+ .steps { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem; opacity: .7; white-space: pre-wrap; word-break: break-all; }
30
+ details { margin-top: .5rem; }
31
+ summary { cursor: pointer; font-size: .8rem; opacity: .7; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <h1>{{TITLE}}</h1>
36
+ <div class="meta">Generated by memorydetective {{VERSION}} at {{TIMESTAMP}}</div>
37
+ {{BODY}}
38
+ </body>
39
+ </html>
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="memorydetective">
6
+ <title>{{TITLE}}</title>
7
+ <style>
8
+ :root { color-scheme: light dark; }
9
+ body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; margin: 0; padding: 2rem; max-width: 960px; background: #fafafa; color: #1c1c1e; }
10
+ @media (prefers-color-scheme: dark) { body { background: #1c1c1e; color: #f2f2f7; } .card { background: #2c2c2e; } th { background: #3a3a3c; } code { background: #3a3a3c; } }
11
+ h1 { font-size: 1.6rem; margin: 0 0 .5rem; }
12
+ h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
13
+ .meta { font-size: .8rem; opacity: .65; margin-bottom: 1.5rem; }
14
+ .card { background: #fff; border: 1px solid rgba(127,127,127,.2); border-radius: 8px; padding: 1rem 1.25rem; margin-bottom: 1rem; }
15
+ .verdict { display: inline-block; padding: .25rem .75rem; border-radius: 999px; font-weight: 600; font-size: .8rem; letter-spacing: .02em; }
16
+ .verdict.pass { background: #34c759; color: #fff; }
17
+ .verdict.fail { background: #ff3b30; color: #fff; }
18
+ .verdict.skip { background: #8e8e93; color: #fff; }
19
+ table { border-collapse: collapse; width: 100%; margin: .5rem 0; font-size: .85rem; }
20
+ th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid rgba(127,127,127,.2); }
21
+ th { background: #f2f2f7; font-weight: 600; }
22
+ code { font: .85rem/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; background: #f2f2f7; padding: .15rem .35rem; border-radius: 4px; }
23
+ .badge { font-size: .7rem; padding: .1rem .4rem; border-radius: 4px; }
24
+ .badge.allow { background: rgba(142,142,147,.2); }
25
+ .badge.fail { background: rgba(255,59,48,.15); color: #ff3b30; }
26
+ .stat { display: inline-block; margin-right: 1.5rem; }
27
+ .stat .n { font-size: 1.4rem; font-weight: 600; }
28
+ .stat .label { font-size: .75rem; opacity: .65; text-transform: uppercase; letter-spacing: .05em; }
29
+ .steps { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem; opacity: .7; white-space: pre-wrap; word-break: break-all; }
30
+ details { margin-top: .5rem; }
31
+ summary { cursor: pointer; font-size: .8rem; opacity: .7; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <h1>{{TITLE}}</h1>
36
+ <div class="meta">Generated by memorydetective {{VERSION}} at {{TIMESTAMP}}</div>
37
+ {{BODY}}
38
+ </body>
39
+ </html>