@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.
- package/dist/cli/commands/guard.d.ts +43 -0
- package/dist/cli/commands/guard.d.ts.map +1 -0
- package/dist/cli/commands/guard.js +200 -0
- package/dist/cli/commands/guard.js.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/item.d.ts.map +1 -1
- package/dist/cli/commands/item.js +60 -23
- package/dist/cli/commands/item.js.map +1 -1
- package/dist/cli/commands/plan-import.js +51 -12
- package/dist/cli/commands/plan-import.js.map +1 -1
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +144 -329
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/session/checkpoint.d.ts +19 -0
- package/dist/cli/commands/session/checkpoint.d.ts.map +1 -0
- package/dist/cli/commands/session/checkpoint.js +161 -0
- package/dist/cli/commands/session/checkpoint.js.map +1 -0
- package/dist/cli/commands/session/commands.d.ts +18 -0
- package/dist/cli/commands/session/commands.d.ts.map +1 -0
- package/dist/cli/commands/session/commands.js +259 -0
- package/dist/cli/commands/session/commands.js.map +1 -0
- package/dist/cli/commands/session/context.d.ts +17 -0
- package/dist/cli/commands/session/context.d.ts.map +1 -0
- package/dist/cli/commands/session/context.js +493 -0
- package/dist/cli/commands/session/context.js.map +1 -0
- package/dist/cli/commands/session/create.d.ts +29 -0
- package/dist/cli/commands/session/create.d.ts.map +1 -0
- package/dist/cli/commands/session/create.js +147 -0
- package/dist/cli/commands/session/create.js.map +1 -0
- package/dist/cli/commands/session/format.d.ts +27 -0
- package/dist/cli/commands/session/format.d.ts.map +1 -0
- package/dist/cli/commands/session/format.js +401 -0
- package/dist/cli/commands/session/format.js.map +1 -0
- package/dist/cli/commands/session/index.d.ts +13 -0
- package/dist/cli/commands/session/index.d.ts.map +1 -0
- package/dist/cli/commands/session/index.js +17 -0
- package/dist/cli/commands/session/index.js.map +1 -0
- package/dist/cli/commands/session/log.d.ts +52 -0
- package/dist/cli/commands/session/log.d.ts.map +1 -0
- package/dist/cli/commands/session/log.js +570 -0
- package/dist/cli/commands/session/log.js.map +1 -0
- package/dist/cli/commands/session/types.d.ts +230 -0
- package/dist/cli/commands/session/types.d.ts.map +1 -0
- package/dist/cli/commands/session/types.js +7 -0
- package/dist/cli/commands/session/types.js.map +1 -0
- package/dist/cli/commands/session.d.ts +4 -179
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +6 -1424
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +69 -223
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +95 -37
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +23 -7
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +14 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/parser/file-lock.d.ts +14 -0
- package/dist/parser/file-lock.d.ts.map +1 -0
- package/dist/parser/file-lock.js +124 -0
- package/dist/parser/file-lock.js.map +1 -0
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -0
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/plan-document.d.ts +36 -0
- package/dist/parser/plan-document.d.ts.map +1 -1
- package/dist/parser/plan-document.js +75 -8
- package/dist/parser/plan-document.js.map +1 -1
- package/dist/parser/plans.d.ts.map +1 -1
- package/dist/parser/plans.js +28 -102
- package/dist/parser/plans.js.map +1 -1
- package/dist/parser/shadow.d.ts +5 -1
- package/dist/parser/shadow.d.ts.map +1 -1
- package/dist/parser/shadow.js +29 -17
- package/dist/parser/shadow.js.map +1 -1
- package/dist/parser/validate.d.ts +4 -1
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +50 -35
- package/dist/parser/validate.js.map +1 -1
- package/dist/parser/yaml.d.ts.map +1 -1
- package/dist/parser/yaml.js +322 -297
- package/dist/parser/yaml.js.map +1 -1
- package/dist/schema/task.d.ts +22 -0
- package/dist/schema/task.d.ts.map +1 -1
- package/dist/schema/task.js +7 -0
- package/dist/schema/task.js.map +1 -1
- package/dist/sessions/store.d.ts +254 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +621 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +51 -2
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +25 -0
- package/dist/sessions/types.js.map +1 -1
- package/dist/strings/labels.d.ts +2 -0
- package/dist/strings/labels.d.ts.map +1 -1
- package/dist/strings/labels.js +2 -0
- package/dist/strings/labels.js.map +1 -1
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +21 -5
- package/dist/utils/git.js.map +1 -1
- package/package.json +4 -1
- package/plugin/.claude-plugin/marketplace.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/kspec/skills/review/SKILL.md +37 -0
- package/plugin/plugins/kspec/skills/task-work/SKILL.md +16 -0
- package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +1 -1
- package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +14 -0
- package/templates/agents-sections/05-commit-convention.md +14 -0
- package/templates/skills/review/SKILL.md +37 -0
- package/templates/skills/task-work/SKILL.md +16 -0
- package/templates/skills/triage-inbox/SKILL.md +1 -1
- 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
|