prjct-cli 1.20.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 +52 -0
- package/README.md +49 -0
- package/core/__tests__/storage/analysis-storage.test.ts +364 -0
- 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/storage/analysis-storage.ts +46 -1
- package/dist/bin/prjct.mjs +1061 -640
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.21.0] - 2026-02-10
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add semantic verification for analysis results (PRJ-270) (#163)
|
|
8
|
+
|
|
9
|
+
|
|
3
10
|
## [1.20.0] - 2026-02-10
|
|
4
11
|
|
|
5
12
|
### Features
|
|
@@ -11,6 +18,16 @@
|
|
|
11
18
|
|
|
12
19
|
### Features
|
|
13
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
|
+
|
|
14
31
|
- **Retry with exponential backoff for agent and tool operations** (PRJ-271): Comprehensive retry infrastructure with error classification and circuit breaker
|
|
15
32
|
- RetryPolicy utility with configurable attempts, delays, and exponential backoff (1s→2s→4s)
|
|
16
33
|
- Automatic error classification: transient (EBUSY, EAGAIN, ETIMEDOUT) vs permanent (ENOENT, EPERM)
|
|
@@ -21,6 +38,41 @@
|
|
|
21
38
|
|
|
22
39
|
### Implementation Details
|
|
23
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
|
+
|
|
24
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.
|
|
25
77
|
|
|
26
78
|
**New modules:**
|
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
|
+
})
|
|
@@ -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
|
}
|
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: () =>
|
|
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),
|