@kynetic-ai/spec 0.1.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 (278) hide show
  1. package/README.md +263 -0
  2. package/dist/acp/client.d.ts +159 -0
  3. package/dist/acp/client.d.ts.map +1 -0
  4. package/dist/acp/client.js +255 -0
  5. package/dist/acp/client.js.map +1 -0
  6. package/dist/acp/framing.d.ts +119 -0
  7. package/dist/acp/framing.d.ts.map +1 -0
  8. package/dist/acp/framing.js +302 -0
  9. package/dist/acp/framing.js.map +1 -0
  10. package/dist/acp/index.d.ts +14 -0
  11. package/dist/acp/index.d.ts.map +1 -0
  12. package/dist/acp/index.js +13 -0
  13. package/dist/acp/index.js.map +1 -0
  14. package/dist/acp/types.d.ts +89 -0
  15. package/dist/acp/types.d.ts.map +1 -0
  16. package/dist/acp/types.js +99 -0
  17. package/dist/acp/types.js.map +1 -0
  18. package/dist/agents/adapters.d.ts +55 -0
  19. package/dist/agents/adapters.d.ts.map +1 -0
  20. package/dist/agents/adapters.js +84 -0
  21. package/dist/agents/adapters.js.map +1 -0
  22. package/dist/agents/index.d.ts +8 -0
  23. package/dist/agents/index.d.ts.map +1 -0
  24. package/dist/agents/index.js +10 -0
  25. package/dist/agents/index.js.map +1 -0
  26. package/dist/agents/spawner.d.ts +53 -0
  27. package/dist/agents/spawner.d.ts.map +1 -0
  28. package/dist/agents/spawner.js +83 -0
  29. package/dist/agents/spawner.js.map +1 -0
  30. package/dist/cli/batch.d.ts +82 -0
  31. package/dist/cli/batch.d.ts.map +1 -0
  32. package/dist/cli/batch.js +162 -0
  33. package/dist/cli/batch.js.map +1 -0
  34. package/dist/cli/commands/clone-for-testing.d.ts +6 -0
  35. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -0
  36. package/dist/cli/commands/clone-for-testing.js +176 -0
  37. package/dist/cli/commands/clone-for-testing.js.map +1 -0
  38. package/dist/cli/commands/derive.d.ts +6 -0
  39. package/dist/cli/commands/derive.d.ts.map +1 -0
  40. package/dist/cli/commands/derive.js +450 -0
  41. package/dist/cli/commands/derive.js.map +1 -0
  42. package/dist/cli/commands/help.d.ts +6 -0
  43. package/dist/cli/commands/help.d.ts.map +1 -0
  44. package/dist/cli/commands/help.js +196 -0
  45. package/dist/cli/commands/help.js.map +1 -0
  46. package/dist/cli/commands/inbox.d.ts +6 -0
  47. package/dist/cli/commands/inbox.d.ts.map +1 -0
  48. package/dist/cli/commands/inbox.js +235 -0
  49. package/dist/cli/commands/inbox.js.map +1 -0
  50. package/dist/cli/commands/index.d.ts +20 -0
  51. package/dist/cli/commands/index.d.ts.map +1 -0
  52. package/dist/cli/commands/index.js +21 -0
  53. package/dist/cli/commands/index.js.map +1 -0
  54. package/dist/cli/commands/init.d.ts +6 -0
  55. package/dist/cli/commands/init.d.ts.map +1 -0
  56. package/dist/cli/commands/init.js +245 -0
  57. package/dist/cli/commands/init.js.map +1 -0
  58. package/dist/cli/commands/item.d.ts +6 -0
  59. package/dist/cli/commands/item.d.ts.map +1 -0
  60. package/dist/cli/commands/item.js +1311 -0
  61. package/dist/cli/commands/item.js.map +1 -0
  62. package/dist/cli/commands/link.d.ts +6 -0
  63. package/dist/cli/commands/link.d.ts.map +1 -0
  64. package/dist/cli/commands/link.js +288 -0
  65. package/dist/cli/commands/link.js.map +1 -0
  66. package/dist/cli/commands/log.d.ts +16 -0
  67. package/dist/cli/commands/log.d.ts.map +1 -0
  68. package/dist/cli/commands/log.js +291 -0
  69. package/dist/cli/commands/log.js.map +1 -0
  70. package/dist/cli/commands/meta.d.ts +15 -0
  71. package/dist/cli/commands/meta.d.ts.map +1 -0
  72. package/dist/cli/commands/meta.js +1378 -0
  73. package/dist/cli/commands/meta.js.map +1 -0
  74. package/dist/cli/commands/module.d.ts +6 -0
  75. package/dist/cli/commands/module.d.ts.map +1 -0
  76. package/dist/cli/commands/module.js +102 -0
  77. package/dist/cli/commands/module.js.map +1 -0
  78. package/dist/cli/commands/ralph.d.ts +9 -0
  79. package/dist/cli/commands/ralph.d.ts.map +1 -0
  80. package/dist/cli/commands/ralph.js +465 -0
  81. package/dist/cli/commands/ralph.js.map +1 -0
  82. package/dist/cli/commands/search.d.ts +6 -0
  83. package/dist/cli/commands/search.d.ts.map +1 -0
  84. package/dist/cli/commands/search.js +134 -0
  85. package/dist/cli/commands/search.js.map +1 -0
  86. package/dist/cli/commands/session.d.ts +164 -0
  87. package/dist/cli/commands/session.d.ts.map +1 -0
  88. package/dist/cli/commands/session.js +745 -0
  89. package/dist/cli/commands/session.js.map +1 -0
  90. package/dist/cli/commands/setup.d.ts +26 -0
  91. package/dist/cli/commands/setup.d.ts.map +1 -0
  92. package/dist/cli/commands/setup.js +586 -0
  93. package/dist/cli/commands/setup.js.map +1 -0
  94. package/dist/cli/commands/shadow.d.ts +6 -0
  95. package/dist/cli/commands/shadow.d.ts.map +1 -0
  96. package/dist/cli/commands/shadow.js +299 -0
  97. package/dist/cli/commands/shadow.js.map +1 -0
  98. package/dist/cli/commands/task.d.ts +6 -0
  99. package/dist/cli/commands/task.d.ts.map +1 -0
  100. package/dist/cli/commands/task.js +1514 -0
  101. package/dist/cli/commands/task.js.map +1 -0
  102. package/dist/cli/commands/tasks.d.ts +6 -0
  103. package/dist/cli/commands/tasks.d.ts.map +1 -0
  104. package/dist/cli/commands/tasks.js +347 -0
  105. package/dist/cli/commands/tasks.js.map +1 -0
  106. package/dist/cli/commands/trait.d.ts +10 -0
  107. package/dist/cli/commands/trait.d.ts.map +1 -0
  108. package/dist/cli/commands/trait.js +295 -0
  109. package/dist/cli/commands/trait.js.map +1 -0
  110. package/dist/cli/commands/validate.d.ts +6 -0
  111. package/dist/cli/commands/validate.d.ts.map +1 -0
  112. package/dist/cli/commands/validate.js +626 -0
  113. package/dist/cli/commands/validate.js.map +1 -0
  114. package/dist/cli/exit-codes.d.ts +62 -0
  115. package/dist/cli/exit-codes.d.ts.map +1 -0
  116. package/dist/cli/exit-codes.js +65 -0
  117. package/dist/cli/exit-codes.js.map +1 -0
  118. package/dist/cli/help/content.d.ts +35 -0
  119. package/dist/cli/help/content.d.ts.map +1 -0
  120. package/dist/cli/help/content.js +312 -0
  121. package/dist/cli/help/content.js.map +1 -0
  122. package/dist/cli/index.d.ts +5 -0
  123. package/dist/cli/index.d.ts.map +1 -0
  124. package/dist/cli/index.js +85 -0
  125. package/dist/cli/index.js.map +1 -0
  126. package/dist/cli/introspection.d.ts +87 -0
  127. package/dist/cli/introspection.d.ts.map +1 -0
  128. package/dist/cli/introspection.js +127 -0
  129. package/dist/cli/introspection.js.map +1 -0
  130. package/dist/cli/output.d.ts +56 -0
  131. package/dist/cli/output.d.ts.map +1 -0
  132. package/dist/cli/output.js +467 -0
  133. package/dist/cli/output.js.map +1 -0
  134. package/dist/cli/suggest.d.ts +16 -0
  135. package/dist/cli/suggest.d.ts.map +1 -0
  136. package/dist/cli/suggest.js +72 -0
  137. package/dist/cli/suggest.js.map +1 -0
  138. package/dist/index.d.ts +3 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +5 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/parser/alignment.d.ts +113 -0
  143. package/dist/parser/alignment.d.ts.map +1 -0
  144. package/dist/parser/alignment.js +261 -0
  145. package/dist/parser/alignment.js.map +1 -0
  146. package/dist/parser/assess.d.ts +81 -0
  147. package/dist/parser/assess.d.ts.map +1 -0
  148. package/dist/parser/assess.js +197 -0
  149. package/dist/parser/assess.js.map +1 -0
  150. package/dist/parser/convention-validation.d.ts +48 -0
  151. package/dist/parser/convention-validation.d.ts.map +1 -0
  152. package/dist/parser/convention-validation.js +167 -0
  153. package/dist/parser/convention-validation.js.map +1 -0
  154. package/dist/parser/fix.d.ts +38 -0
  155. package/dist/parser/fix.d.ts.map +1 -0
  156. package/dist/parser/fix.js +185 -0
  157. package/dist/parser/fix.js.map +1 -0
  158. package/dist/parser/index.d.ts +12 -0
  159. package/dist/parser/index.d.ts.map +1 -0
  160. package/dist/parser/index.js +13 -0
  161. package/dist/parser/index.js.map +1 -0
  162. package/dist/parser/items.d.ts +138 -0
  163. package/dist/parser/items.d.ts.map +1 -0
  164. package/dist/parser/items.js +321 -0
  165. package/dist/parser/items.js.map +1 -0
  166. package/dist/parser/meta.d.ts +120 -0
  167. package/dist/parser/meta.d.ts.map +1 -0
  168. package/dist/parser/meta.js +441 -0
  169. package/dist/parser/meta.js.map +1 -0
  170. package/dist/parser/refs.d.ts +185 -0
  171. package/dist/parser/refs.d.ts.map +1 -0
  172. package/dist/parser/refs.js +404 -0
  173. package/dist/parser/refs.js.map +1 -0
  174. package/dist/parser/shadow.d.ts +253 -0
  175. package/dist/parser/shadow.d.ts.map +1 -0
  176. package/dist/parser/shadow.js +1053 -0
  177. package/dist/parser/shadow.js.map +1 -0
  178. package/dist/parser/traits.d.ts +72 -0
  179. package/dist/parser/traits.d.ts.map +1 -0
  180. package/dist/parser/traits.js +120 -0
  181. package/dist/parser/traits.js.map +1 -0
  182. package/dist/parser/validate.d.ts +89 -0
  183. package/dist/parser/validate.d.ts.map +1 -0
  184. package/dist/parser/validate.js +817 -0
  185. package/dist/parser/validate.js.map +1 -0
  186. package/dist/parser/yaml.d.ts +326 -0
  187. package/dist/parser/yaml.d.ts.map +1 -0
  188. package/dist/parser/yaml.js +1383 -0
  189. package/dist/parser/yaml.js.map +1 -0
  190. package/dist/ralph/cli-renderer.d.ts +20 -0
  191. package/dist/ralph/cli-renderer.d.ts.map +1 -0
  192. package/dist/ralph/cli-renderer.js +179 -0
  193. package/dist/ralph/cli-renderer.js.map +1 -0
  194. package/dist/ralph/events.d.ts +65 -0
  195. package/dist/ralph/events.d.ts.map +1 -0
  196. package/dist/ralph/events.js +397 -0
  197. package/dist/ralph/events.js.map +1 -0
  198. package/dist/ralph/index.d.ts +8 -0
  199. package/dist/ralph/index.d.ts.map +1 -0
  200. package/dist/ralph/index.js +10 -0
  201. package/dist/ralph/index.js.map +1 -0
  202. package/dist/schema/common.d.ts +46 -0
  203. package/dist/schema/common.d.ts.map +1 -0
  204. package/dist/schema/common.js +71 -0
  205. package/dist/schema/common.js.map +1 -0
  206. package/dist/schema/inbox.d.ts +90 -0
  207. package/dist/schema/inbox.d.ts.map +1 -0
  208. package/dist/schema/inbox.js +30 -0
  209. package/dist/schema/inbox.js.map +1 -0
  210. package/dist/schema/index.d.ts +6 -0
  211. package/dist/schema/index.d.ts.map +1 -0
  212. package/dist/schema/index.js +7 -0
  213. package/dist/schema/index.js.map +1 -0
  214. package/dist/schema/meta.d.ts +762 -0
  215. package/dist/schema/meta.d.ts.map +1 -0
  216. package/dist/schema/meta.js +144 -0
  217. package/dist/schema/meta.js.map +1 -0
  218. package/dist/schema/spec.d.ts +912 -0
  219. package/dist/schema/spec.d.ts.map +1 -0
  220. package/dist/schema/spec.js +104 -0
  221. package/dist/schema/spec.js.map +1 -0
  222. package/dist/schema/task.d.ts +664 -0
  223. package/dist/schema/task.d.ts.map +1 -0
  224. package/dist/schema/task.js +130 -0
  225. package/dist/schema/task.js.map +1 -0
  226. package/dist/sessions/index.d.ts +11 -0
  227. package/dist/sessions/index.d.ts.map +1 -0
  228. package/dist/sessions/index.js +13 -0
  229. package/dist/sessions/index.js.map +1 -0
  230. package/dist/sessions/store.d.ts +144 -0
  231. package/dist/sessions/store.d.ts.map +1 -0
  232. package/dist/sessions/store.js +325 -0
  233. package/dist/sessions/store.js.map +1 -0
  234. package/dist/sessions/types.d.ts +157 -0
  235. package/dist/sessions/types.d.ts.map +1 -0
  236. package/dist/sessions/types.js +90 -0
  237. package/dist/sessions/types.js.map +1 -0
  238. package/dist/strings/errors.d.ts +420 -0
  239. package/dist/strings/errors.d.ts.map +1 -0
  240. package/dist/strings/errors.js +282 -0
  241. package/dist/strings/errors.js.map +1 -0
  242. package/dist/strings/guidance.d.ts +65 -0
  243. package/dist/strings/guidance.d.ts.map +1 -0
  244. package/dist/strings/guidance.js +66 -0
  245. package/dist/strings/guidance.js.map +1 -0
  246. package/dist/strings/index.d.ts +12 -0
  247. package/dist/strings/index.d.ts.map +1 -0
  248. package/dist/strings/index.js +12 -0
  249. package/dist/strings/index.js.map +1 -0
  250. package/dist/strings/labels.d.ts +74 -0
  251. package/dist/strings/labels.d.ts.map +1 -0
  252. package/dist/strings/labels.js +75 -0
  253. package/dist/strings/labels.js.map +1 -0
  254. package/dist/strings/validation.d.ts +126 -0
  255. package/dist/strings/validation.d.ts.map +1 -0
  256. package/dist/strings/validation.js +135 -0
  257. package/dist/strings/validation.js.map +1 -0
  258. package/dist/utils/commit.d.ts +23 -0
  259. package/dist/utils/commit.d.ts.map +1 -0
  260. package/dist/utils/commit.js +67 -0
  261. package/dist/utils/commit.js.map +1 -0
  262. package/dist/utils/git.d.ts +57 -0
  263. package/dist/utils/git.d.ts.map +1 -0
  264. package/dist/utils/git.js +192 -0
  265. package/dist/utils/git.js.map +1 -0
  266. package/dist/utils/grep.d.ts +28 -0
  267. package/dist/utils/grep.d.ts.map +1 -0
  268. package/dist/utils/grep.js +86 -0
  269. package/dist/utils/grep.js.map +1 -0
  270. package/dist/utils/index.d.ts +8 -0
  271. package/dist/utils/index.d.ts.map +1 -0
  272. package/dist/utils/index.js +6 -0
  273. package/dist/utils/index.js.map +1 -0
  274. package/dist/utils/time.d.ts +18 -0
  275. package/dist/utils/time.d.ts.map +1 -0
  276. package/dist/utils/time.js +61 -0
  277. package/dist/utils/time.js.map +1 -0
  278. package/package.json +62 -0
@@ -0,0 +1,745 @@
1
+ /**
2
+ * Session management commands
3
+ *
4
+ * Provides context for starting/resuming work sessions.
5
+ */
6
+ import chalk from 'chalk';
7
+ import { initContext, loadAllTasks, loadAllItems, loadInboxItems, loadSessionContext, getReadyTasks, ReferenceIndex, } from '../../parser/index.js';
8
+ import { output, error, isJsonMode } from '../output.js';
9
+ import { sessionHeaders, hints, sessionPrompt, errors } from '../../strings/index.js';
10
+ import { parseTimeSpec, formatRelativeTime, isGitRepo, getRecentCommits, getCurrentBranch, getWorkingTreeStatus, formatCommitGuidance, } from '../../utils/index.js';
11
+ import { shadowPull } from '../../parser/shadow.js';
12
+ import { EXIT_CODES } from '../exit-codes.js';
13
+ // ─── Data Gathering ──────────────────────────────────────────────────────────
14
+ function toActiveTaskSummary(task, index) {
15
+ const lastNote = task.notes.length > 0 ? task.notes[task.notes.length - 1] : null;
16
+ const incompleteTodos = task.todos.filter(t => !t.done).length;
17
+ return {
18
+ ref: index.shortUlid(task._ulid),
19
+ title: task.title,
20
+ started_at: task.started_at || null,
21
+ priority: task.priority,
22
+ spec_ref: task.spec_ref || null,
23
+ note_count: task.notes.length,
24
+ last_note_at: lastNote ? lastNote.created_at : null,
25
+ todo_count: task.todos.length,
26
+ incomplete_todos: incompleteTodos,
27
+ };
28
+ }
29
+ function toReadyTaskSummary(task, index) {
30
+ return {
31
+ ref: index.shortUlid(task._ulid),
32
+ title: task.title,
33
+ priority: task.priority,
34
+ spec_ref: task.spec_ref || null,
35
+ tags: task.tags,
36
+ };
37
+ }
38
+ function toBlockedTaskSummary(task, allTasks, index) {
39
+ // Find unmet dependencies
40
+ const unmetDeps = [];
41
+ for (const depRef of task.depends_on) {
42
+ const result = index.resolve(depRef);
43
+ if (result.ok) {
44
+ const depItem = result.item;
45
+ if ('status' in depItem && depItem.status !== 'completed') {
46
+ unmetDeps.push(depRef);
47
+ }
48
+ }
49
+ }
50
+ return {
51
+ ref: index.shortUlid(task._ulid),
52
+ title: task.title,
53
+ blocked_by: task.blocked_by,
54
+ unmet_deps: unmetDeps,
55
+ };
56
+ }
57
+ function toCompletedTaskSummary(task, index) {
58
+ return {
59
+ ref: index.shortUlid(task._ulid),
60
+ title: task.title,
61
+ completed_at: task.completed_at || '',
62
+ closed_reason: task.closed_reason || null,
63
+ origin: task.origin,
64
+ };
65
+ }
66
+ function collectRecentNotes(tasks, index, options) {
67
+ const allNotes = [];
68
+ for (const task of tasks) {
69
+ for (const note of task.notes) {
70
+ const noteDate = new Date(note.created_at);
71
+ // Filter by since date if provided
72
+ if (options.since && noteDate < options.since) {
73
+ continue;
74
+ }
75
+ allNotes.push({
76
+ task_ref: index.shortUlid(task._ulid),
77
+ task_title: task.title,
78
+ note_ulid: note._ulid.slice(0, 8),
79
+ created_at: note.created_at,
80
+ author: note.author || null,
81
+ content: note.content,
82
+ });
83
+ }
84
+ }
85
+ // Sort by date descending, take limit
86
+ return allNotes
87
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
88
+ .slice(0, options.limit);
89
+ }
90
+ function collectIncompleteTodos(tasks, index, options) {
91
+ const allTodos = [];
92
+ for (const task of tasks) {
93
+ for (const todo of task.todos) {
94
+ // Only include incomplete todos
95
+ if (todo.done)
96
+ continue;
97
+ allTodos.push({
98
+ task_ref: index.shortUlid(task._ulid),
99
+ task_title: task.title,
100
+ id: todo.id,
101
+ text: todo.text,
102
+ added_at: todo.added_at,
103
+ added_by: todo.added_by || null,
104
+ });
105
+ }
106
+ }
107
+ // Sort by added_at descending (most recent first), take limit
108
+ return allTodos
109
+ .sort((a, b) => new Date(b.added_at).getTime() - new Date(a.added_at).getTime())
110
+ .slice(0, options.limit);
111
+ }
112
+ /**
113
+ * Gather session context data
114
+ */
115
+ export async function gatherSessionContext(ctx, options) {
116
+ const limit = parseInt(options.limit || '10', 10);
117
+ const sinceDate = options.since ? parseTimeSpec(options.since) : null;
118
+ const showGit = options.git !== false; // default true
119
+ // Load all data
120
+ const allTasks = await loadAllTasks(ctx);
121
+ const items = await loadAllItems(ctx);
122
+ const inboxItems = await loadInboxItems(ctx);
123
+ const index = new ReferenceIndex(allTasks, items);
124
+ // Compute stats
125
+ const stats = {
126
+ total_tasks: allTasks.length,
127
+ in_progress: allTasks.filter((t) => t.status === 'in_progress').length,
128
+ pending_review: allTasks.filter((t) => t.status === 'pending_review').length,
129
+ ready: getReadyTasks(allTasks).length,
130
+ blocked: allTasks.filter((t) => t.status === 'blocked').length,
131
+ completed: allTasks.filter((t) => t.status === 'completed').length,
132
+ inbox_items: inboxItems.length,
133
+ };
134
+ // Get active tasks
135
+ const activeTasks = allTasks
136
+ .filter((t) => t.status === 'in_progress')
137
+ .sort((a, b) => a.priority - b.priority)
138
+ .slice(0, options.full ? undefined : limit)
139
+ .map((t) => toActiveTaskSummary(t, index));
140
+ // Get pending review tasks
141
+ const pendingReviewTasks = allTasks
142
+ .filter((t) => t.status === 'pending_review')
143
+ .sort((a, b) => a.priority - b.priority)
144
+ .slice(0, options.full ? undefined : limit)
145
+ .map((t) => toActiveTaskSummary(t, index));
146
+ // Get recent notes from active tasks
147
+ const recentNotes = collectRecentNotes(allTasks.filter((t) => t.status === 'in_progress'), index, { limit: options.full ? limit * 2 : limit, since: sinceDate });
148
+ // Get incomplete todos from active tasks
149
+ const activeTodos = collectIncompleteTodos(allTasks.filter((t) => t.status === 'in_progress'), index, { limit: options.full ? limit * 2 : limit });
150
+ // Get ready tasks (optionally filtered to automation-eligible only)
151
+ const readyTasks = getReadyTasks(allTasks)
152
+ .filter((t) => !options.eligible || t.automation === 'eligible')
153
+ .slice(0, options.full ? undefined : limit)
154
+ .map((t) => toReadyTaskSummary(t, index));
155
+ // Get blocked tasks
156
+ const blockedTasks = allTasks
157
+ .filter((t) => t.status === 'blocked')
158
+ .slice(0, options.full ? undefined : limit)
159
+ .map((t) => toBlockedTaskSummary(t, allTasks, index));
160
+ // Get recently completed tasks
161
+ const recentlyCompleted = allTasks
162
+ .filter((t) => {
163
+ if (t.status !== 'completed' || !t.completed_at)
164
+ return false;
165
+ const completedDate = new Date(t.completed_at);
166
+ if (sinceDate && completedDate < sinceDate)
167
+ return false;
168
+ return true;
169
+ })
170
+ .sort((a, b) => {
171
+ // Sort by completed_at descending (most recent first)
172
+ const aDate = new Date(a.completed_at || 0);
173
+ const bDate = new Date(b.completed_at || 0);
174
+ return bDate.getTime() - aDate.getTime();
175
+ })
176
+ .slice(0, options.full ? undefined : limit)
177
+ .map((t) => toCompletedTaskSummary(t, index));
178
+ // Get git info
179
+ let branch = null;
180
+ let recentCommits = [];
181
+ let workingTree = null;
182
+ if (showGit && isGitRepo(ctx.rootDir)) {
183
+ branch = getCurrentBranch(ctx.rootDir);
184
+ const commits = getRecentCommits({
185
+ limit: options.full ? limit * 2 : limit,
186
+ since: sinceDate || undefined,
187
+ cwd: ctx.rootDir,
188
+ });
189
+ recentCommits = commits.map((c) => ({
190
+ hash: c.hash,
191
+ full_hash: c.fullHash,
192
+ date: c.date.toISOString(),
193
+ message: c.message,
194
+ author: c.author,
195
+ }));
196
+ workingTree = getWorkingTreeStatus(ctx.rootDir);
197
+ }
198
+ // Get inbox items (oldest first to encourage triage)
199
+ const inboxSummaries = inboxItems
200
+ .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())
201
+ .slice(0, options.full ? undefined : limit)
202
+ .map((item) => ({
203
+ ref: item._ulid.slice(0, 8),
204
+ text: item.text,
205
+ created_at: item.created_at,
206
+ tags: item.tags,
207
+ added_by: item.added_by || null,
208
+ }));
209
+ // Load session context (focus, threads, questions)
210
+ const sessionContext = await loadSessionContext(ctx);
211
+ return {
212
+ generated_at: new Date().toISOString(),
213
+ branch,
214
+ context: sessionContext,
215
+ active_tasks: activeTasks,
216
+ pending_review_tasks: pendingReviewTasks,
217
+ recent_notes: recentNotes,
218
+ active_todos: activeTodos,
219
+ ready_tasks: readyTasks,
220
+ blocked_tasks: blockedTasks,
221
+ recently_completed: recentlyCompleted,
222
+ recent_commits: recentCommits,
223
+ working_tree: workingTree,
224
+ inbox_items: inboxSummaries,
225
+ stats,
226
+ };
227
+ }
228
+ /**
229
+ * Perform session checkpoint - check for uncommitted work before ending session.
230
+ *
231
+ * This is designed for use as a Claude Code stop hook. It checks for:
232
+ * - Uncommitted git changes (staged, unstaged, untracked)
233
+ * - Tasks in in_progress status
234
+ * - Incomplete todos on active tasks
235
+ *
236
+ * Returns a structured result indicating whether the session can end cleanly.
237
+ */
238
+ export async function performCheckpoint(ctx, options) {
239
+ const issues = [];
240
+ const instructions = [];
241
+ // Load tasks
242
+ const allTasks = await loadAllTasks(ctx);
243
+ // Check for in-progress tasks
244
+ const inProgressTasks = allTasks.filter((t) => t.status === 'in_progress');
245
+ for (const task of inProgressTasks) {
246
+ const ref = task.slugs[0] ? `@${task.slugs[0]}` : `@${task._ulid.slice(0, 8)}`;
247
+ issues.push({
248
+ type: 'in_progress_task',
249
+ description: `Task ${ref} is still in progress: ${task.title}`,
250
+ details: {
251
+ ref,
252
+ title: task.title,
253
+ started_at: task.started_at,
254
+ },
255
+ });
256
+ // Check for incomplete todos on this task
257
+ const incompleteTodos = task.todos.filter((t) => !t.done);
258
+ for (const todo of incompleteTodos) {
259
+ issues.push({
260
+ type: 'incomplete_todo',
261
+ description: `Incomplete todo on ${ref}: ${todo.text}`,
262
+ details: {
263
+ task_ref: ref,
264
+ todo_id: todo.id,
265
+ text: todo.text,
266
+ },
267
+ });
268
+ }
269
+ }
270
+ // Check for uncommitted git changes
271
+ if (isGitRepo(ctx.rootDir)) {
272
+ const workingTree = getWorkingTreeStatus(ctx.rootDir);
273
+ if (!workingTree.clean) {
274
+ const changeCount = workingTree.staged.length +
275
+ workingTree.unstaged.length +
276
+ workingTree.untracked.length;
277
+ issues.push({
278
+ type: 'uncommitted_changes',
279
+ description: `${changeCount} uncommitted changes in working tree`,
280
+ details: {
281
+ staged: workingTree.staged.length,
282
+ unstaged: workingTree.unstaged.length,
283
+ untracked: workingTree.untracked.length,
284
+ },
285
+ });
286
+ }
287
+ }
288
+ // Build instructions based on issues
289
+ if (issues.length > 0 && !options.force) {
290
+ instructions.push('Before ending this session, please:');
291
+ const hasInProgress = issues.some((i) => i.type === 'in_progress_task');
292
+ const hasUncommitted = issues.some((i) => i.type === 'uncommitted_changes');
293
+ const hasIncompleteTodos = issues.some((i) => i.type === 'incomplete_todo');
294
+ let step = 1;
295
+ if (hasInProgress) {
296
+ instructions.push(`${step++}. Add notes to in-progress tasks documenting current state`);
297
+ instructions.push(`${step++}. Either complete the tasks or leave them in_progress with clear notes for next session`);
298
+ }
299
+ if (hasIncompleteTodos) {
300
+ instructions.push(`${step++}. Complete or acknowledge incomplete todos on active tasks`);
301
+ }
302
+ if (hasUncommitted) {
303
+ instructions.push(`${step++}. Commit your changes with a descriptive message`);
304
+ // Add WIP commit guidance if there are in-progress tasks
305
+ if (inProgressTasks.length > 0) {
306
+ const task = inProgressTasks[0];
307
+ const guidance = formatCommitGuidance(task, { wip: true });
308
+ instructions.push('');
309
+ instructions.push('Suggested WIP commit:');
310
+ instructions.push(` ${guidance.message}`);
311
+ instructions.push('');
312
+ for (const trailer of guidance.trailers) {
313
+ instructions.push(` ${trailer}`);
314
+ }
315
+ }
316
+ }
317
+ instructions.push('');
318
+ instructions.push('Use: kspec task note @task "Progress notes..." to document state');
319
+ instructions.push('Use: kspec task complete @task --reason "Summary" if task is done');
320
+ }
321
+ // Allow stop if:
322
+ // - No issues found
323
+ // - --force flag passed
324
+ // - This is a retry (stop_hook_active = true from previous block)
325
+ const isRetry = options.stopHookActive === true;
326
+ const ok = issues.length === 0 || options.force === true || isRetry;
327
+ let message;
328
+ if (isRetry && issues.length > 0) {
329
+ message = `[kspec] Session checkpoint: ${issues.length} issue(s) acknowledged - allowing stop`;
330
+ }
331
+ else if (ok) {
332
+ message = '[kspec] Session checkpoint passed - ready to end session';
333
+ }
334
+ else {
335
+ message = `[kspec] Session checkpoint: ${issues.length} issue(s) need attention`;
336
+ }
337
+ return {
338
+ ok,
339
+ message,
340
+ issues,
341
+ instructions,
342
+ };
343
+ }
344
+ // ─── Output Formatting ───────────────────────────────────────────────────────
345
+ function formatCheckpointResult(result) {
346
+ if (result.ok) {
347
+ console.log(chalk.green(result.message));
348
+ }
349
+ else {
350
+ console.log(chalk.yellow(result.message));
351
+ console.log('');
352
+ for (const issue of result.issues) {
353
+ const icon = issue.type === 'uncommitted_changes'
354
+ ? chalk.yellow('⚠')
355
+ : issue.type === 'in_progress_task'
356
+ ? chalk.blue('●')
357
+ : chalk.gray('○');
358
+ console.log(` ${icon} ${issue.description}`);
359
+ }
360
+ if (result.instructions.length > 0) {
361
+ console.log('');
362
+ for (const instruction of result.instructions) {
363
+ console.log(chalk.gray(instruction));
364
+ }
365
+ }
366
+ }
367
+ }
368
+ function formatSessionContext(ctx, options) {
369
+ const isBrief = !options.full;
370
+ // Header
371
+ console.log(`\n${sessionHeaders.title}`);
372
+ const age = formatRelativeTime(new Date(ctx.generated_at));
373
+ if (ctx.branch) {
374
+ console.log(chalk.gray(`Branch: ${ctx.branch} | Generated: ${age}`));
375
+ }
376
+ else {
377
+ console.log(chalk.gray(`Generated: ${age}`));
378
+ }
379
+ // Stats summary
380
+ const pendingReviewNote = ctx.stats.pending_review > 0
381
+ ? `${ctx.stats.pending_review} awaiting review, `
382
+ : '';
383
+ const inboxNote = ctx.stats.inbox_items > 0
384
+ ? ` | Inbox: ${ctx.stats.inbox_items}`
385
+ : '';
386
+ console.log(chalk.gray(`Tasks: ${ctx.stats.in_progress} active, ${pendingReviewNote}${ctx.stats.ready} ready, ` +
387
+ `${ctx.stats.blocked} blocked, ${ctx.stats.completed}/${ctx.stats.total_tasks} completed${inboxNote}`));
388
+ // Session context section (focus, threads, questions)
389
+ if (ctx.context && (ctx.context.focus || ctx.context.threads.length > 0 || ctx.context.open_questions.length > 0)) {
390
+ console.log('\n--- Session Context ---');
391
+ if (ctx.context.focus) {
392
+ console.log(` ${chalk.cyan('Focus:')} ${ctx.context.focus}`);
393
+ }
394
+ if (ctx.context.threads.length > 0) {
395
+ console.log(` ${chalk.cyan('Active Threads:')}`);
396
+ for (const thread of ctx.context.threads) {
397
+ console.log(` - ${thread}`);
398
+ }
399
+ }
400
+ if (ctx.context.open_questions.length > 0) {
401
+ console.log(` ${chalk.cyan('Open Questions:')}`);
402
+ for (const question of ctx.context.open_questions) {
403
+ console.log(` - ${question}`);
404
+ }
405
+ }
406
+ }
407
+ // Active tasks section
408
+ if (ctx.active_tasks.length > 0) {
409
+ console.log(`\n${sessionHeaders.activeWork}`);
410
+ for (const task of ctx.active_tasks) {
411
+ const started = task.started_at
412
+ ? chalk.gray(` (started ${formatRelativeTime(new Date(task.started_at))})`)
413
+ : '';
414
+ const priority = task.priority <= 2
415
+ ? chalk.red(`P${task.priority}`)
416
+ : chalk.gray(`P${task.priority}`);
417
+ console.log(` ${chalk.blue('[in_progress]')} ${priority} ${task.ref} ${task.title}${started}`);
418
+ }
419
+ }
420
+ else {
421
+ console.log(`\n${sessionHeaders.noActiveWork}`);
422
+ }
423
+ // Awaiting review section
424
+ if (ctx.pending_review_tasks.length > 0) {
425
+ console.log(`\n${sessionHeaders.awaitingReview}`);
426
+ for (const task of ctx.pending_review_tasks) {
427
+ const priority = task.priority <= 2
428
+ ? chalk.red(`P${task.priority}`)
429
+ : chalk.gray(`P${task.priority}`);
430
+ console.log(` ${chalk.yellow('[pending_review]')} ${priority} ${task.ref} ${task.title}`);
431
+ }
432
+ }
433
+ // Recently completed section
434
+ if (ctx.recently_completed.length > 0) {
435
+ console.log(`\n${sessionHeaders.recentlyCompleted}`);
436
+ const observationPromotedTasks = [];
437
+ for (const task of ctx.recently_completed) {
438
+ const completedAge = formatRelativeTime(new Date(task.completed_at));
439
+ let reason = '';
440
+ if (task.closed_reason) {
441
+ const maxLen = isBrief ? 60 : 120;
442
+ const truncated = task.closed_reason.length > maxLen
443
+ ? task.closed_reason.slice(0, maxLen).trim() + '...'
444
+ : task.closed_reason;
445
+ reason = chalk.gray(` - ${truncated}`);
446
+ }
447
+ console.log(` ${chalk.green('[completed]')} ${task.ref} ${task.title} ${chalk.gray(`(${completedAge})`)}${reason}`);
448
+ // Track tasks that came from observations
449
+ if (task.origin === 'observation_promotion') {
450
+ observationPromotedTasks.push(task.ref);
451
+ }
452
+ }
453
+ // Show reminder about resolving observations
454
+ if (observationPromotedTasks.length > 0) {
455
+ console.log(chalk.yellow(`\n ℹ Consider resolving linked observations: ${observationPromotedTasks.join(', ')}`));
456
+ console.log(chalk.gray(` Run: kspec meta observations --pending-resolution`));
457
+ }
458
+ }
459
+ // Recent notes section
460
+ if (ctx.recent_notes.length > 0) {
461
+ console.log(`\n${sessionHeaders.recentNotes}`);
462
+ for (const note of ctx.recent_notes) {
463
+ const age = formatRelativeTime(new Date(note.created_at));
464
+ const author = note.author ? chalk.gray(` by ${note.author}`) : '';
465
+ console.log(` ${chalk.yellow(age)} on ${note.task_ref}${author}:`);
466
+ // Truncate content in brief mode
467
+ let content = note.content.trim();
468
+ if (isBrief && content.length > 200) {
469
+ content = content.slice(0, 200).trim() + '...';
470
+ }
471
+ // Indent content, limit lines in brief mode
472
+ const lines = content.split('\n');
473
+ const maxLines = isBrief ? 3 : lines.length;
474
+ for (const line of lines.slice(0, maxLines)) {
475
+ console.log(` ${chalk.white(line)}`);
476
+ }
477
+ if (isBrief && lines.length > maxLines) {
478
+ console.log(chalk.gray(` ... (${lines.length - maxLines} more lines)`));
479
+ }
480
+ }
481
+ }
482
+ // Incomplete todos section
483
+ if (ctx.active_todos.length > 0) {
484
+ console.log(`\n${sessionHeaders.incompleteTodos}`);
485
+ for (const todo of ctx.active_todos) {
486
+ console.log(` ${chalk.yellow('[ ]')} ${todo.task_ref}#${todo.id}: ${todo.text}`);
487
+ }
488
+ }
489
+ // Ready tasks section
490
+ if (ctx.ready_tasks.length > 0) {
491
+ console.log(`\n${sessionHeaders.readyTasks}`);
492
+ for (const task of ctx.ready_tasks) {
493
+ const priority = task.priority <= 2
494
+ ? chalk.red(`P${task.priority}`)
495
+ : chalk.gray(`P${task.priority}`);
496
+ const tags = task.tags.length > 0 ? chalk.cyan(` #${task.tags.join(' #')}`) : '';
497
+ console.log(` ${priority} ${task.ref} ${task.title}${tags}`);
498
+ }
499
+ }
500
+ // Blocked tasks section
501
+ if (ctx.blocked_tasks.length > 0) {
502
+ console.log(`\n${sessionHeaders.blocked}`);
503
+ for (const task of ctx.blocked_tasks) {
504
+ console.log(` ${chalk.red('[blocked]')} ${task.ref} ${task.title}`);
505
+ if (task.blocked_by.length > 0) {
506
+ console.log(chalk.gray(` Blockers: ${task.blocked_by.join(', ')}`));
507
+ }
508
+ if (task.unmet_deps.length > 0) {
509
+ console.log(chalk.gray(` Waiting on: ${task.unmet_deps.join(', ')}`));
510
+ }
511
+ }
512
+ }
513
+ // Git commits section
514
+ if (ctx.recent_commits.length > 0) {
515
+ console.log(`\n${sessionHeaders.recentCommits}`);
516
+ for (const commit of ctx.recent_commits) {
517
+ const age = formatRelativeTime(new Date(commit.date));
518
+ console.log(` ${chalk.yellow(commit.hash)} ${commit.message} ${chalk.gray(`(${age}, ${commit.author})`)}`);
519
+ }
520
+ }
521
+ // Inbox section (oldest first to encourage triage)
522
+ if (ctx.inbox_items.length > 0) {
523
+ console.log(`\n${sessionHeaders.inbox}`);
524
+ for (const item of ctx.inbox_items) {
525
+ const age = formatRelativeTime(new Date(item.created_at));
526
+ const author = item.added_by ? ` by ${item.added_by}` : '';
527
+ const tags = item.tags.length > 0 ? chalk.cyan(` [${item.tags.join(', ')}]`) : '';
528
+ // Truncate text in brief mode
529
+ let text = item.text;
530
+ if (isBrief && text.length > 60) {
531
+ text = text.slice(0, 60).trim() + '...';
532
+ }
533
+ console.log(` ${chalk.magenta(item.ref)} ${chalk.gray(`(${age}${author})`)}${tags}`);
534
+ console.log(` ${text}`);
535
+ }
536
+ console.log(` ${hints.inboxPromote}`);
537
+ }
538
+ // Working tree section
539
+ if (ctx.working_tree && !ctx.working_tree.clean) {
540
+ console.log(`\n${sessionHeaders.workingTree}`);
541
+ if (ctx.working_tree.staged.length > 0) {
542
+ console.log(chalk.green(' Staged:'));
543
+ for (const file of ctx.working_tree.staged) {
544
+ console.log(` ${chalk.green(file.status[0].toUpperCase())} ${file.path}`);
545
+ }
546
+ }
547
+ if (ctx.working_tree.unstaged.length > 0) {
548
+ console.log(chalk.red(' Modified:'));
549
+ for (const file of ctx.working_tree.unstaged) {
550
+ console.log(` ${chalk.red(file.status[0].toUpperCase())} ${file.path}`);
551
+ }
552
+ }
553
+ if (ctx.working_tree.untracked.length > 0) {
554
+ console.log(chalk.gray(' Untracked:'));
555
+ const limit = isBrief ? 5 : ctx.working_tree.untracked.length;
556
+ for (const path of ctx.working_tree.untracked.slice(0, limit)) {
557
+ console.log(` ${chalk.gray('?')} ${path}`);
558
+ }
559
+ if (isBrief && ctx.working_tree.untracked.length > limit) {
560
+ console.log(chalk.gray(` ... and ${ctx.working_tree.untracked.length - limit} more`));
561
+ }
562
+ }
563
+ }
564
+ else if (ctx.working_tree?.clean) {
565
+ console.log(`\n${sessionHeaders.workingTreeClean}`);
566
+ }
567
+ // Quick Commands section - contextual hints based on state
568
+ const quickCommands = [];
569
+ if (ctx.active_tasks.length > 0) {
570
+ const ref = ctx.active_tasks[0].ref;
571
+ quickCommands.push(`kspec task note @${ref} "Progress..." ${chalk.gray('# document work')}`);
572
+ quickCommands.push(`kspec task complete @${ref} --reason "..." ${chalk.gray('# finish task')}`);
573
+ }
574
+ else if (ctx.ready_tasks.length > 0) {
575
+ const ref = ctx.ready_tasks[0].ref;
576
+ quickCommands.push(`kspec task start @${ref} ${chalk.gray('# begin work')}`);
577
+ }
578
+ if (ctx.inbox_items.length > 0) {
579
+ const ref = ctx.inbox_items[0].ref;
580
+ quickCommands.push(`kspec inbox promote @${ref} --title "..." ${chalk.gray('# convert to task')}`);
581
+ }
582
+ if (ctx.working_tree && !ctx.working_tree.clean) {
583
+ quickCommands.push(`git add . && git commit -m "..." ${chalk.gray('# commit changes')}`);
584
+ }
585
+ if (quickCommands.length > 0) {
586
+ console.log(`\n${sessionHeaders.quickCommands}`);
587
+ for (const hint of quickCommands) {
588
+ console.log(` ${hint}`);
589
+ }
590
+ }
591
+ console.log(''); // Final newline
592
+ }
593
+ // ─── Command Registration ────────────────────────────────────────────────────
594
+ async function sessionStartAction(options) {
595
+ try {
596
+ const ctx = await initContext();
597
+ // AC-2: Pull remote changes before showing session context
598
+ let syncResult = null;
599
+ if (ctx.shadow?.enabled) {
600
+ syncResult = await shadowPull(ctx.shadow.worktreeDir);
601
+ // AC-3: Warn about conflicts but continue with local state
602
+ if (syncResult.hadConflict) {
603
+ console.log(chalk.yellow('⚠ Shadow sync conflict detected. Run `kspec shadow resolve` to fix.'));
604
+ console.log(chalk.gray(' Continuing with local state...'));
605
+ console.log('');
606
+ }
607
+ else if (syncResult.pulled) {
608
+ console.log(chalk.gray('ℹ Synced shadow branch from remote'));
609
+ }
610
+ }
611
+ const sessionCtx = await gatherSessionContext(ctx, options);
612
+ output(sessionCtx, () => formatSessionContext(sessionCtx, options));
613
+ }
614
+ catch (err) {
615
+ error(errors.failures.gatherSessionContext, err);
616
+ process.exit(EXIT_CODES.ERROR);
617
+ }
618
+ }
619
+ /**
620
+ * Read stdin if available (non-blocking check for hook input)
621
+ */
622
+ async function readStdinIfAvailable() {
623
+ // Check if stdin is a TTY (interactive) - if so, don't try to read
624
+ if (process.stdin.isTTY) {
625
+ return null;
626
+ }
627
+ return new Promise((resolve) => {
628
+ let data = '';
629
+ const timeout = setTimeout(() => {
630
+ process.stdin.removeAllListeners();
631
+ resolve(data || null);
632
+ }, 100); // 100ms timeout for stdin
633
+ process.stdin.setEncoding('utf8');
634
+ process.stdin.on('data', (chunk) => {
635
+ data += chunk;
636
+ });
637
+ process.stdin.on('end', () => {
638
+ clearTimeout(timeout);
639
+ resolve(data || null);
640
+ });
641
+ process.stdin.on('error', () => {
642
+ clearTimeout(timeout);
643
+ resolve(null);
644
+ });
645
+ process.stdin.resume();
646
+ });
647
+ }
648
+ /**
649
+ * Parse Claude Code hook input from stdin
650
+ */
651
+ function parseHookInput(stdin) {
652
+ if (!stdin)
653
+ return null;
654
+ try {
655
+ return JSON.parse(stdin.trim());
656
+ }
657
+ catch {
658
+ return null;
659
+ }
660
+ }
661
+ // ─── Prompt Check (UserPromptSubmit Hook) ────────────────────────────────────
662
+ /**
663
+ * Output spec-first reminder for UserPromptSubmit hook.
664
+ *
665
+ * This is a simple context injection - always outputs the reminder,
666
+ * and Claude (Opus) is smart enough to apply it when relevant.
667
+ */
668
+ async function sessionPromptCheckAction() {
669
+ // Lean, instructive reminder with kspec prefix
670
+ console.log(sessionPrompt.specCheck);
671
+ }
672
+ async function sessionCheckpointAction(options) {
673
+ try {
674
+ // Read stdin for Claude Code hook input
675
+ const stdin = await readStdinIfAvailable();
676
+ const hookInput = parseHookInput(stdin);
677
+ // Check if this is a retry (stop hook already active)
678
+ if (hookInput?.stop_hook_active) {
679
+ options.stopHookActive = true;
680
+ }
681
+ const ctx = await initContext();
682
+ const result = await performCheckpoint(ctx, options);
683
+ // Output format depends on mode:
684
+ // - JSON mode (--json): Output Claude Code hook format {"decision": "block", "reason": "..."}
685
+ // - Human mode: Output formatted checkpoint result
686
+ if (isJsonMode()) {
687
+ if (!result.ok) {
688
+ // Build reason message with issues and instructions
689
+ const issueLines = result.issues.map(i => `- ${i.description}`).join('\n');
690
+ const instructionLines = result.instructions.filter(i => i.trim()).join('\n');
691
+ const reason = `${result.message}\n\nIssues:\n${issueLines}\n\n${instructionLines}`;
692
+ console.log(JSON.stringify({ decision: 'block', reason }));
693
+ }
694
+ // If ok, exit silently (Claude Code expects no output when allowing stop)
695
+ }
696
+ else {
697
+ formatCheckpointResult(result);
698
+ if (!result.ok) {
699
+ process.exit(EXIT_CODES.ERROR);
700
+ }
701
+ }
702
+ }
703
+ catch (err) {
704
+ error(errors.failures.runCheckpoint, err);
705
+ process.exit(EXIT_CODES.ERROR);
706
+ }
707
+ }
708
+ /**
709
+ * Register the 'session' command group and aliases
710
+ */
711
+ export function registerSessionCommands(program) {
712
+ const session = program
713
+ .command('session')
714
+ .description('Session management and context');
715
+ session
716
+ .command('start')
717
+ .alias('resume')
718
+ .description('Surface relevant context for starting a new working session')
719
+ .option('--brief', 'Compact summary (default)')
720
+ .option('--full', 'Comprehensive context dump')
721
+ .option('--since <time>', 'Filter by recency (ISO8601 or relative: 1h, 2d, 1w)')
722
+ .option('--no-git', 'Skip git commit information')
723
+ .option('-n, --limit <n>', 'Limit items per section', '10')
724
+ .action(sessionStartAction);
725
+ session
726
+ .command('checkpoint')
727
+ .description('Pre-stop hook: check for uncommitted work before ending session')
728
+ .option('--force', 'Allow session end regardless of issues')
729
+ .action(sessionCheckpointAction);
730
+ session
731
+ .command('prompt-check')
732
+ .description('UserPromptSubmit hook: inject spec-first reminder')
733
+ .action(sessionPromptCheckAction);
734
+ // Top-level alias: kspec context
735
+ program
736
+ .command('context')
737
+ .description('Alias for session start - surface session context')
738
+ .option('--brief', 'Compact summary (default)')
739
+ .option('--full', 'Comprehensive context dump')
740
+ .option('--since <time>', 'Filter by recency (ISO8601 or relative: 1h, 2d, 1w)')
741
+ .option('--no-git', 'Skip git commit information')
742
+ .option('-n, --limit <n>', 'Limit items per section', '10')
743
+ .action(sessionStartAction);
744
+ }
745
+ //# sourceMappingURL=session.js.map