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 +149 -0
- package/README.md +90 -0
- package/core/__tests__/storage/analysis-storage.test.ts +364 -0
- package/core/__tests__/storage/state-storage-feedback.test.ts +463 -0
- package/core/__tests__/storage/state-storage-history.test.ts +469 -0
- package/core/commands/analysis.ts +96 -1
- package/core/commands/commands.ts +1 -1
- package/core/commands/workflow.ts +5 -2
- package/core/index.ts +5 -1
- package/core/schemas/analysis.ts +441 -0
- package/core/schemas/state.ts +43 -0
- package/core/services/agent-generator.ts +70 -1
- package/core/services/sync-service.ts +115 -4
- package/core/storage/analysis-storage.ts +46 -1
- package/core/storage/state-storage.ts +190 -3
- package/dist/bin/prjct.mjs +1316 -649
- package/package.json +1 -1
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
|
+
})
|