@qatonic_innovations/qaios 0.5.1 → 0.5.3
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 +11 -13
- package/dist/index.js +107 -52
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# QAIOS
|
|
2
2
|
|
|
3
|
-
> **AI QA engineer in your terminal.** Designs, writes, runs, heals, and explores tests for web UI and APIs — with
|
|
3
|
+
> **AI QA engineer in your terminal.** Designs, writes, runs, heals, and explores tests for web UI and APIs — with tamper-evident local history.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@qatonic_innovations/qaios)
|
|
6
6
|
[](https://github.com/qatonic/qaios/blob/main/LICENSE)
|
|
@@ -10,7 +10,7 @@ QAIOS acts like a QA engineer on your team. Point it at a feature description or
|
|
|
10
10
|
|
|
11
11
|
**Mental model: Claude Code, but for QA work instead of feature coding.**
|
|
12
12
|
|
|
13
|
-
> **Status:
|
|
13
|
+
> **Status: pre-1.0 (v0.5.2 released).** Core flows — `init`, `doctor`, test design
|
|
14
14
|
> & generation, Playwright execution, self-healing, accessibility, and the
|
|
15
15
|
> audit log — work today. Expect rough edges and please
|
|
16
16
|
> [report issues](https://github.com/qatonic/qaios/issues). After installing,
|
|
@@ -95,7 +95,7 @@ Generated:
|
|
|
95
95
|
tests/auth/LoginPage.po.ts
|
|
96
96
|
.qaios/specs/01JTZ1…/DesignSpec.json
|
|
97
97
|
|
|
98
|
-
Cost: 11,403 tokens · $0.06 (3 of
|
|
98
|
+
Cost: 11,403 tokens · $0.06 (3 of 20 LLM calls used)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
> **Gates:** on HIGH/CRITICAL-risk features QAIOS pauses for review. Approve interactively with `qaios review`, or pass `--yes` to auto-approve for a non-interactive run.
|
|
@@ -117,7 +117,7 @@ Cost: 11,403 tokens · $0.06 (3 of 15 LLM calls used)
|
|
|
117
117
|
| `qaios review` | Walk pending human-review gates and approve/reject; resumes the workflow. |
|
|
118
118
|
| `qaios history [--verify]` | List workflows, inspect the audit log, and verify the hash chain. |
|
|
119
119
|
| `qaios config get\|set\|show\|edit` | Read/write `.qaios/config.yaml` (schema-validated). |
|
|
120
|
-
| `qaios mcp list\|add\|remove\|enable\|disable\|test` | Manage MCP servers (GitHub, Jira,
|
|
120
|
+
| `qaios mcp list\|add\|remove\|enable\|disable\|test` | Manage stdio MCP servers (GitHub, Jira, custom). |
|
|
121
121
|
|
|
122
122
|
Run `qaios <command> --help` for the full option list of any command.
|
|
123
123
|
|
|
@@ -250,7 +250,7 @@ qaios config set mode TRUST
|
|
|
250
250
|
|
|
251
251
|
## Cost & privacy
|
|
252
252
|
|
|
253
|
-
- Skills
|
|
253
|
+
- Skills use Anthropic or OpenAI per `llm.provider`. Each workflow is capped at `min(20 calls, $1.00)` by default (configurable) and aborts if exceeded.
|
|
254
254
|
- **Local-first.** No telemetry, no phone-home. The only outbound traffic is (a) LLM calls to your configured provider (Anthropic or OpenAI) with the prompts QAIOS builds, and (b) any MCP servers you configure. You bring your own API key; QAIOS does not proxy your requests. See [SECURITY.md](https://github.com/qatonic/qaios/blob/main/SECURITY.md) for exactly what's sent.
|
|
255
255
|
|
|
256
256
|
---
|
|
@@ -280,20 +280,18 @@ output for CI consumption.
|
|
|
280
280
|
- [Commands Reference](https://github.com/qatonic/qaios/blob/main/docs/commands.md) — every CLI command
|
|
281
281
|
- [Skills Reference](https://github.com/qatonic/qaios/blob/main/docs/skills-reference.md) — what each skill does
|
|
282
282
|
- [MCP Servers](https://github.com/qatonic/qaios/blob/main/docs/mcp-servers.md) — adding integrations
|
|
283
|
+
- [JSON Output](https://github.com/qatonic/qaios/blob/main/docs/json-output.md) — automation compatibility
|
|
284
|
+
- [Compatibility Policy](https://github.com/qatonic/qaios/blob/main/docs/compatibility.md) — stability and deprecation
|
|
283
285
|
- A runnable end-to-end example lives in [`examples/live-saucedemo`](https://github.com/qatonic/qaios/tree/main/examples/live-saucedemo).
|
|
284
286
|
|
|
285
287
|
---
|
|
286
288
|
|
|
287
289
|
## Status & roadmap
|
|
288
290
|
|
|
289
|
-
**v0.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
-
|
|
293
|
-
- **v0.7** — performance + security testing
|
|
294
|
-
- **v1.0** — database testing, stable public API
|
|
295
|
-
|
|
296
|
-
The roadmap is gated on real-world validation, not a calendar.
|
|
291
|
+
**v0.5.2 is released.** v0.5.3 focuses on truth and consistency before the
|
|
292
|
+
trustworthy-alpha, agent-runtime, quality-intelligence, ecosystem, and stable-OS phases.
|
|
293
|
+
The [active roadmap](https://github.com/qatonic/qaios/blob/main/QAIOS_PRODUCT_AND_ENGINEERING_ROADMAP.md)
|
|
294
|
+
is validation-gated, not calendar-driven.
|
|
297
295
|
|
|
298
296
|
---
|
|
299
297
|
|
package/dist/index.js
CHANGED
|
@@ -4301,7 +4301,7 @@ function tempForTier(tier) {
|
|
|
4301
4301
|
}
|
|
4302
4302
|
var DEFAULT_LLM_TIMEOUT_MS = 24e4;
|
|
4303
4303
|
var DEFAULT_MAX_OUTPUT_TOKENS = 16384;
|
|
4304
|
-
var MAX_OUTPUT_TOKENS_CEILING =
|
|
4304
|
+
var MAX_OUTPUT_TOKENS_CEILING = 21e3;
|
|
4305
4305
|
function llmTimeoutMs() {
|
|
4306
4306
|
const raw = process.env["QAIOS_LLM_TIMEOUT_MS"];
|
|
4307
4307
|
if (raw === void 0) return DEFAULT_LLM_TIMEOUT_MS;
|
|
@@ -8404,7 +8404,7 @@ var McpClient = class {
|
|
|
8404
8404
|
for (const s of opts.servers) {
|
|
8405
8405
|
if (s.enabled !== false) this.registry.set(s.name, s);
|
|
8406
8406
|
}
|
|
8407
|
-
this.connectionFactory = opts.connectionFactory ??
|
|
8407
|
+
this.connectionFactory = opts.connectionFactory ?? createDefaultConnectionFactory(opts.clientVersion ?? "0.0.0");
|
|
8408
8408
|
if (opts.auditLogger !== void 0) this.auditLogger = opts.auditLogger;
|
|
8409
8409
|
if (opts.workflowId !== void 0) this.workflowId = opts.workflowId;
|
|
8410
8410
|
if (opts.skillId !== void 0) this.skillId = opts.skillId;
|
|
@@ -8555,11 +8555,14 @@ var McpClient = class {
|
|
|
8555
8555
|
});
|
|
8556
8556
|
}
|
|
8557
8557
|
};
|
|
8558
|
-
|
|
8558
|
+
function createDefaultConnectionFactory(clientVersion) {
|
|
8559
|
+
return async (config) => connectDefault(config, clientVersion);
|
|
8560
|
+
}
|
|
8561
|
+
async function connectDefault(config, clientVersion) {
|
|
8559
8562
|
if (config.type !== "stdio") {
|
|
8560
8563
|
throw new McpError(
|
|
8561
8564
|
"qaios.mcp.transport_not_supported",
|
|
8562
|
-
`MCP transport "${config.type}" is not supported
|
|
8565
|
+
`MCP transport "${config.type}" is not supported by this release \u2014 only stdio is available.`,
|
|
8563
8566
|
{ serverName: config.name }
|
|
8564
8567
|
);
|
|
8565
8568
|
}
|
|
@@ -8584,7 +8587,7 @@ var defaultConnectionFactory = async (config) => {
|
|
|
8584
8587
|
args: [...config.args],
|
|
8585
8588
|
env: buildSubprocessEnv(resolved)
|
|
8586
8589
|
});
|
|
8587
|
-
const client = new Client({ name: "qaios", version:
|
|
8590
|
+
const client = new Client({ name: "qaios", version: clientVersion }, { capabilities: {} });
|
|
8588
8591
|
await client.connect(transport);
|
|
8589
8592
|
return {
|
|
8590
8593
|
name: config.name,
|
|
@@ -8618,7 +8621,7 @@ var defaultConnectionFactory = async (config) => {
|
|
|
8618
8621
|
await client.close();
|
|
8619
8622
|
}
|
|
8620
8623
|
};
|
|
8621
|
-
}
|
|
8624
|
+
}
|
|
8622
8625
|
function narrowContent(block) {
|
|
8623
8626
|
if (typeof block !== "object" || block === null) return null;
|
|
8624
8627
|
const b = block;
|
|
@@ -8766,14 +8769,54 @@ function runDoctor(opts = {}) {
|
|
|
8766
8769
|
checks.push(checkConfig(qaiosDir, qaiosExists));
|
|
8767
8770
|
checks.push(checkPlaywright(cwd));
|
|
8768
8771
|
checks.push(checkAxeCore(cwd));
|
|
8769
|
-
checks.push(
|
|
8770
|
-
name: "MCP servers",
|
|
8771
|
-
status: "skip",
|
|
8772
|
-
detail: "wired up in W8-T1 (MCP client)"
|
|
8773
|
-
});
|
|
8772
|
+
checks.push(checkMcpServers(qaiosDir, qaiosExists));
|
|
8774
8773
|
const exitCode = checks.some((c) => c.status === "fail") ? ExitCode.TOOL_ERROR : ExitCode.SUCCESS;
|
|
8775
8774
|
return { exitCode, checks };
|
|
8776
8775
|
}
|
|
8776
|
+
function checkMcpServers(qaiosDir, qaiosExists) {
|
|
8777
|
+
const name = "MCP servers";
|
|
8778
|
+
if (!qaiosExists) return { name, status: "skip", detail: "skipped (no .qaios/)" };
|
|
8779
|
+
const dbPath = path15.join(qaiosDir, "workflows.db");
|
|
8780
|
+
if (!existsSync(dbPath)) return { name, status: "skip", detail: "skipped (no workflows.db)" };
|
|
8781
|
+
let storage;
|
|
8782
|
+
try {
|
|
8783
|
+
storage = Storage.open(dbPath, { skipMigrations: true });
|
|
8784
|
+
const servers = new McpServersRepository(storage.db).list();
|
|
8785
|
+
if (servers.length === 0) {
|
|
8786
|
+
return { name, status: "ok", detail: "no servers configured" };
|
|
8787
|
+
}
|
|
8788
|
+
const unsupported = servers.filter((server) => server.type !== "stdio");
|
|
8789
|
+
const invalidStdio = servers.filter(
|
|
8790
|
+
(server) => server.type === "stdio" && !server.command?.trim()
|
|
8791
|
+
);
|
|
8792
|
+
if (unsupported.length > 0 || invalidStdio.length > 0) {
|
|
8793
|
+
const details = [];
|
|
8794
|
+
if (unsupported.length > 0) {
|
|
8795
|
+
details.push(
|
|
8796
|
+
`unsupported transport on ${unsupported.map((server) => server.name).join(", ")} (only stdio is available)`
|
|
8797
|
+
);
|
|
8798
|
+
}
|
|
8799
|
+
if (invalidStdio.length > 0) {
|
|
8800
|
+
details.push(`missing command on ${invalidStdio.map((server) => server.name).join(", ")}`);
|
|
8801
|
+
}
|
|
8802
|
+
return { name, status: "warn", detail: details.join("; ") };
|
|
8803
|
+
}
|
|
8804
|
+
const enabled = servers.filter((server) => server.enabled).length;
|
|
8805
|
+
return {
|
|
8806
|
+
name,
|
|
8807
|
+
status: "ok",
|
|
8808
|
+
detail: `${servers.length} configured (${enabled} enabled); local configuration valid`
|
|
8809
|
+
};
|
|
8810
|
+
} catch (err) {
|
|
8811
|
+
return {
|
|
8812
|
+
name,
|
|
8813
|
+
status: "fail",
|
|
8814
|
+
detail: `could not inspect MCP configuration: ${err.message}`
|
|
8815
|
+
};
|
|
8816
|
+
} finally {
|
|
8817
|
+
storage?.close();
|
|
8818
|
+
}
|
|
8819
|
+
}
|
|
8777
8820
|
function checkConfig(qaiosDir, qaiosExists) {
|
|
8778
8821
|
const name = "config.yaml";
|
|
8779
8822
|
if (!qaiosExists) return { name, status: "skip", detail: "skipped (no .qaios/)" };
|
|
@@ -9197,8 +9240,8 @@ function scopeFooterApi(scope) {
|
|
|
9197
9240
|
}
|
|
9198
9241
|
var STATUS_OR_RESPONSE_PATTERN = /\b[1-5]\d\d\b|response\./i;
|
|
9199
9242
|
var MIN_SCENARIOS_PER_ENDPOINT = 3;
|
|
9200
|
-
function endpointStepPattern(
|
|
9201
|
-
const escaped =
|
|
9243
|
+
function endpointStepPattern(path21, method) {
|
|
9244
|
+
const escaped = path21.split("/").map(
|
|
9202
9245
|
(seg) => /^\{.*\}$/.test(seg) ? "[^/\\s?#]+" : seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
9203
9246
|
).join("/");
|
|
9204
9247
|
return new RegExp(`\\b${method}\\b[\\s\\S]*?${escaped}(?=$|[\\s?#])`, "i");
|
|
@@ -10244,12 +10287,13 @@ var WriteWebInput = z.object({
|
|
|
10244
10287
|
});
|
|
10245
10288
|
var WRITE_WEB_HTML_MAX_CHARS = 5e4;
|
|
10246
10289
|
var HEADER_PLACEHOLDERS = {
|
|
10290
|
+
version: "<qaios-version will be filled by runtime>",
|
|
10247
10291
|
workflowId: "<ulid will be filled by runtime>",
|
|
10248
10292
|
generatedAt: "<iso-8601 will be filled by runtime>",
|
|
10249
10293
|
designSpecPath: "<workflow-id>"
|
|
10250
10294
|
};
|
|
10251
|
-
function injectGeneratedFileHeader(contents, workflowId, generatedAtIso) {
|
|
10252
|
-
return contents.split(HEADER_PLACEHOLDERS.workflowId).join(workflowId).split(HEADER_PLACEHOLDERS.generatedAt).join(generatedAtIso).split(HEADER_PLACEHOLDERS.designSpecPath).join(workflowId);
|
|
10295
|
+
function injectGeneratedFileHeader(contents, workflowId, generatedAtIso, qaiosVersion) {
|
|
10296
|
+
return contents.split(HEADER_PLACEHOLDERS.version).join(qaiosVersion).split(HEADER_PLACEHOLDERS.workflowId).join(workflowId).split(HEADER_PLACEHOLDERS.generatedAt).join(generatedAtIso).split(HEADER_PLACEHOLDERS.designSpecPath).join(workflowId);
|
|
10253
10297
|
}
|
|
10254
10298
|
var IMPORT_ALLOW_LIST = ["@playwright/test"];
|
|
10255
10299
|
var ASSERTION_RE = /\bexpect\s*\(/;
|
|
@@ -10353,7 +10397,7 @@ Output requirements:
|
|
|
10353
10397
|
- Header comment on every generated file (emit literally; the runtime
|
|
10354
10398
|
fills the placeholder tokens):
|
|
10355
10399
|
|
|
10356
|
-
// Generated by QAIOS
|
|
10400
|
+
// Generated by QAIOS v${HEADER_PLACEHOLDERS.version} (workflow: ${HEADER_PLACEHOLDERS.workflowId})
|
|
10357
10401
|
// Generated at: ${HEADER_PLACEHOLDERS.generatedAt}
|
|
10358
10402
|
// DesignSpec: .qaios/specs/${HEADER_PLACEHOLDERS.designSpecPath}/DesignSpec.json
|
|
10359
10403
|
// Edit freely \u2014 QAIOS will preserve manual changes when re-generating.
|
|
@@ -10462,7 +10506,7 @@ Output requirements:
|
|
|
10462
10506
|
- Header comment on every generated file (emit literally; the runtime
|
|
10463
10507
|
fills the placeholder tokens):
|
|
10464
10508
|
|
|
10465
|
-
// Generated by QAIOS
|
|
10509
|
+
// Generated by QAIOS v${HEADER_PLACEHOLDERS.version} (workflow: ${HEADER_PLACEHOLDERS.workflowId})
|
|
10466
10510
|
// Generated at: ${HEADER_PLACEHOLDERS.generatedAt}
|
|
10467
10511
|
// DesignSpec: .qaios/specs/${HEADER_PLACEHOLDERS.designSpecPath}/DesignSpec.json
|
|
10468
10512
|
// Edit freely \u2014 QAIOS will preserve manual changes when re-generating.
|
|
@@ -10554,6 +10598,16 @@ var skills = {
|
|
|
10554
10598
|
"explore.charter": exploreCharterSkill,
|
|
10555
10599
|
"audit.a11y": auditA11ySkill
|
|
10556
10600
|
};
|
|
10601
|
+
var moduleDir = path15.dirname(fileURLToPath(import.meta.url));
|
|
10602
|
+
function getQaiosVersion() {
|
|
10603
|
+
try {
|
|
10604
|
+
const packagePath = path15.resolve(moduleDir, "..", "package.json");
|
|
10605
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
10606
|
+
return pkg.version?.trim() || "0.0.0";
|
|
10607
|
+
} catch {
|
|
10608
|
+
return "0.0.0";
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10557
10611
|
|
|
10558
10612
|
// src/llm.ts
|
|
10559
10613
|
function resolveLlmClient(injected, llmConfig) {
|
|
@@ -10768,7 +10822,12 @@ async function runA11y(opts) {
|
|
|
10768
10822
|
if (target !== "stdout" && mcpClient === void 0) {
|
|
10769
10823
|
const repoMcp = new McpServersRepository(storage.db);
|
|
10770
10824
|
const servers = repoMcp.list();
|
|
10771
|
-
mcpClient = new McpClient({
|
|
10825
|
+
mcpClient = new McpClient({
|
|
10826
|
+
servers,
|
|
10827
|
+
auditLogger,
|
|
10828
|
+
workflowId,
|
|
10829
|
+
clientVersion: getQaiosVersion()
|
|
10830
|
+
});
|
|
10772
10831
|
}
|
|
10773
10832
|
for (const issue of violations) {
|
|
10774
10833
|
const filed = await fileIssueAsDefect({
|
|
@@ -11090,28 +11149,27 @@ async function runExplore(opts) {
|
|
|
11090
11149
|
const sevCount = countBySeverity(findings);
|
|
11091
11150
|
writeOut(`Session complete. Duration: ${formatDuration(exploreOutput.durationActual)}`);
|
|
11092
11151
|
writeOut(
|
|
11093
|
-
`
|
|
11152
|
+
`Unverified hypotheses: ${findings.length}` + (findings.length > 0 ? ` (${sevSummary(sevCount)})` : "")
|
|
11094
11153
|
);
|
|
11095
11154
|
for (const f of findings) {
|
|
11096
|
-
writeOut(` ${GLYPH2.finding} [
|
|
11155
|
+
writeOut(` ${GLYPH2.finding} [UNVERIFIED/${f.severity.toUpperCase()}] ${f.title}`);
|
|
11097
11156
|
}
|
|
11098
11157
|
writeOut("");
|
|
11099
11158
|
let defectsFiled = 0;
|
|
11100
|
-
|
|
11159
|
+
const target = opts.defectTarget ?? config?.defects?.target ?? "stdout";
|
|
11160
|
+
if (target !== "stdout" && findings.length > 0) {
|
|
11161
|
+
writeOut(
|
|
11162
|
+
`${GLYPH2.warn} External defect filing is disabled for unverified exploratory hypotheses. Reproduce the finding independently before filing it.`
|
|
11163
|
+
);
|
|
11164
|
+
} else if (opts.noDefects === true || findings.length === 0) {
|
|
11101
11165
|
if (findings.length > 0 && opts.noDefects === true) {
|
|
11102
11166
|
writeOut("Skipping defect filing (--no-defects).");
|
|
11103
11167
|
}
|
|
11104
11168
|
} else {
|
|
11105
|
-
const target = opts.defectTarget ?? config?.defects?.target ?? "stdout";
|
|
11106
11169
|
const repo = opts.defectRepo ?? config?.defects?.repo;
|
|
11107
11170
|
const project = opts.defectProject ?? config?.defects?.project;
|
|
11108
11171
|
writeOut(`Filing defects (target: ${target})...`);
|
|
11109
|
-
|
|
11110
|
-
if (target !== "stdout" && mcpClient === void 0) {
|
|
11111
|
-
const repoMcp = new McpServersRepository(storage.db);
|
|
11112
|
-
const servers = repoMcp.list();
|
|
11113
|
-
mcpClient = new McpClient({ servers, auditLogger, workflowId });
|
|
11114
|
-
}
|
|
11172
|
+
const mcpClient = opts.mcpClient;
|
|
11115
11173
|
for (const f of findings) {
|
|
11116
11174
|
const filed = await fileFindingAsDefect({
|
|
11117
11175
|
finding: f,
|
|
@@ -11133,9 +11191,6 @@ async function runExplore(opts) {
|
|
|
11133
11191
|
writeOut(` ${GLYPH2.failed} ${f.title} \u2014 ${filed.error ?? "unknown failure"}`);
|
|
11134
11192
|
}
|
|
11135
11193
|
}
|
|
11136
|
-
if (mcpClient !== void 0 && opts.mcpClient === void 0) {
|
|
11137
|
-
await mcpClient.close();
|
|
11138
|
-
}
|
|
11139
11194
|
}
|
|
11140
11195
|
finalize2("succeeded");
|
|
11141
11196
|
return {
|
|
@@ -11143,6 +11198,7 @@ async function runExplore(opts) {
|
|
|
11143
11198
|
workflowId,
|
|
11144
11199
|
charter: exploreOutput.charter,
|
|
11145
11200
|
findings,
|
|
11201
|
+
findingsVerification: "unverified",
|
|
11146
11202
|
defectsFiled
|
|
11147
11203
|
};
|
|
11148
11204
|
} finally {
|
|
@@ -12472,12 +12528,20 @@ function addServer(repo, opts, writeOut) {
|
|
|
12472
12528
|
exitCode: ExitCode.USER_ERROR,
|
|
12473
12529
|
error: {
|
|
12474
12530
|
code: "qaios.mcp.no_command_or_url",
|
|
12475
|
-
message: "qaios mcp add requires a <command
|
|
12531
|
+
message: "qaios mcp add requires a <command> argument for a stdio server."
|
|
12532
|
+
}
|
|
12533
|
+
};
|
|
12534
|
+
}
|
|
12535
|
+
const type = opts.type ?? "stdio";
|
|
12536
|
+
if (type !== "stdio" || /^https?:\/\//i.test(opts.commandOrUrl)) {
|
|
12537
|
+
return {
|
|
12538
|
+
exitCode: ExitCode.USER_ERROR,
|
|
12539
|
+
error: {
|
|
12540
|
+
code: "qaios.mcp.transport_not_supported",
|
|
12541
|
+
message: "Only MCP stdio transport is supported in this release. HTTP and SSE registration are disabled until their runtime implementations and contract tests ship."
|
|
12476
12542
|
}
|
|
12477
12543
|
};
|
|
12478
12544
|
}
|
|
12479
|
-
const inferredType = /^https?:\/\//i.test(opts.commandOrUrl) ? "http" : "stdio";
|
|
12480
|
-
const type = opts.type ?? inferredType;
|
|
12481
12545
|
const env = { ...opts.env ?? {} };
|
|
12482
12546
|
const secretKeys = Object.entries(env).filter(([, v]) => looksLikeSecret(v)).map(([k]) => k);
|
|
12483
12547
|
if (secretKeys.length > 0) {
|
|
@@ -12496,7 +12560,7 @@ function addServer(repo, opts, writeOut) {
|
|
|
12496
12560
|
env,
|
|
12497
12561
|
enabled: opts.enabled !== false,
|
|
12498
12562
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12499
|
-
|
|
12563
|
+
command: opts.commandOrUrl
|
|
12500
12564
|
};
|
|
12501
12565
|
const persisted = repo.upsert(config);
|
|
12502
12566
|
writeOut(
|
|
@@ -12574,7 +12638,8 @@ async function testServer(repo, opts, writeOut) {
|
|
|
12574
12638
|
const client = opts.mcpClient ?? new McpClient({
|
|
12575
12639
|
// For `test` we always include the target server even if it's
|
|
12576
12640
|
// disabled — the user explicitly asked us to test it.
|
|
12577
|
-
servers: [{ ...config, enabled: true }]
|
|
12641
|
+
servers: [{ ...config, enabled: true }],
|
|
12642
|
+
clientVersion: getQaiosVersion()
|
|
12578
12643
|
});
|
|
12579
12644
|
const ownsClient = opts.mcpClient === void 0;
|
|
12580
12645
|
const MCP_TEST_TIMEOUT_MS = 15e3;
|
|
@@ -14457,8 +14522,8 @@ ${epSummary}`;
|
|
|
14457
14522
|
gateConfig.autoExpireOnTimeout = "auto_approve";
|
|
14458
14523
|
}
|
|
14459
14524
|
const costCap = {
|
|
14460
|
-
maxCalls: config?.llm?.maxLlmCallsPerWorkflow ??
|
|
14461
|
-
maxUsdCents: config?.llm?.costAlertThresholdUsdCents ??
|
|
14525
|
+
maxCalls: config?.llm?.maxLlmCallsPerWorkflow ?? 20,
|
|
14526
|
+
maxUsdCents: config?.llm?.costAlertThresholdUsdCents ?? 100
|
|
14462
14527
|
};
|
|
14463
14528
|
const orchestrator = new Orchestrator({
|
|
14464
14529
|
storage,
|
|
@@ -14536,7 +14601,8 @@ ${epSummary}`;
|
|
|
14536
14601
|
const injected = injectGeneratedFileHeader(
|
|
14537
14602
|
file.contents,
|
|
14538
14603
|
result.workflowId,
|
|
14539
|
-
generatedAtIso
|
|
14604
|
+
generatedAtIso,
|
|
14605
|
+
getQaiosVersion()
|
|
14540
14606
|
);
|
|
14541
14607
|
writeFileSync(absPath, injected);
|
|
14542
14608
|
filesWritten.push(file.path);
|
|
@@ -14634,20 +14700,9 @@ Ran generated tests: ${runSummary.passedCount} passed, ${runSummary.failedCount}
|
|
|
14634
14700
|
}
|
|
14635
14701
|
|
|
14636
14702
|
// src/index.ts
|
|
14637
|
-
var __dirname2 = path15.dirname(fileURLToPath(import.meta.url));
|
|
14638
|
-
function readPackageVersion() {
|
|
14639
|
-
for (const candidate of [path15.resolve(__dirname2, "..", "package.json")]) {
|
|
14640
|
-
try {
|
|
14641
|
-
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
14642
|
-
if (pkg.version) return pkg.version;
|
|
14643
|
-
} catch {
|
|
14644
|
-
}
|
|
14645
|
-
}
|
|
14646
|
-
return "0.0.0";
|
|
14647
|
-
}
|
|
14648
14703
|
function buildProgram() {
|
|
14649
14704
|
const program = new Command();
|
|
14650
|
-
program.name("qaios").description("AI QA engineer in your terminal").version(
|
|
14705
|
+
program.name("qaios").description("AI QA engineer in your terminal").version(getQaiosVersion(), "-V, --version", "print QAIOS version, exit 0").option("--workflow <id>", "explicitly set or resume a workflow ID").option("--mode <mode>", "override mode for this run (LITE | FULL | TRUST)").option("--non-interactive", "disable interactive prompts").option("--verbose", "log level: debug").option("--debug", "log level: trace").option("--quiet", "only errors to stderr; minimum stdout output").option("--json", "emit primary output as JSON (machine-readable)").option("--no-color", "disable ANSI color").showHelpAfterError();
|
|
14651
14706
|
program.command("init [path]").description("Bootstrap a project: create .qaios/, QAIOS.md, run migrations").option("--mode <mode>", "mode to write to config (LITE | FULL | TRUST)").option("--framework <name>", 'test framework (only "playwright" in v0.1)').option("--test-dir <path>", "relative path to test directory").option("--non-interactive", "skip prompts; use defaults").option("--force", "overwrite existing .qaios/").action((cwdArg, cmdOpts) => {
|
|
14652
14707
|
const initOpts = {
|
|
14653
14708
|
nonInteractive: Boolean(cmdOpts["nonInteractive"]),
|
|
@@ -14998,7 +15053,7 @@ function buildProgram() {
|
|
|
14998
15053
|
mcpGroup.command("list").description("Show every registered MCP server").action((cmdOpts, command) => {
|
|
14999
15054
|
void runMcpVerb("list", cmdOpts, command, {});
|
|
15000
15055
|
});
|
|
15001
|
-
mcpGroup.command("add <name> <command
|
|
15056
|
+
mcpGroup.command("add <name> <command>").description("Register an MCP stdio server command").option("--type <type>", "transport: stdio (HTTP/SSE are not yet supported)", "stdio").option(
|
|
15002
15057
|
"--arg <arg>",
|
|
15003
15058
|
"stdio command-line arg (repeatable)",
|
|
15004
15059
|
(v, prev = []) => [...prev, v]
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qatonic_innovations/qaios",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "AI QA engineer in your terminal — designs, writes, runs, heals, and explores tests
|
|
5
|
+
"description": "AI QA engineer in your terminal — designs, writes, runs, heals, and explores tests with tamper-evident local history.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/qatonic/qaios#readme",
|
|
8
8
|
"repository": {
|