clavix 4.7.0 → 4.8.1
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/dist/cli/commands/execute.js +29 -9
- package/dist/cli/commands/verify.d.ts +28 -0
- package/dist/cli/commands/verify.js +347 -0
- package/dist/core/adapters/amp-adapter.d.ts +3 -0
- package/dist/core/adapters/amp-adapter.js +1 -0
- package/dist/core/adapters/cline-adapter.d.ts +3 -0
- package/dist/core/adapters/cline-adapter.js +1 -0
- package/dist/core/adapters/codebuddy-adapter.d.ts +3 -0
- package/dist/core/adapters/codebuddy-adapter.js +1 -0
- package/dist/core/adapters/codex-adapter.d.ts +3 -0
- package/dist/core/adapters/codex-adapter.js +1 -0
- package/dist/core/adapters/cursor-adapter.d.ts +3 -0
- package/dist/core/adapters/cursor-adapter.js +1 -0
- package/dist/core/adapters/droid-adapter.d.ts +3 -0
- package/dist/core/adapters/droid-adapter.js +1 -0
- package/dist/core/adapters/instructions-generator.js +9 -2
- package/dist/core/adapters/kilocode-adapter.d.ts +3 -0
- package/dist/core/adapters/kilocode-adapter.js +1 -0
- package/dist/core/adapters/opencode-adapter.d.ts +3 -0
- package/dist/core/adapters/opencode-adapter.js +1 -0
- package/dist/core/adapters/roocode-adapter.d.ts +3 -0
- package/dist/core/adapters/roocode-adapter.js +1 -0
- package/dist/core/adapters/windsurf-adapter.d.ts +3 -0
- package/dist/core/adapters/windsurf-adapter.js +1 -0
- package/dist/core/basic-checklist-generator.d.ts +35 -0
- package/dist/core/basic-checklist-generator.js +344 -0
- package/dist/core/checklist-parser.d.ts +48 -0
- package/dist/core/checklist-parser.js +238 -0
- package/dist/core/command-transformer.d.ts +55 -0
- package/dist/core/command-transformer.js +65 -0
- package/dist/core/prompt-manager.d.ts +7 -0
- package/dist/core/prompt-manager.js +47 -22
- package/dist/core/verification-hooks.d.ts +67 -0
- package/dist/core/verification-hooks.js +309 -0
- package/dist/core/verification-manager.d.ts +106 -0
- package/dist/core/verification-manager.js +422 -0
- package/dist/templates/slash-commands/_canonical/execute.md +72 -1
- package/dist/templates/slash-commands/_canonical/verify.md +292 -0
- package/dist/templates/slash-commands/_components/agent-protocols/verification-methods.md +184 -0
- package/dist/types/agent.d.ts +4 -0
- package/dist/types/verification.d.ts +204 -0
- package/dist/types/verification.js +8 -0
- package/dist/utils/template-loader.d.ts +1 -1
- package/dist/utils/template-loader.js +5 -1
- package/package.json +1 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clavix v4.8: Checklist Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses validation checklists, edge cases, and risk sections from
|
|
5
|
+
* deep mode prompt output files.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for parsing checklist sections
|
|
9
|
+
*/
|
|
10
|
+
const PATTERNS = {
|
|
11
|
+
// Section headers (support both ## and ### headers)
|
|
12
|
+
validationSection: /#{2,3}\s*Validation\s+Checklist[\s\S]*?(?=#{2,3}|$)/i,
|
|
13
|
+
edgeCaseSection: /#{2,3}\s*Edge\s+Cases?\s+to\s+Consider[\s\S]*?(?=#{2,3}|$)/i,
|
|
14
|
+
riskSection: /#{2,3}\s*What\s+Could\s+Go\s+Wrong[\s\S]*?(?=#{2,3}|$)/i,
|
|
15
|
+
// Item patterns
|
|
16
|
+
checklistItem: /[☐□○●◯]\s*(.+)$/gm, // Various checkbox characters
|
|
17
|
+
categoryHeader: /\*\*(.+?):\*\*/g,
|
|
18
|
+
edgeCaseItem: /[•\-*]\s*\*\*(.+?)\*\*:?\s*(.+?)$/gm,
|
|
19
|
+
riskItem: /[•\-*]\s*\*\*(.+?)\*\*:?\s*(.+?)$/gm,
|
|
20
|
+
bulletItem: /[•\-*]\s+(.+?)$/gm,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Keywords for determining verification type
|
|
24
|
+
*/
|
|
25
|
+
const VERIFICATION_TYPE_KEYWORDS = {
|
|
26
|
+
automated: [
|
|
27
|
+
'compiles',
|
|
28
|
+
'builds',
|
|
29
|
+
'tests pass',
|
|
30
|
+
'all tests',
|
|
31
|
+
'test coverage',
|
|
32
|
+
'lint',
|
|
33
|
+
'typecheck',
|
|
34
|
+
'no errors',
|
|
35
|
+
'exit code',
|
|
36
|
+
'npm test',
|
|
37
|
+
'npm run',
|
|
38
|
+
'build succeeds',
|
|
39
|
+
'build passes',
|
|
40
|
+
],
|
|
41
|
+
semiAutomated: [
|
|
42
|
+
'renders',
|
|
43
|
+
'displays',
|
|
44
|
+
'console errors',
|
|
45
|
+
'console warnings',
|
|
46
|
+
'no warnings',
|
|
47
|
+
'ui renders',
|
|
48
|
+
'responsive',
|
|
49
|
+
'screen sizes',
|
|
50
|
+
'visual',
|
|
51
|
+
'layout',
|
|
52
|
+
],
|
|
53
|
+
// Everything else defaults to manual
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Parse checklist items from prompt content
|
|
57
|
+
*/
|
|
58
|
+
export class ChecklistParser {
|
|
59
|
+
/**
|
|
60
|
+
* Parse all checklist sections from prompt content
|
|
61
|
+
*/
|
|
62
|
+
parse(content) {
|
|
63
|
+
const validationItems = this.parseValidationSection(content);
|
|
64
|
+
const edgeCases = this.parseEdgeCaseSection(content);
|
|
65
|
+
const risks = this.parseRiskSection(content);
|
|
66
|
+
const totalItems = validationItems.length + edgeCases.length + risks.length;
|
|
67
|
+
return {
|
|
68
|
+
validationItems,
|
|
69
|
+
edgeCases,
|
|
70
|
+
risks,
|
|
71
|
+
hasChecklist: totalItems > 0,
|
|
72
|
+
totalItems,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse the Validation Checklist section
|
|
77
|
+
*/
|
|
78
|
+
parseValidationSection(content) {
|
|
79
|
+
const sectionMatch = content.match(PATTERNS.validationSection);
|
|
80
|
+
if (!sectionMatch)
|
|
81
|
+
return [];
|
|
82
|
+
const section = sectionMatch[0];
|
|
83
|
+
const items = [];
|
|
84
|
+
let currentGroup;
|
|
85
|
+
let itemIndex = 0;
|
|
86
|
+
// Split by lines and process
|
|
87
|
+
const lines = section.split('\n');
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
// Check for category header
|
|
90
|
+
const categoryMatch = line.match(/\*\*(.+?):\*\*/);
|
|
91
|
+
if (categoryMatch) {
|
|
92
|
+
currentGroup = categoryMatch[1].trim();
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Check for checklist item (Unicode checkbox or markdown checkbox)
|
|
96
|
+
const itemMatch = line.match(/(?:[☐□○●◯]|-\s*\[\s*\])\s*(.+)$/);
|
|
97
|
+
if (itemMatch) {
|
|
98
|
+
const itemContent = itemMatch[1].trim();
|
|
99
|
+
const verificationType = this.detectVerificationType(itemContent);
|
|
100
|
+
items.push({
|
|
101
|
+
id: `validation-${++itemIndex}`,
|
|
102
|
+
category: 'validation',
|
|
103
|
+
content: itemContent,
|
|
104
|
+
group: currentGroup,
|
|
105
|
+
verificationType,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return items;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Parse the Edge Cases to Consider section
|
|
113
|
+
*/
|
|
114
|
+
parseEdgeCaseSection(content) {
|
|
115
|
+
const sectionMatch = content.match(PATTERNS.edgeCaseSection);
|
|
116
|
+
if (!sectionMatch)
|
|
117
|
+
return [];
|
|
118
|
+
const section = sectionMatch[0];
|
|
119
|
+
const items = [];
|
|
120
|
+
let itemIndex = 0;
|
|
121
|
+
// Match edge case items with scenario and consideration
|
|
122
|
+
const regex = /[•\-*]\s*\*\*(.+?)\*\*:?\s*(.+?)$/gm;
|
|
123
|
+
let match;
|
|
124
|
+
while ((match = regex.exec(section)) !== null) {
|
|
125
|
+
const scenario = match[1].trim();
|
|
126
|
+
const consideration = match[2].trim();
|
|
127
|
+
const itemContent = `${scenario}: ${consideration}`;
|
|
128
|
+
items.push({
|
|
129
|
+
id: `edge-case-${++itemIndex}`,
|
|
130
|
+
category: 'edge-case',
|
|
131
|
+
content: itemContent,
|
|
132
|
+
group: scenario,
|
|
133
|
+
verificationType: 'manual', // Edge cases are typically manual
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Also try to match simple bullet items without bold scenario
|
|
137
|
+
if (items.length === 0) {
|
|
138
|
+
const simpleRegex = /[•\-*]\s+([^*\n]+)$/gm;
|
|
139
|
+
while ((match = simpleRegex.exec(section)) !== null) {
|
|
140
|
+
const itemContent = match[1].trim();
|
|
141
|
+
if (itemContent && !itemContent.startsWith('#')) {
|
|
142
|
+
items.push({
|
|
143
|
+
id: `edge-case-${++itemIndex}`,
|
|
144
|
+
category: 'edge-case',
|
|
145
|
+
content: itemContent,
|
|
146
|
+
verificationType: 'manual',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return items;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parse the What Could Go Wrong section
|
|
155
|
+
*/
|
|
156
|
+
parseRiskSection(content) {
|
|
157
|
+
const sectionMatch = content.match(PATTERNS.riskSection);
|
|
158
|
+
if (!sectionMatch)
|
|
159
|
+
return [];
|
|
160
|
+
const section = sectionMatch[0];
|
|
161
|
+
const items = [];
|
|
162
|
+
let itemIndex = 0;
|
|
163
|
+
// Match risk items with bold titles
|
|
164
|
+
const regex = /[•\-*]\s*\*\*(.+?)\*\*:?\s*(.*)$/gm;
|
|
165
|
+
let match;
|
|
166
|
+
while ((match = regex.exec(section)) !== null) {
|
|
167
|
+
const risk = match[1].trim();
|
|
168
|
+
const details = match[2]?.trim() || '';
|
|
169
|
+
const itemContent = details ? `${risk}: ${details}` : risk;
|
|
170
|
+
items.push({
|
|
171
|
+
id: `risk-${++itemIndex}`,
|
|
172
|
+
category: 'risk',
|
|
173
|
+
content: itemContent,
|
|
174
|
+
group: risk,
|
|
175
|
+
verificationType: 'manual', // Risks are typically manual verification
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Also try to match simple bullet items
|
|
179
|
+
if (items.length === 0) {
|
|
180
|
+
const simpleRegex = /[•\-*]\s+([^*\n]+)$/gm;
|
|
181
|
+
while ((match = simpleRegex.exec(section)) !== null) {
|
|
182
|
+
const itemContent = match[1].trim();
|
|
183
|
+
if (itemContent && !itemContent.startsWith('#')) {
|
|
184
|
+
items.push({
|
|
185
|
+
id: `risk-${++itemIndex}`,
|
|
186
|
+
category: 'risk',
|
|
187
|
+
content: itemContent,
|
|
188
|
+
verificationType: 'manual',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return items;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Detect verification type based on item content
|
|
197
|
+
*/
|
|
198
|
+
detectVerificationType(content) {
|
|
199
|
+
const lowerContent = content.toLowerCase();
|
|
200
|
+
// Check for automated keywords
|
|
201
|
+
for (const keyword of VERIFICATION_TYPE_KEYWORDS.automated) {
|
|
202
|
+
if (lowerContent.includes(keyword)) {
|
|
203
|
+
return 'automated';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Check for semi-automated keywords
|
|
207
|
+
for (const keyword of VERIFICATION_TYPE_KEYWORDS.semiAutomated) {
|
|
208
|
+
if (lowerContent.includes(keyword)) {
|
|
209
|
+
return 'semi-automated';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Default to manual
|
|
213
|
+
return 'manual';
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get a summary of the parsed checklist
|
|
217
|
+
*/
|
|
218
|
+
getSummary(checklist) {
|
|
219
|
+
const allItems = [...checklist.validationItems, ...checklist.edgeCases, ...checklist.risks];
|
|
220
|
+
return {
|
|
221
|
+
validation: checklist.validationItems.length,
|
|
222
|
+
edgeCases: checklist.edgeCases.length,
|
|
223
|
+
risks: checklist.risks.length,
|
|
224
|
+
automated: allItems.filter((i) => i.verificationType === 'automated').length,
|
|
225
|
+
semiAutomated: allItems.filter((i) => i.verificationType === 'semi-automated').length,
|
|
226
|
+
manual: allItems.filter((i) => i.verificationType === 'manual').length,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if content has any checklist sections
|
|
231
|
+
*/
|
|
232
|
+
hasChecklist(content) {
|
|
233
|
+
return (PATTERNS.validationSection.test(content) ||
|
|
234
|
+
PATTERNS.edgeCaseSection.test(content) ||
|
|
235
|
+
PATTERNS.riskSection.test(content));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=checklist-parser.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { IntegrationFeatures } from '../types/agent.js';
|
|
2
|
+
/**
|
|
3
|
+
* CommandTransformer - Transforms slash command references in template content
|
|
4
|
+
*
|
|
5
|
+
* Handles conversion between command formats:
|
|
6
|
+
* - Colon format: /clavix:fast (Claude Code style - uses subdirectories)
|
|
7
|
+
* - Hyphen format: /clavix-fast (Cursor, Droid style - flat files)
|
|
8
|
+
*
|
|
9
|
+
* Preserves CLI commands (clavix prompts list) unchanged - only transforms
|
|
10
|
+
* slash commands that start with /clavix:
|
|
11
|
+
*
|
|
12
|
+
* @since v4.8.1
|
|
13
|
+
*/
|
|
14
|
+
export declare class CommandTransformer {
|
|
15
|
+
/**
|
|
16
|
+
* Matches /clavix:commandname pattern
|
|
17
|
+
* Supports hyphenated commands like task-complete
|
|
18
|
+
* Does NOT match CLI usage (no leading slash)
|
|
19
|
+
*/
|
|
20
|
+
private static readonly SLASH_COMMAND_PATTERN;
|
|
21
|
+
/** Default command format (canonical/Claude Code style) */
|
|
22
|
+
private static readonly DEFAULT_SEPARATOR;
|
|
23
|
+
/**
|
|
24
|
+
* Transform slash command references in content based on adapter's command format
|
|
25
|
+
*
|
|
26
|
+
* @param content - Template content with canonical /clavix:command references
|
|
27
|
+
* @param features - Adapter's integration features (may include commandFormat)
|
|
28
|
+
* @returns Transformed content with correct command format
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // For Cursor/Droid (hyphen format):
|
|
32
|
+
* transform('/clavix:fast', { commandFormat: { separator: '-' } })
|
|
33
|
+
* // Returns: '/clavix-fast'
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // For Claude Code (colon format, default):
|
|
37
|
+
* transform('/clavix:fast', { commandFormat: { separator: ':' } })
|
|
38
|
+
* // Returns: '/clavix:fast' (unchanged)
|
|
39
|
+
*/
|
|
40
|
+
static transform(content: string, features?: IntegrationFeatures): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the formatted command name for a specific adapter
|
|
43
|
+
* Useful for generating documentation or references
|
|
44
|
+
*
|
|
45
|
+
* @param commandName - Base command name (e.g., 'fast', 'execute', 'task-complete')
|
|
46
|
+
* @param features - Adapter's integration features
|
|
47
|
+
* @returns Formatted slash command (e.g., '/clavix:fast' or '/clavix-fast')
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* formatCommand('execute', { commandFormat: { separator: '-' } })
|
|
51
|
+
* // Returns: '/clavix-execute'
|
|
52
|
+
*/
|
|
53
|
+
static formatCommand(commandName: string, features?: IntegrationFeatures): string;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=command-transformer.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommandTransformer - Transforms slash command references in template content
|
|
3
|
+
*
|
|
4
|
+
* Handles conversion between command formats:
|
|
5
|
+
* - Colon format: /clavix:fast (Claude Code style - uses subdirectories)
|
|
6
|
+
* - Hyphen format: /clavix-fast (Cursor, Droid style - flat files)
|
|
7
|
+
*
|
|
8
|
+
* Preserves CLI commands (clavix prompts list) unchanged - only transforms
|
|
9
|
+
* slash commands that start with /clavix:
|
|
10
|
+
*
|
|
11
|
+
* @since v4.8.1
|
|
12
|
+
*/
|
|
13
|
+
export class CommandTransformer {
|
|
14
|
+
/**
|
|
15
|
+
* Matches /clavix:commandname pattern
|
|
16
|
+
* Supports hyphenated commands like task-complete
|
|
17
|
+
* Does NOT match CLI usage (no leading slash)
|
|
18
|
+
*/
|
|
19
|
+
static SLASH_COMMAND_PATTERN = /\/clavix:(\w+(?:-\w+)*)/g;
|
|
20
|
+
/** Default command format (canonical/Claude Code style) */
|
|
21
|
+
static DEFAULT_SEPARATOR = ':';
|
|
22
|
+
/**
|
|
23
|
+
* Transform slash command references in content based on adapter's command format
|
|
24
|
+
*
|
|
25
|
+
* @param content - Template content with canonical /clavix:command references
|
|
26
|
+
* @param features - Adapter's integration features (may include commandFormat)
|
|
27
|
+
* @returns Transformed content with correct command format
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // For Cursor/Droid (hyphen format):
|
|
31
|
+
* transform('/clavix:fast', { commandFormat: { separator: '-' } })
|
|
32
|
+
* // Returns: '/clavix-fast'
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // For Claude Code (colon format, default):
|
|
36
|
+
* transform('/clavix:fast', { commandFormat: { separator: ':' } })
|
|
37
|
+
* // Returns: '/clavix:fast' (unchanged)
|
|
38
|
+
*/
|
|
39
|
+
static transform(content, features) {
|
|
40
|
+
const separator = features?.commandFormat?.separator ?? this.DEFAULT_SEPARATOR;
|
|
41
|
+
// If using canonical format (colon), no transformation needed
|
|
42
|
+
if (separator === ':') {
|
|
43
|
+
return content;
|
|
44
|
+
}
|
|
45
|
+
// Transform /clavix:command to /clavix-command (or other separator)
|
|
46
|
+
return content.replace(this.SLASH_COMMAND_PATTERN, `/clavix${separator}$1`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the formatted command name for a specific adapter
|
|
50
|
+
* Useful for generating documentation or references
|
|
51
|
+
*
|
|
52
|
+
* @param commandName - Base command name (e.g., 'fast', 'execute', 'task-complete')
|
|
53
|
+
* @param features - Adapter's integration features
|
|
54
|
+
* @returns Formatted slash command (e.g., '/clavix:fast' or '/clavix-fast')
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* formatCommand('execute', { commandFormat: { separator: '-' } })
|
|
58
|
+
* // Returns: '/clavix-execute'
|
|
59
|
+
*/
|
|
60
|
+
static formatCommand(commandName, features) {
|
|
61
|
+
const separator = features?.commandFormat?.separator ?? this.DEFAULT_SEPARATOR;
|
|
62
|
+
return `/clavix${separator}${commandName}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=command-transformer.js.map
|
|
@@ -11,6 +11,9 @@ export interface PromptMetadata {
|
|
|
11
11
|
executedAt: string | null;
|
|
12
12
|
ageInDays?: number;
|
|
13
13
|
linkedProject?: string;
|
|
14
|
+
verificationRequired: boolean;
|
|
15
|
+
verified: boolean;
|
|
16
|
+
verifiedAt: string | null;
|
|
14
17
|
}
|
|
15
18
|
export interface PromptsIndex {
|
|
16
19
|
version: string;
|
|
@@ -67,6 +70,10 @@ export declare class PromptManager {
|
|
|
67
70
|
* Mark prompt as executed
|
|
68
71
|
*/
|
|
69
72
|
markExecuted(id: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Mark prompt as verified (v4.8)
|
|
75
|
+
*/
|
|
76
|
+
markVerified(id: string): Promise<void>;
|
|
70
77
|
/**
|
|
71
78
|
* Delete prompts by filter
|
|
72
79
|
*/
|
|
@@ -56,6 +56,10 @@ export class PromptManager {
|
|
|
56
56
|
executed: false,
|
|
57
57
|
executedAt: null,
|
|
58
58
|
linkedProject,
|
|
59
|
+
// Verification tracking (v4.8)
|
|
60
|
+
verificationRequired: true,
|
|
61
|
+
verified: false,
|
|
62
|
+
verifiedAt: null,
|
|
59
63
|
};
|
|
60
64
|
// Create file with frontmatter
|
|
61
65
|
const frontmatter = [
|
|
@@ -66,9 +70,13 @@ export class PromptManager {
|
|
|
66
70
|
`executed: ${metadata.executed}`,
|
|
67
71
|
`originalPrompt: ${originalPrompt}`,
|
|
68
72
|
linkedProject ? `linkedProject: ${linkedProject}` : '',
|
|
73
|
+
`verificationRequired: ${metadata.verificationRequired}`,
|
|
74
|
+
`verified: ${metadata.verified}`,
|
|
69
75
|
'---',
|
|
70
76
|
'',
|
|
71
|
-
]
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join('\n');
|
|
72
80
|
const fileContent = frontmatter + content;
|
|
73
81
|
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
74
82
|
// Update index
|
|
@@ -80,12 +88,12 @@ export class PromptManager {
|
|
|
80
88
|
*/
|
|
81
89
|
async loadPrompt(id) {
|
|
82
90
|
const index = await this.loadIndex();
|
|
83
|
-
const metadata = index.prompts.find(p => p.id === id);
|
|
91
|
+
const metadata = index.prompts.find((p) => p.id === id);
|
|
84
92
|
if (!metadata) {
|
|
85
93
|
return null;
|
|
86
94
|
}
|
|
87
95
|
const filePath = path.join(this.promptsDir, metadata.source, metadata.filename);
|
|
88
|
-
if (!await fs.pathExists(filePath)) {
|
|
96
|
+
if (!(await fs.pathExists(filePath))) {
|
|
89
97
|
return null;
|
|
90
98
|
}
|
|
91
99
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
@@ -105,24 +113,24 @@ export class PromptManager {
|
|
|
105
113
|
// Ensure index exists when filtering by source (for corruption recovery tests)
|
|
106
114
|
if (filters?.source) {
|
|
107
115
|
const indexPath = this.getIndexPath(filters.source);
|
|
108
|
-
if (!await fs.pathExists(indexPath)) {
|
|
116
|
+
if (!(await fs.pathExists(indexPath))) {
|
|
109
117
|
await this.saveIndex({ version: '1.0', prompts: [] }, filters.source);
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
// Apply filters
|
|
113
121
|
if (filters) {
|
|
114
122
|
if (filters.executed !== undefined) {
|
|
115
|
-
prompts = prompts.filter(p => p.executed === filters.executed);
|
|
123
|
+
prompts = prompts.filter((p) => p.executed === filters.executed);
|
|
116
124
|
}
|
|
117
125
|
if (filters.stale) {
|
|
118
|
-
prompts = prompts.filter(p => this.getPromptAge(p) > 30);
|
|
126
|
+
prompts = prompts.filter((p) => this.getPromptAge(p) > 30);
|
|
119
127
|
}
|
|
120
128
|
if (filters.old) {
|
|
121
|
-
prompts = prompts.filter(p => this.getPromptAge(p) > 7);
|
|
129
|
+
prompts = prompts.filter((p) => this.getPromptAge(p) > 7);
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
// Add age calculation
|
|
125
|
-
prompts = prompts.map(p => ({
|
|
133
|
+
prompts = prompts.map((p) => ({
|
|
126
134
|
...p,
|
|
127
135
|
createdAt: new Date(p.timestamp),
|
|
128
136
|
ageInDays: this.getPromptAge(p),
|
|
@@ -139,19 +147,38 @@ export class PromptManager {
|
|
|
139
147
|
async markExecuted(id) {
|
|
140
148
|
// Load all indexes to find the prompt
|
|
141
149
|
const allPrompts = await this.listPrompts();
|
|
142
|
-
const prompt = allPrompts.find(p => p.id === id);
|
|
150
|
+
const prompt = allPrompts.find((p) => p.id === id);
|
|
143
151
|
if (!prompt) {
|
|
144
152
|
throw new Error(`Prompt not found: ${id}`);
|
|
145
153
|
}
|
|
146
154
|
// Load source-specific index
|
|
147
155
|
const index = await this.loadIndex(prompt.source);
|
|
148
|
-
const indexPrompt = index.prompts.find(p => p.id === id);
|
|
156
|
+
const indexPrompt = index.prompts.find((p) => p.id === id);
|
|
149
157
|
if (indexPrompt) {
|
|
150
158
|
indexPrompt.executed = true;
|
|
151
159
|
indexPrompt.executedAt = new Date().toISOString();
|
|
152
160
|
await this.saveIndex(index, prompt.source);
|
|
153
161
|
}
|
|
154
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Mark prompt as verified (v4.8)
|
|
165
|
+
*/
|
|
166
|
+
async markVerified(id) {
|
|
167
|
+
// Load all indexes to find the prompt
|
|
168
|
+
const allPrompts = await this.listPrompts();
|
|
169
|
+
const prompt = allPrompts.find((p) => p.id === id);
|
|
170
|
+
if (!prompt) {
|
|
171
|
+
throw new Error(`Prompt not found: ${id}`);
|
|
172
|
+
}
|
|
173
|
+
// Load source-specific index
|
|
174
|
+
const index = await this.loadIndex(prompt.source);
|
|
175
|
+
const indexPrompt = index.prompts.find((p) => p.id === id);
|
|
176
|
+
if (indexPrompt) {
|
|
177
|
+
indexPrompt.verified = true;
|
|
178
|
+
indexPrompt.verifiedAt = new Date().toISOString();
|
|
179
|
+
await this.saveIndex(index, prompt.source);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
155
182
|
/**
|
|
156
183
|
* Delete prompts by filter
|
|
157
184
|
*/
|
|
@@ -174,7 +201,7 @@ export class PromptManager {
|
|
|
174
201
|
// Update each source index
|
|
175
202
|
for (const [source, deletedIds] of bySource.entries()) {
|
|
176
203
|
const index = await this.loadIndex(source);
|
|
177
|
-
index.prompts = index.prompts.filter(p => !deletedIds.has(p.id));
|
|
204
|
+
index.prompts = index.prompts.filter((p) => !deletedIds.has(p.id));
|
|
178
205
|
await this.saveIndex(index, source);
|
|
179
206
|
}
|
|
180
207
|
return deleteCount;
|
|
@@ -201,15 +228,13 @@ export class PromptManager {
|
|
|
201
228
|
const allPrompts = await this.listPrompts();
|
|
202
229
|
const stats = {
|
|
203
230
|
totalPrompts: allPrompts.length,
|
|
204
|
-
fastPrompts: allPrompts.filter(p => p.source === 'fast').length,
|
|
205
|
-
deepPrompts: allPrompts.filter(p => p.source === 'deep').length,
|
|
206
|
-
executedPrompts: allPrompts.filter(p => p.executed).length,
|
|
207
|
-
pendingPrompts: allPrompts.filter(p => !p.executed).length,
|
|
208
|
-
stalePrompts: allPrompts.filter(p => (p.ageInDays || 0) > 30).length,
|
|
209
|
-
oldPrompts: allPrompts.filter(p => (p.ageInDays || 0) > 7).length,
|
|
210
|
-
oldestPromptAge: allPrompts.length > 0
|
|
211
|
-
? Math.max(...allPrompts.map(p => p.ageInDays || 0))
|
|
212
|
-
: 0,
|
|
231
|
+
fastPrompts: allPrompts.filter((p) => p.source === 'fast').length,
|
|
232
|
+
deepPrompts: allPrompts.filter((p) => p.source === 'deep').length,
|
|
233
|
+
executedPrompts: allPrompts.filter((p) => p.executed).length,
|
|
234
|
+
pendingPrompts: allPrompts.filter((p) => !p.executed).length,
|
|
235
|
+
stalePrompts: allPrompts.filter((p) => (p.ageInDays || 0) > 30).length,
|
|
236
|
+
oldPrompts: allPrompts.filter((p) => (p.ageInDays || 0) > 7).length,
|
|
237
|
+
oldestPromptAge: allPrompts.length > 0 ? Math.max(...allPrompts.map((p) => p.ageInDays || 0)) : 0,
|
|
213
238
|
};
|
|
214
239
|
return stats;
|
|
215
240
|
}
|
|
@@ -230,7 +255,7 @@ export class PromptManager {
|
|
|
230
255
|
}
|
|
231
256
|
// Load specific source index
|
|
232
257
|
const indexPath = this.getIndexPath(source);
|
|
233
|
-
if (!await fs.pathExists(indexPath)) {
|
|
258
|
+
if (!(await fs.pathExists(indexPath))) {
|
|
234
259
|
return {
|
|
235
260
|
version: '1.0',
|
|
236
261
|
prompts: [],
|
|
@@ -267,7 +292,7 @@ export class PromptManager {
|
|
|
267
292
|
async addToIndex(metadata) {
|
|
268
293
|
const index = await this.loadIndex(metadata.source);
|
|
269
294
|
// Remove any existing entry with same ID (shouldn't happen, but be safe)
|
|
270
|
-
index.prompts = index.prompts.filter(p => p.id !== metadata.id);
|
|
295
|
+
index.prompts = index.prompts.filter((p) => p.id !== metadata.id);
|
|
271
296
|
// Add new entry
|
|
272
297
|
index.prompts.push(metadata);
|
|
273
298
|
await this.saveIndex(index, metadata.source);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clavix v4.8: Verification Hooks
|
|
3
|
+
*
|
|
4
|
+
* Detects and executes CLI hooks for automated verification of checklist items.
|
|
5
|
+
* Supports npm, yarn, and pnpm package managers.
|
|
6
|
+
*/
|
|
7
|
+
import { VerificationHook, HookResult, HookType, DetectedHooks } from '../types/verification.js';
|
|
8
|
+
/**
|
|
9
|
+
* Verification Hooks Manager
|
|
10
|
+
*/
|
|
11
|
+
export declare class VerificationHooks {
|
|
12
|
+
private readonly cwd;
|
|
13
|
+
private detectedHooks;
|
|
14
|
+
constructor(cwd?: string);
|
|
15
|
+
/**
|
|
16
|
+
* Detect available hooks in the project
|
|
17
|
+
*/
|
|
18
|
+
detectHooks(): Promise<DetectedHooks>;
|
|
19
|
+
/**
|
|
20
|
+
* Detect package manager used in the project
|
|
21
|
+
*/
|
|
22
|
+
private detectPackageManager;
|
|
23
|
+
/**
|
|
24
|
+
* Create a hook definition
|
|
25
|
+
*/
|
|
26
|
+
private createHook;
|
|
27
|
+
/**
|
|
28
|
+
* Run a specific hook
|
|
29
|
+
*/
|
|
30
|
+
runHook(hook: VerificationHook): Promise<HookResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Run all detected hooks
|
|
33
|
+
*/
|
|
34
|
+
runAllHooks(): Promise<HookResult[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Run a hook by type
|
|
37
|
+
*/
|
|
38
|
+
runHookByType(type: HookType): Promise<HookResult | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Execute a command and capture output
|
|
41
|
+
*/
|
|
42
|
+
private executeCommand;
|
|
43
|
+
/**
|
|
44
|
+
* Determine if the hook succeeded
|
|
45
|
+
*/
|
|
46
|
+
private determineSuccess;
|
|
47
|
+
/**
|
|
48
|
+
* Determine confidence level in the result
|
|
49
|
+
*/
|
|
50
|
+
private determineConfidence;
|
|
51
|
+
/**
|
|
52
|
+
* Get hook by type
|
|
53
|
+
*/
|
|
54
|
+
getHook(type: HookType): Promise<VerificationHook | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if a hook type is available
|
|
57
|
+
*/
|
|
58
|
+
hasHook(type: HookType): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Get summary of available hooks
|
|
61
|
+
*/
|
|
62
|
+
getHookSummary(): Promise<{
|
|
63
|
+
available: HookType[];
|
|
64
|
+
unavailable: HookType[];
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=verification-hooks.d.ts.map
|