tryassay 0.1.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/LICENSE +21 -0
- package/README.md +553 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +80 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/assess.d.ts +6 -0
- package/dist/commands/assess.js +267 -0
- package/dist/commands/assess.js.map +1 -0
- package/dist/commands/describe.d.ts +3 -0
- package/dist/commands/describe.js +114 -0
- package/dist/commands/describe.js.map +1 -0
- package/dist/commands/extract.d.ts +4 -0
- package/dist/commands/extract.js +144 -0
- package/dist/commands/extract.js.map +1 -0
- package/dist/commands/hallucinate.d.ts +3 -0
- package/dist/commands/hallucinate.js +100 -0
- package/dist/commands/hallucinate.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +39 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/regenerate.d.ts +3 -0
- package/dist/commands/regenerate.js +158 -0
- package/dist/commands/regenerate.js.map +1 -0
- package/dist/commands/remediate.d.ts +5 -0
- package/dist/commands/remediate.js +155 -0
- package/dist/commands/remediate.js.map +1 -0
- package/dist/commands/report.d.ts +3 -0
- package/dist/commands/report.js +84 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/reverse.d.ts +9 -0
- package/dist/commands/reverse.js +115 -0
- package/dist/commands/reverse.js.map +1 -0
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +112 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/lib/anthropic.d.ts +13 -0
- package/dist/lib/anthropic.js +60 -0
- package/dist/lib/anthropic.js.map +1 -0
- package/dist/lib/assessment-reporter.d.ts +5 -0
- package/dist/lib/assessment-reporter.js +266 -0
- package/dist/lib/assessment-reporter.js.map +1 -0
- package/dist/lib/claim-extractor.d.ts +6 -0
- package/dist/lib/claim-extractor.js +138 -0
- package/dist/lib/claim-extractor.js.map +1 -0
- package/dist/lib/code-verifier.d.ts +7 -0
- package/dist/lib/code-verifier.js +265 -0
- package/dist/lib/code-verifier.js.map +1 -0
- package/dist/lib/codebase-indexer.d.ts +15 -0
- package/dist/lib/codebase-indexer.js +156 -0
- package/dist/lib/codebase-indexer.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +38 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constraint-engine.d.ts +2 -0
- package/dist/lib/constraint-engine.js +337 -0
- package/dist/lib/constraint-engine.js.map +1 -0
- package/dist/lib/fs-utils.d.ts +1 -0
- package/dist/lib/fs-utils.js +11 -0
- package/dist/lib/fs-utils.js.map +1 -0
- package/dist/lib/guided-generator.d.ts +2 -0
- package/dist/lib/guided-generator.js +195 -0
- package/dist/lib/guided-generator.js.map +1 -0
- package/dist/lib/inventory-extractor.d.ts +7 -0
- package/dist/lib/inventory-extractor.js +238 -0
- package/dist/lib/inventory-extractor.js.map +1 -0
- package/dist/lib/prompts.d.ts +3 -0
- package/dist/lib/prompts.js +50 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/publisher.d.ts +2 -0
- package/dist/lib/publisher.js +71 -0
- package/dist/lib/publisher.js.map +1 -0
- package/dist/lib/remediation-generator.d.ts +2 -0
- package/dist/lib/remediation-generator.js +136 -0
- package/dist/lib/remediation-generator.js.map +1 -0
- package/dist/lib/remediator.d.ts +7 -0
- package/dist/lib/remediator.js +209 -0
- package/dist/lib/remediator.js.map +1 -0
- package/dist/lib/report-generator.d.ts +8 -0
- package/dist/lib/report-generator.js +190 -0
- package/dist/lib/report-generator.js.map +1 -0
- package/dist/lib/requirements-generator.d.ts +14 -0
- package/dist/lib/requirements-generator.js +311 -0
- package/dist/lib/requirements-generator.js.map +1 -0
- package/dist/lib/spec-synthesizer.d.ts +2 -0
- package/dist/lib/spec-synthesizer.js +136 -0
- package/dist/lib/spec-synthesizer.js.map +1 -0
- package/dist/lib/system-prompts.d.ts +12 -0
- package/dist/lib/system-prompts.js +254 -0
- package/dist/lib/system-prompts.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { getClient, MODEL } from './anthropic.js';
|
|
2
|
+
import { readFileContent } from './codebase-indexer.js';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Maximum number of imported files to follow (1 level deep).
|
|
6
|
+
*/
|
|
7
|
+
const MAX_IMPORTS = 10;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum characters to include per imported file.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_IMPORT_CHARS = 3000;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum characters for the main component file.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_COMPONENT_CHARS = 10_000;
|
|
16
|
+
const REQUIREMENT_SYSTEM_PROMPT = `You are a requirements analyst. Given a route definition and the source code for its component (plus any imported modules), generate a structured requirement document.
|
|
17
|
+
|
|
18
|
+
INSTRUCTIONS:
|
|
19
|
+
|
|
20
|
+
1. BEHAVIORS: Identify all observable behaviors the component implements.
|
|
21
|
+
- Data loading patterns (pagination, filtering, sorting, search)
|
|
22
|
+
- User interactions (forms, buttons, modals, dialogs, tabs)
|
|
23
|
+
- Data mutations (create, update, delete operations)
|
|
24
|
+
- Permission/guard checks
|
|
25
|
+
- Error handling and loading states
|
|
26
|
+
- Navigation behavior (links, redirects)
|
|
27
|
+
- Real-time updates (subscriptions, polling)
|
|
28
|
+
|
|
29
|
+
2. SCAFFOLDING FLAGS: Detect any incomplete or placeholder code.
|
|
30
|
+
- TODO / FIXME comments (include the line content)
|
|
31
|
+
- "Coming Soon", "Placeholder", "Mock" text in rendered UI
|
|
32
|
+
- Empty event handlers: onClick={() => {}}, onClick={undefined}, onChange={() => {}}
|
|
33
|
+
- Hardcoded or mock data (arrays of fake data, MOCK_ prefixed constants)
|
|
34
|
+
- console.log("TODO") or console.log("FIXME")
|
|
35
|
+
- Disabled features with no path to enable
|
|
36
|
+
- Stub functions that return null, undefined, or empty objects
|
|
37
|
+
- setTimeout used to fake async operations
|
|
38
|
+
|
|
39
|
+
3. TABLES: Identify database tables referenced (from queries, API calls, hook names).
|
|
40
|
+
|
|
41
|
+
4. ROLES: Identify required roles/permissions (from guards, permission checks, role conditionals).
|
|
42
|
+
|
|
43
|
+
OUTPUT FORMAT:
|
|
44
|
+
Return ONLY a JSON object (no markdown fences, no commentary):
|
|
45
|
+
{
|
|
46
|
+
"behaviors": [
|
|
47
|
+
"Lists all invoices with server-side pagination",
|
|
48
|
+
"Supports search filtering by invoice number and customer name"
|
|
49
|
+
],
|
|
50
|
+
"scaffoldingFlags": [
|
|
51
|
+
"TODO comment at line 45: '// TODO: implement export'",
|
|
52
|
+
"'Coming Soon' text for export feature",
|
|
53
|
+
"Empty onClick handler on 'Generate Report' button"
|
|
54
|
+
],
|
|
55
|
+
"tables": ["invoices", "customers", "payments"],
|
|
56
|
+
"roles": ["admin", "finance_manager"]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Rules:
|
|
60
|
+
- behaviors: Each entry is a single sentence describing one observable behavior. Be specific.
|
|
61
|
+
- scaffoldingFlags: Each entry describes the exact scaffolding issue with location if possible.
|
|
62
|
+
- tables: Database table names only. Derive from query strings, Supabase .from() calls, Prisma model references, API endpoint patterns.
|
|
63
|
+
- roles: Role/permission names. Derive from guard checks, role conditionals, permission constants.
|
|
64
|
+
- If no scaffolding is found, return an empty array (do NOT invent flags).
|
|
65
|
+
- If you cannot determine tables or roles, return empty arrays.
|
|
66
|
+
- Be thorough in behaviors — capture every user-facing interaction.`;
|
|
67
|
+
/**
|
|
68
|
+
* Supported code extensions for import resolution.
|
|
69
|
+
*/
|
|
70
|
+
const RESOLVABLE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
71
|
+
/**
|
|
72
|
+
* Extract relative import paths from source code.
|
|
73
|
+
* Returns raw import specifiers (e.g., './hooks/useInvoices', '@/lib/utils').
|
|
74
|
+
*/
|
|
75
|
+
function extractImportPaths(source) {
|
|
76
|
+
const relative = [];
|
|
77
|
+
const aliased = [];
|
|
78
|
+
// Relative imports
|
|
79
|
+
const relRegex = /import\s+.*?\s+from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = relRegex.exec(source)) !== null) {
|
|
82
|
+
relative.push(match[1]);
|
|
83
|
+
}
|
|
84
|
+
// Alias imports (@/...)
|
|
85
|
+
const aliasRegex = /import\s+.*?\s+from\s+['"]@\/([^'"]+)['"]/g;
|
|
86
|
+
while ((match = aliasRegex.exec(source)) !== null) {
|
|
87
|
+
aliased.push(match[1]);
|
|
88
|
+
}
|
|
89
|
+
return { relative, aliased };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve an import specifier to a file path that exists in the codebase index.
|
|
93
|
+
* Handles missing extensions and index files.
|
|
94
|
+
*/
|
|
95
|
+
function resolveImportPath(specifier, componentDir, fileTree) {
|
|
96
|
+
// Normalize the path relative to project root
|
|
97
|
+
const basePath = specifier.startsWith('@/')
|
|
98
|
+
? `src/${specifier.slice(2)}`
|
|
99
|
+
: resolve(componentDir, specifier).replace(/^\//, '');
|
|
100
|
+
// Try exact match first
|
|
101
|
+
if (fileTree.has(basePath))
|
|
102
|
+
return basePath;
|
|
103
|
+
// Try adding extensions
|
|
104
|
+
for (const ext of RESOLVABLE_EXTENSIONS) {
|
|
105
|
+
if (fileTree.has(basePath + ext))
|
|
106
|
+
return basePath + ext;
|
|
107
|
+
}
|
|
108
|
+
// Try index files (e.g., ./hooks -> ./hooks/index.ts)
|
|
109
|
+
for (const ext of RESOLVABLE_EXTENSIONS) {
|
|
110
|
+
const indexPath = `${basePath}/index${ext}`;
|
|
111
|
+
if (fileTree.has(indexPath))
|
|
112
|
+
return indexPath;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Gather imported file contents for a component (1 level deep).
|
|
118
|
+
* Returns a map of relative path -> file content (truncated).
|
|
119
|
+
*/
|
|
120
|
+
async function gatherImportedFiles(componentPath, componentSource, index) {
|
|
121
|
+
const imports = new Map();
|
|
122
|
+
const fileTreeSet = new Set(index.fileTree);
|
|
123
|
+
const componentDir = dirname(componentPath);
|
|
124
|
+
const { relative, aliased } = extractImportPaths(componentSource);
|
|
125
|
+
// Combine all import specifiers
|
|
126
|
+
const allSpecifiers = [
|
|
127
|
+
...relative,
|
|
128
|
+
...aliased.map((a) => `@/${a}`),
|
|
129
|
+
];
|
|
130
|
+
let count = 0;
|
|
131
|
+
for (const specifier of allSpecifiers) {
|
|
132
|
+
if (count >= MAX_IMPORTS)
|
|
133
|
+
break;
|
|
134
|
+
const resolved = resolveImportPath(specifier, componentDir, fileTreeSet);
|
|
135
|
+
if (!resolved || imports.has(resolved))
|
|
136
|
+
continue;
|
|
137
|
+
const content = await readFileContent(index.rootPath, resolved);
|
|
138
|
+
if (content) {
|
|
139
|
+
const truncated = content.length > MAX_IMPORT_CHARS
|
|
140
|
+
? content.slice(0, MAX_IMPORT_CHARS) + '\n[... truncated]'
|
|
141
|
+
: content;
|
|
142
|
+
imports.set(resolved, truncated);
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return imports;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse the JSON response from Claude, handling code fences and truncation.
|
|
150
|
+
*/
|
|
151
|
+
function parseRequirementResponse(rawText) {
|
|
152
|
+
let jsonText = rawText.trim();
|
|
153
|
+
// Remove opening code fence
|
|
154
|
+
if (jsonText.startsWith('```')) {
|
|
155
|
+
const firstNewline = jsonText.indexOf('\n');
|
|
156
|
+
if (firstNewline !== -1) {
|
|
157
|
+
jsonText = jsonText.slice(firstNewline + 1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Remove closing code fence
|
|
161
|
+
const lastFence = jsonText.lastIndexOf('```');
|
|
162
|
+
if (lastFence !== -1) {
|
|
163
|
+
jsonText = jsonText.slice(0, lastFence);
|
|
164
|
+
}
|
|
165
|
+
jsonText = jsonText.trim();
|
|
166
|
+
// Handle truncation: close unclosed structures
|
|
167
|
+
if (!jsonText.endsWith('}')) {
|
|
168
|
+
const lastCloseBrace = jsonText.lastIndexOf('}');
|
|
169
|
+
if (lastCloseBrace !== -1) {
|
|
170
|
+
jsonText = jsonText.slice(0, lastCloseBrace + 1);
|
|
171
|
+
let openBraces = 0;
|
|
172
|
+
let openBrackets = 0;
|
|
173
|
+
for (const ch of jsonText) {
|
|
174
|
+
if (ch === '{')
|
|
175
|
+
openBraces++;
|
|
176
|
+
if (ch === '}')
|
|
177
|
+
openBraces--;
|
|
178
|
+
if (ch === '[')
|
|
179
|
+
openBrackets++;
|
|
180
|
+
if (ch === ']')
|
|
181
|
+
openBrackets--;
|
|
182
|
+
}
|
|
183
|
+
let suffix = '';
|
|
184
|
+
for (let i = 0; i < openBrackets; i++)
|
|
185
|
+
suffix += ']';
|
|
186
|
+
for (let i = 0; i < openBraces; i++)
|
|
187
|
+
suffix += '}';
|
|
188
|
+
jsonText += suffix;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = JSON.parse(jsonText);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw new Error(`Failed to parse requirement response as JSON.\n` +
|
|
197
|
+
`First 300 chars: ${jsonText.slice(0, 300)}`);
|
|
198
|
+
}
|
|
199
|
+
const obj = parsed;
|
|
200
|
+
return {
|
|
201
|
+
behaviors: Array.isArray(obj.behaviors) ? obj.behaviors.map(String) : [],
|
|
202
|
+
scaffoldingFlags: Array.isArray(obj.scaffoldingFlags)
|
|
203
|
+
? obj.scaffoldingFlags.map(String)
|
|
204
|
+
: [],
|
|
205
|
+
tables: Array.isArray(obj.tables) ? obj.tables.map(String) : [],
|
|
206
|
+
roles: Array.isArray(obj.roles) ? obj.roles.map(String) : [],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Generate a structured requirement for a single route.
|
|
211
|
+
*
|
|
212
|
+
* Reads the route's component file, follows 1 level of imports to gather
|
|
213
|
+
* related code (hooks, utilities), and sends all context to Claude to
|
|
214
|
+
* produce a RouteRequirement with behaviors and scaffolding flags.
|
|
215
|
+
*/
|
|
216
|
+
export async function generateRequirement(route, index, onProgress) {
|
|
217
|
+
const client = getClient();
|
|
218
|
+
// Read the main component file
|
|
219
|
+
onProgress?.(` Reading component: ${route.component}`);
|
|
220
|
+
const componentSource = await readFileContent(index.rootPath, route.component);
|
|
221
|
+
if (!componentSource) {
|
|
222
|
+
// Component file not found -- return a minimal requirement with a flag
|
|
223
|
+
return {
|
|
224
|
+
requirement: {
|
|
225
|
+
id: '',
|
|
226
|
+
route: route.path,
|
|
227
|
+
domain: route.domain,
|
|
228
|
+
component: route.component,
|
|
229
|
+
guard: route.guard,
|
|
230
|
+
roles: route.roles ?? [],
|
|
231
|
+
tables: route.tables ?? [],
|
|
232
|
+
behaviors: [],
|
|
233
|
+
scaffoldingFlags: [`Component file not found: ${route.component}`],
|
|
234
|
+
},
|
|
235
|
+
inputTokens: 0,
|
|
236
|
+
outputTokens: 0,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Truncate very large component files for the prompt
|
|
240
|
+
const componentContent = componentSource.length > MAX_COMPONENT_CHARS
|
|
241
|
+
? componentSource.slice(0, MAX_COMPONENT_CHARS) + '\n[... truncated]'
|
|
242
|
+
: componentSource;
|
|
243
|
+
// Follow 1 level of imports
|
|
244
|
+
onProgress?.(` Resolving imports for ${route.component}...`);
|
|
245
|
+
const importedFiles = await gatherImportedFiles(route.component, componentSource, index);
|
|
246
|
+
// Build imported files context
|
|
247
|
+
let importsContext = '';
|
|
248
|
+
if (importedFiles.size > 0) {
|
|
249
|
+
onProgress?.(` Found ${importedFiles.size} imported file(s)`);
|
|
250
|
+
for (const [path, content] of importedFiles) {
|
|
251
|
+
importsContext += `\n--- IMPORTED: ${path} ---\n${content}\n`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Build the user message
|
|
255
|
+
const userMessage = [
|
|
256
|
+
`Analyze this route and its component code to generate a structured requirement.`,
|
|
257
|
+
``,
|
|
258
|
+
`ROUTE INFORMATION:`,
|
|
259
|
+
`- Path: ${route.path}`,
|
|
260
|
+
`- Component: ${route.component}`,
|
|
261
|
+
`- Domain: ${route.domain}`,
|
|
262
|
+
route.guard ? `- Guard: ${route.guard}` : null,
|
|
263
|
+
route.roles?.length ? `- Known roles: ${route.roles.join(', ')}` : null,
|
|
264
|
+
route.tables?.length ? `- Known tables: ${route.tables.join(', ')}` : null,
|
|
265
|
+
``,
|
|
266
|
+
`COMPONENT SOURCE:`,
|
|
267
|
+
`--- FILE: ${route.component} ---`,
|
|
268
|
+
componentContent,
|
|
269
|
+
importsContext ? `\nIMPORTED MODULES:${importsContext}` : null,
|
|
270
|
+
]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join('\n');
|
|
273
|
+
onProgress?.(` Sending to Claude for requirement generation...`);
|
|
274
|
+
const response = await client.messages.create({
|
|
275
|
+
model: MODEL,
|
|
276
|
+
max_tokens: 4_000,
|
|
277
|
+
system: REQUIREMENT_SYSTEM_PROMPT,
|
|
278
|
+
messages: [
|
|
279
|
+
{
|
|
280
|
+
role: 'user',
|
|
281
|
+
content: userMessage,
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
const rawText = response.content
|
|
286
|
+
.filter((b) => b.type === 'text')
|
|
287
|
+
.map((b) => b.text)
|
|
288
|
+
.join('');
|
|
289
|
+
const parsed = parseRequirementResponse(rawText);
|
|
290
|
+
// Merge Claude's findings with inventory data.
|
|
291
|
+
// Claude may discover additional tables/roles beyond what inventory found.
|
|
292
|
+
const mergedTables = Array.from(new Set([...(route.tables ?? []), ...parsed.tables]));
|
|
293
|
+
const mergedRoles = Array.from(new Set([...(route.roles ?? []), ...parsed.roles]));
|
|
294
|
+
const requirement = {
|
|
295
|
+
id: '', // Caller assigns sequential IDs
|
|
296
|
+
route: route.path,
|
|
297
|
+
domain: route.domain,
|
|
298
|
+
component: route.component,
|
|
299
|
+
guard: route.guard,
|
|
300
|
+
roles: mergedRoles,
|
|
301
|
+
tables: mergedTables,
|
|
302
|
+
behaviors: parsed.behaviors,
|
|
303
|
+
scaffoldingFlags: parsed.scaffoldingFlags,
|
|
304
|
+
};
|
|
305
|
+
return {
|
|
306
|
+
requirement,
|
|
307
|
+
inputTokens: response.usage.input_tokens,
|
|
308
|
+
outputTokens: response.usage.output_tokens,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=requirements-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirements-generator.js","sourceRoot":"","sources":["../../src/lib/requirements-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getClient, MODEL } from './anthropic.js';
|
|
2
|
+
const SYNTHESIS_SYSTEM_PROMPT = `You are a formal specification synthesizer. Given a coding task description and target programming language, you generate a comprehensive set of formal specifications that any correct implementation MUST satisfy.
|
|
3
|
+
|
|
4
|
+
SYNTHESIS RULES:
|
|
5
|
+
|
|
6
|
+
1. Generate 10-30 specifications covering ALL of these categories:
|
|
7
|
+
- correctness: Happy-path behavior, expected outputs for standard inputs
|
|
8
|
+
- edge-case: Boundary values, empty inputs, maximum values, off-by-one scenarios
|
|
9
|
+
- error-handling: Invalid inputs, missing data, exception behavior
|
|
10
|
+
- security: Injection, overflow, unauthorized access, input sanitization
|
|
11
|
+
- type-safety: Type constraints, null/undefined handling, coercion traps
|
|
12
|
+
- performance: Time/space complexity, resource limits, scaling behavior
|
|
13
|
+
|
|
14
|
+
2. Each specification must have:
|
|
15
|
+
- id: Sequential "SPEC-001", "SPEC-002", etc.
|
|
16
|
+
- category: One of correctness, edge-case, error-handling, security, type-safety, performance
|
|
17
|
+
- severity: How bad is it if the code violates this spec?
|
|
18
|
+
- critical: Would be a bug causing incorrect results or crashes
|
|
19
|
+
- high: Significant quality issue, data loss, or security flaw
|
|
20
|
+
- medium: Best practice violation, poor robustness
|
|
21
|
+
- low: Nice-to-have, minor optimization
|
|
22
|
+
- description: Clear statement of what the code must do or guarantee
|
|
23
|
+
- assertion: A specific, testable assertion (pseudocode or natural language test case) that can be verified programmatically against the generated code
|
|
24
|
+
- rationale: Why this specification matters
|
|
25
|
+
|
|
26
|
+
3. Assertions must be SPECIFIC and TESTABLE:
|
|
27
|
+
- GOOD: "sorted([3,1,2]) returns [1,2,3]"
|
|
28
|
+
- GOOD: "rateLimiter(100, '1m').check('user1') returns { allowed: true, remaining: 99 }"
|
|
29
|
+
- GOOD: "Passing null as input throws TypeError with message containing 'expected string'"
|
|
30
|
+
- BAD: "Function works correctly" (too vague)
|
|
31
|
+
- BAD: "Handles errors" (not testable)
|
|
32
|
+
|
|
33
|
+
4. Prioritize CRITICAL specs first, then HIGH, then MEDIUM, then LOW.
|
|
34
|
+
|
|
35
|
+
5. Think adversarially: What inputs would break a naive implementation? What edge cases do developers commonly miss?
|
|
36
|
+
|
|
37
|
+
OUTPUT FORMAT:
|
|
38
|
+
Return ONLY a JSON array of specification objects. No markdown fences, no commentary.`;
|
|
39
|
+
const VALID_CATEGORIES = [
|
|
40
|
+
'correctness', 'security', 'performance', 'error-handling', 'edge-case', 'type-safety',
|
|
41
|
+
];
|
|
42
|
+
const VALID_SEVERITIES = [
|
|
43
|
+
'critical', 'high', 'medium', 'low',
|
|
44
|
+
];
|
|
45
|
+
function validateSpec(raw, index) {
|
|
46
|
+
if (!raw.description || typeof raw.description !== 'string')
|
|
47
|
+
return null;
|
|
48
|
+
if (!raw.assertion || typeof raw.assertion !== 'string')
|
|
49
|
+
return null;
|
|
50
|
+
const category = VALID_CATEGORIES.includes(raw.category)
|
|
51
|
+
? raw.category
|
|
52
|
+
: 'correctness';
|
|
53
|
+
const severity = VALID_SEVERITIES.includes(raw.severity)
|
|
54
|
+
? raw.severity
|
|
55
|
+
: 'medium';
|
|
56
|
+
return {
|
|
57
|
+
id: raw.id || `SPEC-${String(index + 1).padStart(3, '0')}`,
|
|
58
|
+
category,
|
|
59
|
+
severity,
|
|
60
|
+
description: raw.description.trim(),
|
|
61
|
+
assertion: raw.assertion.trim(),
|
|
62
|
+
rationale: typeof raw.rationale === 'string' ? raw.rationale.trim() : 'No rationale provided',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function synthesizeSpecs(task, language, onProgress) {
|
|
66
|
+
const client = getClient();
|
|
67
|
+
onProgress?.('Sending task to Claude for spec synthesis (streaming)...');
|
|
68
|
+
const stream = client.messages.stream({
|
|
69
|
+
model: MODEL,
|
|
70
|
+
max_tokens: 16_000,
|
|
71
|
+
system: SYNTHESIS_SYSTEM_PROMPT,
|
|
72
|
+
messages: [
|
|
73
|
+
{
|
|
74
|
+
role: 'user',
|
|
75
|
+
content: `Generate formal specifications for the following coding task.\n\nLanguage: ${language}\n\nTask:\n${task}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
let rawText = '';
|
|
80
|
+
stream.on('text', (text) => {
|
|
81
|
+
rawText += text;
|
|
82
|
+
});
|
|
83
|
+
const finalMessage = await stream.finalMessage();
|
|
84
|
+
onProgress?.('Parsing synthesis results...');
|
|
85
|
+
// Strip markdown code fences if present
|
|
86
|
+
let jsonText = rawText.trim();
|
|
87
|
+
// Remove opening code fence
|
|
88
|
+
if (jsonText.startsWith('```')) {
|
|
89
|
+
const firstNewline = jsonText.indexOf('\n');
|
|
90
|
+
if (firstNewline !== -1) {
|
|
91
|
+
jsonText = jsonText.slice(firstNewline + 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Remove closing code fence
|
|
95
|
+
const lastFence = jsonText.lastIndexOf('```');
|
|
96
|
+
if (lastFence !== -1) {
|
|
97
|
+
jsonText = jsonText.slice(0, lastFence);
|
|
98
|
+
}
|
|
99
|
+
jsonText = jsonText.trim();
|
|
100
|
+
// If response was truncated (no closing ]), try to repair by closing the array
|
|
101
|
+
if (!jsonText.endsWith(']')) {
|
|
102
|
+
const lastCloseBrace = jsonText.lastIndexOf('}');
|
|
103
|
+
if (lastCloseBrace !== -1) {
|
|
104
|
+
jsonText = jsonText.slice(0, lastCloseBrace + 1) + '\n]';
|
|
105
|
+
onProgress?.('Note: Response was truncated. Recovered partial specs.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(jsonText);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw new Error(`Failed to parse Claude's spec synthesis response as JSON.\n` +
|
|
114
|
+
`First 200 chars: ${jsonText.slice(0, 200)}`);
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(parsed)) {
|
|
117
|
+
throw new Error('Expected a JSON array of specs from Claude.');
|
|
118
|
+
}
|
|
119
|
+
const specs = [];
|
|
120
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
121
|
+
const validated = validateSpec(parsed[i], i);
|
|
122
|
+
if (validated) {
|
|
123
|
+
specs.push(validated);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
task,
|
|
128
|
+
language,
|
|
129
|
+
specs,
|
|
130
|
+
totalSpecs: specs.length,
|
|
131
|
+
synthesizedAt: new Date().toISOString(),
|
|
132
|
+
inputTokens: finalMessage.usage.input_tokens,
|
|
133
|
+
outputTokens: finalMessage.usage.output_tokens,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=spec-synthesizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-synthesizer.js","sourceRoot":"","sources":["../../src/lib/spec-synthesizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sFAoCsD,CAAC;AAWvF,MAAM,gBAAgB,GAAmB;IACvC,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa;CACvF,CAAC;AAEF,MAAM,gBAAgB,GAAoB;IACxC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK;CACpC,CAAC;AAEF,SAAS,YAAY,CAAC,GAAY,EAAE,KAAa;IAC/C,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzE,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAErE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAwB,CAAC;QACtE,CAAC,CAAE,GAAG,CAAC,QAAyB;QAChC,CAAC,CAAC,aAAa,CAAC;IAElB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAyB,CAAC;QACvE,CAAC,CAAE,GAAG,CAAC,QAA0B;QACjC,CAAC,CAAC,QAAQ,CAAC;IAEb,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,QAAQ,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;QAC1D,QAAQ;QACR,QAAQ;QACR,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE;QACnC,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE;QAC/B,SAAS,EAAE,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,uBAAuB;KAC9F,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,QAAgB,EAChB,UAAkC;IAElC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,UAAU,EAAE,CAAC,0DAA0D,CAAC,CAAC;IAEzE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,KAAK;QACZ,UAAU,EAAE,MAAM;QAClB,MAAM,EAAE,uBAAuB;QAC/B,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,8EAA8E,QAAQ,cAAc,IAAI,EAAE;aACpH;SACF;KACF,CAAC,CAAC;IAEH,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACzB,OAAO,IAAI,IAAI,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;IAEjD,UAAU,EAAE,CAAC,8BAA8B,CAAC,CAAC;IAE7C,wCAAwC;IACxC,IAAI,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAE9B,4BAA4B;IAC5B,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACxB,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC9C,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACrB,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1C,CAAC;IAED,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAE3B,+EAA+E;IAC/E,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YACzD,UAAU,EAAE,CAAC,wDAAwD,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC7D,oBAAoB,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC7C,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAY,EAAE,CAAC,CAAC,CAAC;QACxD,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,KAAK;QACL,UAAU,EAAE,KAAK,CAAC,MAAM;QACxB,aAAa,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACvC,WAAW,EAAE,YAAY,CAAC,KAAK,CAAC,YAAY;QAC5C,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,aAAa;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AssayConfig, HallucinationType, VerificationReport, ExtractionResult } from '../types.js';
|
|
2
|
+
export declare function getSystemPrompt(type: HallucinationType, config: AssayConfig): string;
|
|
3
|
+
export declare function getUserPrompt(type: HallucinationType, config: AssayConfig): string;
|
|
4
|
+
export interface RegenerationContext {
|
|
5
|
+
config: AssayConfig;
|
|
6
|
+
type: HallucinationType;
|
|
7
|
+
priorDocument: string;
|
|
8
|
+
extraction: ExtractionResult;
|
|
9
|
+
verification: VerificationReport;
|
|
10
|
+
}
|
|
11
|
+
export declare function getRegenerationSystemPrompt(ctx: RegenerationContext): string;
|
|
12
|
+
export declare function getRegenerationUserPrompt(ctx: RegenerationContext): string;
|