@kynetic-ai/spec 0.4.0 → 0.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 (125) hide show
  1. package/dist/cli/commands/guard.d.ts +43 -0
  2. package/dist/cli/commands/guard.d.ts.map +1 -0
  3. package/dist/cli/commands/guard.js +200 -0
  4. package/dist/cli/commands/guard.js.map +1 -0
  5. package/dist/cli/commands/index.d.ts +1 -0
  6. package/dist/cli/commands/index.d.ts.map +1 -1
  7. package/dist/cli/commands/index.js +1 -0
  8. package/dist/cli/commands/index.js.map +1 -1
  9. package/dist/cli/commands/item.d.ts.map +1 -1
  10. package/dist/cli/commands/item.js +60 -23
  11. package/dist/cli/commands/item.js.map +1 -1
  12. package/dist/cli/commands/plan-import.js +51 -12
  13. package/dist/cli/commands/plan-import.js.map +1 -1
  14. package/dist/cli/commands/ralph.d.ts.map +1 -1
  15. package/dist/cli/commands/ralph.js +144 -329
  16. package/dist/cli/commands/ralph.js.map +1 -1
  17. package/dist/cli/commands/session/checkpoint.d.ts +19 -0
  18. package/dist/cli/commands/session/checkpoint.d.ts.map +1 -0
  19. package/dist/cli/commands/session/checkpoint.js +161 -0
  20. package/dist/cli/commands/session/checkpoint.js.map +1 -0
  21. package/dist/cli/commands/session/commands.d.ts +18 -0
  22. package/dist/cli/commands/session/commands.d.ts.map +1 -0
  23. package/dist/cli/commands/session/commands.js +259 -0
  24. package/dist/cli/commands/session/commands.js.map +1 -0
  25. package/dist/cli/commands/session/context.d.ts +17 -0
  26. package/dist/cli/commands/session/context.d.ts.map +1 -0
  27. package/dist/cli/commands/session/context.js +493 -0
  28. package/dist/cli/commands/session/context.js.map +1 -0
  29. package/dist/cli/commands/session/create.d.ts +29 -0
  30. package/dist/cli/commands/session/create.d.ts.map +1 -0
  31. package/dist/cli/commands/session/create.js +147 -0
  32. package/dist/cli/commands/session/create.js.map +1 -0
  33. package/dist/cli/commands/session/format.d.ts +27 -0
  34. package/dist/cli/commands/session/format.d.ts.map +1 -0
  35. package/dist/cli/commands/session/format.js +401 -0
  36. package/dist/cli/commands/session/format.js.map +1 -0
  37. package/dist/cli/commands/session/index.d.ts +13 -0
  38. package/dist/cli/commands/session/index.d.ts.map +1 -0
  39. package/dist/cli/commands/session/index.js +17 -0
  40. package/dist/cli/commands/session/index.js.map +1 -0
  41. package/dist/cli/commands/session/log.d.ts +52 -0
  42. package/dist/cli/commands/session/log.d.ts.map +1 -0
  43. package/dist/cli/commands/session/log.js +570 -0
  44. package/dist/cli/commands/session/log.js.map +1 -0
  45. package/dist/cli/commands/session/types.d.ts +230 -0
  46. package/dist/cli/commands/session/types.d.ts.map +1 -0
  47. package/dist/cli/commands/session/types.js +7 -0
  48. package/dist/cli/commands/session/types.js.map +1 -0
  49. package/dist/cli/commands/session.d.ts +4 -179
  50. package/dist/cli/commands/session.d.ts.map +1 -1
  51. package/dist/cli/commands/session.js +6 -1424
  52. package/dist/cli/commands/session.js.map +1 -1
  53. package/dist/cli/commands/setup.d.ts.map +1 -1
  54. package/dist/cli/commands/setup.js +69 -223
  55. package/dist/cli/commands/setup.js.map +1 -1
  56. package/dist/cli/commands/task.d.ts.map +1 -1
  57. package/dist/cli/commands/task.js +95 -37
  58. package/dist/cli/commands/task.js.map +1 -1
  59. package/dist/cli/commands/validate.d.ts.map +1 -1
  60. package/dist/cli/commands/validate.js +23 -7
  61. package/dist/cli/commands/validate.js.map +1 -1
  62. package/dist/cli/index.d.ts.map +1 -1
  63. package/dist/cli/index.js +2 -1
  64. package/dist/cli/index.js.map +1 -1
  65. package/dist/cli/output.d.ts.map +1 -1
  66. package/dist/cli/output.js +14 -2
  67. package/dist/cli/output.js.map +1 -1
  68. package/dist/parser/file-lock.d.ts +14 -0
  69. package/dist/parser/file-lock.d.ts.map +1 -0
  70. package/dist/parser/file-lock.js +124 -0
  71. package/dist/parser/file-lock.js.map +1 -0
  72. package/dist/parser/index.d.ts +1 -0
  73. package/dist/parser/index.d.ts.map +1 -1
  74. package/dist/parser/index.js +1 -0
  75. package/dist/parser/index.js.map +1 -1
  76. package/dist/parser/plan-document.d.ts +36 -0
  77. package/dist/parser/plan-document.d.ts.map +1 -1
  78. package/dist/parser/plan-document.js +75 -8
  79. package/dist/parser/plan-document.js.map +1 -1
  80. package/dist/parser/plans.d.ts.map +1 -1
  81. package/dist/parser/plans.js +28 -102
  82. package/dist/parser/plans.js.map +1 -1
  83. package/dist/parser/shadow.d.ts +5 -1
  84. package/dist/parser/shadow.d.ts.map +1 -1
  85. package/dist/parser/shadow.js +29 -17
  86. package/dist/parser/shadow.js.map +1 -1
  87. package/dist/parser/validate.d.ts +4 -1
  88. package/dist/parser/validate.d.ts.map +1 -1
  89. package/dist/parser/validate.js +50 -35
  90. package/dist/parser/validate.js.map +1 -1
  91. package/dist/parser/yaml.d.ts.map +1 -1
  92. package/dist/parser/yaml.js +322 -297
  93. package/dist/parser/yaml.js.map +1 -1
  94. package/dist/schema/task.d.ts +22 -0
  95. package/dist/schema/task.d.ts.map +1 -1
  96. package/dist/schema/task.js +7 -0
  97. package/dist/schema/task.js.map +1 -1
  98. package/dist/sessions/store.d.ts +254 -1
  99. package/dist/sessions/store.d.ts.map +1 -1
  100. package/dist/sessions/store.js +621 -1
  101. package/dist/sessions/store.js.map +1 -1
  102. package/dist/sessions/types.d.ts +51 -2
  103. package/dist/sessions/types.d.ts.map +1 -1
  104. package/dist/sessions/types.js +25 -0
  105. package/dist/sessions/types.js.map +1 -1
  106. package/dist/strings/labels.d.ts +2 -0
  107. package/dist/strings/labels.d.ts.map +1 -1
  108. package/dist/strings/labels.js +2 -0
  109. package/dist/strings/labels.js.map +1 -1
  110. package/dist/utils/git.d.ts +2 -0
  111. package/dist/utils/git.d.ts.map +1 -1
  112. package/dist/utils/git.js +21 -5
  113. package/dist/utils/git.js.map +1 -1
  114. package/package.json +4 -1
  115. package/plugin/.claude-plugin/marketplace.json +1 -1
  116. package/plugin/.claude-plugin/plugin.json +1 -1
  117. package/plugin/plugins/kspec/skills/review/SKILL.md +37 -0
  118. package/plugin/plugins/kspec/skills/task-work/SKILL.md +16 -0
  119. package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +1 -1
  120. package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +14 -0
  121. package/templates/agents-sections/05-commit-convention.md +14 -0
  122. package/templates/skills/review/SKILL.md +37 -0
  123. package/templates/skills/task-work/SKILL.md +16 -0
  124. package/templates/skills/triage-inbox/SKILL.md +1 -1
  125. package/templates/skills/writing-specs/SKILL.md +14 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Session command module barrel.
3
+ *
4
+ * Re-exports public API from sub-modules. The registerSessionCommands function
5
+ * wires everything up with commander.
6
+ */
7
+ // Data gathering
8
+ export { gatherSessionContext, getIterationStats } from "./context.js";
9
+ // Checkpoint
10
+ export { performCheckpoint } from "./checkpoint.js";
11
+ // Formatting helpers (used by tests)
12
+ export { getDisplayRef, formatPriority, statusColor } from "./format.js";
13
+ // Hook input parsing
14
+ export { parseHookInput } from "./commands.js";
15
+ // Command registration
16
+ export { registerSessionCommands } from "./commands.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/cli/commands/session/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA0BH,iBAAiB;AACjB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEvE,aAAa;AACb,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,qCAAqC;AACrC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzE,qBAAqB;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,uBAAuB;AACvB,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Session log commands (list, show, stats, search).
3
+ *
4
+ * These commands have zero dependency on the session-start code.
5
+ */
6
+ interface SessionLogListOptions {
7
+ status?: string;
8
+ agent?: string;
9
+ since?: string;
10
+ sort?: string;
11
+ count?: boolean;
12
+ limit?: string;
13
+ }
14
+ /**
15
+ * Session log list action handler.
16
+ */
17
+ export declare function sessionLogListAction(options: SessionLogListOptions): Promise<void>;
18
+ interface SessionLogShowOptions {
19
+ events?: boolean;
20
+ type?: string;
21
+ limit?: string;
22
+ context?: string;
23
+ }
24
+ /**
25
+ * Session log show action handler.
26
+ */
27
+ export declare function sessionLogShowAction(sessionRef: string, options: SessionLogShowOptions): Promise<void>;
28
+ interface SessionLogStatsOptions {
29
+ since?: string;
30
+ agent?: string;
31
+ toolUsage?: boolean;
32
+ byDay?: boolean;
33
+ byWeek?: boolean;
34
+ }
35
+ /**
36
+ * Session log stats action handler.
37
+ */
38
+ export declare function sessionLogStatsAction(options: SessionLogStatsOptions): Promise<void>;
39
+ interface SessionLogSearchOptions {
40
+ type?: string;
41
+ since?: string;
42
+ agent?: string;
43
+ limit?: string;
44
+ }
45
+ /**
46
+ * Session log search action handler.
47
+ *
48
+ * AC: @session-log-search ac-1 through ac-7
49
+ */
50
+ export declare function sessionLogSearchAction(pattern: string, options: SessionLogSearchOptions): Promise<void>;
51
+ export {};
52
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/session/log.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkGH,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwFD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA8Df;AAID,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4JD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA0Ff;AAID,UAAU,sBAAsB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA8ED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAkEf;AAID,UAAU,uBAAuB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+CD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAgCf"}
@@ -0,0 +1,570 @@
1
+ /**
2
+ * Session log commands (list, show, stats, search).
3
+ *
4
+ * These commands have zero dependency on the session-start code.
5
+ */
6
+ import chalk from "chalk";
7
+ import { initContext, } from "../../../parser/index.js";
8
+ import { getAllSessionLogSummaries, getSessionLogDetail, resolveSessionId, readEvents, readSessionContext, computeSessionLogStats, computeToolUsageStats, computeTimePeriodStats, searchSessionEvents, deduplicatePhasedToolCalls, } from "../../../sessions/store.js";
9
+ import { SessionStatusSchema } from "../../../sessions/types.js";
10
+ import { formatRelativeTime, parseTimeSpec, } from "../../../utils/index.js";
11
+ import { isObject } from "../../../acp/types.js";
12
+ import { EXIT_CODES } from "../../exit-codes.js";
13
+ import { error, output } from "../../output.js";
14
+ // ─── Shared Helpers ─────────────────────────────────────────────────────────
15
+ /**
16
+ * Format a duration in milliseconds to a compact human-readable string.
17
+ * Omits seconds when minutes are present (e.g., "5m" not "5m 30s").
18
+ */
19
+ function formatDurationCompact(ms) {
20
+ if (ms < 0)
21
+ return "—";
22
+ const totalSec = Math.floor(ms / 1000);
23
+ const hours = Math.floor(totalSec / 3600);
24
+ const minutes = Math.floor((totalSec % 3600) / 60);
25
+ if (hours > 0) {
26
+ return `${hours}h ${minutes}m`;
27
+ }
28
+ if (minutes > 0) {
29
+ return `${minutes}m`;
30
+ }
31
+ return `${totalSec}s`;
32
+ }
33
+ /**
34
+ * Format a duration in milliseconds to a verbose human-readable string.
35
+ * Shows seconds when minutes are present (e.g., "5m 30s" not "5m").
36
+ */
37
+ function formatDurationVerbose(ms) {
38
+ if (ms < 0)
39
+ return "—";
40
+ const totalSec = Math.floor(ms / 1000);
41
+ const hours = Math.floor(totalSec / 3600);
42
+ const minutes = Math.floor((totalSec % 3600) / 60);
43
+ const seconds = totalSec % 60;
44
+ if (hours > 0 && minutes > 0) {
45
+ return `${hours}h ${minutes}m`;
46
+ }
47
+ if (hours > 0) {
48
+ return `${hours}h`;
49
+ }
50
+ if (minutes > 0) {
51
+ return `${minutes}m ${seconds}s`;
52
+ }
53
+ return `${seconds}s`;
54
+ }
55
+ /**
56
+ * Map session status to chalk color function.
57
+ * Unlike statusColor() in format.ts (which handles task statuses),
58
+ * this handles session lifecycle statuses: completed, active, abandoned.
59
+ */
60
+ function sessionStatusColor(status) {
61
+ switch (status) {
62
+ case "completed":
63
+ return chalk.green;
64
+ case "active":
65
+ return chalk.blue;
66
+ case "abandoned":
67
+ return chalk.yellow;
68
+ default:
69
+ return chalk.gray;
70
+ }
71
+ }
72
+ const VALID_SORT_FIELDS = [
73
+ "started_at",
74
+ "duration",
75
+ "events",
76
+ "iterations",
77
+ "tasks_completed",
78
+ ];
79
+ /**
80
+ * Sort session summaries by the specified field.
81
+ * Default: started_at descending.
82
+ *
83
+ * AC: @session-log-list ac-5
84
+ */
85
+ function sortSessions(sessions, sortField) {
86
+ return [...sessions].sort((a, b) => {
87
+ switch (sortField) {
88
+ case "started_at":
89
+ return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
90
+ case "duration":
91
+ return b.duration_ms - a.duration_ms;
92
+ case "events":
93
+ return b.event_count - a.event_count;
94
+ case "iterations":
95
+ return b.iteration_count - a.iteration_count;
96
+ case "tasks_completed":
97
+ return b.tasks_completed - a.tasks_completed;
98
+ default:
99
+ return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
100
+ }
101
+ });
102
+ }
103
+ /**
104
+ * Format the session log list as a table.
105
+ *
106
+ * AC: @session-log-list ac-1
107
+ */
108
+ function formatSessionLogList(sessions) {
109
+ if (sessions.length === 0) {
110
+ // AC: @session-log-list ac-6
111
+ console.log("No sessions found.");
112
+ return;
113
+ }
114
+ // Table header
115
+ console.log(chalk.gray(`${"ID".padEnd(10)} ${"Status".padEnd(11)} ${"Agent".padEnd(20)} ${"Started".padEnd(16)} ${"Duration".padEnd(10)} ${"Events".padEnd(8)} ${"Iters".padEnd(7)} Tasks`));
116
+ console.log(chalk.gray("─".repeat(95)));
117
+ for (const s of sessions) {
118
+ const id = s.id.slice(0, 8);
119
+ const colorFn = sessionStatusColor(s.status);
120
+ const status = colorFn(s.status.padEnd(11));
121
+ const agent = s.agent_type.slice(0, 20).padEnd(20);
122
+ const started = formatRelativeTime(new Date(s.started_at)).padEnd(16);
123
+ const duration = formatDurationCompact(s.duration_ms).padEnd(10);
124
+ const events = String(s.event_count).padEnd(8);
125
+ const iters = String(s.iteration_count).padEnd(7);
126
+ const tasks = String(s.tasks_completed);
127
+ console.log(`${chalk.yellow(id)} ${status} ${chalk.gray(agent)} ${chalk.gray(started)} ${duration} ${events} ${iters} ${tasks}`);
128
+ }
129
+ console.log(chalk.gray(`\n${sessions.length} session(s)`));
130
+ }
131
+ /**
132
+ * Session log list action handler.
133
+ */
134
+ export async function sessionLogListAction(options) {
135
+ try {
136
+ const ctx = await initContext();
137
+ let sessions = await getAllSessionLogSummaries(ctx.specDir);
138
+ // AC: @session-log-list ac-2 - Filter by status
139
+ if (options.status) {
140
+ const parsed = SessionStatusSchema.safeParse(options.status);
141
+ if (!parsed.success) {
142
+ const valid = SessionStatusSchema.options.join(", ");
143
+ error(`Invalid status: '${options.status}'. Valid values: ${valid}`);
144
+ process.exit(EXIT_CODES.USAGE_ERROR);
145
+ }
146
+ const statusFilter = parsed.data;
147
+ sessions = sessions.filter((s) => s.status === statusFilter);
148
+ }
149
+ // AC: @session-log-list ac-4 - Filter by agent type
150
+ if (options.agent) {
151
+ const agentFilter = options.agent;
152
+ sessions = sessions.filter((s) => s.agent_type === agentFilter);
153
+ }
154
+ // AC: @session-log-list ac-3 - Filter by since date
155
+ if (options.since) {
156
+ const sinceDate = parseTimeSpec(options.since);
157
+ if (sinceDate) {
158
+ sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
159
+ }
160
+ }
161
+ // AC: @session-log-list ac-5 - Sort
162
+ const sortField = options.sort && VALID_SORT_FIELDS.includes(options.sort)
163
+ ? options.sort
164
+ : "started_at";
165
+ sessions = sortSessions(sessions, sortField);
166
+ // AC: @session-log-list ac-7 - Limit output count
167
+ if (options.count) {
168
+ // AC: @trait-filterable-list ac-8
169
+ output({ count: sessions.length }, () => {
170
+ console.log(sessions.length);
171
+ });
172
+ return;
173
+ }
174
+ // Apply --limit (after filtering/sorting, before display)
175
+ if (options.limit) {
176
+ const limit = parseInt(options.limit, 10);
177
+ if (!Number.isNaN(limit) && limit > 0) {
178
+ sessions = sessions.slice(0, limit);
179
+ }
180
+ }
181
+ output(sessions, () => formatSessionLogList(sessions));
182
+ }
183
+ catch (err) {
184
+ error("Failed to list session logs", err);
185
+ process.exit(EXIT_CODES.ERROR);
186
+ }
187
+ }
188
+ /**
189
+ * Format an event timestamp as relative time from session start.
190
+ */
191
+ function formatEventTimestamp(eventTs, sessionStartTs) {
192
+ const relativeMs = eventTs - sessionStartTs;
193
+ const totalSec = Math.floor(relativeMs / 1000);
194
+ const minutes = Math.floor(totalSec / 60);
195
+ const seconds = totalSec % 60;
196
+ if (minutes > 0) {
197
+ return `+${minutes}m${seconds}s`;
198
+ }
199
+ return `+${seconds}s`;
200
+ }
201
+ /**
202
+ * Summarize event data for display.
203
+ * Returns a short string describing the event payload.
204
+ */
205
+ function summarizeEventData(event) {
206
+ const data = event.data;
207
+ if (!isObject(data))
208
+ return "";
209
+ // Handle tool_call events
210
+ if (event.type === "session.update") {
211
+ const update = data.update;
212
+ if (isObject(update) && update.sessionUpdate === "tool_call") {
213
+ const meta = update._meta;
214
+ let toolName = "unknown";
215
+ if (isObject(meta)) {
216
+ const claudeCode = meta.claudeCode;
217
+ if (isObject(claudeCode) && typeof claudeCode.toolName === "string") {
218
+ toolName = claudeCode.toolName;
219
+ }
220
+ }
221
+ const rawInput = update.rawInput;
222
+ if (isObject(rawInput) && typeof rawInput.command === "string") {
223
+ const command = rawInput.command;
224
+ const truncated = command.length > 60 ? command.slice(0, 57) + "..." : command;
225
+ return `${toolName}: ${truncated}`;
226
+ }
227
+ return toolName;
228
+ }
229
+ }
230
+ // Handle prompt.sent events
231
+ if (event.type === "prompt.sent") {
232
+ const prompt = data.prompt;
233
+ if (typeof prompt === "string" && prompt.length > 0) {
234
+ const truncated = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
235
+ return truncated;
236
+ }
237
+ }
238
+ // Handle session.start/end
239
+ if (event.type === "session.start") {
240
+ return "Session started";
241
+ }
242
+ if (event.type === "session.end") {
243
+ const reason = data.reason;
244
+ return typeof reason === "string" ? `Session ended: ${reason}` : "Session ended";
245
+ }
246
+ // Default: show first key
247
+ const keys = Object.keys(data);
248
+ if (keys.length > 0) {
249
+ return `{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""}}`;
250
+ }
251
+ return "";
252
+ }
253
+ /**
254
+ * Format the session log show output.
255
+ *
256
+ * AC: @session-log-show ac-1
257
+ */
258
+ function formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs) {
259
+ // AC: @session-log-show ac-1 - Session metadata
260
+ console.log(chalk.bold(`Session ${detail.id.slice(0, 8)}`));
261
+ console.log(chalk.gray("─".repeat(60)));
262
+ console.log(` ID: ${detail.id}`);
263
+ console.log(` Status: ${sessionStatusColor(detail.status)(detail.status)}`);
264
+ console.log(` Agent: ${detail.agent_type}`);
265
+ if (detail.task_id) {
266
+ console.log(` Task: ${detail.task_id}`);
267
+ }
268
+ console.log(` Started: ${detail.started_at}`);
269
+ if (detail.ended_at) {
270
+ console.log(` Ended: ${detail.ended_at}`);
271
+ }
272
+ console.log(` Duration: ${formatDurationCompact(detail.duration_ms)}`);
273
+ console.log(` Events: ${detail.event_count}`);
274
+ console.log(` Iterations: ${detail.iteration_count}`);
275
+ // AC: @session-log-show ac-2 - Per-iteration summary
276
+ if (detail.iterations.length > 0) {
277
+ console.log("\n" + chalk.bold("Iterations"));
278
+ console.log(chalk.gray("─".repeat(60)));
279
+ for (const iter of detail.iterations) {
280
+ const taskInfo = [];
281
+ if (iter.tasks_started.length > 0) {
282
+ taskInfo.push(`started: ${iter.tasks_started.join(", ")}`);
283
+ }
284
+ if (iter.tasks_completed.length > 0) {
285
+ taskInfo.push(`completed: ${iter.tasks_completed.join(", ")}`);
286
+ }
287
+ const taskStr = taskInfo.length > 0 ? ` | ${taskInfo.join(" | ")}` : "";
288
+ console.log(` ${chalk.cyan(`[${iter.iteration}]`)} ${iter.event_count} events${taskStr}`);
289
+ }
290
+ }
291
+ // AC: @session-log-show ac-3 - Event timeline
292
+ if (events !== null) {
293
+ console.log("\n" + chalk.bold("Events"));
294
+ console.log(chalk.gray("─".repeat(60)));
295
+ if (events.length === 0) {
296
+ console.log(chalk.gray(" No events to display."));
297
+ }
298
+ else {
299
+ for (const event of events) {
300
+ const timestamp = formatEventTimestamp(event.ts, sessionStartTs);
301
+ const summary = summarizeEventData(event);
302
+ const typeColor = event.type === "session.start" || event.type === "session.end"
303
+ ? chalk.green
304
+ : event.type === "session.update"
305
+ ? chalk.blue
306
+ : chalk.gray;
307
+ console.log(` ${chalk.yellow(timestamp.padEnd(10))} ${typeColor(event.type.padEnd(16))} ${chalk.gray(summary)}`);
308
+ }
309
+ }
310
+ }
311
+ // AC: @session-log-show ac-6 - Context snapshot
312
+ if (contextSnapshot !== null) {
313
+ console.log("\n" + chalk.bold("Context Snapshot"));
314
+ console.log(chalk.gray("─".repeat(60)));
315
+ console.log(JSON.stringify(contextSnapshot, null, 2));
316
+ }
317
+ }
318
+ /**
319
+ * Session log show action handler.
320
+ */
321
+ export async function sessionLogShowAction(sessionRef, options) {
322
+ try {
323
+ const ctx = await initContext();
324
+ // AC: @session-log-show ac-7, ac-8, ac-9 - Resolve session ID
325
+ const resolution = await resolveSessionId(ctx.specDir, sessionRef);
326
+ if (!resolution.ok) {
327
+ if (resolution.error === "not_found") {
328
+ // AC: @session-log-show ac-9
329
+ error(`Session not found: ${sessionRef}`);
330
+ process.exit(EXIT_CODES.NOT_FOUND);
331
+ }
332
+ else {
333
+ // AC: @session-log-show ac-8
334
+ error(`Ambiguous session ID prefix. Matches:\n ${resolution.matches.join("\n ")}\nPlease provide a more specific prefix.`);
335
+ process.exit(EXIT_CODES.VALIDATION_FAILED);
336
+ }
337
+ }
338
+ const sessionId = resolution.id;
339
+ // Get session detail
340
+ const detail = await getSessionLogDetail(ctx.specDir, sessionId);
341
+ if (!detail) {
342
+ error(`Session not found: ${sessionId}`);
343
+ process.exit(EXIT_CODES.NOT_FOUND);
344
+ }
345
+ // AC: @session-log-show ac-3, ac-4, ac-5 - Event timeline
346
+ let events = null;
347
+ if (options.events) {
348
+ let allEvents = deduplicatePhasedToolCalls(await readEvents(ctx.specDir, sessionId));
349
+ // AC: @session-log-show ac-4 - Filter by type
350
+ if (options.type) {
351
+ const typeFilter = options.type;
352
+ allEvents = allEvents.filter((e) => e.type === typeFilter);
353
+ }
354
+ // AC: @session-log-show ac-5 - Limit to last N events
355
+ if (options.limit) {
356
+ const limit = parseInt(options.limit, 10);
357
+ if (!Number.isNaN(limit) && limit > 0) {
358
+ allEvents = allEvents.slice(-limit);
359
+ }
360
+ }
361
+ events = allEvents;
362
+ }
363
+ // AC: @session-log-show ac-6 - Context snapshot
364
+ let contextSnapshot = null;
365
+ if (options.context) {
366
+ const iterNum = parseInt(options.context, 10);
367
+ if (!Number.isNaN(iterNum) && iterNum > 0) {
368
+ contextSnapshot = await readSessionContext(ctx.specDir, sessionId, iterNum);
369
+ if (contextSnapshot === null) {
370
+ error(`No context snapshot found for iteration ${iterNum}`);
371
+ process.exit(EXIT_CODES.NOT_FOUND);
372
+ }
373
+ }
374
+ else {
375
+ error(`Invalid iteration number: ${options.context}`);
376
+ process.exit(EXIT_CODES.USAGE_ERROR);
377
+ }
378
+ }
379
+ const sessionStartTs = new Date(detail.started_at).getTime();
380
+ // Build JSON output structure
381
+ const jsonOutput = {
382
+ ...detail,
383
+ ...(events !== null ? { events } : {}),
384
+ ...(contextSnapshot !== null ? { context: contextSnapshot } : {}),
385
+ };
386
+ output(jsonOutput, () => formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs));
387
+ }
388
+ catch (err) {
389
+ error("Failed to show session log", err);
390
+ process.exit(EXIT_CODES.ERROR);
391
+ }
392
+ }
393
+ /**
394
+ * Format the session log stats output.
395
+ *
396
+ * AC: @session-log-stats ac-1, ac-2, ac-3
397
+ */
398
+ function formatSessionLogStats(stats, toolUsage, timePeriods, groupBy) {
399
+ // AC: @session-log-stats ac-1 - Totals
400
+ console.log(chalk.bold("Session Statistics"));
401
+ console.log(chalk.gray("─".repeat(50)));
402
+ console.log(` Total Sessions: ${stats.total_sessions}`);
403
+ console.log(` Total Events: ${stats.total_events}`);
404
+ console.log(` Total Iterations: ${stats.total_iterations}`);
405
+ console.log(` Tasks Completed: ${stats.total_tasks_completed}`);
406
+ console.log(` Total Duration: ${formatDurationVerbose(stats.total_duration_ms)}`);
407
+ // AC: @session-log-stats ac-2 - Averages
408
+ console.log("\n" + chalk.bold("Averages"));
409
+ console.log(chalk.gray("─".repeat(50)));
410
+ console.log(` Avg Duration/Session: ${formatDurationVerbose(stats.avg_duration_ms)}`);
411
+ console.log(` Avg Iterations/Session: ${stats.avg_iterations_per_session}`);
412
+ console.log(` Avg Tasks/Session: ${stats.avg_tasks_per_session}`);
413
+ // AC: @session-log-stats ac-3 - Status breakdown
414
+ if (stats.status_breakdown.length > 0) {
415
+ console.log("\n" + chalk.bold("Status Breakdown"));
416
+ console.log(chalk.gray("─".repeat(50)));
417
+ for (const item of stats.status_breakdown) {
418
+ console.log(` ${sessionStatusColor(item.status)(item.status.padEnd(12))} ${String(item.count).padEnd(6)} ${item.percentage}%`);
419
+ }
420
+ }
421
+ // AC: @session-log-stats ac-6 - Tool usage
422
+ if (toolUsage !== null && toolUsage.length > 0) {
423
+ console.log("\n" + chalk.bold("Top Tool Usage"));
424
+ console.log(chalk.gray("─".repeat(50)));
425
+ for (const tool of toolUsage) {
426
+ console.log(` ${tool.tool_name.padEnd(20)} ${String(tool.count).padEnd(8)} ${tool.percentage}%`);
427
+ }
428
+ }
429
+ // AC: @session-log-stats ac-7 - Time periods
430
+ if (timePeriods !== null && timePeriods.length > 0) {
431
+ const label = groupBy === "week" ? "By Week" : "By Day";
432
+ console.log("\n" + chalk.bold(label));
433
+ console.log(chalk.gray("─".repeat(50)));
434
+ console.log(chalk.gray(` ${"Period".padEnd(14)} ${"Sessions".padEnd(10)} ${"Tasks".padEnd(8)} Duration`));
435
+ for (const period of timePeriods) {
436
+ console.log(` ${period.period.padEnd(14)} ${String(period.sessions_count).padEnd(10)} ${String(period.tasks_completed).padEnd(8)} ${formatDurationVerbose(period.total_duration_ms)}`);
437
+ }
438
+ }
439
+ }
440
+ /**
441
+ * Session log stats action handler.
442
+ */
443
+ export async function sessionLogStatsAction(options) {
444
+ try {
445
+ const ctx = await initContext();
446
+ let sessions = await getAllSessionLogSummaries(ctx.specDir);
447
+ // AC: @session-log-stats ac-4 - Filter by since
448
+ if (options.since) {
449
+ const sinceDate = parseTimeSpec(options.since);
450
+ if (sinceDate) {
451
+ sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
452
+ }
453
+ }
454
+ // AC: @session-log-stats ac-5 - Filter by agent type
455
+ if (options.agent) {
456
+ const agentFilter = options.agent;
457
+ sessions = sessions.filter((s) => s.agent_type === agentFilter);
458
+ }
459
+ // AC: @session-log-stats ac-8 - No sessions match criteria
460
+ if (sessions.length === 0) {
461
+ output({ message: "No sessions match criteria" }, () => {
462
+ console.log("No sessions match criteria.");
463
+ });
464
+ return;
465
+ }
466
+ // Compute base stats
467
+ const stats = computeSessionLogStats(sessions);
468
+ // AC: @session-log-stats ac-6 - Tool usage (optional)
469
+ let toolUsage = null;
470
+ if (options.toolUsage) {
471
+ const sessionIds = sessions.map((s) => s.id);
472
+ toolUsage = await computeToolUsageStats(ctx.specDir, sessionIds);
473
+ }
474
+ // AC: @session-log-stats ac-7 - Time periods (optional)
475
+ let timePeriods = null;
476
+ let groupBy = null;
477
+ if (options.byDay) {
478
+ groupBy = "day";
479
+ timePeriods = computeTimePeriodStats(sessions, "day");
480
+ }
481
+ else if (options.byWeek) {
482
+ groupBy = "week";
483
+ timePeriods = computeTimePeriodStats(sessions, "week");
484
+ }
485
+ // Build output structure
486
+ const jsonOutput = { stats };
487
+ if (toolUsage !== null) {
488
+ jsonOutput.tool_usage = toolUsage;
489
+ }
490
+ if (timePeriods !== null) {
491
+ jsonOutput.time_periods = timePeriods;
492
+ }
493
+ output(jsonOutput, () => formatSessionLogStats(stats, toolUsage, timePeriods, groupBy));
494
+ }
495
+ catch (err) {
496
+ error("Failed to compute session log stats", err);
497
+ process.exit(EXIT_CODES.ERROR);
498
+ }
499
+ }
500
+ /**
501
+ * Format the session log search output.
502
+ *
503
+ * AC: @session-log-search ac-1, ac-4
504
+ */
505
+ function formatSessionLogSearch(results) {
506
+ if (results.length === 0) {
507
+ // AC: @session-log-search ac-6
508
+ console.log("No matches found.");
509
+ return;
510
+ }
511
+ let totalMatches = 0;
512
+ for (const session of results) {
513
+ totalMatches += session.matches.length;
514
+ }
515
+ console.log(chalk.bold(`Found ${totalMatches} match(es) in ${results.length} session(s)`));
516
+ console.log(chalk.gray("─".repeat(60)));
517
+ for (const session of results) {
518
+ // Session header
519
+ console.log(`\n${chalk.cyan(`Session ${session.session_id.slice(0, 8)}`)} ` +
520
+ `${chalk.gray(`(${session.agent_type}, started ${formatRelativeTime(new Date(session.started_at))})`)}`);
521
+ // AC: @session-log-search ac-4 - Show matches with session ID, timestamp, type, excerpt
522
+ for (const match of session.matches) {
523
+ const ts = new Date(match.timestamp).toISOString();
524
+ const typeColor = match.event_type === "session.start" || match.event_type === "session.end"
525
+ ? chalk.green
526
+ : match.event_type === "session.update"
527
+ ? chalk.blue
528
+ : chalk.gray;
529
+ console.log(` ${chalk.yellow(ts)} ${typeColor(match.event_type.padEnd(16))}`);
530
+ // Content excerpt on next line, indented
531
+ console.log(` ${chalk.gray(match.content_excerpt)}`);
532
+ }
533
+ }
534
+ }
535
+ /**
536
+ * Session log search action handler.
537
+ *
538
+ * AC: @session-log-search ac-1 through ac-7
539
+ */
540
+ export async function sessionLogSearchAction(pattern, options) {
541
+ try {
542
+ const ctx = await initContext();
543
+ // Parse options - validate limit as positive integer
544
+ let limit = 50;
545
+ if (options.limit) {
546
+ const parsed = parseInt(options.limit, 10);
547
+ if (Number.isNaN(parsed) || parsed <= 0) {
548
+ error(`Invalid limit: ${options.limit}. Must be a positive integer.`);
549
+ process.exit(EXIT_CODES.USAGE_ERROR);
550
+ }
551
+ limit = parsed;
552
+ }
553
+ const sinceDate = options.since ? parseTimeSpec(options.since) : undefined;
554
+ // AC: @session-log-search ac-1, ac-2, ac-3, ac-5, ac-7
555
+ const results = await searchSessionEvents(ctx.specDir, pattern, {
556
+ eventType: options.type,
557
+ sinceDate: sinceDate || undefined,
558
+ agentType: options.agent,
559
+ limit,
560
+ });
561
+ // AC: @session-log-search ac-6 - No matches found message
562
+ // exit code 0 regardless (per @trait-semantic-exit-codes ac-5)
563
+ output(results, () => formatSessionLogSearch(results));
564
+ }
565
+ catch (err) {
566
+ error("Failed to search session logs", err);
567
+ process.exit(EXIT_CODES.ERROR);
568
+ }
569
+ }
570
+ //# sourceMappingURL=log.js.map