clavix 4.7.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Clavix v4.8: Verification Manager
3
+ *
4
+ * Manages verification state, execution flow, and persistence.
5
+ * Coordinates between checklist parsing, hook execution, and result storage.
6
+ */
7
+ import fs from 'fs-extra';
8
+ import * as path from 'path';
9
+ import { ChecklistParser } from './checklist-parser.js';
10
+ import { VerificationHooks } from './verification-hooks.js';
11
+ import { PromptManager } from './prompt-manager.js';
12
+ /**
13
+ * Verification Manager
14
+ */
15
+ export class VerificationManager {
16
+ promptManager;
17
+ checklistParser;
18
+ verificationHooks;
19
+ outputDir;
20
+ constructor(baseDir) {
21
+ this.outputDir = baseDir || path.join(process.cwd(), '.clavix', 'outputs', 'prompts');
22
+ this.promptManager = new PromptManager(this.outputDir);
23
+ this.checklistParser = new ChecklistParser();
24
+ this.verificationHooks = new VerificationHooks();
25
+ }
26
+ /**
27
+ * Initialize verification for a prompt
28
+ */
29
+ async initializeVerification(promptId) {
30
+ // Load prompt
31
+ const promptData = await this.promptManager.loadPrompt(promptId);
32
+ if (!promptData) {
33
+ throw new Error(`Prompt not found: ${promptId}`);
34
+ }
35
+ // Parse checklist from prompt content
36
+ const checklist = this.checklistParser.parse(promptData.content);
37
+ // Get all items
38
+ const items = [...checklist.validationItems, ...checklist.edgeCases, ...checklist.risks];
39
+ // Detect available hooks
40
+ const detectedHooks = await this.verificationHooks.detectHooks();
41
+ // Create initial report
42
+ const report = {
43
+ version: '1.0',
44
+ promptId,
45
+ source: promptData.metadata.source,
46
+ startedAt: new Date().toISOString(),
47
+ status: items.length > 0 ? 'pending' : 'completed',
48
+ items,
49
+ results: items.map((item) => ({
50
+ itemId: item.id,
51
+ status: 'pending',
52
+ method: item.verificationType === 'automated' ? 'automated' : 'manual',
53
+ confidence: 'low',
54
+ verifiedAt: '',
55
+ })),
56
+ summary: this.calculateSummary([]),
57
+ detectedHooks,
58
+ };
59
+ // Save initial report
60
+ await this.saveReport(report);
61
+ return report;
62
+ }
63
+ /**
64
+ * Get verification report path for a prompt
65
+ */
66
+ getReportPath(promptId, source) {
67
+ return path.join(this.outputDir, source, `${promptId}.verification.json`);
68
+ }
69
+ /**
70
+ * Load verification report
71
+ */
72
+ async loadReport(promptId) {
73
+ // Try both sources
74
+ for (const source of ['deep', 'fast']) {
75
+ const reportPath = this.getReportPath(promptId, source);
76
+ if (await fs.pathExists(reportPath)) {
77
+ try {
78
+ return await fs.readJson(reportPath);
79
+ }
80
+ catch {
81
+ // Corrupt file, ignore
82
+ }
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ /**
88
+ * Save verification report
89
+ */
90
+ async saveReport(report) {
91
+ const reportPath = this.getReportPath(report.promptId, report.source);
92
+ await fs.ensureDir(path.dirname(reportPath));
93
+ await fs.writeJson(reportPath, report, { spaces: 2 });
94
+ }
95
+ /**
96
+ * Mark a single item as verified
97
+ */
98
+ async markItemVerified(promptId, itemId, status, options = {}) {
99
+ let report = await this.loadReport(promptId);
100
+ if (!report) {
101
+ // Initialize if doesn't exist
102
+ report = await this.initializeVerification(promptId);
103
+ }
104
+ // Find and update result
105
+ const resultIndex = report.results.findIndex((r) => r.itemId === itemId);
106
+ if (resultIndex === -1) {
107
+ throw new Error(`Item not found: ${itemId}`);
108
+ }
109
+ const item = report.items.find((i) => i.id === itemId);
110
+ report.results[resultIndex] = {
111
+ itemId,
112
+ status,
113
+ method: options.method || (item?.verificationType === 'automated' ? 'automated' : 'manual'),
114
+ confidence: options.confidence || 'medium',
115
+ evidence: options.evidence,
116
+ reason: options.reason,
117
+ verifiedAt: new Date().toISOString(),
118
+ };
119
+ // Recalculate summary and status
120
+ report.summary = this.calculateSummary(report.results);
121
+ report.status = this.calculateReportStatus(report.results);
122
+ if (report.status === 'completed') {
123
+ report.completedAt = new Date().toISOString();
124
+ }
125
+ await this.saveReport(report);
126
+ return report;
127
+ }
128
+ /**
129
+ * Run automated verification for a prompt
130
+ */
131
+ async runAutomatedVerification(promptId) {
132
+ let report = await this.loadReport(promptId);
133
+ if (!report) {
134
+ report = await this.initializeVerification(promptId);
135
+ }
136
+ // Find automated items that are pending
137
+ const automatedItems = report.items.filter((item) => item.verificationType === 'automated' &&
138
+ report.results.find((r) => r.itemId === item.id)?.status === 'pending');
139
+ if (automatedItems.length === 0) {
140
+ return report;
141
+ }
142
+ // Detect hooks
143
+ const detectedHooks = await this.verificationHooks.detectHooks();
144
+ // Run relevant hooks
145
+ const hookResults = await this.verificationHooks.runAllHooks();
146
+ // Map hook results to checklist items
147
+ for (const item of automatedItems) {
148
+ const lowerContent = item.content.toLowerCase();
149
+ // Match item to hook result
150
+ let matched = false;
151
+ for (const hookResult of hookResults) {
152
+ if (this.matchItemToHook(lowerContent, hookResult.hook.name)) {
153
+ const resultIndex = report.results.findIndex((r) => r.itemId === item.id);
154
+ if (resultIndex !== -1) {
155
+ report.results[resultIndex] = {
156
+ itemId: item.id,
157
+ status: hookResult.success ? 'passed' : 'failed',
158
+ method: 'automated',
159
+ confidence: hookResult.confidence,
160
+ evidence: this.truncateOutput(hookResult.output),
161
+ reason: hookResult.success ? undefined : 'Hook failed',
162
+ verifiedAt: new Date().toISOString(),
163
+ };
164
+ matched = true;
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ // If no hook matched, mark as requiring manual verification
170
+ if (!matched) {
171
+ const resultIndex = report.results.findIndex((r) => r.itemId === item.id);
172
+ if (resultIndex !== -1 && report.results[resultIndex].status === 'pending') {
173
+ report.results[resultIndex].method = 'manual';
174
+ }
175
+ }
176
+ }
177
+ // Update summary and status
178
+ report.summary = this.calculateSummary(report.results);
179
+ report.status = this.calculateReportStatus(report.results);
180
+ report.detectedHooks = detectedHooks;
181
+ await this.saveReport(report);
182
+ return report;
183
+ }
184
+ /**
185
+ * Match checklist item content to hook type
186
+ */
187
+ matchItemToHook(content, hookName) {
188
+ const hookKeywords = {
189
+ test: ['tests pass', 'test pass', 'all tests', 'unit test', 'test coverage'],
190
+ build: ['compiles', 'builds', 'build succeeds', 'no errors', 'runs without errors'],
191
+ lint: ['lint', 'no warnings', 'style guide', 'conventions'],
192
+ typecheck: ['typecheck', 'type check', 'type errors', 'typescript'],
193
+ };
194
+ const keywords = hookKeywords[hookName] || [];
195
+ return keywords.some((kw) => content.includes(kw));
196
+ }
197
+ /**
198
+ * Truncate output for storage
199
+ */
200
+ truncateOutput(output, maxLength = 500) {
201
+ if (output.length <= maxLength) {
202
+ return output;
203
+ }
204
+ return output.substring(0, maxLength) + '... (truncated)';
205
+ }
206
+ /**
207
+ * Calculate summary from results
208
+ */
209
+ calculateSummary(results) {
210
+ const total = results.length;
211
+ const passed = results.filter((r) => r.status === 'passed').length;
212
+ const failed = results.filter((r) => r.status === 'failed').length;
213
+ const skipped = results.filter((r) => r.status === 'skipped').length;
214
+ const notApplicable = results.filter((r) => r.status === 'not-applicable').length;
215
+ const automatedChecks = results.filter((r) => r.method === 'automated').length;
216
+ const manualChecks = results.filter((r) => r.method === 'manual' || r.method === 'semi-automated').length;
217
+ const denominator = total - skipped - notApplicable;
218
+ const coveragePercent = denominator > 0 ? Math.round((passed / denominator) * 100) : 0;
219
+ return {
220
+ total,
221
+ passed,
222
+ failed,
223
+ skipped,
224
+ notApplicable,
225
+ coveragePercent,
226
+ automatedChecks,
227
+ manualChecks,
228
+ };
229
+ }
230
+ /**
231
+ * Calculate overall report status
232
+ */
233
+ calculateReportStatus(results) {
234
+ const pending = results.filter((r) => r.status === 'pending').length;
235
+ const failed = results.filter((r) => r.status === 'failed').length;
236
+ if (pending === results.length) {
237
+ return 'pending';
238
+ }
239
+ if (pending > 0) {
240
+ return 'in-progress';
241
+ }
242
+ if (failed > 0) {
243
+ return 'requires-attention';
244
+ }
245
+ return 'completed';
246
+ }
247
+ /**
248
+ * Get pending items from report
249
+ */
250
+ getPendingItems(report) {
251
+ const pendingIds = new Set(report.results.filter((r) => r.status === 'pending').map((r) => r.itemId));
252
+ return report.items.filter((item) => pendingIds.has(item.id));
253
+ }
254
+ /**
255
+ * Get failed items from report
256
+ */
257
+ getFailedItems(report) {
258
+ return report.results
259
+ .filter((r) => r.status === 'failed')
260
+ .map((result) => ({
261
+ item: report.items.find((i) => i.id === result.itemId),
262
+ result,
263
+ }))
264
+ .filter((r) => r.item);
265
+ }
266
+ /**
267
+ * Check if verification is complete
268
+ */
269
+ isComplete(report) {
270
+ return report.status === 'completed';
271
+ }
272
+ /**
273
+ * Check if verification requires attention (has failures)
274
+ */
275
+ requiresAttention(report) {
276
+ return report.status === 'requires-attention';
277
+ }
278
+ /**
279
+ * Delete verification report
280
+ */
281
+ async deleteReport(promptId) {
282
+ for (const source of ['deep', 'fast']) {
283
+ const reportPath = this.getReportPath(promptId, source);
284
+ if (await fs.pathExists(reportPath)) {
285
+ await fs.remove(reportPath);
286
+ return true;
287
+ }
288
+ }
289
+ return false;
290
+ }
291
+ /**
292
+ * Get all verification reports
293
+ */
294
+ async listReports() {
295
+ const reports = [];
296
+ for (const source of ['deep', 'fast']) {
297
+ const sourceDir = path.join(this.outputDir, source);
298
+ if (await fs.pathExists(sourceDir)) {
299
+ const files = await fs.readdir(sourceDir);
300
+ for (const file of files) {
301
+ if (file.endsWith('.verification.json')) {
302
+ try {
303
+ const report = await fs.readJson(path.join(sourceDir, file));
304
+ reports.push(report);
305
+ }
306
+ catch {
307
+ // Ignore corrupt files
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ return reports;
314
+ }
315
+ /**
316
+ * Get verification status for a prompt
317
+ */
318
+ async getVerificationStatus(promptId) {
319
+ const report = await this.loadReport(promptId);
320
+ if (!report) {
321
+ return {
322
+ hasReport: false,
323
+ status: null,
324
+ summary: null,
325
+ };
326
+ }
327
+ return {
328
+ hasReport: true,
329
+ status: report.status,
330
+ summary: report.summary,
331
+ };
332
+ }
333
+ /**
334
+ * Format verification report for display
335
+ */
336
+ formatReportForDisplay(report) {
337
+ const lines = [];
338
+ const sep = '═'.repeat(70);
339
+ lines.push(sep);
340
+ lines.push(' VERIFICATION REPORT');
341
+ lines.push(` ${report.promptId}`);
342
+ lines.push(sep);
343
+ lines.push('');
344
+ // Group results by category
345
+ const byCategory = new Map();
346
+ for (const item of report.items) {
347
+ const result = report.results.find((r) => r.itemId === item.id);
348
+ if (!result)
349
+ continue;
350
+ const category = item.category;
351
+ if (!byCategory.has(category)) {
352
+ byCategory.set(category, []);
353
+ }
354
+ byCategory.get(category).push({ item, result });
355
+ }
356
+ // Display each category
357
+ for (const [category, items] of byCategory.entries()) {
358
+ const categoryName = category === 'validation'
359
+ ? 'VALIDATION CHECKLIST'
360
+ : category === 'edge-case'
361
+ ? 'EDGE CASES'
362
+ : 'RISKS';
363
+ lines.push(`📋 ${categoryName} (${items.length} items)`);
364
+ lines.push('');
365
+ for (const { item, result } of items) {
366
+ const statusIcon = this.getStatusIcon(result.status);
367
+ const method = result.method === 'automated'
368
+ ? '[automated]'
369
+ : result.method === 'semi-automated'
370
+ ? '[semi-auto]'
371
+ : '[manual]';
372
+ lines.push(`${statusIcon} ${method} ${item.content}`);
373
+ if (result.evidence) {
374
+ lines.push(` Evidence: ${result.evidence.substring(0, 80)}`);
375
+ }
376
+ if (result.status === 'failed' && result.reason) {
377
+ lines.push(` Reason: ${result.reason}`);
378
+ }
379
+ if (result.confidence) {
380
+ lines.push(` Confidence: ${result.confidence.toUpperCase()}`);
381
+ }
382
+ lines.push('');
383
+ }
384
+ }
385
+ // Summary
386
+ lines.push(sep);
387
+ lines.push(' SUMMARY');
388
+ lines.push(sep);
389
+ lines.push(`Total: ${report.summary.total} items`);
390
+ lines.push(`Passed: ${report.summary.passed} (${report.summary.coveragePercent}%)`);
391
+ lines.push(`Failed: ${report.summary.failed}${report.summary.failed > 0 ? ' (requires attention)' : ''}`);
392
+ lines.push(`Skipped: ${report.summary.skipped}`);
393
+ lines.push('');
394
+ lines.push(`Automated: ${report.summary.automatedChecks} checks`);
395
+ lines.push(`Manual: ${report.summary.manualChecks} checks`);
396
+ if (report.summary.failed > 0) {
397
+ lines.push('');
398
+ lines.push(`⚠️ ${report.summary.failed} item(s) require attention before marking complete`);
399
+ }
400
+ lines.push(sep);
401
+ return lines.join('\n');
402
+ }
403
+ /**
404
+ * Get status icon for display
405
+ */
406
+ getStatusIcon(status) {
407
+ switch (status) {
408
+ case 'passed':
409
+ return '✅';
410
+ case 'failed':
411
+ return '❌';
412
+ case 'skipped':
413
+ return '⏭️';
414
+ case 'not-applicable':
415
+ return '➖';
416
+ case 'pending':
417
+ default:
418
+ return '⏳';
419
+ }
420
+ }
421
+ }
422
+ //# sourceMappingURL=verification-manager.js.map
@@ -74,7 +74,78 @@ clavix execute --id <prompt-id>
74
74
  1. Run `clavix execute --latest`
75
75
  2. Read displayed prompt content
76
76
  3. Implement requirements
77
- 4. After completion, cleanup: `clavix prompts clear --executed`
77
+ 4. **REQUIRED: Run verification**: `clavix verify --latest`
78
+ 5. After verification passes, cleanup: `clavix prompts clear --executed`
79
+
80
+ ---
81
+
82
+ ## ⚠️ REQUIRED: Post-Implementation Verification
83
+
84
+ **Verification is a REQUIRED step after implementation.**
85
+
86
+ After implementing the prompt requirements, you MUST verify your implementation against the checklist:
87
+
88
+ ```bash
89
+ clavix verify --latest
90
+ ```
91
+
92
+ ### What Verification Does
93
+
94
+ 1. **Loads the checklist** from your executed prompt (validation items, edge cases, risks)
95
+ 2. **Runs automated hooks** (test, build, lint) for items that can be verified programmatically
96
+ 3. **Guides manual verification** for items requiring human judgment
97
+ 4. **Generates a verification report** with pass/fail status for each item
98
+
99
+ ### Verification Report
100
+
101
+ The verification report shows:
102
+ - ✅ **Passed items** - Implementation covers the requirement
103
+ - ❌ **Failed items** - Implementation does NOT cover (with reason why)
104
+ - ⏭️ **Skipped items** - To be verified later
105
+ - ➖ **N/A items** - Does not apply to this implementation
106
+
107
+ ### Fast Mode Prompts
108
+
109
+ Fast mode prompts don't have comprehensive checklists. When verifying a fast mode prompt:
110
+ - A **basic checklist is generated** based on detected intent
111
+ - Covers essential items (compiles, requirements met, no errors)
112
+ - For comprehensive checklists, use `/clavix:deep` instead
113
+
114
+ ### Verification Commands
115
+
116
+ ```bash
117
+ # Verify latest executed prompt
118
+ clavix verify --latest
119
+
120
+ # Verify specific prompt
121
+ clavix verify --id <prompt-id>
122
+
123
+ # Show verification status
124
+ clavix verify --status
125
+
126
+ # Re-run failed items only
127
+ clavix verify --retry-failed
128
+
129
+ # Export verification report
130
+ clavix verify --export markdown
131
+ clavix verify --export json
132
+ ```
133
+
134
+ ### After Verification
135
+
136
+ **If all items pass:**
137
+ ```bash
138
+ # Cleanup executed prompts
139
+ clavix prompts clear --executed
140
+
141
+ # Or archive the project
142
+ /clavix:archive
143
+ ```
144
+
145
+ **If items fail:**
146
+ 1. Review failed items and reasons
147
+ 2. Fix implementation issues
148
+ 3. Re-run verification: `clavix verify --retry-failed --id <prompt-id>`
78
149
 
79
150
  ---
80
151