prjct-cli 1.19.0 → 1.21.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 +126 -2
- package/README.md +49 -0
- package/core/__tests__/storage/analysis-storage.test.ts +364 -0
- package/core/__tests__/utils/retry.test.ts +381 -0
- package/core/agentic/tool-registry.ts +40 -12
- package/core/commands/analysis.ts +96 -1
- package/core/commands/commands.ts +1 -1
- package/core/index.ts +5 -1
- package/core/schemas/analysis.ts +441 -0
- package/core/services/agent-generator.ts +35 -8
- package/core/services/agent-service.ts +17 -12
- package/core/storage/analysis-storage.ts +46 -1
- package/core/utils/retry.ts +318 -0
- package/dist/bin/prjct.mjs +1307 -655
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,135 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.
|
|
3
|
+
## [1.21.0] - 2026-02-10
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add semantic verification for analysis results (PRJ-270) (#163)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.20.0] - 2026-02-10
|
|
4
11
|
|
|
5
12
|
### Features
|
|
6
13
|
|
|
7
|
-
-
|
|
14
|
+
- add retry with exponential backoff for agent and tool operations (#162)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [1.20.0] - 2026-02-09
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **Semantic verification for analysis results** (PRJ-270): Validate analysis consistency before sealing
|
|
22
|
+
- Framework verification: checks frameworks exist in package.json dependencies (case-insensitive matching)
|
|
23
|
+
- Language verification: validates languages match actual file extensions (.ts → TypeScript)
|
|
24
|
+
- Pattern location verification: confirms pattern files exist in project
|
|
25
|
+
- File count verification: validates count accuracy (within 10% tolerance)
|
|
26
|
+
- Anti-pattern file verification: ensures anti-pattern files exist when referenced
|
|
27
|
+
- CLI command: `prjct verify --semantic` with human-readable and JSON output
|
|
28
|
+
- Parallel execution of all 5 checks using Promise.all() (~100-200ms for typical projects)
|
|
29
|
+
- Comprehensive test suite with 10 new test cases covering all scenarios (28 tests total, 63 assertions)
|
|
30
|
+
|
|
31
|
+
- **Retry with exponential backoff for agent and tool operations** (PRJ-271): Comprehensive retry infrastructure with error classification and circuit breaker
|
|
32
|
+
- RetryPolicy utility with configurable attempts, delays, and exponential backoff (1s→2s→4s)
|
|
33
|
+
- Automatic error classification: transient (EBUSY, EAGAIN, ETIMEDOUT) vs permanent (ENOENT, EPERM)
|
|
34
|
+
- Circuit breaker protection: opens after 5 consecutive failures, auto-closes after 60s
|
|
35
|
+
- Agent initialization retries (3 attempts with 1s base delay)
|
|
36
|
+
- Tool operations retry (Read/Write/Bash with 2 attempts)
|
|
37
|
+
- Resilient parallel agent generation using Promise.allSettled()
|
|
38
|
+
|
|
39
|
+
### Implementation Details
|
|
40
|
+
|
|
41
|
+
#### Semantic Verification (PRJ-270)
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
**Modified modules:**
|
|
46
|
+
- `core/schemas/analysis.ts` — Added 5 verification functions, 1 orchestrator, 2 helpers, and 2 interfaces (SemanticCheckResult, SemanticVerificationReport)
|
|
47
|
+
- `core/storage/analysis-storage.ts` — Added semanticVerify() method to AnalysisStorage class
|
|
48
|
+
- `core/commands/analysis.ts` — Extended verify() with --semantic flag and added semanticVerify() helper
|
|
49
|
+
- `core/commands/commands.ts` — Updated verify() signature to include semantic?: boolean
|
|
50
|
+
- `core/index.ts` — Updated verify command handler to pass semantic flag
|
|
51
|
+
- `core/__tests__/storage/analysis-storage.test.ts` — Added 10 comprehensive test cases (+345 lines)
|
|
52
|
+
- `README.md` — Added analysis verification documentation section
|
|
53
|
+
- `CHANGELOG.md` — Documented semantic verification feature
|
|
54
|
+
|
|
55
|
+
**Verification functions:**
|
|
56
|
+
1. `verifyFrameworks()` — Checks frameworks exist in package.json (case-insensitive partial matching)
|
|
57
|
+
2. `verifyLanguages()` — Validates languages match file extensions (.ts → TypeScript)
|
|
58
|
+
3. `verifyPatternLocations()` — Confirms pattern files exist in project
|
|
59
|
+
4. `verifyFileCount()` — Validates count accuracy (10% tolerance for temporary files/caches)
|
|
60
|
+
5. `verifyAntiPatternFiles()` — Ensures anti-pattern files exist when referenced
|
|
61
|
+
|
|
62
|
+
**Helper functions:**
|
|
63
|
+
- `getProjectExtensions()` — Recursively scans project for file extensions (ignores node_modules, .git, dist, build, .next, .turbo, coverage)
|
|
64
|
+
- `countProjectFiles()` — Counts all files in project with same ignore patterns
|
|
65
|
+
|
|
66
|
+
**Key features:**
|
|
67
|
+
- Parallel execution using Promise.all() for performance (~100-200ms typical)
|
|
68
|
+
- Skip conditions for null/undefined/empty arrays (prevents false failures)
|
|
69
|
+
- 10% tolerance for file count (accounts for temporary files, caches)
|
|
70
|
+
- Case-insensitive partial matching for frameworks
|
|
71
|
+
- Returns SemanticVerificationReport following VerificationReport pattern from sync-verifier
|
|
72
|
+
- Zero breaking changes: all 18 existing tests pass
|
|
73
|
+
|
|
74
|
+
#### Retry Infrastructure (PRJ-271)
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
**New modules:**
|
|
79
|
+
- `core/utils/retry.ts` (320 lines) — Core retry infrastructure with RetryPolicy class, error classification, circuit breaker
|
|
80
|
+
- `core/__tests__/utils/retry.test.ts` (380 lines) — 21 comprehensive tests with 53 assertions
|
|
81
|
+
- `ACCEPTANCE-PRJ-271.md` — Full acceptance criteria verification (22 criteria verified)
|
|
82
|
+
|
|
83
|
+
**Modified modules:**
|
|
84
|
+
- `core/services/agent-service.ts` — Wrapped initialize() with retry policy (3 attempts)
|
|
85
|
+
- `core/agentic/tool-registry.ts` — Added retry to Read/Write/Bash tools (2 attempts each)
|
|
86
|
+
- `core/services/agent-generator.ts` — Changed to Promise.allSettled() with per-agent retry
|
|
87
|
+
|
|
88
|
+
**Key features:**
|
|
89
|
+
- Exponential backoff: 1s, 2s, 4s (configurable base/max)
|
|
90
|
+
- Error classification: automatic transient vs permanent detection
|
|
91
|
+
- Circuit breaker: per-operation tracking, 5 failure threshold, 60s cooldown
|
|
92
|
+
- Two default policies: defaultAgentRetryPolicy (3 attempts), defaultToolRetryPolicy (2 attempts)
|
|
93
|
+
- Zero breaking changes: all 968 existing tests pass
|
|
94
|
+
|
|
95
|
+
### Learnings
|
|
96
|
+
|
|
97
|
+
- **RetryPolicy pattern:** Wrapping operations with retry execution provides clean separation of retry logic from business logic
|
|
98
|
+
- **Error classification strategies:** Using error code sets (EBUSY, EAGAIN) for transient vs (ENOENT, EPERM) for permanent enables automatic decision-making
|
|
99
|
+
- **Promise.allSettled() for resilient parallel operations:** Prevents one failure from blocking other operations, enables partial success
|
|
100
|
+
- **Circuit breaker implementation:** Per-operation state tracking prevents cascading failures while allowing recovery
|
|
101
|
+
|
|
102
|
+
### Test Plan
|
|
103
|
+
|
|
104
|
+
#### For QA
|
|
105
|
+
|
|
106
|
+
1. **Agent Initialization Retry**
|
|
107
|
+
- Temporarily make file system busy during agent initialization
|
|
108
|
+
- Verify agent initialization retries up to 3 times
|
|
109
|
+
- Confirm permanent errors (unsupported agent) fail immediately
|
|
110
|
+
|
|
111
|
+
2. **Tool Operations Retry**
|
|
112
|
+
- Test Read/Write/Bash with transient errors (EBUSY, ETIMEDOUT)
|
|
113
|
+
- Verify operations retry automatically (2 attempts)
|
|
114
|
+
- Confirm permanent errors (ENOENT, EPERM) return null/false without retry
|
|
115
|
+
|
|
116
|
+
3. **Circuit Breaker**
|
|
117
|
+
- Trigger 5 consecutive failures on same operation
|
|
118
|
+
- Verify circuit breaker opens and blocks further attempts
|
|
119
|
+
- Wait 60 seconds and verify circuit closes automatically
|
|
120
|
+
|
|
121
|
+
4. **Parallel Agent Generation**
|
|
122
|
+
- Simulate one agent generation failure during sync
|
|
123
|
+
- Verify other agents generate successfully (Promise.allSettled behavior)
|
|
124
|
+
- Check logs for failure warnings
|
|
125
|
+
|
|
126
|
+
#### For Users
|
|
127
|
+
|
|
128
|
+
**What changed:** The system is now more resilient against transient failures. Operations like agent initialization, file reads/writes, and command execution will automatically retry when they encounter temporary errors (disk busy, timeouts, etc).
|
|
129
|
+
|
|
130
|
+
**How to use:** No action required - retry logic works automatically. Users will experience fewer random failures during normal operations.
|
|
8
131
|
|
|
132
|
+
**Breaking changes:** None. All changes are backward compatible. Existing tests (968 total) all pass.
|
|
9
133
|
|
|
10
134
|
## [1.19.0] - 2026-02-09
|
|
11
135
|
|
package/README.md
CHANGED
|
@@ -128,10 +128,59 @@ All agents share the same project storage, so you can switch between them freely
|
|
|
128
128
|
```bash
|
|
129
129
|
prjct start # First-time setup (Claude/Gemini)
|
|
130
130
|
prjct init # Initialize project (+ Cursor setup)
|
|
131
|
+
prjct sync # Analyze project and generate context
|
|
132
|
+
prjct verify # Verify analysis integrity (cryptographic)
|
|
133
|
+
prjct verify --semantic # Verify analysis consistency (semantic)
|
|
131
134
|
prjct --version # Show version + provider status
|
|
132
135
|
prjct --help # Show help
|
|
133
136
|
```
|
|
134
137
|
|
|
138
|
+
### Analysis Verification
|
|
139
|
+
|
|
140
|
+
prjct provides two types of analysis verification to ensure data integrity and logical consistency:
|
|
141
|
+
|
|
142
|
+
#### Cryptographic Verification (Default)
|
|
143
|
+
```bash
|
|
144
|
+
prjct verify [--json]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Verifies the integrity of sealed analysis results using cryptographic signatures. This ensures:
|
|
148
|
+
- Analysis data hasn't been tampered with
|
|
149
|
+
- Sealed analysis matches the original analysis
|
|
150
|
+
- Hash signatures are valid
|
|
151
|
+
|
|
152
|
+
**When to use:** After sealing an analysis (`prjct seal`) to confirm data integrity.
|
|
153
|
+
|
|
154
|
+
#### Semantic Verification (PRJ-270)
|
|
155
|
+
```bash
|
|
156
|
+
prjct verify --semantic [--json]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Validates that analysis results match the actual project state. This checks:
|
|
160
|
+
- ✓ **Frameworks** exist in `package.json` dependencies
|
|
161
|
+
- ✓ **Languages** match actual file extensions (.ts → TypeScript)
|
|
162
|
+
- ✓ **Pattern locations** reference real files in the project
|
|
163
|
+
- ✓ **File count** is accurate (within 10% tolerance)
|
|
164
|
+
- ✓ **Anti-pattern files** exist when referenced
|
|
165
|
+
|
|
166
|
+
**When to use:** Before sealing an analysis to catch logical inconsistencies or after project changes to validate analysis accuracy.
|
|
167
|
+
|
|
168
|
+
**Example output:**
|
|
169
|
+
```
|
|
170
|
+
Semantic Verification Report
|
|
171
|
+
────────────────────────────
|
|
172
|
+
✓ Framework verification: passed (2 frameworks validated)
|
|
173
|
+
✓ Language verification: passed (1 language validated)
|
|
174
|
+
✓ Pattern locations: passed (12 patterns verified)
|
|
175
|
+
✓ File count verification: passed (324 files, within tolerance)
|
|
176
|
+
✓ Anti-pattern files: passed (3 anti-patterns verified)
|
|
177
|
+
|
|
178
|
+
Result: PASSED (5/5 checks)
|
|
179
|
+
Total time: 145ms
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Both verification modes support `--json` flag for programmatic use.
|
|
183
|
+
|
|
135
184
|
## Environment Variables
|
|
136
185
|
|
|
137
186
|
### 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
|
+
})
|