@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.
@@ -0,0 +1,182 @@
1
+ -- Initial schema for QAIOS v0.1.
2
+ -- Source of truth: docs/internal/QAIOS_DataSchemas.md §3.
3
+ -- Forward-only; no down-migrations in v0.x.
4
+
5
+ PRAGMA journal_mode = WAL;
6
+ PRAGMA foreign_keys = ON;
7
+
8
+ -- ── workflows ──────────────────────────────────────────────────────
9
+ CREATE TABLE workflows (
10
+ id TEXT PRIMARY KEY, -- ULID
11
+ command TEXT NOT NULL,
12
+ args_json TEXT NOT NULL, -- JSON
13
+ state TEXT NOT NULL,
14
+ mode TEXT NOT NULL CHECK (mode IN ('LITE','FULL','TRUST')),
15
+ status TEXT NOT NULL CHECK (status IN ('running','blocked','succeeded','failed','cancelled')),
16
+ blocked_reason TEXT,
17
+ cost_tokens INTEGER NOT NULL DEFAULT 0,
18
+ cost_usd_cents INTEGER NOT NULL DEFAULT 0,
19
+ created_at TEXT NOT NULL,
20
+ updated_at TEXT NOT NULL,
21
+ finished_at TEXT
22
+ );
23
+
24
+ CREATE INDEX idx_workflows_state ON workflows(state);
25
+ CREATE INDEX idx_workflows_status ON workflows(status);
26
+ CREATE INDEX idx_workflows_created_at ON workflows(created_at);
27
+
28
+ -- ── audit_log (append-only, hash-chained) ──────────────────────────
29
+ CREATE TABLE audit_log (
30
+ id TEXT PRIMARY KEY, -- ULID
31
+ workflow_id TEXT NOT NULL,
32
+ phase TEXT NOT NULL,
33
+ skill_id TEXT,
34
+ event TEXT NOT NULL,
35
+ payload_json TEXT NOT NULL,
36
+ model_call_json TEXT,
37
+ prev_hash TEXT NOT NULL,
38
+ hash TEXT NOT NULL,
39
+ timestamp TEXT NOT NULL,
40
+ actor TEXT NOT NULL CHECK (actor IN ('agent','human','system')),
41
+ actor_id TEXT,
42
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id)
43
+ );
44
+
45
+ CREATE INDEX idx_audit_workflow ON audit_log(workflow_id);
46
+ CREATE INDEX idx_audit_event ON audit_log(event);
47
+ CREATE INDEX idx_audit_timestamp ON audit_log(timestamp);
48
+
49
+ -- Block UPDATE / DELETE on audit_log via triggers (defense in depth)
50
+ CREATE TRIGGER audit_log_no_update
51
+ BEFORE UPDATE ON audit_log
52
+ BEGIN
53
+ SELECT RAISE(ABORT, 'audit_log is append-only');
54
+ END;
55
+
56
+ CREATE TRIGGER audit_log_no_delete
57
+ BEFORE DELETE ON audit_log
58
+ BEGIN
59
+ SELECT RAISE(ABORT, 'audit_log is append-only');
60
+ END;
61
+
62
+ -- ── artifacts ──────────────────────────────────────────────────────
63
+ CREATE TABLE artifacts (
64
+ id TEXT PRIMARY KEY,
65
+ workflow_id TEXT NOT NULL,
66
+ skill_id TEXT NOT NULL,
67
+ type TEXT NOT NULL,
68
+ path TEXT NOT NULL,
69
+ content_hash TEXT NOT NULL,
70
+ confidence REAL NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
71
+ created_at TEXT NOT NULL,
72
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id)
73
+ );
74
+
75
+ CREATE INDEX idx_artifacts_workflow ON artifacts(workflow_id);
76
+ CREATE INDEX idx_artifacts_type ON artifacts(type);
77
+
78
+ -- ── runs ───────────────────────────────────────────────────────────
79
+ CREATE TABLE runs (
80
+ id TEXT PRIMARY KEY,
81
+ workflow_id TEXT NOT NULL,
82
+ test_pattern TEXT,
83
+ status TEXT NOT NULL,
84
+ passed_count INTEGER NOT NULL DEFAULT 0,
85
+ failed_count INTEGER NOT NULL DEFAULT 0,
86
+ flaky_count INTEGER NOT NULL DEFAULT 0,
87
+ skipped_count INTEGER NOT NULL DEFAULT 0,
88
+ duration_ms INTEGER NOT NULL DEFAULT 0,
89
+ artifacts_path TEXT,
90
+ started_at TEXT NOT NULL,
91
+ finished_at TEXT,
92
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id)
93
+ );
94
+
95
+ -- ── test_results (per-test rows within a run) ─────────────────────
96
+ CREATE TABLE test_results (
97
+ id TEXT PRIMARY KEY,
98
+ run_id TEXT NOT NULL,
99
+ test_name TEXT NOT NULL,
100
+ test_file TEXT NOT NULL,
101
+ status TEXT NOT NULL,
102
+ duration_ms INTEGER NOT NULL,
103
+ error_message TEXT,
104
+ classification_json TEXT, -- output of classify.result
105
+ FOREIGN KEY (run_id) REFERENCES runs(id)
106
+ );
107
+
108
+ CREATE INDEX idx_test_results_run ON test_results(run_id);
109
+ CREATE INDEX idx_test_results_status ON test_results(status);
110
+
111
+ -- ── gates ──────────────────────────────────────────────────────────
112
+ CREATE TABLE gates (
113
+ id TEXT PRIMARY KEY,
114
+ workflow_id TEXT NOT NULL,
115
+ skill_id TEXT NOT NULL,
116
+ gate_type TEXT NOT NULL,
117
+ risk_level TEXT NOT NULL,
118
+ confidence REAL NOT NULL,
119
+ required_action TEXT NOT NULL,
120
+ status TEXT NOT NULL CHECK (status IN ('pending','approved','rejected','expired')),
121
+ payload_json TEXT NOT NULL,
122
+ resolved_by TEXT,
123
+ resolution_reason TEXT,
124
+ created_at TEXT NOT NULL,
125
+ resolved_at TEXT
126
+ );
127
+
128
+ CREATE INDEX idx_gates_workflow ON gates(workflow_id);
129
+ CREATE INDEX idx_gates_status ON gates(status);
130
+
131
+ -- ── mcp_servers ────────────────────────────────────────────────────
132
+ CREATE TABLE mcp_servers (
133
+ name TEXT PRIMARY KEY,
134
+ type TEXT NOT NULL CHECK (type IN ('stdio','http','sse')),
135
+ command TEXT,
136
+ url TEXT,
137
+ args_json TEXT, -- JSON array
138
+ env_json TEXT, -- JSON object
139
+ enabled INTEGER NOT NULL DEFAULT 1, -- 0 or 1
140
+ installed_at TEXT NOT NULL
141
+ );
142
+
143
+ -- ── visual_baselines ──────────────────────────────────────────────
144
+ CREATE TABLE visual_baselines (
145
+ id TEXT PRIMARY KEY,
146
+ snapshot_id TEXT NOT NULL,
147
+ name TEXT NOT NULL,
148
+ route TEXT NOT NULL,
149
+ state TEXT,
150
+ viewport TEXT NOT NULL,
151
+ image_path TEXT NOT NULL,
152
+ approved_at TEXT NOT NULL,
153
+ approved_by TEXT,
154
+ workflow_id TEXT NOT NULL,
155
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id)
156
+ );
157
+
158
+ CREATE INDEX idx_baselines_name ON visual_baselines(name, viewport);
159
+
160
+ -- ── visual_diffs (pending review) ─────────────────────────────────
161
+ CREATE TABLE visual_diffs (
162
+ id TEXT PRIMARY KEY,
163
+ workflow_id TEXT NOT NULL,
164
+ baseline_id TEXT NOT NULL,
165
+ current_image_path TEXT NOT NULL,
166
+ diff_image_path TEXT NOT NULL,
167
+ pixels_changed INTEGER NOT NULL,
168
+ percentage_changed REAL NOT NULL,
169
+ classification_json TEXT,
170
+ status TEXT NOT NULL CHECK (status IN ('pending','approved','rejected','auto_resolved')),
171
+ created_at TEXT NOT NULL,
172
+ resolved_at TEXT,
173
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id),
174
+ FOREIGN KEY (baseline_id) REFERENCES visual_baselines(id)
175
+ );
176
+
177
+ -- ── config (key-value) ────────────────────────────────────────────
178
+ CREATE TABLE config (
179
+ key TEXT PRIMARY KEY,
180
+ value_json TEXT NOT NULL,
181
+ updated_at TEXT NOT NULL
182
+ );
@@ -0,0 +1,50 @@
1
+ -- 0002 — enforce one baseline per (name, viewport).
2
+ --
3
+ -- The original schema had only a plain INDEX on (name, viewport), so the
4
+ -- capture `--update` path (which INSERTs rather than upserts) could create
5
+ -- duplicate baselines for the same name+viewport. findByNameViewport() then
6
+ -- returned an arbitrary row, so a check could silently diff against a stale
7
+ -- baseline. This migration removes existing duplicates (keeping the most
8
+ -- recently approved row per name+viewport) and replaces the plain index with
9
+ -- a UNIQUE one so duplicates can never be inserted again.
10
+
11
+ -- 1. Delete older duplicate rows, keeping the one with the newest
12
+ -- approved_at per (name, viewport). Ties broken by id (rowid is not
13
+ -- used since the PK is a TEXT ULID, which is itself time-ordered).
14
+ DELETE FROM visual_baselines
15
+ WHERE id NOT IN (
16
+ SELECT keep_id FROM (
17
+ SELECT id AS keep_id
18
+ FROM visual_baselines b
19
+ WHERE NOT EXISTS (
20
+ SELECT 1 FROM visual_baselines other
21
+ WHERE other.name = b.name
22
+ AND other.viewport = b.viewport
23
+ AND (
24
+ other.approved_at > b.approved_at
25
+ OR (other.approved_at = b.approved_at AND other.id > b.id)
26
+ )
27
+ )
28
+ )
29
+ );
30
+
31
+ -- 2. Replace the non-unique index with a UNIQUE one.
32
+ DROP INDEX IF EXISTS idx_baselines_name;
33
+ CREATE UNIQUE INDEX idx_baselines_name_viewport_unique
34
+ ON visual_baselines(name, viewport);
35
+
36
+ -- 3. Execution lease columns on workflows.
37
+ -- A non-terminal workflow can currently be resumed (or double-run)
38
+ -- concurrently — re-executing side effects (test runs, file writes,
39
+ -- defect filing). These columns let the orchestrator claim an
40
+ -- exclusive, time-bounded lease via an atomic conditional UPDATE so a
41
+ -- second runner is refused while the first is live. NULL = unleased.
42
+ ALTER TABLE workflows ADD COLUMN lease_owner TEXT;
43
+ ALTER TABLE workflows ADD COLUMN lease_expires_at TEXT;
44
+
45
+ -- 4. Persist the failing test's error STACK, not just its message.
46
+ -- The Playwright report parser already extracts the stack, but the
47
+ -- adapter dropped it — so classify.result (whose prompt relies on
48
+ -- stack-trace cues to tell a broken locator from an environmental
49
+ -- failure) never saw it. Add the column so it's stored and forwarded.
50
+ ALTER TABLE test_results ADD COLUMN error_stack TEXT;
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // capture-screenshot.mjs — Playwright headless screenshot capture for
3
+ // W6-T2 (visual baselines). Runs in the user's project so it can
4
+ // dynamic-import their installed `playwright` package.
5
+ //
6
+ // Reads inputs from env vars:
7
+ // QAIOS_SCREENSHOT_URL — full URL to navigate to (required)
8
+ // QAIOS_SCREENSHOT_OUTPUT — file path to write the PNG (required)
9
+ // QAIOS_SCREENSHOT_VIEWPORT_W — viewport width (required, integer)
10
+ // QAIOS_SCREENSHOT_VIEWPORT_H — viewport height (required, integer)
11
+ // QAIOS_SCREENSHOT_TIMEOUT_MS — navigation timeout (default 15_000)
12
+ // QAIOS_SCREENSHOT_WAIT_UNTIL — load|domcontentloaded|networkidle
13
+ // (default 'networkidle' — stable for
14
+ // visual diffs)
15
+ // QAIOS_SCREENSHOT_FULL_PAGE — '1' for fullPage (default '1')
16
+ //
17
+ // Per BuildPlan W6-T2: page.screenshot({ animations: 'disabled' }) is
18
+ // used unconditionally for stability — same input → same PNG → same SHA.
19
+
20
+ import { writeFileSync } from 'node:fs';
21
+
22
+ const url = process.env.QAIOS_SCREENSHOT_URL;
23
+ const out = process.env.QAIOS_SCREENSHOT_OUTPUT;
24
+ const w = Number.parseInt(process.env.QAIOS_SCREENSHOT_VIEWPORT_W ?? '', 10);
25
+ const h = Number.parseInt(process.env.QAIOS_SCREENSHOT_VIEWPORT_H ?? '', 10);
26
+
27
+ if (!url || !out || !Number.isFinite(w) || !Number.isFinite(h)) {
28
+ process.stderr.write(
29
+ 'capture-screenshot: QAIOS_SCREENSHOT_URL/OUTPUT/VIEWPORT_W/VIEWPORT_H all required\n',
30
+ );
31
+ process.exit(2);
32
+ }
33
+
34
+ const timeoutMs = Number.parseInt(process.env.QAIOS_SCREENSHOT_TIMEOUT_MS ?? '15000', 10);
35
+ const waitUntil = process.env.QAIOS_SCREENSHOT_WAIT_UNTIL ?? 'networkidle';
36
+ const fullPage = process.env.QAIOS_SCREENSHOT_FULL_PAGE !== '0';
37
+
38
+ let chromium;
39
+ try {
40
+ ({ chromium } = await import('playwright'));
41
+ } catch (err) {
42
+ process.stderr.write(
43
+ `capture-screenshot: failed to load 'playwright' from project — is it installed? ${err?.message ?? err}\n`,
44
+ );
45
+ process.exit(3);
46
+ }
47
+
48
+ const browser = await chromium.launch({ headless: true });
49
+ try {
50
+ const context = await browser.newContext({
51
+ viewport: { width: w, height: h },
52
+ // Disable animations + reduce motion for screenshot stability.
53
+ reducedMotion: 'reduce',
54
+ });
55
+ const page = await context.newPage();
56
+ await page.goto(url, { timeout: timeoutMs, waitUntil });
57
+ const buffer = await page.screenshot({
58
+ type: 'png',
59
+ fullPage,
60
+ animations: 'disabled',
61
+ caret: 'hide',
62
+ });
63
+ writeFileSync(out, buffer);
64
+ } catch (err) {
65
+ process.stderr.write(`capture-screenshot: ${err?.message ?? err}\n`);
66
+ process.exit(4);
67
+ } finally {
68
+ await browser.close();
69
+ }
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@qatonic_innovations/qaios",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "AI QA engineer in your terminal — designs, writes, runs, heals, and explores tests for web UI and APIs with audit-grade traceability.",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/qatonic/qaios#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/qatonic/qaios.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/qatonic/qaios/issues"
15
+ },
16
+ "keywords": [
17
+ "qa",
18
+ "testing",
19
+ "playwright",
20
+ "ai",
21
+ "agent",
22
+ "anthropic",
23
+ "claude",
24
+ "mcp",
25
+ "cli",
26
+ "test-generation",
27
+ "test-automation",
28
+ "visual-regression",
29
+ "self-healing"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20.0.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "bin": {
38
+ "qaios": "./dist/index.js"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "!dist/**/*.map",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "dependencies": {
47
+ "@anthropic-ai/sdk": "^0.40.0",
48
+ "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "better-sqlite3": "^11.7.0",
50
+ "commander": "^12.1.0",
51
+ "ink": "^5.2.1",
52
+ "pino": "^9.5.0",
53
+ "pino-pretty": "^11.3.0",
54
+ "pixelmatch": "^7.2.0",
55
+ "pngjs": "^7.0.0",
56
+ "react": "^18.3.1",
57
+ "typescript": "^5.7.2",
58
+ "ulid": "^2.3.0",
59
+ "yaml": "^2.6.1",
60
+ "zod": "^3.23.8",
61
+ "zod-to-json-schema": "^3.24.0"
62
+ },
63
+ "devDependencies": {
64
+ "@types/better-sqlite3": "^7.6.12",
65
+ "@types/pixelmatch": "^5.2.6",
66
+ "@types/pngjs": "^6.0.5",
67
+ "@types/react": "^18.3.28",
68
+ "ink-testing-library": "^4.0.0",
69
+ "@qaios/runtime": "0.0.0",
70
+ "@qaios/skills": "0.0.0",
71
+ "@qaios/shared": "0.0.0"
72
+ },
73
+ "scripts": {
74
+ "build": "tsup && node scripts/copy-templates.mjs && node scripts/copy-runtime-assets.mjs",
75
+ "test": "vitest run",
76
+ "test:unit": "vitest run test/unit",
77
+ "test:integration": "vitest run test/integration --passWithNoTests",
78
+ "test:coverage": "vitest run --coverage",
79
+ "typecheck": "tsc --noEmit"
80
+ }
81
+ }