chain-insights 0.2.16

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/bin/cli.js +10 -0
  4. package/bin/install.cjs +252 -0
  5. package/bin/mcp-proxy.cjs +10 -0
  6. package/dist/active-BSrxLKwn.mjs +50 -0
  7. package/dist/active-BSrxLKwn.mjs.map +1 -0
  8. package/dist/active-Dv7Tu-O4.cjs +68 -0
  9. package/dist/app-BjjuQM0B.mjs +155 -0
  10. package/dist/app-BjjuQM0B.mjs.map +1 -0
  11. package/dist/app-Dq1TdB6p.cjs +161 -0
  12. package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
  13. package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
  14. package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
  15. package/dist/assets/bg-pattern.png +0 -0
  16. package/dist/assets/logo.png +0 -0
  17. package/dist/call-args-DQA2QcRA.cjs +27 -0
  18. package/dist/call-args-Lk_wOJxd.mjs +29 -0
  19. package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
  20. package/dist/capabilities-CB97WMA5.cjs +83 -0
  21. package/dist/capabilities-DliMBim-.mjs +84 -0
  22. package/dist/capabilities-DliMBim-.mjs.map +1 -0
  23. package/dist/cases-By7INiOa.mjs +6 -0
  24. package/dist/cases-CDcNU91B.cjs +9 -0
  25. package/dist/chunk-CZWwpsFl.cjs +43 -0
  26. package/dist/cli.cjs +752 -0
  27. package/dist/cli.d.cts +1 -0
  28. package/dist/cli.d.mts +1 -0
  29. package/dist/cli.mjs +753 -0
  30. package/dist/cli.mjs.map +1 -0
  31. package/dist/client-D4Bq0rp9.mjs +111 -0
  32. package/dist/client-D4Bq0rp9.mjs.map +1 -0
  33. package/dist/client-D4fZgIaO.cjs +132 -0
  34. package/dist/config-Bmdl5hdk.cjs +67 -0
  35. package/dist/config-BwrBYmiC.mjs +44 -0
  36. package/dist/config-BwrBYmiC.mjs.map +1 -0
  37. package/dist/data-extractor-BNGj7ECT.cjs +347 -0
  38. package/dist/data-extractor-DFzsa5CS.mjs +336 -0
  39. package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
  40. package/dist/dossier-BsroDgD3.mjs +76 -0
  41. package/dist/dossier-BsroDgD3.mjs.map +1 -0
  42. package/dist/dossier-DtxREpPm.cjs +76 -0
  43. package/dist/evidence-BGcdKxuV.cjs +200 -0
  44. package/dist/evidence-BhvFW-y_.mjs +195 -0
  45. package/dist/evidence-BhvFW-y_.mjs.map +1 -0
  46. package/dist/format-Ce1RObVl.mjs +22 -0
  47. package/dist/format-Ce1RObVl.mjs.map +1 -0
  48. package/dist/format-DOrPvXEr.cjs +20 -0
  49. package/dist/frontmatter-D8wWCeOa.mjs +26 -0
  50. package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
  51. package/dist/frontmatter-DgAuai7E.cjs +35 -0
  52. package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
  53. package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
  54. package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
  55. package/dist/graph-reports-C4TBjCkM.mjs +63 -0
  56. package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
  57. package/dist/graph-reports-DU05YCei.cjs +64 -0
  58. package/dist/html-generator-CAv81IWH.cjs +85 -0
  59. package/dist/html-generator-V6Bp0uRb.mjs +68 -0
  60. package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
  61. package/dist/index.cjs +31 -0
  62. package/dist/index.d.cts +187 -0
  63. package/dist/index.d.cts.map +1 -0
  64. package/dist/index.d.mts +187 -0
  65. package/dist/index.d.mts.map +1 -0
  66. package/dist/index.mjs +9 -0
  67. package/dist/init-BjuFt54X.cjs +232 -0
  68. package/dist/init-CaOsHTIo.mjs +232 -0
  69. package/dist/init-CaOsHTIo.mjs.map +1 -0
  70. package/dist/mcp-proxy.cjs +1257 -0
  71. package/dist/mcp-proxy.d.cts +12 -0
  72. package/dist/mcp-proxy.d.cts.map +1 -0
  73. package/dist/mcp-proxy.d.mts +12 -0
  74. package/dist/mcp-proxy.d.mts.map +1 -0
  75. package/dist/mcp-proxy.mjs +1255 -0
  76. package/dist/mcp-proxy.mjs.map +1 -0
  77. package/dist/output-root-CFYms3ad.cjs +43 -0
  78. package/dist/output-root-CmWM7aV2.mjs +33 -0
  79. package/dist/output-root-CmWM7aV2.mjs.map +1 -0
  80. package/dist/parser-BUIWW1OH.cjs +182 -0
  81. package/dist/parser-DO0_SssG.mjs +182 -0
  82. package/dist/parser-DO0_SssG.mjs.map +1 -0
  83. package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
  84. package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
  85. package/dist/public-tools-XSpkz2ky.cjs +2556 -0
  86. package/dist/resolver-C2ZS7oC8.mjs +201 -0
  87. package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
  88. package/dist/resolver-zYbu4wDV.cjs +203 -0
  89. package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
  90. package/dist/runner-1Eq55OYb.cjs +148 -0
  91. package/dist/runner-BhUHbiHG.mjs +149 -0
  92. package/dist/runner-BhUHbiHG.mjs.map +1 -0
  93. package/dist/schema-4XpzDFQM.cjs +55 -0
  94. package/dist/schema-8d0rVIdZ.mjs +37 -0
  95. package/dist/schema-8d0rVIdZ.mjs.map +1 -0
  96. package/dist/schema-cache-9CksD7tX.mjs +34 -0
  97. package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
  98. package/dist/schema-cache-CgWRCN2N.cjs +36 -0
  99. package/dist/selector-CkFcTXzz.cjs +10 -0
  100. package/dist/selector-xjm6NTHI.mjs +12 -0
  101. package/dist/selector-xjm6NTHI.mjs.map +1 -0
  102. package/dist/server-BkM5xrXb.mjs +45 -0
  103. package/dist/server-BkM5xrXb.mjs.map +1 -0
  104. package/dist/server-DXowbpfi.cjs +54 -0
  105. package/dist/session-BpNylyuJ.cjs +115 -0
  106. package/dist/session-CcTgYxsj.mjs +115 -0
  107. package/dist/session-CcTgYxsj.mjs.map +1 -0
  108. package/dist/setup-DOpKPrlx.cjs +81 -0
  109. package/dist/setup-DyrWHuwQ.mjs +80 -0
  110. package/dist/setup-DyrWHuwQ.mjs.map +1 -0
  111. package/dist/store-BiUhQOIf.cjs +230 -0
  112. package/dist/store-BoWE-Gtl.mjs +225 -0
  113. package/dist/store-BoWE-Gtl.mjs.map +1 -0
  114. package/dist/templates/graph.html +1406 -0
  115. package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
  116. package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
  117. package/dist/tool-visibility-CwgY205r.cjs +36 -0
  118. package/dist/tools-Cp2jAAAb.mjs +100 -0
  119. package/dist/tools-Cp2jAAAb.mjs.map +1 -0
  120. package/dist/tools-f_vJUZAF.cjs +139 -0
  121. package/dist/topup-server-BZuQifvh.cjs +940 -0
  122. package/dist/topup-server-DUjyFftI.mjs +919 -0
  123. package/dist/topup-server-DUjyFftI.mjs.map +1 -0
  124. package/dist/version-1gP19Lhi.mjs +8 -0
  125. package/dist/version-1gP19Lhi.mjs.map +1 -0
  126. package/dist/version-BNGtdpmH.cjs +18 -0
  127. package/dist/viz-BlCJe6Tk.mjs +35 -0
  128. package/dist/viz-BlCJe6Tk.mjs.map +1 -0
  129. package/dist/viz-ClezVXrJ.cjs +44 -0
  130. package/dist/wallet-BMelXBYP.mjs +104 -0
  131. package/dist/wallet-BMelXBYP.mjs.map +1 -0
  132. package/dist/wallet-RnvvSpV2.cjs +146 -0
  133. package/docs/architecture.md +145 -0
  134. package/docs/contributing.md +68 -0
  135. package/docs/debugging.md +68 -0
  136. package/docs/development.md +44 -0
  137. package/docs/graph-tools.md +251 -0
  138. package/docs/images/graph-mcp-iframe.png +0 -0
  139. package/docs/images/graph-visualization.png +0 -0
  140. package/docs/images/topup-page.png +0 -0
  141. package/docs/investigation-workspaces.md +151 -0
  142. package/docs/mcp-proxy.md +180 -0
  143. package/package.json +59 -0
  144. package/skills/chain-insights-developer-experience/SKILL.md +101 -0
  145. package/skills/chain-insights-investigation/SKILL.md +285 -0
  146. package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
  147. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
  148. package/skills/chain-insights-trace-funds/SKILL.md +249 -0
  149. package/skills/ci-case/SKILL.md +43 -0
  150. package/skills/ci-status/SKILL.md +45 -0
  151. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
  152. package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
  153. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
@@ -0,0 +1,225 @@
1
+ import { n as serializeFrontmatter, t as parseFrontmatter } from "./frontmatter-D8wWCeOa.mjs";
2
+ import { n as workspaceOutputPaths } from "./output-root-CmWM7aV2.mjs";
3
+ import path from "node:path";
4
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
5
+ import * as z from "zod";
6
+ //#region src/cases/schema.ts
7
+ const caseIdRegex = /^\d{8}_\d{3}_[a-z0-9][a-z0-9-]*$/;
8
+ const CaseStatusEnum = z.enum([
9
+ "open",
10
+ "active",
11
+ "suspended",
12
+ "closed"
13
+ ]);
14
+ const CaseSchema = z.object({
15
+ id: z.string().regex(caseIdRegex, "Invalid case ID format"),
16
+ name: z.string().min(1).max(200),
17
+ status: CaseStatusEnum.default("open"),
18
+ created: z.string().datetime(),
19
+ updated: z.string().datetime(),
20
+ tags: z.array(z.string()).default([]),
21
+ description: z.string().default(""),
22
+ slug: z.string().optional()
23
+ });
24
+ z.object({
25
+ id: z.string().min(1),
26
+ caseId: z.string().regex(caseIdRegex),
27
+ source: z.string().min(1),
28
+ timestamp: z.string().datetime(),
29
+ queryParams: z.string().default("")
30
+ });
31
+ z.object({
32
+ address: z.string().min(1).max(100),
33
+ type: z.enum([
34
+ "eoa",
35
+ "contract",
36
+ "exchange",
37
+ "mixer",
38
+ "unknown"
39
+ ]).default("unknown"),
40
+ firstSeen: z.string().datetime(),
41
+ lastSeen: z.string().datetime(),
42
+ riskTags: z.string().default("")
43
+ });
44
+ z.object({
45
+ sessionId: z.string().min(1),
46
+ caseId: z.string().regex(caseIdRegex),
47
+ startTime: z.string().datetime(),
48
+ endTime: z.string().optional(),
49
+ status: z.enum(["active", "ended"]).default("active")
50
+ });
51
+ //#endregion
52
+ //#region src/cases/store.ts
53
+ const casesRoot = () => workspaceOutputPaths().casesRoot;
54
+ function caseDir(id) {
55
+ return path.join(casesRoot(), id);
56
+ }
57
+ function generateCaseId(name, existingIds) {
58
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
59
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 40);
60
+ const todayNums = existingIds.filter((id) => id.startsWith(date + "_")).map((id) => parseInt(id.split("_")[1] ?? "0", 10)).filter((n) => !isNaN(n));
61
+ const next = todayNums.length > 0 ? Math.max(...todayNums) + 1 : 1;
62
+ return `${date}_${String(next).padStart(3, "0")}_${slug}`;
63
+ }
64
+ const CaseStore = {
65
+ async create(input) {
66
+ const root = casesRoot();
67
+ await mkdir(root, { recursive: true });
68
+ const existingIds = await readdir(root).catch(() => []);
69
+ const id = generateCaseId(input.name, existingIds);
70
+ const slug = id.split("_").slice(2).join("_");
71
+ const now = (/* @__PURE__ */ new Date()).toISOString();
72
+ const tags = input.tags;
73
+ const dir = caseDir(id);
74
+ await mkdir(path.join(dir, "evidence"), { recursive: true });
75
+ await mkdir(path.join(dir, "dossiers"), { recursive: true });
76
+ const fm = {
77
+ id,
78
+ name: input.name,
79
+ status: "open",
80
+ created: now,
81
+ updated: now,
82
+ tags: tags.join(","),
83
+ description: input.description,
84
+ slug
85
+ };
86
+ const body = [
87
+ `# ${input.name}`,
88
+ "",
89
+ `Opened: ${now}`,
90
+ `Status: open`,
91
+ "",
92
+ "## Question",
93
+ "",
94
+ input.description || "TBD",
95
+ "",
96
+ "## Current Assessment",
97
+ "",
98
+ "TBD",
99
+ "",
100
+ "## Top Findings",
101
+ "",
102
+ "| Finding | Confidence | Evidence |",
103
+ "|---|---:|---|",
104
+ "",
105
+ "## Next Actions",
106
+ "",
107
+ "- TBD",
108
+ "",
109
+ "## Reports",
110
+ ""
111
+ ].join("\n");
112
+ await writeFile(path.join(dir, "case.md"), serializeFrontmatter(fm, body), { mode: 384 });
113
+ const manifest = JSON.stringify({
114
+ caseId: id,
115
+ entries: []
116
+ }, null, 2) + "\n";
117
+ await writeFile(path.join(dir, "manifest.json"), manifest, { mode: 384 });
118
+ return CaseSchema.parse({
119
+ id,
120
+ name: input.name,
121
+ status: "open",
122
+ created: now,
123
+ updated: now,
124
+ tags,
125
+ description: input.description,
126
+ slug
127
+ });
128
+ },
129
+ async setStatus(id, status) {
130
+ const dir = caseDir(id);
131
+ const filePath = path.join(dir, "case.md");
132
+ const { frontmatter, body } = parseFrontmatter(await readFile(filePath, "utf8"));
133
+ const now = (/* @__PURE__ */ new Date()).toISOString();
134
+ frontmatter["status"] = status;
135
+ frontmatter["updated"] = now;
136
+ await writeFile(filePath, serializeFrontmatter(frontmatter, body), { mode: 384 });
137
+ const tags = (frontmatter["tags"] ?? "").split(",").filter(Boolean);
138
+ return CaseSchema.parse({
139
+ id,
140
+ name: frontmatter["name"] ?? "",
141
+ status,
142
+ created: frontmatter["created"] ?? now,
143
+ updated: now,
144
+ tags,
145
+ description: frontmatter["description"] ?? ""
146
+ });
147
+ },
148
+ async list() {
149
+ const root = casesRoot();
150
+ try {
151
+ const ids = await readdir(root);
152
+ const cases = [];
153
+ for (const id of ids) try {
154
+ const { frontmatter } = parseFrontmatter(await readFile(path.join(caseDir(id), "case.md"), "utf8"));
155
+ cases.push({
156
+ id,
157
+ name: frontmatter["name"] ?? id,
158
+ status: frontmatter["status"] ?? "open",
159
+ created: frontmatter["created"] ?? ""
160
+ });
161
+ } catch (err) {
162
+ const nodeErr = err;
163
+ if (nodeErr.code !== "ENOENT" && nodeErr.code !== "ENOTDIR") throw err;
164
+ }
165
+ return cases.sort((a, b) => b.created.localeCompare(a.created) || b.id.localeCompare(a.id)).map(({ id, name, status }) => ({
166
+ id,
167
+ name,
168
+ status
169
+ }));
170
+ } catch (err) {
171
+ if (err.code === "ENOENT") return [];
172
+ throw err;
173
+ }
174
+ },
175
+ async get(id) {
176
+ const dir = caseDir(id);
177
+ const { frontmatter } = parseFrontmatter(await readFile(path.join(dir, "case.md"), "utf8"));
178
+ const tags = (frontmatter["tags"] ?? "").split(",").filter(Boolean);
179
+ return CaseSchema.parse({
180
+ id,
181
+ name: frontmatter["name"] ?? "",
182
+ status: frontmatter["status"] ?? "open",
183
+ created: frontmatter["created"] ?? (/* @__PURE__ */ new Date()).toISOString(),
184
+ updated: frontmatter["updated"] ?? (/* @__PURE__ */ new Date()).toISOString(),
185
+ tags,
186
+ description: frontmatter["description"] ?? ""
187
+ });
188
+ },
189
+ async loadContext(id) {
190
+ const dir = caseDir(id);
191
+ const { frontmatter } = parseFrontmatter(await readFile(path.join(dir, "case.md"), "utf8"));
192
+ const tags = (frontmatter["tags"] ?? "").split(",").filter(Boolean);
193
+ const { SessionStore } = await import("./session-CcTgYxsj.mjs");
194
+ const { DossierStore } = await import("./dossier-BsroDgD3.mjs");
195
+ const [latestSession, dossierSummaries, manifest] = await Promise.all([
196
+ SessionStore.getLatest(id),
197
+ DossierStore.listSummaries(id),
198
+ readFile(path.join(dir, "manifest.json"), "utf8").catch(() => "{\"entries\":[]}")
199
+ ]);
200
+ const evidenceCount = JSON.parse(manifest).entries.length;
201
+ const lastSession = latestSession ? {
202
+ sessionId: latestSession.frontmatter["sessionId"] ?? "",
203
+ startTime: latestSession.frontmatter["startTime"] ?? "",
204
+ endTime: latestSession.frontmatter["endTime"] || void 0,
205
+ body: latestSession.body
206
+ } : null;
207
+ return {
208
+ case: {
209
+ id,
210
+ name: frontmatter["name"] ?? "",
211
+ status: frontmatter["status"] ?? "open",
212
+ created: frontmatter["created"] ?? "",
213
+ updated: frontmatter["updated"] ?? "",
214
+ tags
215
+ },
216
+ lastSession,
217
+ dossierSummaries,
218
+ evidenceCount
219
+ };
220
+ }
221
+ };
222
+ //#endregion
223
+ export { CaseStore, casesRoot, generateCaseId, CaseStatusEnum as n, CaseSchema as t };
224
+
225
+ //# sourceMappingURL=store-BoWE-Gtl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-BoWE-Gtl.mjs","names":["nodeErr"],"sources":["../src/cases/schema.ts","../src/cases/store.ts"],"sourcesContent":["import * as z from 'zod'\n\n// Case ID format: YYYYMMDD_NNN_slug (e.g. 20260511_001_tornado-mixer)\n// Regex rejects path traversal chars (../, shell chars) per T-03-01 threat model.\nconst caseIdRegex = /^\\d{8}_\\d{3}_[a-z0-9][a-z0-9-]*$/\n\nexport const CaseStatusEnum = z.enum(['open', 'active', 'suspended', 'closed'])\nexport type CaseStatus = z.infer<typeof CaseStatusEnum>\n\nexport const CaseSchema = z.object({\n id: z.string().regex(caseIdRegex, 'Invalid case ID format'),\n name: z.string().min(1).max(200),\n status: CaseStatusEnum.default('open'),\n created: z.string().datetime(),\n updated: z.string().datetime(),\n tags: z.array(z.string()).default([]),\n description: z.string().default(''),\n slug: z.string().optional(),\n})\nexport type Case = z.infer<typeof CaseSchema>\n\nexport const EvidenceSchema = z.object({\n id: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n source: z.string().min(1),\n timestamp: z.string().datetime(),\n queryParams: z.string().default(''),\n})\nexport type Evidence = z.infer<typeof EvidenceSchema>\n\nexport const DossierSchema = z.object({\n address: z.string().min(1).max(100),\n type: z.enum(['eoa', 'contract', 'exchange', 'mixer', 'unknown']).default('unknown'),\n firstSeen: z.string().datetime(),\n lastSeen: z.string().datetime(),\n riskTags: z.string().default(''),\n})\nexport type Dossier = z.infer<typeof DossierSchema>\n\nexport const SessionSchema = z.object({\n sessionId: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n startTime: z.string().datetime(),\n endTime: z.string().optional(),\n status: z.enum(['active', 'ended']).default('active'),\n})\nexport type Session = z.infer<typeof SessionSchema>\n","import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js'\nimport { CaseSchema, type Case, type CaseStatus } from './schema.js'\n\nexport const casesRoot = () => workspaceOutputPaths().casesRoot\n\nfunction caseDir(id: string): string {\n return path.join(casesRoot(), id)\n}\n\nexport function generateCaseId(name: string, existingIds: string[]): string {\n const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')\n const slug = name.toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 40)\n const todayNums = existingIds\n .filter(id => id.startsWith(date + '_'))\n .map(id => parseInt(id.split('_')[1] ?? '0', 10))\n .filter(n => !isNaN(n))\n const next = todayNums.length > 0 ? Math.max(...todayNums) + 1 : 1\n return `${date}_${String(next).padStart(3, '0')}_${slug}`\n}\n\nexport const CaseStore = {\n async create(input: { name: string; tags: string[]; description: string }): Promise<Case> {\n const root = casesRoot()\n await mkdir(root, { recursive: true })\n const existingIds = await readdir(root).catch(() => [])\n const id = generateCaseId(input.name, existingIds)\n const slug = id.split('_').slice(2).join('_')\n const now = new Date().toISOString()\n const tags = input.tags\n\n const dir = caseDir(id)\n await mkdir(path.join(dir, 'evidence'), { recursive: true })\n await mkdir(path.join(dir, 'dossiers'), { recursive: true })\n\n const fm: Record<string, string> = {\n id,\n name: input.name,\n status: 'open',\n created: now,\n updated: now,\n tags: tags.join(','),\n description: input.description,\n slug,\n }\n const body = [\n `# ${input.name}`,\n '',\n `Opened: ${now}`,\n `Status: open`,\n '',\n '## Question',\n '',\n input.description || 'TBD',\n '',\n '## Current Assessment',\n '',\n 'TBD',\n '',\n '## Top Findings',\n '',\n '| Finding | Confidence | Evidence |',\n '|---|---:|---|',\n '',\n '## Next Actions',\n '',\n '- TBD',\n '',\n '## Reports',\n '',\n ].join('\\n')\n await writeFile(path.join(dir, 'case.md'), serializeFrontmatter(fm, body), { mode: 0o600 })\n\n const manifest = JSON.stringify({ caseId: id, entries: [] }, null, 2) + '\\n'\n await writeFile(path.join(dir, 'manifest.json'), manifest, { mode: 0o600 })\n\n return CaseSchema.parse({ id, name: input.name, status: 'open', created: now, updated: now, tags, description: input.description, slug })\n },\n\n async setStatus(id: string, status: CaseStatus): Promise<Case> {\n const dir = caseDir(id)\n const filePath = path.join(dir, 'case.md')\n const raw = await readFile(filePath, 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n const now = new Date().toISOString()\n frontmatter['status'] = status\n frontmatter['updated'] = now\n await writeFile(filePath, serializeFrontmatter(frontmatter, body), { mode: 0o600 })\n\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status,\n created: frontmatter['created'] ?? now,\n updated: now,\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async list(): Promise<Array<{ id: string; name: string; status: string }>> {\n const root = casesRoot()\n try {\n const ids = await readdir(root)\n const cases: Array<{ id: string; name: string; status: string; created: string }> = []\n for (const id of ids) {\n try {\n const raw = await readFile(path.join(caseDir(id), 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n cases.push({\n id,\n name: frontmatter['name'] ?? id,\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n })\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code !== 'ENOENT' && nodeErr.code !== 'ENOTDIR') throw err\n }\n }\n return cases\n .sort((a, b) => b.created.localeCompare(a.created) || b.id.localeCompare(a.id))\n .map(({ id, name, status }) => ({ id, name, status }))\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code === 'ENOENT') return []\n throw err\n }\n },\n\n async get(id: string): Promise<Case> {\n const dir = caseDir(id)\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? new Date().toISOString(),\n updated: frontmatter['updated'] ?? new Date().toISOString(),\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async loadContext(id: string): Promise<{\n case: { id: string; name: string; status: string; created: string; updated: string; tags: string[] };\n lastSession: { sessionId: string; startTime: string; endTime?: string; body: string } | null;\n dossierSummaries: Array<{ address: string; type: string; riskTags: string; firstSeen: string; lastSeen: string }>;\n evidenceCount: number;\n }> {\n const dir = caseDir(id)\n\n // Read case.md\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n\n // Lazy imports to avoid circular deps\n const { SessionStore } = await import('./session.js')\n const { DossierStore } = await import('./dossier.js')\n\n const [latestSession, dossierSummaries, manifest] = await Promise.all([\n SessionStore.getLatest(id),\n DossierStore.listSummaries(id),\n readFile(path.join(dir, 'manifest.json'), 'utf8').catch(() => '{\"entries\":[]}'),\n ])\n\n const manifestData = JSON.parse(manifest) as { entries: unknown[] }\n const evidenceCount = manifestData.entries.length\n\n const lastSession = latestSession\n ? {\n sessionId: latestSession.frontmatter['sessionId'] ?? '',\n startTime: latestSession.frontmatter['startTime'] ?? '',\n endTime: latestSession.frontmatter['endTime'] || undefined,\n body: latestSession.body,\n }\n : null\n\n return {\n case: {\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n updated: frontmatter['updated'] ?? '',\n tags,\n },\n lastSession,\n dossierSummaries,\n evidenceCount,\n }\n },\n}\n"],"mappings":"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"}