qaa-agent 1.9.2 → 1.9.5
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 +206 -179
- package/CLAUDE.md +718 -557
- package/README.md +384 -357
- package/VERSION +1 -1
- package/agents/qa-pipeline-orchestrator.md +1739 -1425
- package/agents/qaa-analyzer.md +0 -1
- package/agents/qaa-bug-detective.md +790 -655
- package/agents/qaa-codebase-mapper.md +50 -1
- package/agents/qaa-discovery.md +421 -384
- package/agents/qaa-e2e-runner.md +715 -577
- package/agents/qaa-executor.md +947 -830
- package/agents/qaa-planner.md +14 -1
- package/agents/qaa-project-researcher.md +533 -400
- package/agents/qaa-scanner.md +77 -1
- package/agents/qaa-testid-injector.md +0 -1
- package/agents/qaa-validator.md +86 -1
- package/bin/install.cjs +375 -253
- package/bin/lib/context7-cache.cjs +299 -0
- package/bin/lib/intent-detector.cjs +488 -0
- package/commands/qa-audit.md +255 -126
- package/commands/qa-create-test.md +666 -365
- package/commands/qa-fix.md +684 -513
- package/commands/qa-map.md +283 -139
- package/commands/qa-pr.md +63 -0
- package/commands/qa-research.md +181 -157
- package/commands/qa-start.md +62 -6
- package/commands/qa-test-report.md +219 -219
- package/frameworks/cypress.json +54 -0
- package/frameworks/jest.json +59 -0
- package/frameworks/playwright.json +58 -0
- package/frameworks/pytest.json +59 -0
- package/frameworks/robot-framework.json +54 -0
- package/frameworks/selenium.json +65 -0
- package/frameworks/vitest.json +57 -0
- package/package.json +5 -2
- package/workflows/qa-analyze.md +100 -4
- package/workflows/qa-from-ticket.md +50 -2
- package/workflows/qa-gap.md +50 -2
- package/workflows/qa-start.md +2048 -1405
- package/workflows/qa-testid.md +50 -2
- package/workflows/qa-validate.md +50 -2
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context7 Cache — version-specific cache of Context7 query responses.
|
|
3
|
+
*
|
|
4
|
+
* Stores cached responses in `.qa-output/frameworks/<framework>-<version>.json`
|
|
5
|
+
* (project-local). When agents need framework documentation, they check the
|
|
6
|
+
* cache first before querying Context7. This:
|
|
7
|
+
* - Reduces Context7 API calls (free tier: 60/hour)
|
|
8
|
+
* - Speeds up subsequent pipeline runs
|
|
9
|
+
* - Auto-invalidates when project version changes (filename includes version)
|
|
10
|
+
*
|
|
11
|
+
* Cache file format:
|
|
12
|
+
* {
|
|
13
|
+
* "framework": "playwright",
|
|
14
|
+
* "version": "1.40.0",
|
|
15
|
+
* "cached_at": "2026-05-05",
|
|
16
|
+
* "ttl_days": 30,
|
|
17
|
+
* "library_id": "/microsoft/playwright/v1.40.0",
|
|
18
|
+
* "metadata_from_template": { ... }, // copied from frameworks/<name>.json
|
|
19
|
+
* "context7_cache": {
|
|
20
|
+
* "queries": {
|
|
21
|
+
* "<query-key>": {
|
|
22
|
+
* "queried_at": "2026-05-05",
|
|
23
|
+
* "response": "...raw response..."
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* },
|
|
27
|
+
* "version_note": {
|
|
28
|
+
* "project_version": "1.40.0",
|
|
29
|
+
* "latest_stable": "1.50.0",
|
|
30
|
+
* "notable_changes": [...]
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* Cache invalidation triggers:
|
|
35
|
+
* 1. Filename includes version → version change auto-invalidates
|
|
36
|
+
* 2. ttl_days expired → entry needs refresh
|
|
37
|
+
* 3. Force refresh via --refresh-cache flag in commands
|
|
38
|
+
*
|
|
39
|
+
* Usage from bash:
|
|
40
|
+
* node bin/lib/context7-cache.cjs read <framework> <version> <query-key>
|
|
41
|
+
* node bin/lib/context7-cache.cjs write <framework> <version> <query-key> <response>
|
|
42
|
+
* node bin/lib/context7-cache.cjs status <framework> <version>
|
|
43
|
+
* node bin/lib/context7-cache.cjs init <framework> <version> # bootstrap from registry
|
|
44
|
+
*
|
|
45
|
+
* Usage from JS:
|
|
46
|
+
* const cache = require('./context7-cache.cjs');
|
|
47
|
+
* const cached = cache.read('playwright', '1.40.0', 'selector-syntax');
|
|
48
|
+
* if (!cached) {
|
|
49
|
+
* const response = await callContext7(...);
|
|
50
|
+
* cache.write('playwright', '1.40.0', 'selector-syntax', response);
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
'use strict';
|
|
55
|
+
|
|
56
|
+
const fs = require('fs');
|
|
57
|
+
const path = require('path');
|
|
58
|
+
|
|
59
|
+
const DEFAULT_TTL_DAYS = 30;
|
|
60
|
+
|
|
61
|
+
// Where the cache lives (project-local)
|
|
62
|
+
function cacheDir(outputDir) {
|
|
63
|
+
return path.join(outputDir || '.qa-output', 'frameworks');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cachePath(framework, version, outputDir) {
|
|
67
|
+
return path.join(cacheDir(outputDir), `${framework}-${version}.json`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function registryPath(framework) {
|
|
71
|
+
return path.join('frameworks', `${framework}.json`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Date helpers ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function today() {
|
|
77
|
+
return new Date().toISOString().slice(0, 10);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function daysBetween(dateStr) {
|
|
81
|
+
const cached = new Date(dateStr);
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const ms = now - cached;
|
|
84
|
+
return Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Core cache operations ─────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initialize a cache file for (framework, version) if it doesn't exist.
|
|
91
|
+
* Bootstraps with metadata from the registry. Returns the cache object.
|
|
92
|
+
*/
|
|
93
|
+
function init(framework, version, outputDir) {
|
|
94
|
+
const cp = cachePath(framework, version, outputDir);
|
|
95
|
+
if (fs.existsSync(cp)) {
|
|
96
|
+
return JSON.parse(fs.readFileSync(cp, 'utf-8'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Load metadata from registry if available
|
|
100
|
+
let metadataFromTemplate = {};
|
|
101
|
+
const rp = registryPath(framework);
|
|
102
|
+
if (fs.existsSync(rp)) {
|
|
103
|
+
try {
|
|
104
|
+
metadataFromTemplate = JSON.parse(fs.readFileSync(rp, 'utf-8'));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// ignore registry parse errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const libraryId = metadataFromTemplate.context7_keys && metadataFromTemplate.context7_keys[0]
|
|
111
|
+
? `/${metadataFromTemplate.context7_keys[0]}/v${version}`
|
|
112
|
+
: null;
|
|
113
|
+
|
|
114
|
+
const cache = {
|
|
115
|
+
framework,
|
|
116
|
+
version,
|
|
117
|
+
cached_at: today(),
|
|
118
|
+
ttl_days: DEFAULT_TTL_DAYS,
|
|
119
|
+
library_id: libraryId,
|
|
120
|
+
metadata_from_template: metadataFromTemplate,
|
|
121
|
+
context7_cache: {
|
|
122
|
+
queries: {}
|
|
123
|
+
},
|
|
124
|
+
version_note: {
|
|
125
|
+
project_version: version,
|
|
126
|
+
latest_stable: null,
|
|
127
|
+
notable_changes: []
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
fs.mkdirSync(cacheDir(outputDir), { recursive: true });
|
|
132
|
+
fs.writeFileSync(cp, JSON.stringify(cache, null, 2) + '\n', 'utf-8');
|
|
133
|
+
return cache;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Read a cached query response. Returns null if not cached or expired.
|
|
138
|
+
*/
|
|
139
|
+
function read(framework, version, queryKey, outputDir) {
|
|
140
|
+
const cp = cachePath(framework, version, outputDir);
|
|
141
|
+
if (!fs.existsSync(cp)) return null;
|
|
142
|
+
|
|
143
|
+
let cache;
|
|
144
|
+
try {
|
|
145
|
+
cache = JSON.parse(fs.readFileSync(cp, 'utf-8'));
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const entry = cache.context7_cache?.queries?.[queryKey];
|
|
151
|
+
if (!entry) return null;
|
|
152
|
+
|
|
153
|
+
// Check TTL
|
|
154
|
+
const ttl = cache.ttl_days || DEFAULT_TTL_DAYS;
|
|
155
|
+
const age = daysBetween(entry.queried_at || cache.cached_at);
|
|
156
|
+
if (age > ttl) return null; // expired
|
|
157
|
+
|
|
158
|
+
return entry.response;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Write a query response to the cache. Creates the cache file if needed.
|
|
163
|
+
*/
|
|
164
|
+
function write(framework, version, queryKey, response, outputDir) {
|
|
165
|
+
const cp = cachePath(framework, version, outputDir);
|
|
166
|
+
let cache;
|
|
167
|
+
if (fs.existsSync(cp)) {
|
|
168
|
+
cache = JSON.parse(fs.readFileSync(cp, 'utf-8'));
|
|
169
|
+
} else {
|
|
170
|
+
cache = init(framework, version, outputDir);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!cache.context7_cache) cache.context7_cache = { queries: {} };
|
|
174
|
+
if (!cache.context7_cache.queries) cache.context7_cache.queries = {};
|
|
175
|
+
|
|
176
|
+
cache.context7_cache.queries[queryKey] = {
|
|
177
|
+
queried_at: today(),
|
|
178
|
+
response: response
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
fs.mkdirSync(cacheDir(outputDir), { recursive: true });
|
|
182
|
+
fs.writeFileSync(cp, JSON.stringify(cache, null, 2) + '\n', 'utf-8');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get cache freshness/status info for (framework, version).
|
|
187
|
+
*/
|
|
188
|
+
function status(framework, version, outputDir) {
|
|
189
|
+
const cp = cachePath(framework, version, outputDir);
|
|
190
|
+
if (!fs.existsSync(cp)) {
|
|
191
|
+
return { exists: false, framework, version };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const cache = JSON.parse(fs.readFileSync(cp, 'utf-8'));
|
|
195
|
+
const ttl = cache.ttl_days || DEFAULT_TTL_DAYS;
|
|
196
|
+
const age = daysBetween(cache.cached_at);
|
|
197
|
+
const queryKeys = Object.keys(cache.context7_cache?.queries || {});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
exists: true,
|
|
201
|
+
framework,
|
|
202
|
+
version,
|
|
203
|
+
cache_path: cp,
|
|
204
|
+
cached_at: cache.cached_at,
|
|
205
|
+
age_days: age,
|
|
206
|
+
ttl_days: ttl,
|
|
207
|
+
expired: age > ttl,
|
|
208
|
+
library_id: cache.library_id,
|
|
209
|
+
cached_queries: queryKeys,
|
|
210
|
+
cached_query_count: queryKeys.length
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Force refresh: delete the cache file. Next read will trigger Context7 query.
|
|
216
|
+
*/
|
|
217
|
+
function clear(framework, version, outputDir) {
|
|
218
|
+
const cp = cachePath(framework, version, outputDir);
|
|
219
|
+
if (fs.existsSync(cp)) {
|
|
220
|
+
fs.unlinkSync(cp);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── Exports ───────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
module.exports = {
|
|
229
|
+
init,
|
|
230
|
+
read,
|
|
231
|
+
write,
|
|
232
|
+
status,
|
|
233
|
+
clear,
|
|
234
|
+
cachePath,
|
|
235
|
+
cacheDir,
|
|
236
|
+
registryPath,
|
|
237
|
+
DEFAULT_TTL_DAYS
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ── CLI ───────────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
if (require.main === module) {
|
|
243
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
244
|
+
|
|
245
|
+
function usage() {
|
|
246
|
+
console.error('Usage:');
|
|
247
|
+
console.error(' context7-cache.cjs init <framework> <version>');
|
|
248
|
+
console.error(' context7-cache.cjs read <framework> <version> <query-key>');
|
|
249
|
+
console.error(' context7-cache.cjs write <framework> <version> <query-key> <response-text>');
|
|
250
|
+
console.error(' context7-cache.cjs status <framework> <version>');
|
|
251
|
+
console.error(' context7-cache.cjs clear <framework> <version>');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
switch (cmd) {
|
|
257
|
+
case 'init': {
|
|
258
|
+
if (args.length < 2) usage();
|
|
259
|
+
const cache = init(args[0], args[1]);
|
|
260
|
+
console.log(JSON.stringify(cache, null, 2));
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'read': {
|
|
264
|
+
if (args.length < 3) usage();
|
|
265
|
+
const r = read(args[0], args[1], args[2]);
|
|
266
|
+
if (r === null) {
|
|
267
|
+
console.log('CACHE_MISS');
|
|
268
|
+
process.exit(2);
|
|
269
|
+
} else {
|
|
270
|
+
console.log(r);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case 'write': {
|
|
275
|
+
if (args.length < 4) usage();
|
|
276
|
+
const response = args.slice(3).join(' ');
|
|
277
|
+
write(args[0], args[1], args[2], response);
|
|
278
|
+
console.log('OK');
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case 'status': {
|
|
282
|
+
if (args.length < 2) usage();
|
|
283
|
+
console.log(JSON.stringify(status(args[0], args[1]), null, 2));
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case 'clear': {
|
|
287
|
+
if (args.length < 2) usage();
|
|
288
|
+
const ok = clear(args[0], args[1]);
|
|
289
|
+
console.log(ok ? 'CLEARED' : 'NOT_FOUND');
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
default:
|
|
293
|
+
usage();
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
console.error('ERROR:', e.message);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
}
|