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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.22.0] - 2026-02-10
4
+
5
+ ### Features
6
+
7
+ - add task-to-analysis feedback loop (PRJ-272) (#165)
8
+ - add task history array with FIFO eviction (PRJ-281) (#164)
9
+
10
+
11
+ ## [1.22.0] - 2026-02-10
12
+
13
+ ### Features
14
+
15
+ - **Task-to-analysis feedback loop** (PRJ-272): Tasks report discoveries back into analysis and agent generation
16
+ - TaskFeedbackSchema: stackConfirmed, patternsDiscovered, agentAccuracy (with rating enum), issuesEncountered
17
+ - Optional `feedback` field on TaskHistoryEntry for backward compatibility
18
+ - `getAggregatedFeedback()` consolidates patterns, stack confirmations, and issues across task history
19
+ - Recurring issues (2+ occurrences) automatically promoted to "known gotchas"
20
+ - Sync incorporates feedback: patterns populate analysis draft, gotchas become anti-patterns
21
+ - Agent generator injects "Recent Learnings" section into domain agents with patterns, gotchas, and accuracy notes
22
+ - Workflow `done()` accepts and passes feedback through to storage
23
+ - 22 new tests covering schema validation, persistence, aggregation, gotcha promotion, and backward compatibility (1020 total)
24
+
25
+ ### Implementation Details
26
+
27
+ Closes the knowledge loop between task execution and project analysis. Previously, discoveries made during tasks were lost when sessions ended. Now, structured feedback persists in task history and feeds into the next sync cycle.
28
+
29
+ **Data flow:** `p. done` (feedback captured) → `taskHistory[].feedback` → `p. sync` → `analysis.patterns` + `agents/*.md` "Recent Learnings"
30
+
31
+ **Modified modules:**
32
+ - `core/schemas/state.ts` — Added TaskFeedbackSchema, extended TaskHistoryEntrySchema with optional feedback field
33
+ - `core/storage/state-storage.ts` — completeTask() accepts feedback, createTaskHistoryEntry() attaches it, getAggregatedFeedback() provides read-side API, toMarkdown() shows feedback in context
34
+ - `core/commands/workflow.ts` — done() passes feedback through options to completeTask()
35
+ - `core/services/sync-service.ts` — saveDraftAnalysis() loads aggregated feedback, injectFeedbackSection() adds learnings to agents
36
+ - `core/services/agent-generator.ts` — generate() accepts TaskFeedbackContext, injectFeedbackSection() appends learnings to domain agents
37
+ - `core/__tests__/storage/state-storage-feedback.test.ts` — 22 comprehensive tests
38
+
39
+ ### Learnings
40
+
41
+ - **SyncService duplicates AgentGenerator:** Both have their own `generateDomainAgent()` — feedback injection needed in both places
42
+ - **Write-Through pattern:** All state flows JSON → MD → Event; feedback follows the same pattern
43
+ - **Backward compatibility via optional fields:** Adding `feedback?: TaskFeedback` to existing schema requires zero migration
44
+
45
+ ### Test Plan
46
+
47
+ #### For QA
48
+ 1. Complete a task with `p. done` — verify feedback stored in `taskHistory[0].feedback`
49
+ 2. Complete multiple tasks with same issue — verify gotcha promotion (2+ occurrences)
50
+ 3. Run `p. sync` after tasks with feedback — verify analysis draft has patterns
51
+ 4. Run `p. sync` with agent regeneration — verify "Recent Learnings" in domain agents
52
+ 5. Complete task WITHOUT feedback — verify backward compatibility
53
+ 6. Run `bun test` — all 1020 tests pass
54
+
55
+ #### For Users
56
+ **What changed:** Task discoveries now persist and improve future agent context automatically.
57
+ **How to use:** Automatic via `p. done` template. No user action required.
58
+ **Breaking changes:** None.
59
+
60
+ ## [1.21.0] - 2026-02-10
61
+
62
+ ### Features
63
+
64
+ - add semantic verification for analysis results (PRJ-270) (#163)
65
+ - **Task history array** (PRJ-281): Replace single previousTask with bounded task history for pattern learning
66
+ - TaskHistoryEntry schema captures completed task metadata: title, classification, timestamps, subtasks, outcome, branch, Linear IDs
67
+ - Automatic history push on task completion with FIFO eviction (max 20 entries)
68
+ - Context injection: shows 3 recent same-type tasks when active, 5 recent when idle
69
+ - Accessor methods: getTaskHistory(), getMostRecentTask(), getTaskHistoryByType()
70
+ - Backward compatible: undefined taskHistory initializes as empty array
71
+ - Comprehensive test suite with 20 test cases (998 tests total pass)
72
+
73
+ ### Implementation Details
74
+
75
+ Replaced single previousTask field with bounded task history array to enable pattern learning and cross-task context for AI agents. When tasks complete, metadata is automatically captured and stored with FIFO eviction.
76
+
77
+ **Modified modules:**
78
+ - `core/schemas/state.ts` — Added TaskHistoryEntrySchema with 12 fields, updated StateJsonSchema, exported TaskHistoryEntry type, updated DEFAULT_STATE
79
+ - `core/storage/state-storage.ts` — Updated completeTask() to push history entries, added createTaskHistoryEntry() helper, added 3 accessor methods, updated toMarkdown() for context injection, updated getDefault()
80
+ - `core/__tests__/storage/state-storage-history.test.ts` (468 lines) — 20 comprehensive tests covering push, eviction, backward compatibility, accessors, and context injection
81
+ - `README.md` — Added Task History section with usage documentation
82
+ - `CHANGELOG.md` — Documented task history feature
83
+
84
+ ### Learnings
85
+
86
+ - **Schema-first design:** Define Zod schemas before implementation ensures type safety and validation at runtime
87
+ - **Type assertions for extended properties:** Use `taskAny = task as any` to access properties not in CurrentTask schema (type, branch, parentDescription)
88
+ - **Context injection in toMarkdown():** The state-storage toMarkdown() method is where context is generated, not context-builder.ts
89
+ - **pathManager mocking for test isolation:** Mock getGlobalProjectPath, getStoragePath, getFilePath to use temp directories in tests
90
+ - **FIFO over LRU:** Simpler implementation with predictable behavior for bounded history
91
+
92
+ ### Test Plan
93
+
94
+ #### For QA
95
+ 1. Complete a task with `p. done` — verify taskHistory entry appears in state.json with all metadata fields
96
+ 2. Complete 25+ tasks — verify only 20 entries remain (oldest dropped)
97
+ 3. Start a bug task — verify context markdown shows recent bug tasks only (not features)
98
+ 4. Test with existing state.json missing taskHistory field — verify backward compatibility
99
+ 5. Verify accessor methods return correct data: getTaskHistory(), getMostRecentTask(), getTaskHistoryByType()
100
+
101
+ #### For Users
102
+ **What changed:** Completed tasks are now tracked in a history array (max 20) instead of only storing the last paused task
103
+ **How to use:** No action needed — task history is automatic on `p. done`
104
+ **Breaking changes:** None — fully backward compatible
105
+
106
+
3
107
  ## [1.20.0] - 2026-02-10
4
108
 
5
109
  ### Features
@@ -11,6 +115,16 @@
11
115
 
12
116
  ### Features
13
117
 
118
+ - **Semantic verification for analysis results** (PRJ-270): Validate analysis consistency before sealing
119
+ - Framework verification: checks frameworks exist in package.json dependencies (case-insensitive matching)
120
+ - Language verification: validates languages match actual file extensions (.ts → TypeScript)
121
+ - Pattern location verification: confirms pattern files exist in project
122
+ - File count verification: validates count accuracy (within 10% tolerance)
123
+ - Anti-pattern file verification: ensures anti-pattern files exist when referenced
124
+ - CLI command: `prjct verify --semantic` with human-readable and JSON output
125
+ - Parallel execution of all 5 checks using Promise.all() (~100-200ms for typical projects)
126
+ - Comprehensive test suite with 10 new test cases covering all scenarios (28 tests total, 63 assertions)
127
+
14
128
  - **Retry with exponential backoff for agent and tool operations** (PRJ-271): Comprehensive retry infrastructure with error classification and circuit breaker
15
129
  - RetryPolicy utility with configurable attempts, delays, and exponential backoff (1s→2s→4s)
16
130
  - Automatic error classification: transient (EBUSY, EAGAIN, ETIMEDOUT) vs permanent (ENOENT, EPERM)
@@ -21,6 +135,41 @@
21
135
 
22
136
  ### Implementation Details
23
137
 
138
+ #### Semantic Verification (PRJ-270)
139
+
140
+ Added semantic verification functions to validate analysis results match actual project state. The verification system runs 5 parallel checks to detect logical inconsistencies before sealing analysis data.
141
+
142
+ **Modified modules:**
143
+ - `core/schemas/analysis.ts` — Added 5 verification functions, 1 orchestrator, 2 helpers, and 2 interfaces (SemanticCheckResult, SemanticVerificationReport)
144
+ - `core/storage/analysis-storage.ts` — Added semanticVerify() method to AnalysisStorage class
145
+ - `core/commands/analysis.ts` — Extended verify() with --semantic flag and added semanticVerify() helper
146
+ - `core/commands/commands.ts` — Updated verify() signature to include semantic?: boolean
147
+ - `core/index.ts` — Updated verify command handler to pass semantic flag
148
+ - `core/__tests__/storage/analysis-storage.test.ts` — Added 10 comprehensive test cases (+345 lines)
149
+ - `README.md` — Added analysis verification documentation section
150
+ - `CHANGELOG.md` — Documented semantic verification feature
151
+
152
+ **Verification functions:**
153
+ 1. `verifyFrameworks()` — Checks frameworks exist in package.json (case-insensitive partial matching)
154
+ 2. `verifyLanguages()` — Validates languages match file extensions (.ts → TypeScript)
155
+ 3. `verifyPatternLocations()` — Confirms pattern files exist in project
156
+ 4. `verifyFileCount()` — Validates count accuracy (10% tolerance for temporary files/caches)
157
+ 5. `verifyAntiPatternFiles()` — Ensures anti-pattern files exist when referenced
158
+
159
+ **Helper functions:**
160
+ - `getProjectExtensions()` — Recursively scans project for file extensions (ignores node_modules, .git, dist, build, .next, .turbo, coverage)
161
+ - `countProjectFiles()` — Counts all files in project with same ignore patterns
162
+
163
+ **Key features:**
164
+ - Parallel execution using Promise.all() for performance (~100-200ms typical)
165
+ - Skip conditions for null/undefined/empty arrays (prevents false failures)
166
+ - 10% tolerance for file count (accounts for temporary files, caches)
167
+ - Case-insensitive partial matching for frameworks
168
+ - Returns SemanticVerificationReport following VerificationReport pattern from sync-verifier
169
+ - Zero breaking changes: all 18 existing tests pass
170
+
171
+ #### Retry Infrastructure (PRJ-271)
172
+
24
173
  Built RetryPolicy utility with exponential backoff, error classification, and circuit breaker. Integrated across agent initialization, tool operations, and parallel agent generation. The system now automatically retries transient failures while failing fast on permanent errors.
25
174
 
26
175
  **New modules:**
package/README.md CHANGED
@@ -123,15 +123,105 @@ All agents share the same project storage, so you can switch between them freely
123
123
  | `p. linear` | - | Linear integration |
124
124
  | `p. github` | - | GitHub Issues integration |
125
125
 
126
+ ## Task History
127
+
128
+ prjct automatically tracks your completed tasks to help AI agents learn from patterns and make better decisions across sessions.
129
+
130
+ ### How It Works
131
+
132
+ When you complete a task (`p. done` / `/done`), prjct stores:
133
+ - Task description and classification (feature, bug, improvement, chore)
134
+ - Start and completion timestamps
135
+ - Number of subtasks and their summaries
136
+ - Git branch name and Linear issue ID (if linked)
137
+ - PR URL (if shipped)
138
+
139
+ This history is:
140
+ - **Bounded**: Maximum 20 entries with FIFO (First-In-First-Out) eviction
141
+ - **Contextual**: Filtered by task type when starting similar work
142
+ - **Persistent**: Survives across sessions and agent types
143
+
144
+ ### Context Injection
145
+
146
+ Task history is automatically injected into the AI agent's context:
147
+ - When **starting a task**: Shows 3 most recent tasks of the same type (e.g., recent bug fixes when starting a new bug)
148
+ - When **idle**: Shows 5 most recent tasks across all types
149
+ - **Purpose**: Helps agents identify patterns, avoid repeating mistakes, and build on previous solutions
150
+
151
+ ### Accessor Methods (for developers)
152
+
153
+ ```typescript
154
+ import { stateStorage } from './storage/state-storage'
155
+
156
+ // Get full task history (max 20 entries, newest first)
157
+ const history = await stateStorage.getTaskHistory(projectId)
158
+
159
+ // Get most recent completed task
160
+ const recent = await stateStorage.getMostRecentTask(projectId)
161
+
162
+ // Get tasks by classification
163
+ const bugs = await stateStorage.getTaskHistoryByType(projectId, 'bug')
164
+ const features = await stateStorage.getTaskHistoryByType(projectId, 'feature')
165
+ ```
166
+
126
167
  ## CLI Commands
127
168
 
128
169
  ```bash
129
170
  prjct start # First-time setup (Claude/Gemini)
130
171
  prjct init # Initialize project (+ Cursor setup)
172
+ prjct sync # Analyze project and generate context
173
+ prjct verify # Verify analysis integrity (cryptographic)
174
+ prjct verify --semantic # Verify analysis consistency (semantic)
131
175
  prjct --version # Show version + provider status
132
176
  prjct --help # Show help
133
177
  ```
134
178
 
179
+ ### Analysis Verification
180
+
181
+ prjct provides two types of analysis verification to ensure data integrity and logical consistency:
182
+
183
+ #### Cryptographic Verification (Default)
184
+ ```bash
185
+ prjct verify [--json]
186
+ ```
187
+
188
+ Verifies the integrity of sealed analysis results using cryptographic signatures. This ensures:
189
+ - Analysis data hasn't been tampered with
190
+ - Sealed analysis matches the original analysis
191
+ - Hash signatures are valid
192
+
193
+ **When to use:** After sealing an analysis (`prjct seal`) to confirm data integrity.
194
+
195
+ #### Semantic Verification (PRJ-270)
196
+ ```bash
197
+ prjct verify --semantic [--json]
198
+ ```
199
+
200
+ Validates that analysis results match the actual project state. This checks:
201
+ - ✓ **Frameworks** exist in `package.json` dependencies
202
+ - ✓ **Languages** match actual file extensions (.ts → TypeScript)
203
+ - ✓ **Pattern locations** reference real files in the project
204
+ - ✓ **File count** is accurate (within 10% tolerance)
205
+ - ✓ **Anti-pattern files** exist when referenced
206
+
207
+ **When to use:** Before sealing an analysis to catch logical inconsistencies or after project changes to validate analysis accuracy.
208
+
209
+ **Example output:**
210
+ ```
211
+ Semantic Verification Report
212
+ ────────────────────────────
213
+ ✓ Framework verification: passed (2 frameworks validated)
214
+ ✓ Language verification: passed (1 language validated)
215
+ ✓ Pattern locations: passed (12 patterns verified)
216
+ ✓ File count verification: passed (324 files, within tolerance)
217
+ ✓ Anti-pattern files: passed (3 anti-patterns verified)
218
+
219
+ Result: PASSED (5/5 checks)
220
+ Total time: 145ms
221
+ ```
222
+
223
+ Both verification modes support `--json` flag for programmatic use.
224
+
135
225
  ## Environment Variables
136
226
 
137
227
  ### Configuration
@@ -14,6 +14,7 @@ import {
14
14
  AnalysisItemSchema,
15
15
  AnalysisStatusSchema,
16
16
  parseAnalysis,
17
+ type SemanticCheckResult,
17
18
  safeParseAnalysis,
18
19
  } from '../../schemas/analysis'
19
20
 
@@ -275,3 +276,366 @@ describe('backward compatibility', () => {
275
276
  expect(result.modelMetadata?.provider).toBe('claude')
276
277
  })
277
278
  })
279
+
280
+ // =============================================================================
281
+ // Semantic Verification (PRJ-270)
282
+ // =============================================================================
283
+
284
+ describe('semantic verification', () => {
285
+ const { semanticVerify } = require('../../schemas/analysis')
286
+ const fs = require('node:fs/promises')
287
+ const path = require('node:path')
288
+ const os = require('node:os')
289
+
290
+ // Helper to create a temporary test project
291
+ async function createTestProject(options: {
292
+ hasPackageJson?: boolean
293
+ packageJsonDeps?: Record<string, string>
294
+ files?: { path: string; content: string }[]
295
+ fileCount?: number
296
+ }) {
297
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-test-'))
298
+
299
+ // Create package.json if requested
300
+ if (options.hasPackageJson) {
301
+ const pkg = {
302
+ name: 'test-project',
303
+ dependencies: options.packageJsonDeps || {},
304
+ }
305
+ await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2))
306
+ }
307
+
308
+ // Create additional files
309
+ if (options.files) {
310
+ for (const file of options.files) {
311
+ const filePath = path.join(tmpDir, file.path)
312
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
313
+ await fs.writeFile(filePath, file.content)
314
+ }
315
+ }
316
+
317
+ return tmpDir
318
+ }
319
+
320
+ // Helper to cleanup test project
321
+ async function cleanupTestProject(tmpDir: string) {
322
+ try {
323
+ await fs.rm(tmpDir, { recursive: true, force: true })
324
+ } catch {
325
+ // Ignore cleanup errors
326
+ }
327
+ }
328
+
329
+ it('should pass all checks for valid analysis', async () => {
330
+ const projectPath = await createTestProject({
331
+ hasPackageJson: true,
332
+ packageJsonDeps: { hono: '^3.0.0', zod: '^3.0.0' },
333
+ files: [
334
+ { path: 'src/index.ts', content: 'export const app = {}' },
335
+ { path: 'src/server.ts', content: 'import { serve } from "bun"' },
336
+ { path: 'patterns/service.ts', content: 'export class UserService {}' },
337
+ ],
338
+ })
339
+
340
+ const analysis = {
341
+ projectId: 'test',
342
+ languages: ['TypeScript'],
343
+ frameworks: ['Hono'],
344
+ configFiles: [],
345
+ fileCount: 4, // package.json + 3 TypeScript files
346
+ patterns: [
347
+ { name: 'Service pattern', description: 'DI pattern', location: 'patterns/service.ts' },
348
+ ],
349
+ antiPatterns: [],
350
+ analyzedAt: '2026-02-10T00:00:00.000Z',
351
+ status: 'draft' as const,
352
+ }
353
+
354
+ const result = await semanticVerify(analysis, projectPath)
355
+
356
+ expect(result.passed).toBe(true)
357
+ expect(result.failedCount).toBe(0)
358
+ expect(result.passedCount).toBeGreaterThan(0)
359
+ expect(result.checks.every((c: SemanticCheckResult) => c.passed)).toBe(true)
360
+
361
+ await cleanupTestProject(projectPath)
362
+ })
363
+
364
+ it('should fail when frameworks are not in package.json', async () => {
365
+ const projectPath = await createTestProject({
366
+ hasPackageJson: true,
367
+ packageJsonDeps: { express: '^4.0.0' }, // Different framework
368
+ files: [{ path: 'src/index.ts', content: '' }],
369
+ })
370
+
371
+ const analysis = {
372
+ projectId: 'test',
373
+ languages: ['TypeScript'],
374
+ frameworks: ['Hono', 'Zod'], // These are not in dependencies
375
+ configFiles: [],
376
+ fileCount: 1,
377
+ patterns: [],
378
+ antiPatterns: [],
379
+ analyzedAt: '2026-02-10T00:00:00.000Z',
380
+ status: 'draft' as const,
381
+ }
382
+
383
+ const result = await semanticVerify(analysis, projectPath)
384
+
385
+ expect(result.passed).toBe(false)
386
+ expect(result.failedCount).toBeGreaterThan(0)
387
+
388
+ const frameworkCheck = result.checks.find(
389
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
390
+ )
391
+ expect(frameworkCheck?.passed).toBe(false)
392
+ expect(frameworkCheck?.error).toContain('not found in dependencies')
393
+
394
+ await cleanupTestProject(projectPath)
395
+ })
396
+
397
+ it('should fail when package.json is missing', async () => {
398
+ const projectPath = await createTestProject({
399
+ hasPackageJson: false, // No package.json
400
+ files: [{ path: 'src/index.ts', content: '' }],
401
+ })
402
+
403
+ const analysis = {
404
+ projectId: 'test',
405
+ languages: ['TypeScript'],
406
+ frameworks: ['Hono'],
407
+ configFiles: [],
408
+ fileCount: 1,
409
+ patterns: [],
410
+ antiPatterns: [],
411
+ analyzedAt: '2026-02-10T00:00:00.000Z',
412
+ status: 'draft' as const,
413
+ }
414
+
415
+ const result = await semanticVerify(analysis, projectPath)
416
+
417
+ const frameworkCheck = result.checks.find(
418
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
419
+ )
420
+ expect(frameworkCheck?.passed).toBe(false)
421
+ expect(frameworkCheck?.error).toContain('package.json not found')
422
+
423
+ await cleanupTestProject(projectPath)
424
+ })
425
+
426
+ it('should fail when declared languages have no matching files', async () => {
427
+ const projectPath = await createTestProject({
428
+ hasPackageJson: true,
429
+ files: [
430
+ { path: 'src/index.js', content: '' }, // Only JS files
431
+ ],
432
+ })
433
+
434
+ const analysis = {
435
+ projectId: 'test',
436
+ languages: ['TypeScript', 'Go'], // Declared but no .ts or .go files
437
+ frameworks: [],
438
+ configFiles: [],
439
+ fileCount: 1,
440
+ patterns: [],
441
+ antiPatterns: [],
442
+ analyzedAt: '2026-02-10T00:00:00.000Z',
443
+ status: 'draft' as const,
444
+ }
445
+
446
+ const result = await semanticVerify(analysis, projectPath)
447
+
448
+ const languageCheck = result.checks.find(
449
+ (c: SemanticCheckResult) => c.name === 'Language verification'
450
+ )
451
+ expect(languageCheck?.passed).toBe(false)
452
+ expect(languageCheck?.error).toContain('without matching files')
453
+
454
+ await cleanupTestProject(projectPath)
455
+ })
456
+
457
+ it('should fail when pattern locations reference missing files', async () => {
458
+ const projectPath = await createTestProject({
459
+ hasPackageJson: true,
460
+ files: [{ path: 'src/index.ts', content: '' }],
461
+ })
462
+
463
+ const analysis = {
464
+ projectId: 'test',
465
+ languages: ['TypeScript'],
466
+ frameworks: [],
467
+ configFiles: [],
468
+ fileCount: 1,
469
+ patterns: [
470
+ { name: 'Service pattern', description: 'DI', location: 'src/service.ts' }, // Doesn't exist
471
+ { name: 'Repository', description: 'Data access', location: 'src/repo.ts' }, // Doesn't exist
472
+ ],
473
+ antiPatterns: [],
474
+ analyzedAt: '2026-02-10T00:00:00.000Z',
475
+ status: 'draft' as const,
476
+ }
477
+
478
+ const result = await semanticVerify(analysis, projectPath)
479
+
480
+ const patternCheck = result.checks.find(
481
+ (c: SemanticCheckResult) => c.name === 'Pattern location verification'
482
+ )
483
+ expect(patternCheck?.passed).toBe(false)
484
+ expect(patternCheck?.error).toContain('not found')
485
+
486
+ await cleanupTestProject(projectPath)
487
+ })
488
+
489
+ it('should fail when file count is inaccurate', async () => {
490
+ const projectPath = await createTestProject({
491
+ hasPackageJson: true,
492
+ files: [
493
+ { path: 'src/a.ts', content: '' },
494
+ { path: 'src/b.ts', content: '' },
495
+ { path: 'src/c.ts', content: '' },
496
+ ], // 4 files total (package.json + 3 .ts files)
497
+ })
498
+
499
+ const analysis = {
500
+ projectId: 'test',
501
+ languages: ['TypeScript'],
502
+ frameworks: [],
503
+ configFiles: [],
504
+ fileCount: 100, // Way off (actual is ~4)
505
+ patterns: [],
506
+ antiPatterns: [],
507
+ analyzedAt: '2026-02-10T00:00:00.000Z',
508
+ status: 'draft' as const,
509
+ }
510
+
511
+ const result = await semanticVerify(analysis, projectPath)
512
+
513
+ const fileCountCheck = result.checks.find(
514
+ (c: SemanticCheckResult) => c.name === 'File count verification'
515
+ )
516
+ expect(fileCountCheck?.passed).toBe(false)
517
+ expect(fileCountCheck?.error).toContain('mismatch')
518
+
519
+ await cleanupTestProject(projectPath)
520
+ })
521
+
522
+ it('should fail when anti-pattern files are missing', async () => {
523
+ const projectPath = await createTestProject({
524
+ hasPackageJson: true,
525
+ files: [{ path: 'src/index.ts', content: '' }],
526
+ })
527
+
528
+ const analysis = {
529
+ projectId: 'test',
530
+ languages: ['TypeScript'],
531
+ frameworks: [],
532
+ configFiles: [],
533
+ fileCount: 1,
534
+ patterns: [],
535
+ antiPatterns: [
536
+ { issue: 'Missing types', file: 'src/bad.ts', suggestion: 'Add types' }, // File doesn't exist
537
+ ],
538
+ analyzedAt: '2026-02-10T00:00:00.000Z',
539
+ status: 'draft' as const,
540
+ }
541
+
542
+ const result = await semanticVerify(analysis, projectPath)
543
+
544
+ const antiPatternCheck = result.checks.find(
545
+ (c: SemanticCheckResult) => c.name === 'Anti-pattern file verification'
546
+ )
547
+ expect(antiPatternCheck?.passed).toBe(false)
548
+ expect(antiPatternCheck?.error).toContain('not found')
549
+
550
+ await cleanupTestProject(projectPath)
551
+ })
552
+
553
+ it('should skip checks when no data to verify', async () => {
554
+ const projectPath = await createTestProject({
555
+ hasPackageJson: true,
556
+ })
557
+
558
+ const analysis = {
559
+ projectId: 'test',
560
+ languages: [], // No languages declared
561
+ frameworks: [], // No frameworks declared
562
+ configFiles: [],
563
+ fileCount: 1,
564
+ patterns: [], // No patterns with locations
565
+ antiPatterns: [], // No anti-patterns
566
+ analyzedAt: '2026-02-10T00:00:00.000Z',
567
+ status: 'draft' as const,
568
+ }
569
+
570
+ const result = await semanticVerify(analysis, projectPath)
571
+
572
+ expect(result.passed).toBe(true) // All checks skipped, so passes
573
+ expect(
574
+ result.checks.every((c: SemanticCheckResult) => c.output?.includes('skipped') || c.passed)
575
+ ).toBe(true)
576
+
577
+ await cleanupTestProject(projectPath)
578
+ })
579
+
580
+ it('should accept file count within tolerance (10%)', async () => {
581
+ const projectPath = await createTestProject({
582
+ hasPackageJson: true,
583
+ files: [
584
+ { path: 'src/a.ts', content: '' },
585
+ { path: 'src/b.ts', content: '' },
586
+ { path: 'src/c.ts', content: '' },
587
+ { path: 'src/d.ts', content: '' },
588
+ { path: 'src/e.ts', content: '' },
589
+ ], // 6 files total
590
+ })
591
+
592
+ const analysis = {
593
+ projectId: 'test',
594
+ languages: ['TypeScript'],
595
+ frameworks: [],
596
+ configFiles: [],
597
+ fileCount: 6, // Within 10% tolerance
598
+ patterns: [],
599
+ antiPatterns: [],
600
+ analyzedAt: '2026-02-10T00:00:00.000Z',
601
+ status: 'draft' as const,
602
+ }
603
+
604
+ const result = await semanticVerify(analysis, projectPath)
605
+
606
+ const fileCountCheck = result.checks.find(
607
+ (c: SemanticCheckResult) => c.name === 'File count verification'
608
+ )
609
+ expect(fileCountCheck?.passed).toBe(true)
610
+
611
+ await cleanupTestProject(projectPath)
612
+ })
613
+
614
+ it('should handle partial framework matches (case insensitive)', async () => {
615
+ const projectPath = await createTestProject({
616
+ hasPackageJson: true,
617
+ packageJsonDeps: { '@hono/node-server': '^1.0.0' }, // Hono as part of package name
618
+ })
619
+
620
+ const analysis = {
621
+ projectId: 'test',
622
+ languages: [],
623
+ frameworks: ['Hono'], // Should match @hono/node-server
624
+ configFiles: [],
625
+ fileCount: 1,
626
+ patterns: [],
627
+ antiPatterns: [],
628
+ analyzedAt: '2026-02-10T00:00:00.000Z',
629
+ status: 'draft' as const,
630
+ }
631
+
632
+ const result = await semanticVerify(analysis, projectPath)
633
+
634
+ const frameworkCheck = result.checks.find(
635
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
636
+ )
637
+ expect(frameworkCheck?.passed).toBe(true)
638
+
639
+ await cleanupTestProject(projectPath)
640
+ })
641
+ })