qaa-agent 1.9.1 → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +43 -22
  2. package/CLAUDE.md +170 -9
  3. package/README.md +384 -357
  4. package/VERSION +1 -0
  5. package/agents/qa-pipeline-orchestrator.md +336 -22
  6. package/agents/qaa-analyzer.md +0 -1
  7. package/agents/qaa-bug-detective.md +163 -4
  8. package/agents/qaa-codebase-mapper.md +50 -1
  9. package/agents/qaa-discovery.md +421 -384
  10. package/agents/qaa-e2e-runner.md +163 -1
  11. package/agents/qaa-executor.md +142 -1
  12. package/agents/qaa-planner.md +14 -1
  13. package/agents/qaa-project-researcher.md +194 -0
  14. package/agents/qaa-scanner.md +77 -1
  15. package/agents/qaa-testid-injector.md +0 -1
  16. package/agents/qaa-validator.md +86 -1
  17. package/bin/install.cjs +375 -253
  18. package/bin/lib/context7-cache.cjs +299 -0
  19. package/bin/lib/intent-detector.cjs +488 -0
  20. package/commands/qa-audit.md +255 -126
  21. package/commands/qa-create-test.md +666 -365
  22. package/commands/qa-fix.md +684 -513
  23. package/commands/qa-map.md +283 -139
  24. package/commands/qa-pr.md +63 -0
  25. package/commands/qa-research.md +181 -157
  26. package/commands/qa-start.md +62 -6
  27. package/commands/qa-test-report.md +219 -219
  28. package/frameworks/cypress.json +54 -0
  29. package/frameworks/jest.json +59 -0
  30. package/frameworks/playwright.json +58 -0
  31. package/frameworks/pytest.json +59 -0
  32. package/frameworks/robot-framework.json +54 -0
  33. package/frameworks/selenium.json +65 -0
  34. package/frameworks/vitest.json +57 -0
  35. package/package.json +7 -3
  36. package/workflows/qa-analyze.md +100 -4
  37. package/workflows/qa-from-ticket.md +50 -2
  38. package/workflows/qa-gap.md +50 -2
  39. package/workflows/qa-start.md +819 -33
  40. package/workflows/qa-testid.md +50 -2
  41. 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
+ }