prjct-cli 1.20.0 → 1.22.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.
@@ -0,0 +1,469 @@
1
+ /**
2
+ * State Storage Task History Tests
3
+ *
4
+ * Tests for task history functionality in StateStorage:
5
+ * - Task history push on completion
6
+ * - FIFO eviction (max 20 entries)
7
+ * - Backward compatibility (undefined taskHistory)
8
+ * - Accessor methods (getTaskHistory, getMostRecentTask, getTaskHistoryByType)
9
+ * - Context injection (markdown generation with filtering)
10
+ */
11
+
12
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
13
+ import fs from 'node:fs/promises'
14
+ import os from 'node:os'
15
+ import path from 'node:path'
16
+ import pathManager from '../../infrastructure/path-manager'
17
+ import type { CurrentTask, StateJson } from '../../schemas/state'
18
+ import { prjctDb } from '../../storage/database'
19
+ import { stateStorage } from '../../storage/state-storage'
20
+
21
+ // =============================================================================
22
+ // Test Setup
23
+ // =============================================================================
24
+
25
+ let tmpRoot: string | null = null
26
+ let testProjectId: string
27
+
28
+ // Mock pathManager to use temp directory
29
+ const originalGetGlobalProjectPath = pathManager.getGlobalProjectPath.bind(pathManager)
30
+ const originalGetStoragePath = pathManager.getStoragePath.bind(pathManager)
31
+ const originalGetFilePath = pathManager.getFilePath.bind(pathManager)
32
+
33
+ beforeEach(async () => {
34
+ // Create temp directory for test isolation
35
+ tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-history-test-'))
36
+ testProjectId = `test-history-${Date.now()}`
37
+
38
+ // Mock pathManager to use temp directory
39
+ pathManager.getGlobalProjectPath = (projectId: string) => {
40
+ return path.join(tmpRoot!, projectId)
41
+ }
42
+
43
+ pathManager.getStoragePath = (projectId: string, filename: string) => {
44
+ return path.join(tmpRoot!, projectId, 'storage', filename)
45
+ }
46
+
47
+ pathManager.getFilePath = (projectId: string, layer: string, filename: string) => {
48
+ return path.join(tmpRoot!, projectId, layer, filename)
49
+ }
50
+
51
+ // Create storage and sync directories
52
+ const storagePath = pathManager.getStoragePath(testProjectId, '')
53
+ await fs.mkdir(storagePath, { recursive: true })
54
+
55
+ const syncPath = path.join(tmpRoot!, testProjectId, 'sync')
56
+ await fs.mkdir(syncPath, { recursive: true })
57
+ })
58
+
59
+ afterEach(async () => {
60
+ // Close SQLite connections before cleanup
61
+ prjctDb.close()
62
+
63
+ // Restore original pathManager methods
64
+ pathManager.getGlobalProjectPath = originalGetGlobalProjectPath
65
+ pathManager.getStoragePath = originalGetStoragePath
66
+ pathManager.getFilePath = originalGetFilePath
67
+
68
+ // Clean up temp directory
69
+ if (tmpRoot) {
70
+ await fs.rm(tmpRoot, { recursive: true, force: true })
71
+ tmpRoot = null
72
+ }
73
+ })
74
+
75
+ // =============================================================================
76
+ // Helper Functions
77
+ // =============================================================================
78
+
79
+ /**
80
+ * Create a mock task for testing
81
+ */
82
+ function createMockTask(
83
+ overrides: Partial<CurrentTask> & Record<string, unknown> = {}
84
+ ): CurrentTask {
85
+ return {
86
+ id: `task-${Date.now()}`,
87
+ description: 'Test task',
88
+ startedAt: new Date().toISOString(),
89
+ sessionId: `session-${Date.now()}`,
90
+ ...overrides,
91
+ } as CurrentTask
92
+ }
93
+
94
+ /**
95
+ * Complete a task and return the state
96
+ */
97
+ async function completeTaskAndGetState(projectId: string, task: CurrentTask): Promise<StateJson> {
98
+ // Start the task
99
+ await stateStorage.startTask(projectId, task)
100
+
101
+ // Complete the task
102
+ await stateStorage.completeTask(projectId)
103
+
104
+ // Return the state
105
+ return await stateStorage.read(projectId)
106
+ }
107
+
108
+ // =============================================================================
109
+ // Tests: Task History Push on Completion
110
+ // =============================================================================
111
+
112
+ describe('Task History - Push on Completion', () => {
113
+ it('should add completed task to taskHistory array', async () => {
114
+ const task = createMockTask({
115
+ description: 'Test task 1',
116
+ })
117
+
118
+ const state = await completeTaskAndGetState(testProjectId, task)
119
+
120
+ expect(state.taskHistory).toBeDefined()
121
+ expect(state.taskHistory?.length).toBe(1)
122
+ expect(state.taskHistory![0].title).toBe('Test task 1')
123
+ })
124
+
125
+ it('should include all required metadata in history entry', async () => {
126
+ const task = createMockTask({
127
+ description: 'Test task with metadata',
128
+ linearId: 'PRJ-123',
129
+ linearUuid: 'uuid-123',
130
+ })
131
+
132
+ const state = await completeTaskAndGetState(testProjectId, task)
133
+
134
+ const entry = state.taskHistory![0]
135
+ expect(entry.taskId).toBe(task.id)
136
+ expect(entry.title).toBe('Test task with metadata')
137
+ expect(entry.startedAt).toBeDefined() // startedAt is set by startTask()
138
+ expect(entry.completedAt).toBeDefined()
139
+ expect(entry.subtaskCount).toBe(0)
140
+ expect(entry.subtaskSummaries).toEqual([])
141
+ expect(entry.outcome).toBe('Task completed')
142
+ expect(entry.linearId).toBe('PRJ-123')
143
+ expect(entry.linearUuid).toBe('uuid-123')
144
+ })
145
+
146
+ it('should extract subtask summaries from completed task', async () => {
147
+ const task = createMockTask({
148
+ description: 'Task with subtasks',
149
+ subtasks: [
150
+ {
151
+ id: 'subtask-1',
152
+ description: 'First subtask',
153
+ domain: 'backend',
154
+ agent: 'backend.md',
155
+ status: 'completed',
156
+ dependsOn: [],
157
+ summary: {
158
+ title: 'Subtask 1 Complete',
159
+ description: 'Did some work',
160
+ filesChanged: [{ path: 'file1.ts', action: 'modified' }],
161
+ whatWasDone: ['Made changes'],
162
+ outputForNextAgent: 'Ready for next step',
163
+ },
164
+ },
165
+ {
166
+ id: 'subtask-2',
167
+ description: 'Second subtask',
168
+ domain: 'backend',
169
+ agent: 'backend.md',
170
+ status: 'pending',
171
+ dependsOn: [],
172
+ },
173
+ ],
174
+ })
175
+
176
+ const state = await completeTaskAndGetState(testProjectId, task)
177
+
178
+ const entry = state.taskHistory![0]
179
+ expect(entry.subtaskCount).toBe(2)
180
+ expect(entry.subtaskSummaries.length).toBe(1) // Only completed with summary
181
+ expect(entry.subtaskSummaries[0].title).toBe('Subtask 1 Complete')
182
+ })
183
+
184
+ it('should preserve order - newest entries first', async () => {
185
+ // Complete 3 tasks
186
+ for (let i = 1; i <= 3; i++) {
187
+ const task = createMockTask({
188
+ description: `Task ${i}`,
189
+ })
190
+ await completeTaskAndGetState(testProjectId, task)
191
+ }
192
+
193
+ const state = await stateStorage.read(testProjectId)
194
+
195
+ expect(state.taskHistory?.length).toBe(3)
196
+ expect(state.taskHistory![0].title).toBe('Task 3') // Newest first
197
+ expect(state.taskHistory![1].title).toBe('Task 2')
198
+ expect(state.taskHistory![2].title).toBe('Task 1') // Oldest last
199
+ })
200
+ })
201
+
202
+ // =============================================================================
203
+ // Tests: FIFO Eviction
204
+ // =============================================================================
205
+
206
+ describe('Task History - FIFO Eviction', () => {
207
+ it('should enforce max 20 entries', async () => {
208
+ // Complete 25 tasks
209
+ for (let i = 1; i <= 25; i++) {
210
+ const task = createMockTask({
211
+ description: `Task ${i}`,
212
+ })
213
+ await completeTaskAndGetState(testProjectId, task)
214
+ }
215
+
216
+ const state = await stateStorage.read(testProjectId)
217
+
218
+ expect(state.taskHistory?.length).toBe(20) // Max 20
219
+ })
220
+
221
+ it('should drop oldest entries when exceeding limit', async () => {
222
+ // Complete 22 tasks
223
+ for (let i = 1; i <= 22; i++) {
224
+ const task = createMockTask({
225
+ description: `Task ${i}`,
226
+ })
227
+ await completeTaskAndGetState(testProjectId, task)
228
+ }
229
+
230
+ const state = await stateStorage.read(testProjectId)
231
+
232
+ expect(state.taskHistory?.length).toBe(20)
233
+ expect(state.taskHistory![0].title).toBe('Task 22') // Newest
234
+ expect(state.taskHistory![19].title).toBe('Task 3') // Oldest kept
235
+ // Task 1 and Task 2 should be dropped
236
+ })
237
+ })
238
+
239
+ // =============================================================================
240
+ // Tests: Backward Compatibility
241
+ // =============================================================================
242
+
243
+ describe('Task History - Backward Compatibility', () => {
244
+ it('should initialize taskHistory as empty array when undefined', async () => {
245
+ // Read state before any tasks (should use default)
246
+ const state = await stateStorage.read(testProjectId)
247
+
248
+ expect(state.taskHistory).toBeDefined()
249
+ expect(Array.isArray(state.taskHistory)).toBe(true)
250
+ expect(state.taskHistory?.length).toBe(0)
251
+ })
252
+
253
+ it('should handle missing taskHistory field in existing state', async () => {
254
+ // Manually write state without taskHistory field
255
+ const stateFile = pathManager.getStoragePath(testProjectId, 'state.json')
256
+
257
+ await fs.writeFile(
258
+ stateFile,
259
+ JSON.stringify({
260
+ currentTask: null,
261
+ pausedTasks: [],
262
+ lastUpdated: new Date().toISOString(),
263
+ // No taskHistory field
264
+ })
265
+ )
266
+
267
+ // Complete a task - should work without error
268
+ const task = createMockTask({ description: 'New task' })
269
+ const state = await completeTaskAndGetState(testProjectId, task)
270
+
271
+ expect(state.taskHistory).toBeDefined()
272
+ expect(state.taskHistory?.length).toBe(1)
273
+ })
274
+ })
275
+
276
+ // =============================================================================
277
+ // Tests: Accessor Methods
278
+ // =============================================================================
279
+
280
+ describe('Task History - Accessor Methods', () => {
281
+ beforeEach(async () => {
282
+ // Setup: Complete 3 tasks with different classifications
283
+ const tasks = [
284
+ { description: 'Feature task 1', type: 'feature' },
285
+ { description: 'Bug fix', type: 'bug' },
286
+ { description: 'Feature task 2', type: 'feature' },
287
+ ]
288
+
289
+ for (const taskData of tasks) {
290
+ const task = createMockTask(taskData)
291
+ await completeTaskAndGetState(testProjectId, task)
292
+ }
293
+ })
294
+
295
+ it('getTaskHistory() should return full history', async () => {
296
+ const history = await stateStorage.getTaskHistory(testProjectId)
297
+
298
+ expect(history.length).toBe(3)
299
+ expect(history[0].title).toBe('Feature task 2') // Newest first
300
+ })
301
+
302
+ it('getMostRecentTask() should return latest entry', async () => {
303
+ const recent = await stateStorage.getMostRecentTask(testProjectId)
304
+
305
+ expect(recent).not.toBeNull()
306
+ expect(recent!.title).toBe('Feature task 2')
307
+ })
308
+
309
+ it('getMostRecentTask() should return null when no history', async () => {
310
+ const emptyProjectId = `empty-${Date.now()}`
311
+
312
+ // Create storage directory for empty project
313
+ const storagePath = pathManager.getStoragePath(emptyProjectId, '')
314
+ await fs.mkdir(storagePath, { recursive: true })
315
+
316
+ const recent = await stateStorage.getMostRecentTask(emptyProjectId)
317
+
318
+ expect(recent).toBeNull()
319
+ })
320
+
321
+ it('getTaskHistoryByType() should filter by classification', async () => {
322
+ const featureHistory = await stateStorage.getTaskHistoryByType(testProjectId, 'feature')
323
+
324
+ expect(featureHistory.length).toBe(2)
325
+ expect(featureHistory.every((h) => h.classification === 'feature')).toBe(true)
326
+ })
327
+
328
+ it('getTaskHistoryByType() should return empty array for no matches', async () => {
329
+ const choreHistory = await stateStorage.getTaskHistoryByType(testProjectId, 'chore')
330
+
331
+ expect(choreHistory.length).toBe(0)
332
+ })
333
+ })
334
+
335
+ // =============================================================================
336
+ // Tests: Context Injection (Markdown Generation)
337
+ // =============================================================================
338
+
339
+ describe('Task History - Context Injection', () => {
340
+ it('should include task history section in markdown when history exists', async () => {
341
+ // Complete a task
342
+ const task = createMockTask({ description: 'Completed task' })
343
+ await completeTaskAndGetState(testProjectId, task)
344
+
345
+ // Generate markdown
346
+ const state = await stateStorage.read(testProjectId)
347
+ const markdown = (stateStorage as any).toMarkdown(state)
348
+
349
+ expect(markdown).toContain('Recent tasks')
350
+ expect(markdown).toContain('Completed task')
351
+ expect(markdown).toContain('Task history helps identify patterns')
352
+ })
353
+
354
+ it('should filter by current task classification when task is active', async () => {
355
+ // Complete several tasks
356
+ const tasks = [
357
+ { description: 'Feature 1', type: 'feature' },
358
+ { description: 'Bug 1', type: 'bug' },
359
+ { description: 'Feature 2', type: 'feature' },
360
+ ]
361
+
362
+ for (const taskData of tasks) {
363
+ const task = createMockTask(taskData)
364
+ await completeTaskAndGetState(testProjectId, task)
365
+ }
366
+
367
+ // Start a new bug task (should filter history to bugs only)
368
+ const currentTask = createMockTask({ description: 'Current bug', type: 'bug' })
369
+ await stateStorage.startTask(testProjectId, currentTask)
370
+
371
+ const state = await stateStorage.read(testProjectId)
372
+ const markdown = (stateStorage as any).toMarkdown(state)
373
+
374
+ expect(markdown).toContain('Recent bug tasks')
375
+ expect(markdown).toContain('Bug 1')
376
+ expect(markdown).not.toContain('Feature 1')
377
+ })
378
+
379
+ it('should show max 5 recent tasks when no current task', async () => {
380
+ // Complete 7 tasks
381
+ for (let i = 1; i <= 7; i++) {
382
+ const task = createMockTask({ description: `Task ${i}` })
383
+ await completeTaskAndGetState(testProjectId, task)
384
+ }
385
+
386
+ const state = await stateStorage.read(testProjectId)
387
+ const markdown = (stateStorage as any).toMarkdown(state)
388
+
389
+ expect(markdown).toContain('Recent tasks (5)')
390
+ expect(markdown).toContain('Task 7')
391
+ expect(markdown).toContain('Task 3')
392
+ expect(markdown).not.toContain('Task 2') // Too old
393
+ })
394
+
395
+ it('should show max 3 entries of same type when task is active', async () => {
396
+ // Complete 5 feature tasks
397
+ for (let i = 1; i <= 5; i++) {
398
+ const task = createMockTask({ description: `Feature ${i}`, type: 'feature' })
399
+ await completeTaskAndGetState(testProjectId, task)
400
+ }
401
+
402
+ // Start a new feature task
403
+ const currentTask = createMockTask({ description: 'Current feature', type: 'feature' })
404
+ await stateStorage.startTask(testProjectId, currentTask)
405
+
406
+ const state = await stateStorage.read(testProjectId)
407
+ const markdown = (stateStorage as any).toMarkdown(state)
408
+
409
+ expect(markdown).toContain('Recent feature tasks (3)')
410
+ expect(markdown).toContain('Feature 5')
411
+ expect(markdown).toContain('Feature 4')
412
+ expect(markdown).toContain('Feature 3')
413
+ expect(markdown).not.toContain('Feature 2') // Too old (beyond 3)
414
+ })
415
+
416
+ it('should not show history section when taskHistory is empty', async () => {
417
+ const state = await stateStorage.read(testProjectId)
418
+ const markdown = (stateStorage as any).toMarkdown(state)
419
+
420
+ expect(markdown).not.toContain('Recent tasks')
421
+ expect(markdown).not.toContain('Task history helps identify patterns')
422
+ })
423
+
424
+ it('should include completion time and subtask count', async () => {
425
+ const task = createMockTask({
426
+ description: 'Task with details',
427
+ subtasks: [
428
+ {
429
+ id: 'st-1',
430
+ description: 'Subtask 1',
431
+ domain: 'backend',
432
+ agent: 'backend.md',
433
+ status: 'completed',
434
+ dependsOn: [],
435
+ },
436
+ {
437
+ id: 'st-2',
438
+ description: 'Subtask 2',
439
+ domain: 'backend',
440
+ agent: 'backend.md',
441
+ status: 'completed',
442
+ dependsOn: [],
443
+ },
444
+ ],
445
+ })
446
+
447
+ await completeTaskAndGetState(testProjectId, task)
448
+
449
+ const state = await stateStorage.read(testProjectId)
450
+ const markdown = (stateStorage as any).toMarkdown(state)
451
+
452
+ expect(markdown).toContain('2 subtasks')
453
+ expect(markdown).toContain('Completed:')
454
+ })
455
+
456
+ it('should include Linear ID when present', async () => {
457
+ const task = createMockTask({
458
+ description: 'Task with Linear',
459
+ linearId: 'PRJ-456',
460
+ })
461
+
462
+ await completeTaskAndGetState(testProjectId, task)
463
+
464
+ const state = await stateStorage.read(testProjectId)
465
+ const markdown = (stateStorage as any).toMarkdown(state)
466
+
467
+ expect(markdown).toContain('Linear: PRJ-456')
468
+ })
469
+ })
@@ -922,11 +922,21 @@ export class AnalysisCommands extends PrjctCommandsBase {
922
922
 
923
923
  /**
924
924
  * prjct verify - Verify integrity of sealed analysis (PRJ-263)
925
+ *
926
+ * Modes:
927
+ * - Default: Cryptographic verification (signature check)
928
+ * - --semantic: Semantic verification (data accuracy check, PRJ-270)
925
929
  */
926
930
  async verify(
927
931
  projectPath: string = process.cwd(),
928
- options: { json?: boolean } = {}
932
+ options: { json?: boolean; semantic?: boolean } = {}
929
933
  ): Promise<CommandResult> {
934
+ // Semantic verification mode (PRJ-270)
935
+ if (options.semantic) {
936
+ return this.semanticVerify(projectPath, options)
937
+ }
938
+
939
+ // Default: Cryptographic verification (PRJ-263)
930
940
  try {
931
941
  const initResult = await this.ensureProjectInit(projectPath)
932
942
  if (!initResult.success) return initResult
@@ -958,6 +968,91 @@ export class AnalysisCommands extends PrjctCommandsBase {
958
968
  }
959
969
  }
960
970
 
971
+ /**
972
+ * prjct analysis verify --semantic - Semantic verification of analysis results (PRJ-270)
973
+ *
974
+ * Validates that analysis data matches actual project state:
975
+ * - Frameworks exist in package.json
976
+ * - Languages match file extensions
977
+ * - Pattern locations reference real files
978
+ * - File count is accurate
979
+ * - Anti-pattern files exist
980
+ */
981
+ async semanticVerify(
982
+ projectPath: string = process.cwd(),
983
+ options: { json?: boolean } = {}
984
+ ): Promise<CommandResult> {
985
+ try {
986
+ const initResult = await this.ensureProjectInit(projectPath)
987
+ if (!initResult.success) return initResult
988
+
989
+ const projectId = await configManager.getProjectId(projectPath)
990
+ if (!projectId) {
991
+ if (options.json) {
992
+ console.log(JSON.stringify({ success: false, error: 'No project ID found' }))
993
+ } else {
994
+ out.fail('No project ID found')
995
+ }
996
+ return { success: false, error: 'No project ID found' }
997
+ }
998
+
999
+ // Get project path from project.json
1000
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
1001
+ let repoPath = projectPath
1002
+ try {
1003
+ const projectJson = JSON.parse(
1004
+ await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
1005
+ )
1006
+ repoPath = projectJson.repoPath || projectPath
1007
+ } catch {
1008
+ // Use fallback projectPath
1009
+ }
1010
+
1011
+ // Run semantic verification
1012
+ const result = await analysisStorage.semanticVerify(projectId, repoPath)
1013
+
1014
+ // JSON output mode
1015
+ if (options.json) {
1016
+ console.log(JSON.stringify(result))
1017
+ return { success: result.passed, data: result }
1018
+ }
1019
+
1020
+ // Human-readable output
1021
+ console.log('')
1022
+ if (result.passed) {
1023
+ out.done('Semantic verification passed')
1024
+ console.log(
1025
+ ` ${result.passedCount}/${result.checks.length} checks passed (${result.totalMs}ms)`
1026
+ )
1027
+ } else {
1028
+ out.fail('Semantic verification failed')
1029
+ console.log(` ${result.failedCount}/${result.checks.length} checks failed`)
1030
+ }
1031
+ console.log('')
1032
+
1033
+ // Show check details
1034
+ console.log('Check Results:')
1035
+ for (const check of result.checks) {
1036
+ const icon = check.passed ? '✓' : '✗'
1037
+ const status = check.passed
1038
+ ? `${check.output} (${check.durationMs}ms)`
1039
+ : check.error || 'Failed'
1040
+ console.log(` ${icon} ${check.name}: ${status}`)
1041
+ }
1042
+ console.log('')
1043
+
1044
+ return { success: result.passed, data: result }
1045
+ } catch (error) {
1046
+ const errMsg = getErrorMessage(error)
1047
+ if (options.json) {
1048
+ console.log(JSON.stringify({ success: false, error: errMsg }))
1049
+ } else {
1050
+ out.fail(errMsg)
1051
+ }
1052
+ return { success: false, error: errMsg }
1053
+ }
1054
+ }
1055
+
961
1056
  /**
962
1057
  * Get session activity stats from today's events
963
1058
  * @see PRJ-89
@@ -240,7 +240,7 @@ class PrjctCommands {
240
240
 
241
241
  async verify(
242
242
  projectPath: string = process.cwd(),
243
- options: { json?: boolean } = {}
243
+ options: { json?: boolean; semantic?: boolean } = {}
244
244
  ): Promise<CommandResult> {
245
245
  return this.analysis.verify(projectPath, options)
246
246
  }
@@ -19,6 +19,7 @@ import {
19
19
  import { linearService } from '../integrations/linear'
20
20
  import outcomeRecorder from '../outcomes/recorder'
21
21
  import { generateUUID } from '../schemas'
22
+ import type { TaskFeedback } from '../schemas/state'
22
23
  import { queueStorage, stateStorage } from '../storage'
23
24
  import type { CommandResult } from '../types'
24
25
  import { getErrorMessage } from '../types/fs'
@@ -174,10 +175,11 @@ export class WorkflowCommands extends PrjctCommandsBase {
174
175
 
175
176
  /**
176
177
  * /p:done - Complete current task
178
+ * Optionally accepts structured feedback for the task-to-analysis feedback loop (PRJ-272)
177
179
  */
178
180
  async done(
179
181
  projectPath: string = process.cwd(),
180
- options: { skipHooks?: boolean } = {}
182
+ options: { skipHooks?: boolean; feedback?: TaskFeedback } = {}
181
183
  ): Promise<CommandResult> {
182
184
  try {
183
185
  const initResult = await this.ensureProjectInit(projectPath)
@@ -249,7 +251,8 @@ export class WorkflowCommands extends PrjctCommandsBase {
249
251
  }
250
252
 
251
253
  // Write-through: Complete task (JSON → MD → Event)
252
- await stateStorage.completeTask(projectId)
254
+ // Pass feedback for the task-to-analysis feedback loop (PRJ-272)
255
+ await stateStorage.completeTask(projectId, options.feedback)
253
256
 
254
257
  // Sync to Linear if task has linearId
255
258
  const linearId = (currentTask as { linearId?: string }).linearId
package/core/index.ts CHANGED
@@ -173,7 +173,11 @@ async function main(): Promise<void> {
173
173
  full: options.full === true,
174
174
  }),
175
175
  seal: () => commands.seal(process.cwd(), { json: options.json === true }),
176
- verify: () => commands.verify(process.cwd(), { json: options.json === true }),
176
+ verify: () =>
177
+ commands.verify(process.cwd(), {
178
+ json: options.json === true,
179
+ semantic: options.semantic === true,
180
+ }),
177
181
  start: () => commands.start(),
178
182
  // Context (for Claude templates)
179
183
  context: (p) => commands.context(p),