@pcircle/footprint 1.5.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.
Files changed (250) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +225 -123
  3. package/SKILL.md +72 -28
  4. package/bin/footprint.js +16 -0
  5. package/dist/src/adapters/claude.d.ts +2 -0
  6. package/dist/src/adapters/claude.d.ts.map +1 -0
  7. package/dist/src/adapters/claude.js +7 -0
  8. package/dist/src/adapters/claude.js.map +1 -0
  9. package/dist/src/adapters/codex.d.ts +2 -0
  10. package/dist/src/adapters/codex.d.ts.map +1 -0
  11. package/dist/src/adapters/codex.js +7 -0
  12. package/dist/src/adapters/codex.js.map +1 -0
  13. package/dist/src/adapters/gemini.d.ts +2 -0
  14. package/dist/src/adapters/gemini.d.ts.map +1 -0
  15. package/dist/src/adapters/gemini.js +7 -0
  16. package/dist/src/adapters/gemini.js.map +1 -0
  17. package/dist/src/adapters/index.d.ts +5 -0
  18. package/dist/src/adapters/index.d.ts.map +1 -0
  19. package/dist/src/adapters/index.js +12 -0
  20. package/dist/src/adapters/index.js.map +1 -0
  21. package/dist/src/adapters/structured-prefix.d.ts +10 -0
  22. package/dist/src/adapters/structured-prefix.d.ts.map +1 -0
  23. package/dist/src/adapters/structured-prefix.js +59 -0
  24. package/dist/src/adapters/structured-prefix.js.map +1 -0
  25. package/dist/src/adapters/types.d.ts +32 -0
  26. package/dist/src/adapters/types.d.ts.map +1 -0
  27. package/dist/src/adapters/types.js +2 -0
  28. package/dist/src/adapters/types.js.map +1 -0
  29. package/dist/src/cli/context-flow.d.ts +92 -0
  30. package/dist/src/cli/context-flow.d.ts.map +1 -0
  31. package/dist/src/cli/context-flow.js +724 -0
  32. package/dist/src/cli/context-flow.js.map +1 -0
  33. package/dist/src/cli/history-display.d.ts +27 -0
  34. package/dist/src/cli/history-display.d.ts.map +1 -0
  35. package/dist/src/cli/history-display.js +167 -0
  36. package/dist/src/cli/history-display.js.map +1 -0
  37. package/dist/src/cli/index.js +924 -0
  38. package/dist/src/cli/index.js.map +1 -1
  39. package/dist/src/cli/launch-spec.d.ts +31 -0
  40. package/dist/src/cli/launch-spec.d.ts.map +1 -0
  41. package/dist/src/cli/launch-spec.js +182 -0
  42. package/dist/src/cli/launch-spec.js.map +1 -0
  43. package/dist/src/cli/live-demo.d.ts +34 -0
  44. package/dist/src/cli/live-demo.d.ts.map +1 -0
  45. package/dist/src/cli/live-demo.js +254 -0
  46. package/dist/src/cli/live-demo.js.map +1 -0
  47. package/dist/src/cli/pty-transcript.d.ts +34 -0
  48. package/dist/src/cli/pty-transcript.d.ts.map +1 -0
  49. package/dist/src/cli/pty-transcript.js +174 -0
  50. package/dist/src/cli/pty-transcript.js.map +1 -0
  51. package/dist/src/cli/session-display.d.ts +74 -0
  52. package/dist/src/cli/session-display.d.ts.map +1 -0
  53. package/dist/src/cli/session-display.js +922 -0
  54. package/dist/src/cli/session-display.js.map +1 -0
  55. package/dist/src/cli/session-execution.d.ts +55 -0
  56. package/dist/src/cli/session-execution.d.ts.map +1 -0
  57. package/dist/src/cli/session-execution.js +817 -0
  58. package/dist/src/cli/session-execution.js.map +1 -0
  59. package/dist/src/cli/session-runtime.d.ts +5 -0
  60. package/dist/src/cli/session-runtime.d.ts.map +1 -0
  61. package/dist/src/cli/session-runtime.js +11 -0
  62. package/dist/src/cli/session-runtime.js.map +1 -0
  63. package/dist/src/cli/setup.d.ts.map +1 -1
  64. package/dist/src/cli/setup.js +2 -0
  65. package/dist/src/cli/setup.js.map +1 -1
  66. package/dist/src/index.d.ts +4 -0
  67. package/dist/src/index.d.ts.map +1 -1
  68. package/dist/src/index.js +148 -7
  69. package/dist/src/index.js.map +1 -1
  70. package/dist/src/ingestion/deterministic.d.ts +3 -0
  71. package/dist/src/ingestion/deterministic.d.ts.map +1 -0
  72. package/dist/src/ingestion/deterministic.js +862 -0
  73. package/dist/src/ingestion/deterministic.js.map +1 -0
  74. package/dist/src/ingestion/index.d.ts +5 -0
  75. package/dist/src/ingestion/index.d.ts.map +1 -0
  76. package/dist/src/ingestion/index.js +27 -0
  77. package/dist/src/ingestion/index.js.map +1 -0
  78. package/dist/src/ingestion/semantic.d.ts +6 -0
  79. package/dist/src/ingestion/semantic.d.ts.map +1 -0
  80. package/dist/src/ingestion/semantic.js +627 -0
  81. package/dist/src/ingestion/semantic.js.map +1 -0
  82. package/dist/src/ingestion/types.d.ts +10 -0
  83. package/dist/src/ingestion/types.d.ts.map +1 -0
  84. package/dist/src/ingestion/types.js +2 -0
  85. package/dist/src/ingestion/types.js.map +1 -0
  86. package/dist/src/lib/context-memory.d.ts +140 -0
  87. package/dist/src/lib/context-memory.d.ts.map +1 -0
  88. package/dist/src/lib/context-memory.js +974 -0
  89. package/dist/src/lib/context-memory.js.map +1 -0
  90. package/dist/src/lib/history-handoff.d.ts +43 -0
  91. package/dist/src/lib/history-handoff.d.ts.map +1 -0
  92. package/dist/src/lib/history-handoff.js +179 -0
  93. package/dist/src/lib/history-handoff.js.map +1 -0
  94. package/dist/src/lib/observability.d.ts +3 -0
  95. package/dist/src/lib/observability.d.ts.map +1 -0
  96. package/dist/src/lib/observability.js +63 -0
  97. package/dist/src/lib/observability.js.map +1 -0
  98. package/dist/src/lib/session-artifacts.d.ts +51 -0
  99. package/dist/src/lib/session-artifacts.d.ts.map +1 -0
  100. package/dist/src/lib/session-artifacts.js +132 -0
  101. package/dist/src/lib/session-artifacts.js.map +1 -0
  102. package/dist/src/lib/session-filters.d.ts +11 -0
  103. package/dist/src/lib/session-filters.d.ts.map +1 -0
  104. package/dist/src/lib/session-filters.js +16 -0
  105. package/dist/src/lib/session-filters.js.map +1 -0
  106. package/dist/src/lib/session-history.d.ts +50 -0
  107. package/dist/src/lib/session-history.d.ts.map +1 -0
  108. package/dist/src/lib/session-history.js +73 -0
  109. package/dist/src/lib/session-history.js.map +1 -0
  110. package/dist/src/lib/session-trends.d.ts +129 -0
  111. package/dist/src/lib/session-trends.d.ts.map +1 -0
  112. package/dist/src/lib/session-trends.js +361 -0
  113. package/dist/src/lib/session-trends.js.map +1 -0
  114. package/dist/src/lib/storage/database.d.ts +212 -1
  115. package/dist/src/lib/storage/database.d.ts.map +1 -1
  116. package/dist/src/lib/storage/database.js +1694 -114
  117. package/dist/src/lib/storage/database.js.map +1 -1
  118. package/dist/src/lib/storage/export-sessions.d.ts +33 -0
  119. package/dist/src/lib/storage/export-sessions.d.ts.map +1 -0
  120. package/dist/src/lib/storage/export-sessions.js +525 -0
  121. package/dist/src/lib/storage/export-sessions.js.map +1 -0
  122. package/dist/src/lib/storage/index.d.ts +7 -6
  123. package/dist/src/lib/storage/index.d.ts.map +1 -1
  124. package/dist/src/lib/storage/index.js +6 -5
  125. package/dist/src/lib/storage/index.js.map +1 -1
  126. package/dist/src/lib/storage/schema.d.ts +6 -1
  127. package/dist/src/lib/storage/schema.d.ts.map +1 -1
  128. package/dist/src/lib/storage/schema.js +337 -2
  129. package/dist/src/lib/storage/schema.js.map +1 -1
  130. package/dist/src/lib/storage/types.d.ts +122 -0
  131. package/dist/src/lib/storage/types.d.ts.map +1 -1
  132. package/dist/src/prompts/skill-prompt.d.ts.map +1 -1
  133. package/dist/src/prompts/skill-prompt.js +13 -0
  134. package/dist/src/prompts/skill-prompt.js.map +1 -1
  135. package/dist/src/tools/confirm-context-link.d.ts +62 -0
  136. package/dist/src/tools/confirm-context-link.d.ts.map +1 -0
  137. package/dist/src/tools/confirm-context-link.js +36 -0
  138. package/dist/src/tools/confirm-context-link.js.map +1 -0
  139. package/dist/src/tools/context-schemas.d.ts +694 -0
  140. package/dist/src/tools/context-schemas.d.ts.map +1 -0
  141. package/dist/src/tools/context-schemas.js +171 -0
  142. package/dist/src/tools/context-schemas.js.map +1 -0
  143. package/dist/src/tools/export-sessions.d.ts +111 -0
  144. package/dist/src/tools/export-sessions.d.ts.map +1 -0
  145. package/dist/src/tools/export-sessions.js +136 -0
  146. package/dist/src/tools/export-sessions.js.map +1 -0
  147. package/dist/src/tools/get-context.d.ts +208 -0
  148. package/dist/src/tools/get-context.d.ts.map +1 -0
  149. package/dist/src/tools/get-context.js +27 -0
  150. package/dist/src/tools/get-context.js.map +1 -0
  151. package/dist/src/tools/get-history-handoff.d.ts +109 -0
  152. package/dist/src/tools/get-history-handoff.d.ts.map +1 -0
  153. package/dist/src/tools/get-history-handoff.js +85 -0
  154. package/dist/src/tools/get-history-handoff.js.map +1 -0
  155. package/dist/src/tools/get-history-trends.d.ts +155 -0
  156. package/dist/src/tools/get-history-trends.d.ts.map +1 -0
  157. package/dist/src/tools/get-history-trends.js +123 -0
  158. package/dist/src/tools/get-history-trends.js.map +1 -0
  159. package/dist/src/tools/get-session-artifacts.d.ts +151 -0
  160. package/dist/src/tools/get-session-artifacts.d.ts.map +1 -0
  161. package/dist/src/tools/get-session-artifacts.js +184 -0
  162. package/dist/src/tools/get-session-artifacts.js.map +1 -0
  163. package/dist/src/tools/get-session-decisions.d.ts +69 -0
  164. package/dist/src/tools/get-session-decisions.d.ts.map +1 -0
  165. package/dist/src/tools/get-session-decisions.js +99 -0
  166. package/dist/src/tools/get-session-decisions.js.map +1 -0
  167. package/dist/src/tools/get-session-messages.d.ts +55 -0
  168. package/dist/src/tools/get-session-messages.d.ts.map +1 -0
  169. package/dist/src/tools/get-session-messages.js +89 -0
  170. package/dist/src/tools/get-session-messages.js.map +1 -0
  171. package/dist/src/tools/get-session-narrative.d.ts +72 -0
  172. package/dist/src/tools/get-session-narrative.d.ts.map +1 -0
  173. package/dist/src/tools/get-session-narrative.js +106 -0
  174. package/dist/src/tools/get-session-narrative.js.map +1 -0
  175. package/dist/src/tools/get-session-timeline.d.ts +55 -0
  176. package/dist/src/tools/get-session-timeline.d.ts.map +1 -0
  177. package/dist/src/tools/get-session-timeline.js +93 -0
  178. package/dist/src/tools/get-session-timeline.js.map +1 -0
  179. package/dist/src/tools/get-session-trends.d.ts +108 -0
  180. package/dist/src/tools/get-session-trends.d.ts.map +1 -0
  181. package/dist/src/tools/get-session-trends.js +130 -0
  182. package/dist/src/tools/get-session-trends.js.map +1 -0
  183. package/dist/src/tools/get-session.d.ts +251 -0
  184. package/dist/src/tools/get-session.d.ts.map +1 -0
  185. package/dist/src/tools/get-session.js +290 -0
  186. package/dist/src/tools/get-session.js.map +1 -0
  187. package/dist/src/tools/index.d.ts +22 -0
  188. package/dist/src/tools/index.d.ts.map +1 -1
  189. package/dist/src/tools/index.js +22 -0
  190. package/dist/src/tools/index.js.map +1 -1
  191. package/dist/src/tools/list-contexts.d.ts +50 -0
  192. package/dist/src/tools/list-contexts.d.ts.map +1 -0
  193. package/dist/src/tools/list-contexts.js +28 -0
  194. package/dist/src/tools/list-contexts.js.map +1 -0
  195. package/dist/src/tools/list-sessions.d.ts +86 -0
  196. package/dist/src/tools/list-sessions.d.ts.map +1 -0
  197. package/dist/src/tools/list-sessions.js +97 -0
  198. package/dist/src/tools/list-sessions.js.map +1 -0
  199. package/dist/src/tools/merge-contexts.d.ts +58 -0
  200. package/dist/src/tools/merge-contexts.d.ts.map +1 -0
  201. package/dist/src/tools/merge-contexts.js +27 -0
  202. package/dist/src/tools/merge-contexts.js.map +1 -0
  203. package/dist/src/tools/move-session-context.d.ts +62 -0
  204. package/dist/src/tools/move-session-context.d.ts.map +1 -0
  205. package/dist/src/tools/move-session-context.js +33 -0
  206. package/dist/src/tools/move-session-context.js.map +1 -0
  207. package/dist/src/tools/reingest-session.d.ts +31 -0
  208. package/dist/src/tools/reingest-session.d.ts.map +1 -0
  209. package/dist/src/tools/reingest-session.js +43 -0
  210. package/dist/src/tools/reingest-session.js.map +1 -0
  211. package/dist/src/tools/reject-context-link.d.ts +58 -0
  212. package/dist/src/tools/reject-context-link.d.ts.map +1 -0
  213. package/dist/src/tools/reject-context-link.js +26 -0
  214. package/dist/src/tools/reject-context-link.js.map +1 -0
  215. package/dist/src/tools/resolve-context.d.ts +287 -0
  216. package/dist/src/tools/resolve-context.d.ts.map +1 -0
  217. package/dist/src/tools/resolve-context.js +35 -0
  218. package/dist/src/tools/resolve-context.js.map +1 -0
  219. package/dist/src/tools/search-history.d.ts +86 -0
  220. package/dist/src/tools/search-history.d.ts.map +1 -0
  221. package/dist/src/tools/search-history.js +103 -0
  222. package/dist/src/tools/search-history.js.map +1 -0
  223. package/dist/src/tools/session-ui-metadata.d.ts +15 -0
  224. package/dist/src/tools/session-ui-metadata.d.ts.map +1 -0
  225. package/dist/src/tools/session-ui-metadata.js +15 -0
  226. package/dist/src/tools/session-ui-metadata.js.map +1 -0
  227. package/dist/src/tools/set-active-context.d.ts +58 -0
  228. package/dist/src/tools/set-active-context.d.ts.map +1 -0
  229. package/dist/src/tools/set-active-context.js +26 -0
  230. package/dist/src/tools/set-active-context.js.map +1 -0
  231. package/dist/src/tools/split-context.d.ts +62 -0
  232. package/dist/src/tools/split-context.d.ts.map +1 -0
  233. package/dist/src/tools/split-context.js +36 -0
  234. package/dist/src/tools/split-context.js.map +1 -0
  235. package/dist/src/tools/verify-footprint.js +1 -1
  236. package/dist/src/tools/verify-footprint.js.map +1 -1
  237. package/dist/src/types.d.ts +2 -0
  238. package/dist/src/types.d.ts.map +1 -1
  239. package/dist/src/ui/register.d.ts +6 -1
  240. package/dist/src/ui/register.d.ts.map +1 -1
  241. package/dist/src/ui/register.js +60 -16
  242. package/dist/src/ui/register.js.map +1 -1
  243. package/dist/ui/dashboard.html +236 -865
  244. package/dist/ui/detail.html +107 -248
  245. package/dist/ui/export.html +115 -298
  246. package/dist/ui/session-dashboard-live.html +264 -0
  247. package/dist/ui/session-dashboard.html +329 -0
  248. package/dist/ui/session-detail-live.html +336 -0
  249. package/dist/ui/session-detail.html +355 -0
  250. package/package.json +49 -9
@@ -0,0 +1,862 @@
1
+ const COMMAND_PREFIXES = [
2
+ "pnpm",
3
+ "npm",
4
+ "yarn",
5
+ "bun",
6
+ "npx",
7
+ "node",
8
+ "python",
9
+ "pytest",
10
+ "vitest",
11
+ "jest",
12
+ "cargo",
13
+ "go test",
14
+ "git",
15
+ "docker",
16
+ "make",
17
+ ];
18
+ const COMMAND_PATTERN = /\b(?:pnpm|npm|yarn|bun|npx|node|python|pytest|vitest|jest|cargo|go test|git|docker|make)\b/i;
19
+ const TEST_PATTERN = /\b(?:test|tests|vitest|jest|pytest|spec|PASS|FAIL|failing)\b/i;
20
+ const DECISIONLESS_ERROR_PATTERN = /\b(?:error|failed|retry|exception)\b/i;
21
+ const FILE_PATTERN = /\b(?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]+|\b[\w.-]+\.(?:ts|tsx|js|jsx|json|md|yml|yaml|py|go|rs|sql|sh)\b/g;
22
+ function truncate(value, maxLength = 140) {
23
+ const trimmed = value.trim();
24
+ if (trimmed.length <= maxLength) {
25
+ return trimmed;
26
+ }
27
+ return `${trimmed.slice(0, maxLength - 3)}...`;
28
+ }
29
+ function parseJson(value) {
30
+ if (!value) {
31
+ return null;
32
+ }
33
+ try {
34
+ return JSON.parse(value);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ function toStringArray(value) {
41
+ if (!Array.isArray(value)) {
42
+ return [];
43
+ }
44
+ return value.filter((item) => typeof item === "string");
45
+ }
46
+ function getString(value) {
47
+ return typeof value === "string" && value.trim() ? value.trim() : null;
48
+ }
49
+ function getPrimaryCommand(text) {
50
+ const normalized = text.trim().toLowerCase();
51
+ for (const prefix of COMMAND_PREFIXES) {
52
+ if (normalized === prefix ||
53
+ normalized.startsWith(`${prefix} `) ||
54
+ normalized.includes(` ${prefix} `)) {
55
+ return prefix;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ function normalizeWhitespace(value) {
61
+ return value.trim().replace(/\s+/g, " ");
62
+ }
63
+ function sanitizeIssueKey(value) {
64
+ return value
65
+ .trim()
66
+ .toLowerCase()
67
+ .replace(/[^a-z0-9]+/g, "-")
68
+ .replace(/^-+|-+$/g, "");
69
+ }
70
+ function resolveCommandParts(commandInput, argInput, fallbackText) {
71
+ let command = normalizeWhitespace(commandInput ?? "");
72
+ let args = [...argInput];
73
+ if (command && args.length === 0 && /\s/.test(command)) {
74
+ const [first, ...rest] = command.split(" ");
75
+ command = first;
76
+ args = rest;
77
+ }
78
+ const fallbackInvocation = normalizeWhitespace(fallbackText);
79
+ const joinedInvocation = normalizeWhitespace([command, ...args].filter(Boolean).join(" "));
80
+ const invocation = joinedInvocation || fallbackInvocation;
81
+ const primaryCommand = getPrimaryCommand(invocation) ?? (command || null);
82
+ if (args.length === 0 && invocation) {
83
+ const tokens = invocation.split(" ");
84
+ if (primaryCommand?.includes(" ")) {
85
+ const primaryTokens = primaryCommand.split(" ");
86
+ if (primaryTokens.every((token, index) => tokens[index]?.toLowerCase() === token)) {
87
+ args = tokens.slice(primaryTokens.length);
88
+ }
89
+ }
90
+ else if (primaryCommand && tokens[0]?.toLowerCase() === primaryCommand) {
91
+ args = tokens.slice(1);
92
+ }
93
+ }
94
+ return {
95
+ command: primaryCommand,
96
+ args,
97
+ invocation: invocation || "Command activity captured",
98
+ };
99
+ }
100
+ function getPackageManager(command) {
101
+ return command && /^(?:pnpm|npm|yarn|bun)$/i.test(command) ? command : null;
102
+ }
103
+ function getScriptName(packageManager, args) {
104
+ if (!packageManager) {
105
+ return null;
106
+ }
107
+ const scriptArgs = args.filter((arg) => arg && !arg.startsWith("-"));
108
+ if (scriptArgs.length === 0) {
109
+ return null;
110
+ }
111
+ if (["run", "exec", "dlx"].includes(scriptArgs[0])) {
112
+ return scriptArgs[1] ?? null;
113
+ }
114
+ return scriptArgs[0] ?? null;
115
+ }
116
+ function getPayloadText(payload) {
117
+ if (!payload) {
118
+ return [];
119
+ }
120
+ const textFields = [
121
+ "stderr",
122
+ "stdout",
123
+ "output",
124
+ "error",
125
+ "message",
126
+ "details",
127
+ ];
128
+ return textFields
129
+ .map((key) => payload[key])
130
+ .filter((value) => typeof value === "string" && Boolean(value.trim()));
131
+ }
132
+ function extractDependencyMetadata(category, packageManager, args) {
133
+ if (category !== "install" || !packageManager) {
134
+ return { dependencyAction: null, dependencyNames: [] };
135
+ }
136
+ const tokens = args.filter((arg) => Boolean(arg));
137
+ const actionIndex = tokens.findIndex((token) => !token.startsWith("-"));
138
+ if (actionIndex === -1) {
139
+ return { dependencyAction: "install", dependencyNames: [] };
140
+ }
141
+ const actionToken = tokens[actionIndex].toLowerCase();
142
+ const normalizedAction = actionToken === "add"
143
+ ? "add"
144
+ : ["remove", "rm", "uninstall"].includes(actionToken)
145
+ ? "remove"
146
+ : ["upgrade", "up", "update"].includes(actionToken)
147
+ ? "update"
148
+ : "install";
149
+ const dependencyNames = tokens
150
+ .slice(actionIndex + 1)
151
+ .filter((token) => token &&
152
+ !token.startsWith("-") &&
153
+ !/^(?:install|add|remove|rm|uninstall|upgrade|up|update)$/i.test(token));
154
+ return {
155
+ dependencyAction: normalizedAction,
156
+ dependencyNames,
157
+ };
158
+ }
159
+ function extractErrorCode(text) {
160
+ const tsMatch = text.match(/\bTS\d{3,5}\b/i);
161
+ if (tsMatch) {
162
+ return tsMatch[0].toUpperCase();
163
+ }
164
+ const nodeMatch = text.match(/\b(?:E[A-Z0-9]{3,}|ERR_[A-Z0-9_]+)\b/);
165
+ return nodeMatch?.[0] ?? null;
166
+ }
167
+ function extractLintRuleId(text, category) {
168
+ if (category !== "lint") {
169
+ return null;
170
+ }
171
+ const lines = text.split(/\r?\n/).reverse();
172
+ const lintRulePattern = /(?:^|\s)(?:\d+:\d+\s+)?(?:error|warning)\s+.+?(?:\s{2,}|\t+)((?:@[\w-]+\/)?[a-z][\w-]*(?:\/[a-z][\w-]*)?(?:-[a-z0-9][\w-]*)*)\s*$/i;
173
+ for (const line of lines) {
174
+ const match = line.match(lintRulePattern);
175
+ if (match?.[1]) {
176
+ return match[1];
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+ function extractTestIdentifiers(text) {
182
+ const explicitLine = text
183
+ .split(/\r?\n/)
184
+ .find((candidate) => /\b(?:FAIL|PASS)\b/i.test(candidate));
185
+ if (!explicitLine) {
186
+ return { testSuite: null, testCase: null };
187
+ }
188
+ const line = explicitLine;
189
+ const normalized = normalizeWhitespace(line.replace(/^\s*(?:FAIL|PASS)\s+/i, ""));
190
+ if (!normalized) {
191
+ return { testSuite: null, testCase: null };
192
+ }
193
+ const [suitePart, ...caseParts] = normalized
194
+ .split(/\s+>\s+/)
195
+ .map((part) => truncate(part, 96));
196
+ return {
197
+ testSuite: suitePart ?? null,
198
+ testCase: caseParts.length > 0 ? caseParts.join(" > ") : null,
199
+ };
200
+ }
201
+ function extractFailureSignature(text, category) {
202
+ const errorCode = extractErrorCode(text);
203
+ const lintRuleId = extractLintRuleId(text, category);
204
+ if (lintRuleId) {
205
+ return {
206
+ failureSignatureKey: `lint-rule:${sanitizeIssueKey(lintRuleId)}`,
207
+ failureSignatureLabel: `ESLint ${lintRuleId}`,
208
+ errorCode,
209
+ lintRuleId,
210
+ };
211
+ }
212
+ if (errorCode?.startsWith("TS")) {
213
+ return {
214
+ failureSignatureKey: `typescript:${sanitizeIssueKey(errorCode)}`,
215
+ failureSignatureLabel: `TypeScript ${errorCode}`,
216
+ errorCode,
217
+ lintRuleId: null,
218
+ };
219
+ }
220
+ const signatures = [
221
+ {
222
+ pattern: /\b(?:EACCES|EPERM|permission denied)\b/i,
223
+ key: "permission-denied",
224
+ label: "Permission denied",
225
+ },
226
+ {
227
+ pattern: /\b(?:ENOTFOUND|ECONNRESET|EAI_AGAIN|network error|connection refused)\b/i,
228
+ key: "network",
229
+ label: "Network failure",
230
+ },
231
+ {
232
+ pattern: /\b(?:cannot find module|module not found|cannot resolve)\b/i,
233
+ key: "module-not-found",
234
+ label: "Module not found",
235
+ },
236
+ {
237
+ pattern: /\b(?:command not found|missing script|script not found)\b/i,
238
+ key: "missing-command",
239
+ label: "Command or script missing",
240
+ },
241
+ {
242
+ pattern: /\b(?:timed? out|timeout)\b/i,
243
+ key: "timeout",
244
+ label: "Timeout",
245
+ },
246
+ {
247
+ pattern: /\b(?:unauthorized|forbidden|401|403)\b/i,
248
+ key: "auth",
249
+ label: "Authentication or authorization failure",
250
+ },
251
+ {
252
+ pattern: /\bassert(?:ion(?:error)?)?\b/i,
253
+ key: "assertion",
254
+ label: "Assertion failure",
255
+ },
256
+ ];
257
+ const matched = signatures.find((signature) => signature.pattern.test(text));
258
+ if (matched) {
259
+ return {
260
+ failureSignatureKey: category === "command" ? matched.key : `${category}:${matched.key}`,
261
+ failureSignatureLabel: matched.label,
262
+ errorCode,
263
+ lintRuleId: null,
264
+ };
265
+ }
266
+ return {
267
+ failureSignatureKey: errorCode
268
+ ? `${category}:${sanitizeIssueKey(errorCode)}`
269
+ : null,
270
+ failureSignatureLabel: errorCode ? `Error code ${errorCode}` : null,
271
+ errorCode,
272
+ lintRuleId: null,
273
+ };
274
+ }
275
+ function classifyCommandCategory(text) {
276
+ if (/\b(?:vitest|jest|pytest|cargo test|go test|pnpm test|npm test|yarn test|bun test)\b/i.test(text)) {
277
+ return "test";
278
+ }
279
+ if (/\b(?:typecheck|tsc(?:\s|$)|tsc --noEmit|vue-tsc)\b/i.test(text)) {
280
+ return "typecheck";
281
+ }
282
+ if (/\b(?:lint|eslint|oxlint|biome check|ruff check)\b/i.test(text)) {
283
+ return "lint";
284
+ }
285
+ if (/\b(?:build|vite build|next build|rollup|webpack|tsup)\b/i.test(text)) {
286
+ return "build";
287
+ }
288
+ if (/\b(?:format|prettier|biome format|ruff format|cargo fmt|gofmt)\b/i.test(text)) {
289
+ return "format";
290
+ }
291
+ if (/\b(?:pnpm|npm|yarn|bun)\b/i.test(text) &&
292
+ /\b(?:install|add|remove|upgrade|update|dedupe|prune|unlink|link)\b/i.test(text)) {
293
+ return "install";
294
+ }
295
+ if (/\b(?:migrate|migration|db push|prisma(?:\s+db\s+push|\s+migrate)|drizzle-kit|alembic|flyway|sequelize db:migrate|knex migrate)\b/i.test(text)) {
296
+ return "migration";
297
+ }
298
+ if (/\b(?:deploy|release|publish|wrangler deploy|vercel(?:\s|$)|docker push|kubectl apply|terraform apply)\b/i.test(text)) {
299
+ return "deploy";
300
+ }
301
+ if (/\b(?:dev|start|serve|preview|watch)\b/i.test(text) &&
302
+ /\b(?:pnpm|npm|yarn|bun|vite|next|wrangler)\b/i.test(text)) {
303
+ return "dev-server";
304
+ }
305
+ if (/\bgit\b/i.test(text)) {
306
+ return "git";
307
+ }
308
+ if (/\b(?:docker|podman|kubectl|helm)\b/i.test(text)) {
309
+ return "container";
310
+ }
311
+ if (/\b(?:pnpm|npm|yarn|bun|npx)\b/i.test(text)) {
312
+ return "package-manager";
313
+ }
314
+ if (/\b(?:node|python|tsx|bash|sh|deno)\b/i.test(text)) {
315
+ return "runtime";
316
+ }
317
+ return "command";
318
+ }
319
+ function classifyCommandFamily(command, invocation) {
320
+ if (getPackageManager(command)) {
321
+ return "package-manager";
322
+ }
323
+ if (command === "git") {
324
+ return "git";
325
+ }
326
+ if (/\b(?:docker|podman|kubectl|helm)\b/i.test(invocation)) {
327
+ return "container";
328
+ }
329
+ if (command &&
330
+ /^(?:node|python|tsx|bash|sh|deno|pytest|vitest|jest|cargo test|go test)$/i.test(command)) {
331
+ return "runtime";
332
+ }
333
+ if (command === "make") {
334
+ return "task-runner";
335
+ }
336
+ return "command";
337
+ }
338
+ function humanizeKey(value) {
339
+ return value.replace(/-/g, " ");
340
+ }
341
+ function buildIssueFamilyIdentity(options) {
342
+ if (["command", "package-manager"].includes(options.category)) {
343
+ return { issueFamilyKey: null, issueFamilyLabel: null };
344
+ }
345
+ let familyBase = null;
346
+ let familyLabel = null;
347
+ if (options.category === "test") {
348
+ if (options.framework && options.framework !== "generic") {
349
+ familyBase = options.framework;
350
+ familyLabel = `${options.framework} tests`;
351
+ }
352
+ else if (options.packageManager) {
353
+ familyBase = options.packageManager;
354
+ familyLabel = `${options.packageManager} tests`;
355
+ }
356
+ else if (options.commandFamily && options.commandFamily !== "command") {
357
+ familyBase = options.commandFamily;
358
+ familyLabel = `${humanizeKey(options.commandFamily)} tests`;
359
+ }
360
+ else if (options.command) {
361
+ familyBase = options.command;
362
+ familyLabel = `${options.command} tests`;
363
+ }
364
+ }
365
+ else if (options.category === "migration") {
366
+ if (options.scriptName && options.scriptName !== "migrate") {
367
+ familyBase = options.scriptName;
368
+ familyLabel = `${options.scriptName} migrations`;
369
+ }
370
+ else if (options.command) {
371
+ familyBase = options.command;
372
+ familyLabel = `${options.command} migrations`;
373
+ }
374
+ }
375
+ else if (options.category === "deploy") {
376
+ if (options.command) {
377
+ familyBase = options.command;
378
+ familyLabel = `${options.command} deploy`;
379
+ }
380
+ }
381
+ else if (options.packageManager) {
382
+ familyBase = options.packageManager;
383
+ familyLabel = `${options.packageManager} ${humanizeKey(options.category)}`;
384
+ }
385
+ else if (options.commandFamily && options.commandFamily !== "command") {
386
+ familyBase = options.commandFamily;
387
+ familyLabel = `${humanizeKey(options.commandFamily)} ${humanizeKey(options.category)}`;
388
+ }
389
+ else if (options.command) {
390
+ familyBase = options.command;
391
+ familyLabel = `${options.command} ${humanizeKey(options.category)}`;
392
+ }
393
+ const sanitized = familyBase ? sanitizeIssueKey(familyBase) : "";
394
+ return {
395
+ issueFamilyKey: sanitized
396
+ ? `${options.category}-family:${sanitized}`
397
+ : null,
398
+ issueFamilyLabel: familyLabel ? truncate(familyLabel, 96) : null,
399
+ };
400
+ }
401
+ function buildIssueIdentity(options) {
402
+ if (["command", "package-manager"].includes(options.category)) {
403
+ return {
404
+ issueKey: null,
405
+ issueLabel: null,
406
+ issueFamilyKey: null,
407
+ issueFamilyLabel: null,
408
+ };
409
+ }
410
+ const issueBase = options.packageManager &&
411
+ options.scriptName &&
412
+ [
413
+ "test",
414
+ "lint",
415
+ "typecheck",
416
+ "build",
417
+ "format",
418
+ "install",
419
+ "dev-server",
420
+ ].includes(options.category)
421
+ ? `${options.packageManager} ${options.scriptName}`
422
+ : options.invocation;
423
+ const normalizedBase = normalizeWhitespace(issueBase);
424
+ if (!normalizedBase) {
425
+ return {
426
+ issueKey: null,
427
+ issueLabel: null,
428
+ ...buildIssueFamilyIdentity(options),
429
+ };
430
+ }
431
+ const issueLabel = options.category === "test" &&
432
+ options.framework &&
433
+ options.framework !== "generic"
434
+ ? truncate(`${normalizedBase} / ${options.framework}`, 96)
435
+ : truncate(normalizedBase, 96);
436
+ const issueKeyBase = options.packageManager &&
437
+ options.scriptName &&
438
+ [
439
+ "test",
440
+ "lint",
441
+ "typecheck",
442
+ "build",
443
+ "format",
444
+ "install",
445
+ "dev-server",
446
+ ].includes(options.category)
447
+ ? `${options.packageManager} ${options.scriptName}`
448
+ : normalizedBase;
449
+ const sanitized = sanitizeIssueKey(issueKeyBase);
450
+ return {
451
+ issueKey: sanitized ? `${options.category}:${sanitized}` : null,
452
+ issueLabel,
453
+ ...buildIssueFamilyIdentity(options),
454
+ };
455
+ }
456
+ function classifyTestFramework(text) {
457
+ if (/\bvitest\b/i.test(text)) {
458
+ return "vitest";
459
+ }
460
+ if (/\bjest\b/i.test(text)) {
461
+ return "jest";
462
+ }
463
+ if (/\bpytest\b/i.test(text)) {
464
+ return "pytest";
465
+ }
466
+ if (/\bcargo test\b/i.test(text)) {
467
+ return "cargo";
468
+ }
469
+ if (/\bgo test\b/i.test(text)) {
470
+ return "go-test";
471
+ }
472
+ return "generic";
473
+ }
474
+ function classifyPathCategory(filePath) {
475
+ if (!filePath) {
476
+ return null;
477
+ }
478
+ if (/(^|\/)(README|CHANGELOG|LICENSE)\b|\.md$/i.test(filePath)) {
479
+ return "docs";
480
+ }
481
+ if (/(^|\/)(package|tsconfig|eslint|vite|vitest|pnpm-lock)\b|\.ya?ml$/i.test(filePath)) {
482
+ return "config";
483
+ }
484
+ if (/(^|\/)(tests?|__tests__|fixtures?)\//i.test(filePath) ||
485
+ /\.(test|spec)\./i.test(filePath)) {
486
+ return "test";
487
+ }
488
+ if (/\.(sql|prisma)$/i.test(filePath)) {
489
+ return "schema";
490
+ }
491
+ return "source";
492
+ }
493
+ function classifyChangeScope(filePath) {
494
+ if (!filePath) {
495
+ return null;
496
+ }
497
+ if (/(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Pipfile|Cargo\.toml|go\.mod)$/i.test(filePath)) {
498
+ return "dependency-manifest";
499
+ }
500
+ if (/(^|\/)(pnpm-lock\.ya?ml|package-lock\.json|yarn\.lock|bun\.lockb|Pipfile\.lock|poetry\.lock|Cargo\.lock|go\.sum)$/i.test(filePath)) {
501
+ return "dependency-lockfile";
502
+ }
503
+ if (/(^|\/)(migrations?|prisma|drizzle)\//i.test(filePath) ||
504
+ /\.(sql|prisma)$/i.test(filePath)) {
505
+ return "migration";
506
+ }
507
+ return classifyPathCategory(filePath);
508
+ }
509
+ function getManifestKind(filePath) {
510
+ if (!filePath) {
511
+ return null;
512
+ }
513
+ const segments = filePath.split("/");
514
+ return segments.at(-1) ?? null;
515
+ }
516
+ function inferOutcome(status, payload) {
517
+ if (typeof payload?.exitCode === "number") {
518
+ return payload.exitCode === 0 ? "succeeded" : "failed";
519
+ }
520
+ if (typeof payload?.passed === "boolean") {
521
+ return payload.passed ? "passed" : "failed";
522
+ }
523
+ if (!status) {
524
+ return null;
525
+ }
526
+ if (/^(?:completed|captured|success|succeeded)$/i.test(status)) {
527
+ return "succeeded";
528
+ }
529
+ if (/^passed$/i.test(status)) {
530
+ return "passed";
531
+ }
532
+ if (/^(?:failed|error|parse-error|interrupted)$/i.test(status)) {
533
+ return "failed";
534
+ }
535
+ if (/^running$/i.test(status)) {
536
+ return "running";
537
+ }
538
+ return status.toLowerCase();
539
+ }
540
+ function buildCommandMetadata(sourceRefs, payload, options) {
541
+ const resolved = resolveCommandParts(getString(payload?.command), toStringArray(payload?.args), options.content ?? options.summary ?? "Command activity captured");
542
+ const payloadText = getPayloadText(payload);
543
+ const textCorpus = [
544
+ resolved.invocation,
545
+ resolved.command ?? "",
546
+ ...resolved.args,
547
+ options.summary ?? "",
548
+ options.content ?? "",
549
+ ...payloadText,
550
+ ].join("\n");
551
+ const category = classifyCommandCategory(textCorpus);
552
+ const commandFamily = classifyCommandFamily(resolved.command, resolved.invocation);
553
+ const packageManager = getPackageManager(resolved.command);
554
+ const scriptName = getScriptName(packageManager, resolved.args);
555
+ const outcome = inferOutcome(options.status ?? null, payload);
556
+ const dependencyMetadata = extractDependencyMetadata(category, packageManager, resolved.args);
557
+ const framework = category === "test" ? classifyTestFramework(textCorpus) : null;
558
+ const testIdentifiers = category === "test"
559
+ ? extractTestIdentifiers(textCorpus)
560
+ : { testSuite: null, testCase: null };
561
+ const failureSignature = outcome === "failed"
562
+ ? extractFailureSignature(textCorpus, category)
563
+ : {
564
+ failureSignatureKey: null,
565
+ failureSignatureLabel: null,
566
+ errorCode: null,
567
+ lintRuleId: null,
568
+ };
569
+ const issueIdentity = buildIssueIdentity({
570
+ category,
571
+ command: resolved.command,
572
+ commandFamily,
573
+ invocation: resolved.invocation,
574
+ packageManager,
575
+ scriptName,
576
+ framework,
577
+ });
578
+ const invocation = truncate(options.summary ?? resolved.invocation);
579
+ return {
580
+ sourceRefs,
581
+ eventType: options.eventType ?? null,
582
+ eventSubType: options.eventSubType ?? null,
583
+ summary: invocation,
584
+ category,
585
+ intent: category,
586
+ commandFamily,
587
+ command: resolved.command,
588
+ args: resolved.args,
589
+ framework,
590
+ packageManager,
591
+ scriptName,
592
+ ...dependencyMetadata,
593
+ ...testIdentifiers,
594
+ ...failureSignature,
595
+ ...issueIdentity,
596
+ status: options.status ?? null,
597
+ outcome,
598
+ payload,
599
+ content: options.content ?? null,
600
+ role: options.role ?? null,
601
+ source: options.source ?? null,
602
+ };
603
+ }
604
+ function buildTestMetadata(sourceRefs, payload, options) {
605
+ const commandInput = getString(payload?.command);
606
+ const payloadText = getPayloadText(payload);
607
+ const resolved = resolveCommandParts(commandInput, toStringArray(payload?.args), [options.summary ?? "", options.content ?? "", commandInput ?? ""].join(" "));
608
+ const summary = truncate(options.summary ?? options.content ?? "Test activity captured");
609
+ const haystack = [
610
+ summary,
611
+ options.content ?? "",
612
+ resolved.invocation,
613
+ ...payloadText,
614
+ ].join("\n");
615
+ const framework = classifyTestFramework(haystack);
616
+ const packageManager = getPackageManager(resolved.command);
617
+ const scriptName = getScriptName(packageManager, resolved.args);
618
+ const commandFamily = classifyCommandFamily(resolved.command, resolved.invocation);
619
+ const outcome = inferOutcome(options.status ?? null, payload);
620
+ const testIdentifiers = extractTestIdentifiers(haystack);
621
+ const failureSignature = outcome === "failed"
622
+ ? extractFailureSignature(haystack, "test")
623
+ : {
624
+ failureSignatureKey: null,
625
+ failureSignatureLabel: null,
626
+ errorCode: null,
627
+ lintRuleId: null,
628
+ };
629
+ const issueIdentity = buildIssueIdentity({
630
+ category: "test",
631
+ command: resolved.command,
632
+ commandFamily,
633
+ invocation: resolved.invocation || summary,
634
+ packageManager,
635
+ scriptName,
636
+ framework,
637
+ });
638
+ return {
639
+ sourceRefs,
640
+ eventType: options.eventType ?? null,
641
+ summary,
642
+ category: "test",
643
+ intent: "test",
644
+ commandFamily,
645
+ command: resolved.command,
646
+ args: resolved.args,
647
+ framework,
648
+ packageManager,
649
+ scriptName,
650
+ ...testIdentifiers,
651
+ ...failureSignature,
652
+ ...issueIdentity,
653
+ status: options.status ?? null,
654
+ outcome,
655
+ passed: typeof payload?.passed === "boolean"
656
+ ? payload.passed
657
+ : /^passed$/i.test(options.status ?? ""),
658
+ payload,
659
+ content: options.content ?? null,
660
+ role: options.role ?? null,
661
+ source: options.source ?? null,
662
+ markers: options.content
663
+ ? {
664
+ failed: DECISIONLESS_ERROR_PATTERN.test(options.content),
665
+ }
666
+ : undefined,
667
+ };
668
+ }
669
+ function buildFileMetadata(sourceRefs, filePath, payload, options) {
670
+ return {
671
+ sourceRefs,
672
+ eventType: options.eventType ?? null,
673
+ summary: truncate(options.summary ??
674
+ (filePath
675
+ ? `File changed: ${filePath}`
676
+ : (options.content ?? "File change captured"))),
677
+ category: "file-change",
678
+ pathCategory: classifyPathCategory(filePath),
679
+ changeScope: classifyChangeScope(filePath),
680
+ manifestKind: getManifestKind(filePath),
681
+ status: options.status ?? null,
682
+ payload,
683
+ content: options.content ?? null,
684
+ };
685
+ }
686
+ function buildGitMetadata(sourceRefs, payload, options) {
687
+ return {
688
+ sourceRefs,
689
+ eventType: options.eventType ?? null,
690
+ summary: truncate(options.summary ?? "Git commit captured"),
691
+ category: "git",
692
+ status: options.status ?? null,
693
+ previousHead: getString(payload?.previousHead),
694
+ currentHead: getString(payload?.currentHead),
695
+ payload,
696
+ };
697
+ }
698
+ function getEventForMessage(detail, message) {
699
+ return (detail.timeline.find((event) => event.relatedMessageId === message.id) ??
700
+ null);
701
+ }
702
+ function makeSourceRefs(...refs) {
703
+ const seen = new Set();
704
+ return refs.filter((ref) => {
705
+ const key = `${ref.type}:${ref.id}`;
706
+ if (seen.has(key)) {
707
+ return false;
708
+ }
709
+ seen.add(key);
710
+ return true;
711
+ });
712
+ }
713
+ function fromTimelineEvent(event) {
714
+ if (event.eventType.startsWith("command.")) {
715
+ const payload = parseJson(event.payload);
716
+ return {
717
+ artifactType: "command-output",
718
+ path: null,
719
+ eventId: event.id,
720
+ metadata: buildCommandMetadata(makeSourceRefs({ type: "event", id: event.id }), payload, {
721
+ eventType: event.eventType,
722
+ eventSubType: event.eventSubType,
723
+ summary: event.summary,
724
+ status: event.status,
725
+ }),
726
+ };
727
+ }
728
+ if (event.eventType.startsWith("test.")) {
729
+ const payload = parseJson(event.payload);
730
+ return {
731
+ artifactType: "test-result",
732
+ path: null,
733
+ eventId: event.id,
734
+ metadata: buildTestMetadata(makeSourceRefs({ type: "event", id: event.id }), payload, {
735
+ eventType: event.eventType,
736
+ summary: event.summary,
737
+ status: event.status,
738
+ }),
739
+ };
740
+ }
741
+ if (event.eventType === "file.changed") {
742
+ const payload = parseJson(event.payload);
743
+ const filePath = typeof payload?.path === "string" ? payload.path : null;
744
+ return {
745
+ artifactType: "file-change",
746
+ path: filePath,
747
+ eventId: event.id,
748
+ metadata: buildFileMetadata(makeSourceRefs({ type: "event", id: event.id }), filePath, payload, {
749
+ eventType: event.eventType,
750
+ summary: event.summary,
751
+ status: event.status,
752
+ }),
753
+ };
754
+ }
755
+ if (event.eventType === "git.commit") {
756
+ const payload = parseJson(event.payload);
757
+ return {
758
+ artifactType: "git-commit",
759
+ path: null,
760
+ eventId: event.id,
761
+ metadata: buildGitMetadata(makeSourceRefs({ type: "event", id: event.id }), payload, {
762
+ eventType: event.eventType,
763
+ summary: event.summary,
764
+ status: event.status,
765
+ }),
766
+ };
767
+ }
768
+ return null;
769
+ }
770
+ function fromMessage(detail, message) {
771
+ const event = getEventForMessage(detail, message);
772
+ const refs = makeSourceRefs({ type: "message", id: message.id }, ...(event ? [{ type: "event", id: event.id }] : []));
773
+ const candidates = [];
774
+ if (COMMAND_PATTERN.test(message.content)) {
775
+ candidates.push({
776
+ artifactType: "command-output",
777
+ path: null,
778
+ eventId: event?.id ?? null,
779
+ metadata: buildCommandMetadata(refs, null, {
780
+ summary: message.content,
781
+ content: message.content,
782
+ role: message.role,
783
+ source: message.source,
784
+ }),
785
+ });
786
+ }
787
+ if (TEST_PATTERN.test(message.content)) {
788
+ candidates.push({
789
+ artifactType: "test-result",
790
+ path: null,
791
+ eventId: event?.id ?? null,
792
+ metadata: buildTestMetadata(refs, null, {
793
+ summary: message.content,
794
+ content: message.content,
795
+ role: message.role,
796
+ source: message.source,
797
+ }),
798
+ });
799
+ }
800
+ const fileMatches = message.content.match(FILE_PATTERN) ?? [];
801
+ for (const filePath of [...new Set(fileMatches)]) {
802
+ candidates.push({
803
+ artifactType: "file-change",
804
+ path: filePath,
805
+ eventId: event?.id ?? null,
806
+ metadata: buildFileMetadata(refs, filePath, null, {
807
+ summary: `Referenced file: ${filePath}`,
808
+ content: message.content,
809
+ }),
810
+ });
811
+ }
812
+ return candidates;
813
+ }
814
+ export function runDeterministicIngestion(db, sessionId) {
815
+ const detail = db.getSessionDetail(sessionId);
816
+ if (!detail) {
817
+ throw new Error(`Session not found: ${sessionId}`);
818
+ }
819
+ const run = db.createIngestionRun({
820
+ sessionId,
821
+ stage: "deterministic",
822
+ status: "running",
823
+ });
824
+ try {
825
+ const artifactCandidates = [];
826
+ for (const event of detail.timeline) {
827
+ const candidate = fromTimelineEvent(event);
828
+ if (candidate) {
829
+ artifactCandidates.push(candidate);
830
+ }
831
+ }
832
+ for (const message of detail.messages) {
833
+ artifactCandidates.push(...fromMessage(detail, message));
834
+ }
835
+ const deduped = new Map();
836
+ for (const artifact of artifactCandidates) {
837
+ const key = [
838
+ artifact.artifactType,
839
+ artifact.eventId ?? "",
840
+ artifact.path ?? "",
841
+ JSON.stringify(artifact.metadata),
842
+ ].join("|");
843
+ if (!deduped.has(key)) {
844
+ deduped.set(key, {
845
+ sessionId,
846
+ eventId: artifact.eventId,
847
+ artifactType: artifact.artifactType,
848
+ path: artifact.path,
849
+ metadata: JSON.stringify(artifact.metadata),
850
+ });
851
+ }
852
+ }
853
+ const artifacts = db.replaceArtifactsForSession(sessionId, Array.from(deduped.values()));
854
+ db.completeIngestionRun(run.id, "completed");
855
+ return artifacts;
856
+ }
857
+ catch (error) {
858
+ db.completeIngestionRun(run.id, "failed", error instanceof Error ? error.message : String(error));
859
+ throw error;
860
+ }
861
+ }
862
+ //# sourceMappingURL=deterministic.js.map