@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,922 @@
1
+ /* global process */
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { reingestSessionHistory } from "../ingestion/index.js";
6
+ import { parseArtifactMetadata } from "../lib/session-artifacts.js";
7
+ import { filterSessionsByHistory } from "../lib/session-filters.js";
8
+ import { buildSessionTrendContext } from "../lib/session-trends.js";
9
+ import { buildPageInfo, DEFAULT_SESSION_DETAIL_PAGE_LIMIT, getSessionLabel, MAX_SESSION_DETAIL_PAGE_LIMIT, toSessionListItem, truncateSummary, } from "../lib/session-history.js";
10
+ import { EvidenceDatabase, exportSessions, } from "../lib/storage/index.js";
11
+ import { ensureParentDir, resolveDbPath } from "./session-execution.js";
12
+ export function printJson(value) {
13
+ console.log(JSON.stringify(value, null, 2));
14
+ }
15
+ function parseRefs(value) {
16
+ if (!value) {
17
+ return [];
18
+ }
19
+ try {
20
+ const parsed = JSON.parse(value);
21
+ if (!Array.isArray(parsed)) {
22
+ return [];
23
+ }
24
+ return parsed.filter((item) => Boolean(item &&
25
+ typeof item === "object" &&
26
+ typeof item.id === "string" &&
27
+ ["message", "event", "artifact"].includes(String(item.type))));
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ }
33
+ function collectSnippets(textNeedle, haystack, limit = 3) {
34
+ const loweredNeedle = textNeedle.toLowerCase();
35
+ return haystack
36
+ .filter((item) => item.toLowerCase().includes(loweredNeedle))
37
+ .slice(0, limit);
38
+ }
39
+ function buildSessionShowData(db, id, options) {
40
+ const session = db.findSessionById(id);
41
+ if (!session) {
42
+ throw new Error(`Session not found: ${id}`);
43
+ }
44
+ const messageLimit = options?.messageLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
45
+ const messageOffset = options?.messageOffset ?? 0;
46
+ const trendLimit = options?.trendLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
47
+ const trendOffset = options?.trendOffset ?? 0;
48
+ const timelineLimit = options?.timelineLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
49
+ const timelineOffset = options?.timelineOffset ?? 0;
50
+ const artifactLimit = options?.artifactLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
51
+ const artifactOffset = options?.artifactOffset ?? 0;
52
+ const narrativeLimit = options?.narrativeLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
53
+ const narrativeOffset = options?.narrativeOffset ?? 0;
54
+ const decisionLimit = options?.decisionLimit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
55
+ const decisionOffset = options?.decisionOffset ?? 0;
56
+ if (!Number.isInteger(messageLimit) || messageLimit <= 0) {
57
+ throw new Error("messageLimit must be a positive integer");
58
+ }
59
+ if (!Number.isInteger(messageLimit) ||
60
+ messageLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
61
+ throw new Error(`messageLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
62
+ }
63
+ if (!Number.isInteger(timelineLimit) || timelineLimit <= 0) {
64
+ throw new Error("timelineLimit must be a positive integer");
65
+ }
66
+ if (!Number.isInteger(timelineLimit) ||
67
+ timelineLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
68
+ throw new Error(`timelineLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
69
+ }
70
+ if (!Number.isInteger(messageOffset) || messageOffset < 0) {
71
+ throw new Error("messageOffset must be a non-negative integer");
72
+ }
73
+ if (!Number.isInteger(trendLimit) || trendLimit <= 0) {
74
+ throw new Error("trendLimit must be a positive integer");
75
+ }
76
+ if (!Number.isInteger(trendLimit) ||
77
+ trendLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
78
+ throw new Error(`trendLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
79
+ }
80
+ if (!Number.isInteger(trendOffset) || trendOffset < 0) {
81
+ throw new Error("trendOffset must be a non-negative integer");
82
+ }
83
+ if (!Number.isInteger(timelineOffset) || timelineOffset < 0) {
84
+ throw new Error("timelineOffset must be a non-negative integer");
85
+ }
86
+ if (!Number.isInteger(artifactLimit) || artifactLimit <= 0) {
87
+ throw new Error("artifactLimit must be a positive integer");
88
+ }
89
+ if (!Number.isInteger(artifactLimit) ||
90
+ artifactLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
91
+ throw new Error(`artifactLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
92
+ }
93
+ if (!Number.isInteger(artifactOffset) || artifactOffset < 0) {
94
+ throw new Error("artifactOffset must be a non-negative integer");
95
+ }
96
+ if (!Number.isInteger(narrativeLimit) || narrativeLimit <= 0) {
97
+ throw new Error("narrativeLimit must be a positive integer");
98
+ }
99
+ if (!Number.isInteger(narrativeLimit) ||
100
+ narrativeLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
101
+ throw new Error(`narrativeLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
102
+ }
103
+ if (!Number.isInteger(narrativeOffset) || narrativeOffset < 0) {
104
+ throw new Error("narrativeOffset must be a non-negative integer");
105
+ }
106
+ if (!Number.isInteger(decisionLimit) || decisionLimit <= 0) {
107
+ throw new Error("decisionLimit must be a positive integer");
108
+ }
109
+ if (!Number.isInteger(decisionLimit) ||
110
+ decisionLimit > MAX_SESSION_DETAIL_PAGE_LIMIT) {
111
+ throw new Error(`decisionLimit must be between 1 and ${MAX_SESSION_DETAIL_PAGE_LIMIT}`);
112
+ }
113
+ if (!Number.isInteger(decisionOffset) || decisionOffset < 0) {
114
+ throw new Error("decisionOffset must be a non-negative integer");
115
+ }
116
+ const messageStats = db.getSessionMessageStats(id);
117
+ const timelineSummary = db.getSessionTimelineStats(id);
118
+ const messages = db.getSessionMessages(id, {
119
+ limit: messageLimit,
120
+ offset: messageOffset,
121
+ });
122
+ const timeline = db.getSessionTimeline(id, {
123
+ limit: timelineLimit,
124
+ offset: timelineOffset,
125
+ });
126
+ const trendContext = buildSessionTrendContext(db, session.id, {
127
+ limit: trendLimit,
128
+ offset: trendOffset,
129
+ });
130
+ const artifactSummary = db.getSessionArtifactSummary(id);
131
+ const artifacts = db.getSessionArtifacts(id, {
132
+ limit: artifactLimit,
133
+ offset: artifactOffset,
134
+ });
135
+ const narrativeTotal = db.countSessionNarratives(id);
136
+ const narratives = db.getSessionNarratives(id, {
137
+ limit: narrativeLimit,
138
+ offset: narrativeOffset,
139
+ });
140
+ const decisionTotal = db.countSessionDecisions(id);
141
+ const decisions = db.getSessionDecisions(id, {
142
+ limit: decisionLimit,
143
+ offset: decisionOffset,
144
+ });
145
+ const ingestionRuns = db.getSessionIngestionRuns(id);
146
+ const messageSummary = {
147
+ total: messageStats.total,
148
+ byRole: messageStats.byRole,
149
+ firstCapturedAt: messageStats.firstCapturedAt,
150
+ lastCapturedAt: messageStats.lastCapturedAt,
151
+ preview: messageStats.previewContent
152
+ ? truncateSummary(messageStats.previewContent)
153
+ : null,
154
+ };
155
+ const messagePage = buildPageInfo(messageSummary.total, messages.length, {
156
+ offset: messageOffset,
157
+ limit: messageLimit,
158
+ });
159
+ const trendPage = trendContext.page ??
160
+ buildPageInfo(trendContext.summary.totalTrends, trendContext.trends.length, {
161
+ offset: trendOffset,
162
+ limit: trendLimit,
163
+ });
164
+ const timelinePage = buildPageInfo(timelineSummary.total, timeline.length, {
165
+ offset: timelineOffset,
166
+ limit: timelineLimit,
167
+ });
168
+ const artifactPage = buildPageInfo(artifactSummary.total, artifacts.length, {
169
+ offset: artifactOffset,
170
+ limit: artifactLimit,
171
+ });
172
+ const narrativePage = buildPageInfo(narrativeTotal, narratives.length, {
173
+ offset: narrativeOffset,
174
+ limit: narrativeLimit,
175
+ });
176
+ const decisionPage = buildPageInfo(decisionTotal, decisions.length, {
177
+ offset: decisionOffset,
178
+ limit: decisionLimit,
179
+ });
180
+ return {
181
+ session: {
182
+ id: session.id,
183
+ host: session.host,
184
+ title: session.title,
185
+ label: getSessionLabel(session),
186
+ status: session.status,
187
+ projectRoot: session.projectRoot,
188
+ cwd: session.cwd,
189
+ startedAt: session.startedAt,
190
+ endedAt: session.endedAt,
191
+ metadata: session.metadata,
192
+ },
193
+ messageSummary,
194
+ messagePage,
195
+ trendPage,
196
+ timelineSummary,
197
+ timelinePage,
198
+ artifactSummary,
199
+ artifactPage,
200
+ narrativePage,
201
+ decisionPage,
202
+ trendContext,
203
+ messages,
204
+ timeline,
205
+ artifacts: artifacts.map((artifact) => {
206
+ const metadata = parseArtifactMetadata(artifact.metadata);
207
+ return {
208
+ ...artifact,
209
+ metadata: metadata.details,
210
+ summary: metadata.summary ?? artifact.path ?? artifact.artifactType,
211
+ category: metadata.category,
212
+ status: metadata.status,
213
+ outcome: metadata.outcome,
214
+ intent: metadata.intent,
215
+ commandFamily: metadata.commandFamily,
216
+ command: metadata.command,
217
+ args: metadata.args,
218
+ framework: metadata.framework,
219
+ packageManager: metadata.packageManager,
220
+ scriptName: metadata.scriptName,
221
+ dependencyAction: metadata.dependencyAction,
222
+ dependencyNames: metadata.dependencyNames,
223
+ failureSignatureKey: metadata.failureSignatureKey,
224
+ failureSignatureLabel: metadata.failureSignatureLabel,
225
+ errorCode: metadata.errorCode,
226
+ lintRuleId: metadata.lintRuleId,
227
+ testSuite: metadata.testSuite,
228
+ testCase: metadata.testCase,
229
+ issueKey: metadata.issueKey,
230
+ issueLabel: metadata.issueLabel,
231
+ issueFamilyKey: metadata.issueFamilyKey,
232
+ issueFamilyLabel: metadata.issueFamilyLabel,
233
+ pathCategory: metadata.pathCategory,
234
+ changeScope: metadata.changeScope,
235
+ manifestKind: metadata.manifestKind,
236
+ sourceRefs: metadata.sourceRefs,
237
+ };
238
+ }),
239
+ narratives: narratives.map((narrative) => ({
240
+ ...narrative,
241
+ sourceRefs: parseRefs(narrative.sourceRefs),
242
+ })),
243
+ decisions: decisions.map((decision) => ({
244
+ ...decision,
245
+ sourceRefs: parseRefs(decision.sourceRefs),
246
+ })),
247
+ ingestionRuns,
248
+ hasNarratives: narrativeTotal > 0,
249
+ };
250
+ }
251
+ function toSingleLine(value) {
252
+ if (!value) {
253
+ return "n/a";
254
+ }
255
+ const normalized = value.replace(/\s+/g, " ").trim();
256
+ return normalized || "n/a";
257
+ }
258
+ function formatPreviewText(value, maxLength = 120) {
259
+ return truncateSummary(toSingleLine(value), maxLength);
260
+ }
261
+ export function printSection(title, lines, writeLine = (line) => console.log(line)) {
262
+ writeLine("");
263
+ writeLine(`${title}:`);
264
+ if (lines.length === 0) {
265
+ writeLine("- none");
266
+ return;
267
+ }
268
+ for (const line of lines) {
269
+ writeLine(`- ${line}`);
270
+ }
271
+ }
272
+ export function printContextBriefing(report, writeLine = (line) => console.log(line)) {
273
+ writeLine(`Context: ${report.context.id}`);
274
+ writeLine(`Label: ${report.context.label}`);
275
+ writeLine(`Workspace: ${report.context.workspaceKey}`);
276
+ writeLine(`Sessions: ${report.context.sessionCount}`);
277
+ writeLine(`Latest: ${report.context.latestSessionLabel}`);
278
+ printSection("Current Truth", [report.currentTruth.summary], writeLine);
279
+ printSection("Active Blockers", report.currentTruth.activeBlockers, writeLine);
280
+ printSection("Open Questions", report.currentTruth.openQuestions, writeLine);
281
+ printSection("Active Decisions", report.activeDecisions.map((decision) => `[${decision.status}] ${decision.title}`), writeLine);
282
+ printSection("Superseded Decisions", report.supersededDecisions.map((decision) => `[${decision.status}] ${decision.title} -> ${decision.supersededByTitle ?? "superseded"}`), writeLine);
283
+ }
284
+ function buildTrendPreviewLines(trends, limit = 3) {
285
+ return trends.slice(0, limit).map((trend) => {
286
+ const family = trend.issueFamilyLabel && trend.issueFamilyLabel !== trend.label
287
+ ? ` | family ${trend.issueFamilyLabel}`
288
+ : "";
289
+ return `${trend.label} | ${trend.kind ?? "unknown"} | session ${trend.sessionAttempts}/${trend.globalAttempts} attempt(s) | latest ${trend.latestOutcome}${family}`;
290
+ });
291
+ }
292
+ function buildArtifactPreviewLines(artifacts, limit = 4) {
293
+ return artifacts.slice(0, limit).map((artifact) => {
294
+ const category = artifact.category ? ` | ${artifact.category}` : "";
295
+ const status = artifact.status ? ` | ${artifact.status}` : "";
296
+ const detail = artifact.failureSignatureLabel ??
297
+ (artifact.testSuite
298
+ ? `${artifact.testSuite}${artifact.testCase ? ` > ${artifact.testCase}` : ""}`
299
+ : artifact.dependencyNames.length > 0
300
+ ? `${artifact.dependencyAction ?? "deps"} ${artifact.dependencyNames.join(", ")}`
301
+ : artifact.changeScope
302
+ ? `${artifact.changeScope}${artifact.manifestKind ? ` ${artifact.manifestKind}` : ""}`
303
+ : null);
304
+ const suffix = detail ? ` | ${formatPreviewText(detail, 80)}` : "";
305
+ return `${artifact.artifactType}${category}${status} | ${formatPreviewText(artifact.summary, 110)}${suffix}`;
306
+ });
307
+ }
308
+ function buildDecisionPreviewLines(decisions, limit = 3) {
309
+ return decisions.slice(0, limit).map((decision) => {
310
+ return `${decision.status} | ${formatPreviewText(decision.title, 80)} | ${formatPreviewText(decision.summary, 100)}`;
311
+ });
312
+ }
313
+ function buildNarrativeHighlightLines(narratives, limit = 5) {
314
+ const handoffNarrative = narratives.find((narrative) => narrative.kind === "handoff");
315
+ const narrative = handoffNarrative ?? narratives[0];
316
+ if (!narrative) {
317
+ return [];
318
+ }
319
+ return narrative.content
320
+ .split(/\r?\n/)
321
+ .map((line) => line.trim())
322
+ .filter(Boolean)
323
+ .slice(0, limit)
324
+ .map((line) => formatPreviewText(line, 140));
325
+ }
326
+ function buildMessagePreviewLines(messages) {
327
+ return messages.map((message) => {
328
+ return `#${message.seq} ${message.role} | ${message.capturedAt} | ${formatPreviewText(message.content, 140)}`;
329
+ });
330
+ }
331
+ function buildTimelinePreviewLines(timeline) {
332
+ return timeline.map((event) => {
333
+ const suffix = event.summary
334
+ ? ` | ${formatPreviewText(event.summary, 120)}`
335
+ : "";
336
+ return `#${event.seq} ${event.eventType} | ${event.status ?? "n/a"} | ${event.startedAt}${suffix}`;
337
+ });
338
+ }
339
+ function listSessionsCliWithOptions(options) {
340
+ const dbPath = resolveDbPath();
341
+ ensureParentDir(dbPath);
342
+ const db = new EvidenceDatabase(dbPath);
343
+ try {
344
+ if (options?.query !== undefined && !options.query.trim()) {
345
+ throw new Error("Query must not be empty");
346
+ }
347
+ if (options?.issueKey !== undefined && !options.issueKey.trim()) {
348
+ throw new Error("issueKey must not be empty");
349
+ }
350
+ const sessions = filterSessionsByHistory(db, {
351
+ query: options?.query,
352
+ issueKey: options?.issueKey,
353
+ host: options?.host,
354
+ status: options?.status,
355
+ }).map(toSessionListItem);
356
+ if (options?.json) {
357
+ printJson({
358
+ filters: {
359
+ query: options.query?.trim() || undefined,
360
+ issueKey: options.issueKey?.trim() || undefined,
361
+ host: options.host,
362
+ status: options.status,
363
+ },
364
+ total: sessions.length,
365
+ sessions,
366
+ });
367
+ return;
368
+ }
369
+ console.log(`Recorded sessions: ${sessions.length}`);
370
+ if (options?.query ||
371
+ options?.issueKey ||
372
+ options?.host ||
373
+ options?.status) {
374
+ console.log(`Filters: query=${options.query?.trim() || "all"} issue=${options.issueKey?.trim() || "all"} host=${options.host ?? "all"} status=${options.status ?? "all"}`);
375
+ }
376
+ for (const session of sessions) {
377
+ console.log(`${session.id} | ${session.host} | ${session.status} | ${session.label} | ${session.startedAt}`);
378
+ }
379
+ }
380
+ finally {
381
+ db.close();
382
+ }
383
+ }
384
+ export function listSessionsCli(options) {
385
+ listSessionsCliWithOptions(options);
386
+ }
387
+ export function showSessionCli(id, options) {
388
+ showSessionCliWithOptions(id, options);
389
+ }
390
+ function showSessionCliWithOptions(id, options) {
391
+ const dbPath = resolveDbPath();
392
+ ensureParentDir(dbPath);
393
+ const db = new EvidenceDatabase(dbPath);
394
+ try {
395
+ const data = buildSessionShowData(db, id, options);
396
+ if (options?.json) {
397
+ printJson(data);
398
+ return;
399
+ }
400
+ console.log(`Session: ${data.session.id}`);
401
+ console.log(`Host: ${data.session.host}`);
402
+ console.log(`Label: ${data.session.label}`);
403
+ console.log(`Status: ${data.session.status}`);
404
+ console.log(`Started: ${data.session.startedAt}`);
405
+ console.log(`Ended: ${data.session.endedAt ?? "running"}`);
406
+ console.log(`CWD: ${data.session.cwd}`);
407
+ console.log(`Narratives: ${data.hasNarratives ? "yes" : "no"}`);
408
+ console.log(`Artifacts: ${data.artifactSummary.total} (file ${data.artifactSummary.byType.fileChange}, command ${data.artifactSummary.byType.commandOutput}, test ${data.artifactSummary.byType.testResult}, git ${data.artifactSummary.byType.gitCommit})`);
409
+ console.log(`Decisions: ${data.decisionPage.total}`);
410
+ console.log(`Ingestion runs: ${data.ingestionRuns.length}`);
411
+ console.log(`Recurring trends: ${data.trendContext.summary.totalTrends} (cross-session ${data.trendContext.summary.crossSessionTrends}, session attempts ${data.trendContext.summary.sessionAttempts}, global attempts ${data.trendContext.summary.globalAttempts})`);
412
+ console.log(`Messages: ${data.messageSummary.total} (user ${data.messageSummary.byRole.user}, assistant ${data.messageSummary.byRole.assistant}, system ${data.messageSummary.byRole.system})`);
413
+ console.log(`Message page: offset ${data.messagePage.offset}, limit ${data.messagePage.limit}, returned ${data.messagePage.returned}`);
414
+ console.log(`Trend page: offset ${data.trendPage.offset}, limit ${data.trendPage.limit}, returned ${data.trendPage.returned}`);
415
+ console.log(`Timeline: ${data.timelineSummary.total} (${data.timelineSummary.eventTypes.join(", ") || "no events"})`);
416
+ console.log(`Timeline page: offset ${data.timelinePage.offset}, limit ${data.timelinePage.limit}, returned ${data.timelinePage.returned}`);
417
+ console.log(`Artifact page: offset ${data.artifactPage.offset}, limit ${data.artifactPage.limit}, returned ${data.artifactPage.returned}`);
418
+ console.log(`Narrative page: offset ${data.narrativePage.offset}, limit ${data.narrativePage.limit}, returned ${data.narrativePage.returned}`);
419
+ console.log(`Decision page: offset ${data.decisionPage.offset}, limit ${data.decisionPage.limit}, returned ${data.decisionPage.returned}`);
420
+ const trendLines = buildTrendPreviewLines(data.trendContext.trends);
421
+ if (data.trendPage.total > data.trendContext.trends.length) {
422
+ trendLines.push(`+${data.trendPage.total - data.trendContext.trends.length} more recurring trend(s)`);
423
+ }
424
+ if (data.trendPage.hasMore) {
425
+ trendLines.push(`Use --trend-offset ${data.trendPage.nextOffset} to continue this recurring trend page.`);
426
+ }
427
+ printSection("Recurring Trend Preview", trendLines);
428
+ const narrativeLines = buildNarrativeHighlightLines(data.narratives);
429
+ if (narrativeLines.length > 0) {
430
+ if (data.narrativePage.hasMore) {
431
+ narrativeLines.push(`Use --narrative-offset ${data.narrativePage.nextOffset} to continue narrative highlights.`);
432
+ }
433
+ printSection("Handoff Highlights", narrativeLines);
434
+ }
435
+ const artifactLines = buildArtifactPreviewLines(data.artifacts);
436
+ if (data.artifacts.length > artifactLines.length) {
437
+ artifactLines.push(`+${data.artifacts.length - artifactLines.length} more artifact(s)`);
438
+ }
439
+ if (data.artifactPage.hasMore) {
440
+ artifactLines.push(`Use --artifact-offset ${data.artifactPage.nextOffset} to continue this artifact page.`);
441
+ }
442
+ printSection("Artifact Preview", artifactLines);
443
+ const decisionLines = buildDecisionPreviewLines(data.decisions);
444
+ if (data.decisionPage.total > decisionLines.length) {
445
+ decisionLines.push(`+${data.decisionPage.total - decisionLines.length} more decision(s)`);
446
+ }
447
+ if (data.decisionPage.hasMore) {
448
+ decisionLines.push(`Use --decision-offset ${data.decisionPage.nextOffset} to continue this decision page.`);
449
+ }
450
+ if (data.decisionPage.total > 0) {
451
+ printSection("Decision Preview", decisionLines);
452
+ }
453
+ const messageLines = buildMessagePreviewLines(data.messages);
454
+ if (data.messagePage.hasMore) {
455
+ messageLines.push(`Use --message-offset ${data.messagePage.nextOffset} to continue this transcript page.`);
456
+ }
457
+ printSection("Transcript Preview", messageLines);
458
+ const timelineLines = buildTimelinePreviewLines(data.timeline);
459
+ if (data.timelinePage.hasMore) {
460
+ timelineLines.push(`Use --timeline-offset ${data.timelinePage.nextOffset} to continue this timeline page.`);
461
+ }
462
+ printSection("Timeline Preview", timelineLines);
463
+ }
464
+ finally {
465
+ db.close();
466
+ }
467
+ }
468
+ export async function exportSessionsCli(sessionIds, options) {
469
+ const dbPath = resolveDbPath();
470
+ ensureParentDir(dbPath);
471
+ const db = new EvidenceDatabase(dbPath);
472
+ try {
473
+ const outputMode = options?.outputMode ?? (options?.json ? "both" : "file");
474
+ const result = await exportSessions(db, {
475
+ sessionIds,
476
+ query: options?.query,
477
+ issueKey: options?.issueKey,
478
+ host: options?.host,
479
+ status: options?.status,
480
+ groupBy: options?.groupBy,
481
+ });
482
+ const maxBase64Size = 75 * 1024 * 1024;
483
+ if ((outputMode === "base64" || outputMode === "both") &&
484
+ result.zipData.length > maxBase64Size) {
485
+ throw new Error(`Export too large for base64 mode (${(result.zipData.length / (1024 * 1024)).toFixed(1)}MB). Use --output-mode file or export fewer sessions.`);
486
+ }
487
+ let filename;
488
+ let base64Data;
489
+ if (outputMode === "file" || outputMode === "both") {
490
+ const outputDir = process.env.FOOTPRINT_DATA_DIR ||
491
+ process.env.FOOTPRINT_EXPORT_DIR ||
492
+ tmpdir();
493
+ filename = path.join(outputDir, result.filename);
494
+ fs.writeFileSync(filename, result.zipData);
495
+ }
496
+ if (outputMode === "base64" || outputMode === "both") {
497
+ base64Data = Buffer.from(result.zipData).toString("base64");
498
+ }
499
+ if (options?.json) {
500
+ printJson({
501
+ ...(filename && { filename }),
502
+ ...(base64Data && { base64Data }),
503
+ checksum: result.checksum,
504
+ sessionCount: result.sessionCount,
505
+ historyGrouping: result.historyGrouping,
506
+ ...(result.filters ? { filters: result.filters } : {}),
507
+ sessions: result.sessions,
508
+ success: true,
509
+ });
510
+ return;
511
+ }
512
+ console.log(`Exported sessions: ${result.sessionCount}`);
513
+ console.log(`Output mode: ${outputMode}`);
514
+ console.log(`History grouping: ${result.historyGrouping}`);
515
+ console.log(`Checksum: ${result.checksum}`);
516
+ if (result.filters) {
517
+ console.log(`Filters: query=${result.filters.query ?? "all"} issue=${result.filters.issueKey ?? "all"} host=${result.filters.host ?? "all"} status=${result.filters.status ?? "all"} groupBy=${result.filters.groupBy ?? result.historyGrouping}`);
518
+ }
519
+ if (filename) {
520
+ console.log(`Filename: ${filename}`);
521
+ }
522
+ if (base64Data) {
523
+ console.log(`Base64 size: ${Math.round(base64Data.length / 1024)}KB`);
524
+ }
525
+ for (const session of result.sessions) {
526
+ console.log(`${session.id} | ${session.host} | ${session.status} | ${session.label}`);
527
+ }
528
+ }
529
+ finally {
530
+ db.close();
531
+ }
532
+ }
533
+ export function ingestSessionCli(id, options) {
534
+ ingestSessionCliWithOptions(id, options);
535
+ }
536
+ function ingestSessionCliWithOptions(id, options) {
537
+ const dbPath = resolveDbPath();
538
+ ensureParentDir(dbPath);
539
+ const db = new EvidenceDatabase(dbPath);
540
+ try {
541
+ const session = db.findSessionById(id);
542
+ if (!session) {
543
+ throw new Error(`Session not found: ${id}`);
544
+ }
545
+ if (session.status === "running") {
546
+ throw new Error(`Session is still running and cannot be reingested yet: ${id}`);
547
+ }
548
+ const summary = reingestSessionHistory(db, id);
549
+ if (options?.json) {
550
+ printJson({
551
+ sessionId: id,
552
+ artifactsCreated: summary.artifactsCreated,
553
+ narrativesCreated: summary.narrativesCreated,
554
+ decisionsCreated: summary.decisionsCreated,
555
+ });
556
+ return;
557
+ }
558
+ console.log(`Reingested session: ${id}`);
559
+ console.log(`Artifacts: ${summary.artifactsCreated}`);
560
+ console.log(`Narratives: ${summary.narrativesCreated}`);
561
+ console.log(`Decisions: ${summary.decisionsCreated}`);
562
+ }
563
+ finally {
564
+ db.close();
565
+ }
566
+ }
567
+ export function showSessionMessagesCli(id, options) {
568
+ const dbPath = resolveDbPath();
569
+ ensureParentDir(dbPath);
570
+ const db = new EvidenceDatabase(dbPath);
571
+ try {
572
+ if (!db.findSessionById(id)) {
573
+ throw new Error(`Session not found: ${id}`);
574
+ }
575
+ if (options?.limit !== undefined &&
576
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
577
+ throw new Error("Limit must be a positive integer");
578
+ }
579
+ if (options?.offset !== undefined &&
580
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
581
+ throw new Error("Offset must be a non-negative integer");
582
+ }
583
+ const offset = options?.offset ?? 0;
584
+ const total = db.countSessionMessages(id);
585
+ const messages = db.getSessionMessages(id, {
586
+ limit: options?.limit,
587
+ offset,
588
+ });
589
+ const page = buildPageInfo(total, messages.length, {
590
+ offset,
591
+ limit: options?.limit ?? Math.max(messages.length, total - offset, 0),
592
+ });
593
+ if (options?.json) {
594
+ printJson({
595
+ sessionId: id,
596
+ total,
597
+ page,
598
+ messages,
599
+ });
600
+ return;
601
+ }
602
+ console.log(`Messages for session: ${id}`);
603
+ console.log(`Showing ${messages.length} of ${total} (offset ${page.offset}, limit ${page.limit})`);
604
+ for (const message of messages) {
605
+ console.log(`message#${message.seq} ${message.role} ${message.capturedAt} ${message.content}`);
606
+ }
607
+ if (page.hasMore) {
608
+ console.log(`More messages available from offset ${page.nextOffset}`);
609
+ }
610
+ }
611
+ finally {
612
+ db.close();
613
+ }
614
+ }
615
+ export function showSessionTrendsCli(id, options) {
616
+ const dbPath = resolveDbPath();
617
+ ensureParentDir(dbPath);
618
+ const db = new EvidenceDatabase(dbPath);
619
+ try {
620
+ if (!db.findSessionById(id)) {
621
+ throw new Error(`Session not found: ${id}`);
622
+ }
623
+ if (options?.limit !== undefined &&
624
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
625
+ throw new Error("Limit must be a positive integer");
626
+ }
627
+ if (options?.offset !== undefined &&
628
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
629
+ throw new Error("Offset must be a non-negative integer");
630
+ }
631
+ const limit = options?.limit ?? DEFAULT_SESSION_DETAIL_PAGE_LIMIT;
632
+ const offset = options?.offset ?? 0;
633
+ const trendContext = buildSessionTrendContext(db, id, {
634
+ limit,
635
+ offset,
636
+ });
637
+ const page = trendContext.page ??
638
+ buildPageInfo(trendContext.summary.totalTrends, trendContext.trends.length, {
639
+ offset,
640
+ limit,
641
+ });
642
+ if (options?.json) {
643
+ printJson({
644
+ sessionId: id,
645
+ summary: trendContext.summary,
646
+ page,
647
+ trends: trendContext.trends,
648
+ });
649
+ return;
650
+ }
651
+ console.log(`Recurring trends for session: ${id}`);
652
+ console.log(`Showing ${trendContext.trends.length} of ${trendContext.summary.totalTrends} (cross-session ${trendContext.summary.crossSessionTrends}, session attempts ${trendContext.summary.sessionAttempts}, global attempts ${trendContext.summary.globalAttempts})`);
653
+ for (const line of buildTrendPreviewLines(trendContext.trends, page.limit)) {
654
+ console.log(line);
655
+ }
656
+ if (page.hasMore) {
657
+ console.log(`More recurring trends available from offset ${page.nextOffset}`);
658
+ }
659
+ }
660
+ finally {
661
+ db.close();
662
+ }
663
+ }
664
+ export function showSessionTimelineCli(id, options) {
665
+ const dbPath = resolveDbPath();
666
+ ensureParentDir(dbPath);
667
+ const db = new EvidenceDatabase(dbPath);
668
+ try {
669
+ if (!db.findSessionById(id)) {
670
+ throw new Error(`Session not found: ${id}`);
671
+ }
672
+ if (options?.limit !== undefined &&
673
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
674
+ throw new Error("Limit must be a positive integer");
675
+ }
676
+ if (options?.offset !== undefined &&
677
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
678
+ throw new Error("Offset must be a non-negative integer");
679
+ }
680
+ const offset = options?.offset ?? 0;
681
+ const total = db.countSessionTimeline(id);
682
+ const summary = db.getSessionTimelineStats(id);
683
+ const timeline = db.getSessionTimeline(id, {
684
+ limit: options?.limit,
685
+ offset,
686
+ });
687
+ const page = buildPageInfo(total, timeline.length, {
688
+ offset,
689
+ limit: options?.limit ?? Math.max(timeline.length, total - offset, 0),
690
+ });
691
+ if (options?.json) {
692
+ printJson({
693
+ sessionId: id,
694
+ timelineSummary: summary,
695
+ total,
696
+ page,
697
+ timeline,
698
+ });
699
+ return;
700
+ }
701
+ console.log(`Timeline for session: ${id}`);
702
+ console.log(`Showing ${timeline.length} of ${summary.total} (${summary.eventTypes.join(", ") || "no events"})`);
703
+ for (const event of timeline) {
704
+ console.log(`event#${event.seq} ${event.eventType} ${event.status ?? "n/a"} ${event.startedAt}${event.summary ? ` ${event.summary}` : ""}`);
705
+ }
706
+ if (page.hasMore) {
707
+ console.log(`More timeline events available from offset ${page.nextOffset}`);
708
+ }
709
+ }
710
+ finally {
711
+ db.close();
712
+ }
713
+ }
714
+ export function showSessionArtifactsCli(id, options) {
715
+ const dbPath = resolveDbPath();
716
+ ensureParentDir(dbPath);
717
+ const db = new EvidenceDatabase(dbPath);
718
+ try {
719
+ if (!db.findSessionById(id)) {
720
+ throw new Error(`Session not found: ${id}`);
721
+ }
722
+ if (options?.limit !== undefined &&
723
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
724
+ throw new Error("Limit must be a positive integer");
725
+ }
726
+ if (options?.offset !== undefined &&
727
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
728
+ throw new Error("Offset must be a non-negative integer");
729
+ }
730
+ const artifactSummary = db.getSessionArtifactSummary(id);
731
+ const offset = options?.offset ?? 0;
732
+ const total = db.countSessionArtifacts(id, {
733
+ artifactType: options?.artifactType,
734
+ });
735
+ const artifacts = db
736
+ .getSessionArtifacts(id, {
737
+ artifactType: options?.artifactType,
738
+ limit: options?.limit,
739
+ offset,
740
+ })
741
+ .map((artifact) => {
742
+ const metadata = parseArtifactMetadata(artifact.metadata);
743
+ return {
744
+ ...artifact,
745
+ summary: metadata.summary ?? artifact.path ?? artifact.artifactType,
746
+ category: metadata.category,
747
+ status: metadata.status,
748
+ outcome: metadata.outcome,
749
+ intent: metadata.intent,
750
+ commandFamily: metadata.commandFamily,
751
+ command: metadata.command,
752
+ args: metadata.args,
753
+ framework: metadata.framework,
754
+ packageManager: metadata.packageManager,
755
+ scriptName: metadata.scriptName,
756
+ dependencyAction: metadata.dependencyAction,
757
+ dependencyNames: metadata.dependencyNames,
758
+ failureSignatureKey: metadata.failureSignatureKey,
759
+ failureSignatureLabel: metadata.failureSignatureLabel,
760
+ errorCode: metadata.errorCode,
761
+ lintRuleId: metadata.lintRuleId,
762
+ testSuite: metadata.testSuite,
763
+ testCase: metadata.testCase,
764
+ issueKey: metadata.issueKey,
765
+ issueLabel: metadata.issueLabel,
766
+ issueFamilyKey: metadata.issueFamilyKey,
767
+ issueFamilyLabel: metadata.issueFamilyLabel,
768
+ pathCategory: metadata.pathCategory,
769
+ changeScope: metadata.changeScope,
770
+ manifestKind: metadata.manifestKind,
771
+ previousHead: metadata.previousHead,
772
+ currentHead: metadata.currentHead,
773
+ sourceRefs: metadata.sourceRefs,
774
+ details: metadata.details,
775
+ };
776
+ });
777
+ const page = buildPageInfo(total, artifacts.length, {
778
+ offset,
779
+ limit: options?.limit ?? Math.max(artifacts.length, total - offset, 0),
780
+ });
781
+ if (options?.json) {
782
+ printJson({
783
+ sessionId: id,
784
+ artifactSummary,
785
+ page,
786
+ artifacts,
787
+ });
788
+ return;
789
+ }
790
+ console.log(`Artifacts for session: ${id}`);
791
+ console.log(`Total: ${artifacts.length} shown, ${page.total} matching current filter, ${artifactSummary.total} overall`);
792
+ console.log(`By type: file ${artifactSummary.byType.fileChange}, command ${artifactSummary.byType.commandOutput}, test ${artifactSummary.byType.testResult}, git ${artifactSummary.byType.gitCommit}`);
793
+ console.log(`Page: offset ${page.offset}, limit ${page.limit}, returned ${page.returned}`);
794
+ for (const [index, artifact] of artifacts.entries()) {
795
+ const detail = artifact.failureSignatureLabel ??
796
+ (artifact.testSuite
797
+ ? `${artifact.testSuite}${artifact.testCase ? ` > ${artifact.testCase}` : ""}`
798
+ : artifact.dependencyNames.length > 0
799
+ ? `${artifact.dependencyAction ?? "deps"} ${artifact.dependencyNames.join(", ")}`
800
+ : artifact.changeScope
801
+ ? `${artifact.changeScope}${artifact.manifestKind ? ` ${artifact.manifestKind}` : ""}`
802
+ : null);
803
+ console.log(`artifact#${index + 1} ${artifact.artifactType}${artifact.category ? ` ${artifact.category}` : ""}${artifact.status ? ` ${artifact.status}` : ""} ${artifact.summary}${artifact.path ? ` [${artifact.path}]` : ""}${detail ? ` | ${detail}` : ""}`);
804
+ }
805
+ if (page.hasMore) {
806
+ console.log(`More artifacts available from offset ${page.nextOffset}`);
807
+ }
808
+ }
809
+ finally {
810
+ db.close();
811
+ }
812
+ }
813
+ export function showSessionNarrativesCli(id, options) {
814
+ const dbPath = resolveDbPath();
815
+ ensureParentDir(dbPath);
816
+ const db = new EvidenceDatabase(dbPath);
817
+ try {
818
+ if (!db.findSessionById(id)) {
819
+ throw new Error(`Session not found: ${id}`);
820
+ }
821
+ if (options?.limit !== undefined &&
822
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
823
+ throw new Error("Limit must be a positive integer");
824
+ }
825
+ if (options?.offset !== undefined &&
826
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
827
+ throw new Error("Offset must be a non-negative integer");
828
+ }
829
+ const offset = options?.offset ?? 0;
830
+ const total = db.countSessionNarratives(id, {
831
+ kind: options?.kind,
832
+ });
833
+ const narratives = db
834
+ .getSessionNarratives(id, {
835
+ kind: options?.kind,
836
+ limit: options?.limit,
837
+ offset,
838
+ })
839
+ .map((narrative) => ({
840
+ ...narrative,
841
+ sourceRefs: parseRefs(narrative.sourceRefs),
842
+ }));
843
+ const page = buildPageInfo(total, narratives.length, {
844
+ offset,
845
+ limit: options?.limit ?? Math.max(narratives.length, total - offset, 0),
846
+ });
847
+ if (options?.json) {
848
+ printJson({
849
+ sessionId: id,
850
+ page,
851
+ narratives,
852
+ });
853
+ return;
854
+ }
855
+ console.log(`Narratives for session: ${id}`);
856
+ console.log(`Total: ${narratives.length} shown, ${page.total} matching`);
857
+ console.log(`Page: offset ${page.offset}, limit ${page.limit}, returned ${page.returned}`);
858
+ for (const narrative of narratives) {
859
+ console.log(`[${narrative.kind}] ${narrative.content}`);
860
+ }
861
+ if (page.hasMore) {
862
+ console.log(`More narratives available from offset ${page.nextOffset}`);
863
+ }
864
+ }
865
+ finally {
866
+ db.close();
867
+ }
868
+ }
869
+ export function showSessionDecisionsCli(id, options) {
870
+ const dbPath = resolveDbPath();
871
+ ensureParentDir(dbPath);
872
+ const db = new EvidenceDatabase(dbPath);
873
+ try {
874
+ if (!db.findSessionById(id)) {
875
+ throw new Error(`Session not found: ${id}`);
876
+ }
877
+ if (options?.limit !== undefined &&
878
+ (!Number.isInteger(options.limit) || options.limit <= 0)) {
879
+ throw new Error("Limit must be a positive integer");
880
+ }
881
+ if (options?.offset !== undefined &&
882
+ (!Number.isInteger(options.offset) || options.offset < 0)) {
883
+ throw new Error("Offset must be a non-negative integer");
884
+ }
885
+ const offset = options?.offset ?? 0;
886
+ const total = db.countSessionDecisions(id);
887
+ const decisions = db
888
+ .getSessionDecisions(id, {
889
+ limit: options?.limit,
890
+ offset,
891
+ })
892
+ .map((decision) => ({
893
+ ...decision,
894
+ sourceRefs: parseRefs(decision.sourceRefs),
895
+ }));
896
+ const page = buildPageInfo(total, decisions.length, {
897
+ offset,
898
+ limit: options?.limit ?? Math.max(decisions.length, total - offset, 0),
899
+ });
900
+ if (options?.json) {
901
+ printJson({
902
+ sessionId: id,
903
+ page,
904
+ decisions,
905
+ });
906
+ return;
907
+ }
908
+ console.log(`Decisions for session: ${id}`);
909
+ console.log(`Total: ${decisions.length} shown, ${page.total} overall`);
910
+ console.log(`Page: offset ${page.offset}, limit ${page.limit}, returned ${page.returned}`);
911
+ for (const decision of decisions) {
912
+ console.log(`[${decision.status}] ${decision.title} :: ${decision.summary}${decision.rationale ? ` :: rationale=${decision.rationale}` : ""}`);
913
+ }
914
+ if (page.hasMore) {
915
+ console.log(`More decisions available from offset ${page.nextOffset}`);
916
+ }
917
+ }
918
+ finally {
919
+ db.close();
920
+ }
921
+ }
922
+ //# sourceMappingURL=session-display.js.map