@qatonic_innovations/qaios 0.3.1 → 0.3.2
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/README.md +3 -1
- package/dist/index.js +61 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,9 @@ The published package is `@qatonic_innovations/qaios`; the installed **command i
|
|
|
48
48
|
```
|
|
49
49
|
Prefer **OpenAI**? Set `llm.provider: openai` and export `OPENAI_API_KEY`
|
|
50
50
|
instead — see [Choose your LLM provider](#choose-your-llm-provider) below.
|
|
51
|
-
Keys are read from the environment and **never written to
|
|
51
|
+
Keys are read from the **environment** (`process.env`) and **never written to
|
|
52
|
+
disk** by QAIOS. QAIOS does not auto-load a `.env` file — export the key in
|
|
53
|
+
your shell (or use a `.env` runner like `dotenv-cli`) before running.
|
|
52
54
|
- **Playwright** in your project, for `qaios run` / `snapshot` / `explore` / `a11y`:
|
|
53
55
|
```bash
|
|
54
56
|
npm i -D @playwright/test && npx playwright install
|
package/dist/index.js
CHANGED
|
@@ -3001,13 +3001,19 @@ var CostTracker = class {
|
|
|
3001
3001
|
const row = this.db.prepare(
|
|
3002
3002
|
`SELECT
|
|
3003
3003
|
COUNT(*) AS calls,
|
|
3004
|
-
COALESCE(SUM(CAST(json_extract(model_call_json, '$.costUsdCents') AS INTEGER)), 0) AS cents
|
|
3004
|
+
COALESCE(SUM(CAST(json_extract(model_call_json, '$.costUsdCents') AS INTEGER)), 0) AS cents,
|
|
3005
|
+
COALESCE(SUM(
|
|
3006
|
+
COALESCE(CAST(json_extract(model_call_json, '$.inputTokens') AS INTEGER), 0) +
|
|
3007
|
+
COALESCE(CAST(json_extract(model_call_json, '$.outputTokens') AS INTEGER), 0) +
|
|
3008
|
+
COALESCE(CAST(json_extract(model_call_json, '$.cacheReadTokens') AS INTEGER), 0) +
|
|
3009
|
+
COALESCE(CAST(json_extract(model_call_json, '$.cacheWriteTokens') AS INTEGER), 0)
|
|
3010
|
+
), 0) AS tokens
|
|
3005
3011
|
FROM audit_log
|
|
3006
3012
|
WHERE workflow_id = ?
|
|
3007
3013
|
AND event = 'model.called'
|
|
3008
3014
|
AND model_call_json IS NOT NULL`
|
|
3009
3015
|
).get(workflowId);
|
|
3010
|
-
return { calls: row.calls, usdCents: row.cents };
|
|
3016
|
+
return { calls: row.calls, usdCents: row.cents, tokens: row.tokens };
|
|
3011
3017
|
}
|
|
3012
3018
|
/**
|
|
3013
3019
|
* Remaining budget for the workflow. Returns negative numbers when
|
|
@@ -5166,6 +5172,7 @@ var Orchestrator = class {
|
|
|
5166
5172
|
if (to === "completed" || to === "failed" || to === "cancelled") {
|
|
5167
5173
|
const snap = this.costTracker.current(workflowId);
|
|
5168
5174
|
updates.costUsdCents = snap.usdCents;
|
|
5175
|
+
updates.costTokens = snap.tokens;
|
|
5169
5176
|
}
|
|
5170
5177
|
this.workflowsRepo.update(workflowId, updates);
|
|
5171
5178
|
const phase = phaseForState(to);
|
|
@@ -9731,7 +9738,8 @@ async function runFix(opts) {
|
|
|
9731
9738
|
const runResult = await adapter.run({
|
|
9732
9739
|
cwd,
|
|
9733
9740
|
workflowId,
|
|
9734
|
-
pattern: resolvedTestFile
|
|
9741
|
+
pattern: resolvedTestFile,
|
|
9742
|
+
extraArgs: ["--retries=2"]
|
|
9735
9743
|
});
|
|
9736
9744
|
const passed = runResult.runRow.failedCount === 0 && runResult.runRow.status !== "errored";
|
|
9737
9745
|
let rolledBack = false;
|
|
@@ -10801,7 +10809,8 @@ async function runReview(opts) {
|
|
|
10801
10809
|
resumedWorkflows: []
|
|
10802
10810
|
};
|
|
10803
10811
|
}
|
|
10804
|
-
|
|
10812
|
+
const decide = opts.autoApprove === true ? () => "approve" : opts.decideForTests;
|
|
10813
|
+
if (opts.nonInteractive === true && decide === void 0) {
|
|
10805
10814
|
return {
|
|
10806
10815
|
exitCode: ExitCode.USER_ERROR,
|
|
10807
10816
|
decisions: [],
|
|
@@ -10809,7 +10818,7 @@ async function runReview(opts) {
|
|
|
10809
10818
|
resumedWorkflows: [],
|
|
10810
10819
|
error: {
|
|
10811
10820
|
code: "qaios.review.requires_tty",
|
|
10812
|
-
message: "qaios review needs an interactive terminal \u2014 pass --auto-approve or run without --non-interactive."
|
|
10821
|
+
message: "qaios review needs an interactive terminal \u2014 pass --auto-approve to approve all pending gates non-interactively, or run without --non-interactive."
|
|
10813
10822
|
}
|
|
10814
10823
|
};
|
|
10815
10824
|
}
|
|
@@ -10843,9 +10852,9 @@ async function runReview(opts) {
|
|
|
10843
10852
|
);
|
|
10844
10853
|
}
|
|
10845
10854
|
};
|
|
10846
|
-
if (
|
|
10855
|
+
if (decide !== void 0) {
|
|
10847
10856
|
for (const gate of pending) {
|
|
10848
|
-
await onDecide(gate,
|
|
10857
|
+
await onDecide(gate, decide(gate));
|
|
10849
10858
|
}
|
|
10850
10859
|
return {
|
|
10851
10860
|
exitCode: ExitCode.SUCCESS,
|
|
@@ -10854,15 +10863,15 @@ async function runReview(opts) {
|
|
|
10854
10863
|
resumedWorkflows: Array.from(resumedSet)
|
|
10855
10864
|
};
|
|
10856
10865
|
}
|
|
10857
|
-
const [{ render }, { GateReview: GateReview2 }] = await Promise.all([
|
|
10866
|
+
const [React3, { render }, { GateReview: GateReview2 }] = await Promise.all([
|
|
10867
|
+
import('react'),
|
|
10858
10868
|
import('ink'),
|
|
10859
10869
|
Promise.resolve().then(() => (init_GateReview(), GateReview_exports))
|
|
10860
10870
|
]);
|
|
10861
10871
|
await new Promise((resolve) => {
|
|
10862
10872
|
let chain = Promise.resolve();
|
|
10863
10873
|
const instance = render(
|
|
10864
|
-
|
|
10865
|
-
GateReview2({
|
|
10874
|
+
React3.createElement(GateReview2, {
|
|
10866
10875
|
gates: pending,
|
|
10867
10876
|
onDecide: (gate, action) => {
|
|
10868
10877
|
chain = chain.then(() => onDecide(gate, action));
|
|
@@ -11119,9 +11128,32 @@ var GLYPH5 = {
|
|
|
11119
11128
|
warn: "\u26A0",
|
|
11120
11129
|
blocked: "\u23F8"
|
|
11121
11130
|
};
|
|
11131
|
+
var SnapshotSpecError = class extends Error {
|
|
11132
|
+
constructor(userMessage) {
|
|
11133
|
+
super(userMessage);
|
|
11134
|
+
this.userMessage = userMessage;
|
|
11135
|
+
}
|
|
11136
|
+
userMessage;
|
|
11137
|
+
};
|
|
11122
11138
|
function loadSnapshotSpec(specPath) {
|
|
11123
|
-
|
|
11124
|
-
|
|
11139
|
+
let raw;
|
|
11140
|
+
try {
|
|
11141
|
+
raw = JSON.parse(readFileSync(specPath, "utf-8"));
|
|
11142
|
+
} catch (err) {
|
|
11143
|
+
throw new SnapshotSpecError(`could not read/parse ${specPath}: ${err.message}`);
|
|
11144
|
+
}
|
|
11145
|
+
const rawSnapshots = Array.isArray(raw["snapshots"]) ? raw["snapshots"] : Array.isArray(raw["designSpec"]?.["snapshots"]) ? raw["designSpec"]["snapshots"] : [];
|
|
11146
|
+
const snapshots = [];
|
|
11147
|
+
rawSnapshots.forEach((s, i) => {
|
|
11148
|
+
const parsed = VisualSnapshot.safeParse(s);
|
|
11149
|
+
if (!parsed.success) {
|
|
11150
|
+
const issues = parsed.error.issues.map((iss) => `${iss.path.join(".") || "(root)"}: ${iss.message}`).join("; ");
|
|
11151
|
+
throw new SnapshotSpecError(
|
|
11152
|
+
`spec ${specPath}: snapshots[${i}] is invalid \u2014 ${issues}. Each snapshot needs: id, name, route, setupSteps[], viewports[], priority (P0\u2013P3).`
|
|
11153
|
+
);
|
|
11154
|
+
}
|
|
11155
|
+
snapshots.push(parsed.data);
|
|
11156
|
+
});
|
|
11125
11157
|
return {
|
|
11126
11158
|
snapshots,
|
|
11127
11159
|
...typeof raw["featureName"] === "string" ? { featureName: raw["featureName"] } : {}
|
|
@@ -11169,7 +11201,18 @@ async function runSnapshotCapture(opts) {
|
|
|
11169
11201
|
}
|
|
11170
11202
|
};
|
|
11171
11203
|
}
|
|
11172
|
-
|
|
11204
|
+
let allSnapshots;
|
|
11205
|
+
try {
|
|
11206
|
+
allSnapshots = loadSnapshotSpec(specAbs).snapshots;
|
|
11207
|
+
} catch (err) {
|
|
11208
|
+
if (err instanceof SnapshotSpecError) {
|
|
11209
|
+
return {
|
|
11210
|
+
exitCode: ExitCode.USER_ERROR,
|
|
11211
|
+
error: { code: "qaios.snapshot_capture.invalid_spec", message: err.userMessage }
|
|
11212
|
+
};
|
|
11213
|
+
}
|
|
11214
|
+
throw err;
|
|
11215
|
+
}
|
|
11173
11216
|
if (allSnapshots.length === 0) {
|
|
11174
11217
|
return {
|
|
11175
11218
|
exitCode: ExitCode.USER_ERROR,
|
|
@@ -11619,14 +11662,14 @@ async function runSnapshotReview(opts) {
|
|
|
11619
11662
|
}
|
|
11620
11663
|
};
|
|
11621
11664
|
}
|
|
11622
|
-
const [{ render }, { SnapshotReview: SnapshotReview2 }] = await Promise.all([
|
|
11665
|
+
const [React3, { render }, { SnapshotReview: SnapshotReview2 }] = await Promise.all([
|
|
11666
|
+
import('react'),
|
|
11623
11667
|
import('ink'),
|
|
11624
11668
|
Promise.resolve().then(() => (init_SnapshotReview(), SnapshotReview_exports))
|
|
11625
11669
|
]);
|
|
11626
11670
|
await new Promise((resolve) => {
|
|
11627
11671
|
const instance = render(
|
|
11628
|
-
|
|
11629
|
-
SnapshotReview2({
|
|
11672
|
+
React3.createElement(SnapshotReview2, {
|
|
11630
11673
|
items,
|
|
11631
11674
|
onDecide,
|
|
11632
11675
|
onExit: () => {
|
|
@@ -12466,11 +12509,12 @@ function buildProgram() {
|
|
|
12466
12509
|
configGroup.command("show").description("Print resolved config (defaults applied)").action((cmdOpts, command) => {
|
|
12467
12510
|
void runConfigVerb("show", cmdOpts, command, {});
|
|
12468
12511
|
});
|
|
12469
|
-
program.command("review").description("Walk pending gates and approve/reject (interactive Ink TUI; W9-T2)").option("--workflow <id>", "restrict to one workflow id").action(async (cmdOpts, command) => {
|
|
12512
|
+
program.command("review").description("Walk pending gates and approve/reject (interactive Ink TUI; W9-T2)").option("--workflow <id>", "restrict to one workflow id").option("--auto-approve", "approve all pending gates without prompting (CI-friendly)").action(async (cmdOpts, command) => {
|
|
12470
12513
|
const globalOpts = command.parent?.opts() ?? {};
|
|
12471
12514
|
const workflowFlag = typeof cmdOpts["workflow"] === "string" ? cmdOpts["workflow"] : typeof globalOpts["workflow"] === "string" ? globalOpts["workflow"] : void 0;
|
|
12472
12515
|
const opts = {
|
|
12473
12516
|
nonInteractive: Boolean(globalOpts["nonInteractive"]),
|
|
12517
|
+
autoApprove: Boolean(cmdOpts["autoApprove"]),
|
|
12474
12518
|
json: Boolean(globalOpts["json"]),
|
|
12475
12519
|
quiet: Boolean(globalOpts["quiet"])
|
|
12476
12520
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qatonic_innovations/qaios",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
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
6
|
"license": "MIT",
|