@qatonic_innovations/qaios 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 QATONIC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ # QAIOS Project Memory
2
+
3
+ > Auto-generated by `qaios init`. Hand-edit this file as your project grows —
4
+ > QAIOS reads it before every skill invocation and uses it to bias test design,
5
+ > writing, and classification toward your conventions.
6
+
7
+ ## Project Overview
8
+
9
+ {{projectName}}
10
+
11
+ <!-- Replace with a few sentences about what this app does, who uses it, and
12
+ what "working correctly" means at a high level. -->
13
+
14
+ ## Stack
15
+
16
+ - **Frontend:** {{stack}}
17
+ - **Backend:** <add your backend>
18
+ - **Database:** <add your database>
19
+ - **Test framework:** {{testFramework}}
20
+
21
+ ## Test Conventions
22
+
23
+ - **Test directory:** `{{testDir}}/`
24
+ - **Spec naming:** `<feature>.spec.ts`
25
+ - **Locator preference:** `data-testid` > `role` > `text`
26
+ - **Avoid:** CSS class selectors (volatile), absolute XPath, locators based on
27
+ generated styles or transient classes
28
+ - **Test-id attribute:** Playwright's `getByTestId()` resolves `data-testid` by
29
+ default. If your app uses a different attribute (e.g. `data-test`, `data-qa`),
30
+ set `testing.testIdAttribute` in `.qaios/config.yaml` so generated specs find
31
+ your elements — otherwise every `getByTestId` locator times out.
32
+
33
+ ## Risk Hotspots
34
+
35
+ <!-- Modules / flows that warrant heavy testing. QAIOS escalates risk on
36
+ anything touching these areas. Examples below; replace with yours. -->
37
+
38
+ - Authentication flow (PII, lockout logic)
39
+ - Payment processing (idempotency, webhooks)
40
+ - File uploads (size limits, MIME validation)
41
+
42
+ ## Domain Oracles
43
+
44
+ <!-- What "correct" means for your specific app. Concrete, observable
45
+ post-conditions QAIOS can assert against. -->
46
+
47
+ - A successful login: redirect to `/dashboard`, session cookie set, user email
48
+ visible in nav
49
+ - A successful payment: order in `confirmed` state, email sent, audit trail
50
+ entry written
51
+
52
+ ## Test Data
53
+
54
+ - **Seed script:** `<path/to/seed-script>`
55
+ - **Test users:** see `<fixtures path>`
56
+ - **PII rule:** never use real emails — always `*@qaios.test`
57
+
58
+ ## CI Context
59
+
60
+ - **Runs on:** <e.g. GitHub Actions, ubuntu-latest>
61
+ - **Env file:** `<path/to/staging.env>`
62
+ - **Quarantine policy:** flaky tests auto-skip after 3 consecutive failures,
63
+ re-evaluated weekly
64
+
65
+ ## Known Flakiness
66
+
67
+ <!-- Document specific known-flaky tests or routes. QAIOS uses this to bias
68
+ classification away from labelling these as real failures. -->
69
+
70
+ - _(none yet — add here as patterns emerge)_
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // axe-runner.mjs — deterministic accessibility scan for `qaios a11y`.
3
+ //
4
+ // Runs in the USER's project (cwd) so it can dynamic-import their
5
+ // installed `@axe-core/playwright` + `playwright` packages — QAIOS
6
+ // bundles neither. Reads inputs from env vars:
7
+ // QAIOS_AXE_URL — target URL (required)
8
+ // QAIOS_AXE_OUTPUT — output path for the findings JSON (required)
9
+ // QAIOS_AXE_TIMEOUT_MS — navigation timeout (default 30_000)
10
+ // QAIOS_AXE_TAGS — comma-separated axe tags (default wcag2a,wcag2aa)
11
+ // QAIOS_AXE_WAIT_UNTIL — load|domcontentloaded|networkidle (default 'domcontentloaded')
12
+ //
13
+ // Emits axe-core's findings (violations + incomplete, each tagged with a
14
+ // `kind`: 'violation' | 'incomplete' | 'best_practice') as the
15
+ // deterministic source of truth. Exit codes mirror capture-page.mjs:
16
+ // 2 = missing env vars
17
+ // 3 = @axe-core/playwright or playwright not installed
18
+ // 4 = navigation / scan error
19
+ // 0 = success (even when violations were found)
20
+
21
+ import { writeFileSync } from 'node:fs';
22
+
23
+ const url = process.env.QAIOS_AXE_URL;
24
+ const out = process.env.QAIOS_AXE_OUTPUT;
25
+ const timeoutMs = Number.parseInt(process.env.QAIOS_AXE_TIMEOUT_MS ?? '30000', 10);
26
+ const waitUntil = process.env.QAIOS_AXE_WAIT_UNTIL ?? 'domcontentloaded';
27
+ const tags = (process.env.QAIOS_AXE_TAGS ?? 'wcag2a,wcag2aa')
28
+ .split(',')
29
+ .map((t) => t.trim())
30
+ .filter((t) => t.length > 0);
31
+
32
+ if (!url || !out) {
33
+ process.stderr.write('axe-runner: QAIOS_AXE_URL and QAIOS_AXE_OUTPUT env vars are required\n');
34
+ process.exit(2);
35
+ }
36
+
37
+ let chromium, AxeBuilder;
38
+ try {
39
+ ({ chromium } = await import('playwright'));
40
+ ({ default: AxeBuilder } = await import('@axe-core/playwright'));
41
+ } catch (err) {
42
+ process.stderr.write(
43
+ `axe-runner: failed to load 'playwright' and '@axe-core/playwright' from the project — ` +
44
+ `install them with \`npm i -D @axe-core/playwright\` (which pulls playwright). ` +
45
+ `${err?.message ?? err}\n`,
46
+ );
47
+ process.exit(3);
48
+ }
49
+
50
+ const browser = await chromium.launch({ headless: true });
51
+ try {
52
+ const context = await browser.newContext();
53
+ const page = await context.newPage();
54
+ await page.goto(url, { timeout: timeoutMs, waitUntil });
55
+
56
+ const builder = new AxeBuilder({ page });
57
+ if (tags.length > 0) builder.withTags(tags);
58
+ const results = await builder.analyze();
59
+
60
+ // axe buckets its findings (https://github.com/dequelabs/axe-core docs):
61
+ // violations — undeniable WCAG defects (must fix).
62
+ // incomplete — needs manual review (axe couldn't be certain, e.g.
63
+ // contrast behind a background image).
64
+ // A rule is a "best practice" (recommendation, not a WCAG break) when
65
+ // its tags include 'best-practice'. We tag each finding with `kind` so
66
+ // the QAIOS side can gate on real violations only and label the rest.
67
+ const isBestPractice = (v) => Array.isArray(v.tags) && v.tags.includes('best-practice');
68
+ const tag = (arr, fallbackKind) =>
69
+ (arr ?? []).map((v) => ({ ...v, kind: isBestPractice(v) ? 'best_practice' : fallbackKind }));
70
+
71
+ const findings = [
72
+ ...tag(results.violations, 'violation'),
73
+ ...tag(results.incomplete, 'incomplete'),
74
+ ];
75
+
76
+ writeFileSync(out, JSON.stringify({ url, findings }), 'utf-8');
77
+ } catch (err) {
78
+ process.stderr.write(`axe-runner: ${err?.message ?? err}\n`);
79
+ process.exit(4);
80
+ } finally {
81
+ await browser.close();
82
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ // capture-page.mjs — Playwright headless page capture for qaios fix.
3
+ //
4
+ // Runs in the USER's project (cwd) so it can dynamic-import their
5
+ // installed `playwright` package. Reads inputs from env vars:
6
+ // QAIOS_CAPTURE_URL — target URL (required)
7
+ // QAIOS_CAPTURE_OUTPUT — output path for {url, html, accessibilityTree} JSON (required)
8
+ // QAIOS_CAPTURE_TIMEOUT_MS — navigation timeout (default 10_000)
9
+ // QAIOS_CAPTURE_WAIT_UNTIL — load|domcontentloaded|networkidle (default 'domcontentloaded')
10
+ //
11
+ // Emits the full HTML; truncation to 50KB happens on the QAIOS side
12
+ // via truncateHtmlSafe so the script stays decoupled from the budget.
13
+
14
+ import { writeFileSync } from 'node:fs';
15
+
16
+ const url = process.env.QAIOS_CAPTURE_URL;
17
+ const out = process.env.QAIOS_CAPTURE_OUTPUT;
18
+ const timeoutMs = Number.parseInt(process.env.QAIOS_CAPTURE_TIMEOUT_MS ?? '10000', 10);
19
+ const waitUntil = process.env.QAIOS_CAPTURE_WAIT_UNTIL ?? 'domcontentloaded';
20
+
21
+ if (!url || !out) {
22
+ process.stderr.write(
23
+ 'capture-page: QAIOS_CAPTURE_URL and QAIOS_CAPTURE_OUTPUT env vars are required\n',
24
+ );
25
+ process.exit(2);
26
+ }
27
+
28
+ let chromium;
29
+ try {
30
+ ({ chromium } = await import('playwright'));
31
+ } catch (err) {
32
+ process.stderr.write(
33
+ `capture-page: failed to load 'playwright' from project — is it installed? ${err?.message ?? err}\n`,
34
+ );
35
+ process.exit(3);
36
+ }
37
+
38
+ const browser = await chromium.launch({ headless: true });
39
+ try {
40
+ const context = await browser.newContext();
41
+ const page = await context.newPage();
42
+ await page.goto(url, { timeout: timeoutMs, waitUntil });
43
+ const html = await page.content();
44
+ let accessibilityTree = null;
45
+ try {
46
+ accessibilityTree = await page.accessibility.snapshot();
47
+ } catch {
48
+ /* accessibility snapshot is best-effort */
49
+ }
50
+ writeFileSync(out, JSON.stringify({ url, html, accessibilityTree }), 'utf-8');
51
+ } catch (err) {
52
+ process.stderr.write(`capture-page: ${err?.message ?? err}\n`);
53
+ process.exit(4);
54
+ } finally {
55
+ await browser.close();
56
+ }
@@ -0,0 +1,17 @@
1
+ import { Command } from 'commander';
2
+
3
+ type CheckStatus = 'ok' | 'warn' | 'fail' | 'skip';
4
+ interface DoctorCheck {
5
+ name: string;
6
+ status: CheckStatus;
7
+ detail: string;
8
+ }
9
+
10
+ /**
11
+ * Build the Commander program. Exported so tests can introspect the parser
12
+ * without spawning a subprocess.
13
+ */
14
+ declare function buildProgram(): Command;
15
+ declare function formatDoctor(checks: DoctorCheck[]): string;
16
+
17
+ export { buildProgram, formatDoctor };