@qatonic_innovations/qaios 0.4.1 → 0.5.1

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 (2) hide show
  1. package/dist/index.js +275 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -905,7 +905,19 @@ var QaiosConfig = z.object({
905
905
  // Playwright defaults to `data-testid`; apps using `data-test`,
906
906
  // `data-qa`, etc. must declare it here so generated specs emit a
907
907
  // `test.use({ testIdAttribute })` and actually find their elements.
908
- testIdAttribute: z.string().default("data-testid")
908
+ testIdAttribute: z.string().default("data-testid"),
909
+ // 0.5 DOM grounding: when true (default), `qaios test` captures the
910
+ // live page at `app.baseUrl` before writing and feeds the real DOM
911
+ // to write.web so it generates locators against actual elements
912
+ // instead of guessing from the DesignSpec. Needs the app reachable
913
+ // at generation time; when it isn't, capture fails fast (see
914
+ // groundCaptureTimeoutMs) and falls back to blind generation. Set
915
+ // false to skip capture entirely (also: `qaios test --no-ground`).
916
+ groundLocators: z.boolean().default(true),
917
+ // Hard cap on the headless page-capture before grounding gives up
918
+ // and falls back. Kept tight so a not-yet-running dev server doesn't
919
+ // stall generation — a real capture against a live page is fast.
920
+ groundCaptureTimeoutMs: z.number().int().positive().default(15e3)
909
921
  }).default({}),
910
922
  visual: z.object({
911
923
  viewports: z.array(
@@ -1103,6 +1115,97 @@ var ulidImpl = monotonicFactory();
1103
1115
  function ulid() {
1104
1116
  return ulidImpl();
1105
1117
  }
1118
+ var GET_BY_TESTID_RE = /getByTestId\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
1119
+ var GET_BY_ROLE_RE = /getByRole\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*\{[^}]*?name\s*:\s*['"`]([^'"`]+)['"`][^}]*\})?\s*\)/g;
1120
+ var GET_BY_TEXT_RE = /getByText\(\s*['"`]([^'"`]+)['"`]/g;
1121
+ function countOccurrences(hay, needle) {
1122
+ if (needle.length === 0) return 0;
1123
+ let count = 0;
1124
+ let from = 0;
1125
+ for (; ; ) {
1126
+ const idx = hay.indexOf(needle, from);
1127
+ if (idx < 0) break;
1128
+ count += 1;
1129
+ from = idx + needle.length;
1130
+ }
1131
+ return count;
1132
+ }
1133
+ function collectA11yNodes(tree, out) {
1134
+ if (tree === null || typeof tree !== "object") return;
1135
+ const node = tree;
1136
+ if (typeof node.role === "string") {
1137
+ out.push({ role: node.role, name: typeof node.name === "string" ? node.name : "" });
1138
+ }
1139
+ if (Array.isArray(node.children)) {
1140
+ for (const child of node.children) collectA11yNodes(child, out);
1141
+ }
1142
+ }
1143
+ function verifyLocatorsAgainstSnapshot(specContents, snapshot) {
1144
+ if (snapshot === void 0) {
1145
+ return { okCount: 0, problems: [], uncheckedCount: 0 };
1146
+ }
1147
+ const html = snapshot.html ?? "";
1148
+ const truncated = html.includes("<!-- truncated -->");
1149
+ const a11yNodes = [];
1150
+ collectA11yNodes(snapshot.accessibilityTree, a11yNodes);
1151
+ const problems = [];
1152
+ let okCount = 0;
1153
+ let uncheckedCount = 0;
1154
+ for (const m of specContents.matchAll(GET_BY_TESTID_RE)) {
1155
+ const id = m[1];
1156
+ const valueHits = countOccurrences(html, `"${id}"`) + countOccurrences(html, `'${id}'`);
1157
+ if (valueHits === 0) {
1158
+ if (truncated) uncheckedCount += 1;
1159
+ else problems.push({ expression: m[0], problem: "missing", count: 0, kind: "testid" });
1160
+ } else if (valueHits > 1) {
1161
+ problems.push({ expression: m[0], problem: "ambiguous", count: valueHits, kind: "testid" });
1162
+ } else {
1163
+ okCount += 1;
1164
+ }
1165
+ }
1166
+ if (a11yNodes.length > 0) {
1167
+ for (const m of specContents.matchAll(GET_BY_ROLE_RE)) {
1168
+ const role = m[1];
1169
+ const name = m[2];
1170
+ const roleMatches = a11yNodes.filter((n) => n.role === role);
1171
+ if (roleMatches.length === 0) {
1172
+ problems.push({ expression: m[0], problem: "missing", count: 0, kind: "role" });
1173
+ } else if (name !== void 0) {
1174
+ const exact = roleMatches.filter((n) => n.name === name);
1175
+ if (exact.length > 1) {
1176
+ problems.push({
1177
+ expression: m[0],
1178
+ problem: "ambiguous",
1179
+ count: exact.length,
1180
+ kind: "role"
1181
+ });
1182
+ } else {
1183
+ okCount += 1;
1184
+ }
1185
+ } else {
1186
+ uncheckedCount += 1;
1187
+ }
1188
+ }
1189
+ } else {
1190
+ for (const _m of specContents.matchAll(GET_BY_ROLE_RE)) uncheckedCount += 1;
1191
+ }
1192
+ for (const m of specContents.matchAll(GET_BY_TEXT_RE)) {
1193
+ const text = m[1];
1194
+ const hits = countOccurrences(html, text);
1195
+ if (hits === 0) {
1196
+ if (truncated) uncheckedCount += 1;
1197
+ else problems.push({ expression: m[0], problem: "missing", count: 0, kind: "text" });
1198
+ } else {
1199
+ okCount += 1;
1200
+ }
1201
+ }
1202
+ return { okCount, problems, uncheckedCount };
1203
+ }
1204
+ function summarizeLocatorProblems(v) {
1205
+ return v.problems.map(
1206
+ (p) => p.problem === "missing" ? `${p.expression} \u2014 matched no element on the captured page` : `${p.expression} \u2014 matched ${p.count} elements (ambiguous; narrow it instead of using .nth/.first)`
1207
+ );
1208
+ }
1106
1209
  var QaiosError = class extends Error {
1107
1210
  code;
1108
1211
  detail;
@@ -4197,6 +4300,8 @@ function tempForTier(tier) {
4197
4300
  }
4198
4301
  }
4199
4302
  var DEFAULT_LLM_TIMEOUT_MS = 24e4;
4303
+ var DEFAULT_MAX_OUTPUT_TOKENS = 16384;
4304
+ var MAX_OUTPUT_TOKENS_CEILING = 65536;
4200
4305
  function llmTimeoutMs() {
4201
4306
  const raw = process.env["QAIOS_LLM_TIMEOUT_MS"];
4202
4307
  if (raw === void 0) return DEFAULT_LLM_TIMEOUT_MS;
@@ -4257,6 +4362,7 @@ var SkillRunner = class {
4257
4362
  const maxAttempts = maxRetries + 1;
4258
4363
  let attempt = 0;
4259
4364
  let lastValidationError = null;
4365
+ let currentMaxTokens = DEFAULT_MAX_OUTPUT_TOKENS;
4260
4366
  while (attempt < maxAttempts) {
4261
4367
  attempt++;
4262
4368
  const userPromptBase = skill.userPrompt(effectiveCtx, input);
@@ -4274,9 +4380,9 @@ var SkillRunner = class {
4274
4380
  // but Sonnet 4.6 routinely hits stopReason=max_tokens at
4275
4381
  // that limit — surfacing as a Zod validation error from
4276
4382
  // truncated tool input. Sonnet supports up to 128k output;
4277
- // 16k is plenty of headroom for v0.1 skill outputs without
4278
- // burning a runaway loop's worth of cost.
4279
- maxTokens: 16384,
4383
+ // 16k is the default headroom, escalated on truncation-retry
4384
+ // (see currentMaxTokens) for genuinely large suites.
4385
+ maxTokens: currentMaxTokens,
4280
4386
  temperature: tempForTier(skill.modelTier)
4281
4387
  };
4282
4388
  const signal = buildCallSignal(ctx.cancelSignal);
@@ -4329,14 +4435,21 @@ var SkillRunner = class {
4329
4435
  });
4330
4436
  if (response.stopReason === "max_tokens") {
4331
4437
  if (attempt < maxAttempts) {
4438
+ const escalated = Math.min(currentMaxTokens * 2, MAX_OUTPUT_TOKENS_CEILING);
4332
4439
  ctx.auditLogger.append({
4333
4440
  workflowId: ctx.workflowId,
4334
4441
  phase: skill.phase,
4335
4442
  skillId: skill.id,
4336
4443
  event: "skill.retry",
4337
- payload: { attempt, reason: "output_truncated_max_tokens" },
4444
+ payload: {
4445
+ attempt,
4446
+ reason: "output_truncated_max_tokens",
4447
+ maxTokensWas: currentMaxTokens,
4448
+ maxTokensNext: escalated
4449
+ },
4338
4450
  actor: "agent"
4339
4451
  });
4452
+ currentMaxTokens = escalated;
4340
4453
  continue;
4341
4454
  }
4342
4455
  ctx.auditLogger.append({
@@ -5237,7 +5350,7 @@ function extractLocatorToken(expr) {
5237
5350
  }
5238
5351
  return null;
5239
5352
  }
5240
- function countOccurrences(haystack, needle) {
5353
+ function countOccurrences2(haystack, needle) {
5241
5354
  if (needle.length === 0) return 0;
5242
5355
  let n = 0;
5243
5356
  let i = haystack.indexOf(needle);
@@ -5281,7 +5394,7 @@ function applyLocatorFix(opts) {
5281
5394
  const cwd = opts.cwd ?? process.cwd();
5282
5395
  const absPath = path15.isAbsolute(opts.testFile) ? opts.testFile : path15.resolve(cwd, opts.testFile);
5283
5396
  const oldContents = readFileSync(absPath, "utf-8");
5284
- const occurrences = countOccurrences(oldContents, opts.fix.oldLocator);
5397
+ const occurrences = countOccurrences2(oldContents, opts.fix.oldLocator);
5285
5398
  if (occurrences === 0) {
5286
5399
  return {
5287
5400
  applied: false,
@@ -6059,12 +6172,65 @@ var writing = async (deps) => {
6059
6172
  const appContext = buildAppContext(deps.args);
6060
6173
  appContext["pomMode"] = typeof deps.args["pomMode"] === "string" && (deps.args["pomMode"] === "off" || deps.args["pomMode"] === "auto" || deps.args["pomMode"] === "on") ? deps.args["pomMode"] : "auto";
6061
6174
  appContext["framework"] = "playwright";
6175
+ const groundUrl = baseUrlFromArgs(deps.args);
6176
+ const groundDisabled = deps.args["noGround"] === true;
6177
+ const groundTimeoutMs = typeof deps.args["groundCaptureTimeoutMs"] === "number" ? deps.args["groundCaptureTimeoutMs"] : 15e3;
6178
+ if (!groundDisabled && groundUrl.length > 0 && deps.capturePageSnapshot !== void 0) {
6179
+ const cwd = typeof deps.args["cwd"] === "string" ? deps.args["cwd"] : process.cwd();
6180
+ try {
6181
+ const snapshot2 = await deps.capturePageSnapshot({
6182
+ url: groundUrl,
6183
+ cwd,
6184
+ timeoutMs: groundTimeoutMs
6185
+ });
6186
+ if (snapshot2 !== null) {
6187
+ appContext["pageSnapshot"] = snapshot2;
6188
+ auditGrounding(deps, "grounding_captured", { url: groundUrl });
6189
+ } else {
6190
+ auditGrounding(deps, "grounding_skipped", {
6191
+ reason: "page capture returned no snapshot",
6192
+ url: groundUrl
6193
+ });
6194
+ }
6195
+ } catch (err) {
6196
+ auditGrounding(deps, "grounding_skipped", {
6197
+ reason: `page capture failed: ${err.message}`,
6198
+ url: groundUrl
6199
+ });
6200
+ }
6201
+ }
6062
6202
  const input = {
6063
6203
  designSpec: designOutput?.["designSpec"] ?? null,
6064
6204
  appContext,
6065
6205
  testDir: typeof deps.args["testDir"] === "string" ? deps.args["testDir"] : "tests"
6066
6206
  };
6067
- return runSkill(deps, skillId, input, "writing_gate");
6207
+ const result = await runSkill(deps, skillId, input, "writing_gate");
6208
+ const snapshot = appContext["pageSnapshot"];
6209
+ if (snapshot !== void 0) {
6210
+ try {
6211
+ const out = result.skillExecution?.output;
6212
+ const files = Array.isArray(out?.files) ? out.files : [];
6213
+ const specText = files.filter((f) => f.kind === "spec" && typeof f.contents === "string").map((f) => f.contents).join("\n");
6214
+ const verification = verifyLocatorsAgainstSnapshot(
6215
+ specText,
6216
+ snapshot
6217
+ );
6218
+ if (verification.problems.length > 0) {
6219
+ const summary = summarizeLocatorProblems(verification);
6220
+ auditGrounding(deps, "grounding_warning", {
6221
+ problems: summary,
6222
+ okCount: verification.okCount,
6223
+ uncheckedCount: verification.uncheckedCount
6224
+ });
6225
+ result.warnings = [
6226
+ `${summary.length} generated locator(s) don't cleanly match the live page:`,
6227
+ ...summary.map((s) => ` \u2022 ${s}`)
6228
+ ];
6229
+ }
6230
+ } catch {
6231
+ }
6232
+ }
6233
+ return result;
6068
6234
  };
6069
6235
  var writingGate = async (deps) => {
6070
6236
  const writeKey2 = Object.keys(deps.prior).find((k) => k.startsWith("write."));
@@ -6441,6 +6607,16 @@ function auditHeal(deps, kind, payload) {
6441
6607
  actor: "system"
6442
6608
  });
6443
6609
  }
6610
+ function auditGrounding(deps, kind, payload) {
6611
+ deps.auditLogger.append({
6612
+ workflowId: deps.workflow.id,
6613
+ phase: "WRITING",
6614
+ skillId: "write.web",
6615
+ event: "skill.completed",
6616
+ payload: { kind: `write.${kind}`, ...payload },
6617
+ actor: "system"
6618
+ });
6619
+ }
6444
6620
  function auditDefect(deps, kind, payload) {
6445
6621
  deps.auditLogger.append({
6446
6622
  workflowId: deps.workflow.id,
@@ -6583,7 +6759,11 @@ var Orchestrator = class {
6583
6759
  this.costTracker = new CostTracker(deps.storage.db);
6584
6760
  this.gatesRepo = new GatesRepository(deps.storage.db);
6585
6761
  this.costCap = deps.costCap === null ? null : deps.costCap ?? DEFAULT_COST_CAP;
6586
- this.capturePageSnapshot = deps.capturePageSnapshot ?? ((o) => capturePageSnapshot({ url: o.url, cwd: o.cwd }));
6762
+ this.capturePageSnapshot = deps.capturePageSnapshot ?? ((o) => capturePageSnapshot({
6763
+ url: o.url,
6764
+ cwd: o.cwd,
6765
+ ...o.timeoutMs !== void 0 ? { timeoutMs: o.timeoutMs } : {}
6766
+ }));
6587
6767
  }
6588
6768
  deps;
6589
6769
  workflowsRepo;
@@ -6977,7 +7157,8 @@ var Orchestrator = class {
6977
7157
  workflowId,
6978
7158
  skillId: result.skillExecution.skillId,
6979
7159
  durationMs: Date.now() - skillStart,
6980
- output: result.skillExecution.output
7160
+ output: result.skillExecution.output,
7161
+ ...result.warnings !== void 0 && result.warnings.length > 0 ? { warnings: result.warnings } : {}
6981
7162
  });
6982
7163
  }
6983
7164
  if (this.costCap !== null) {
@@ -10045,10 +10226,23 @@ var WriteWebInput = z.object({
10045
10226
  // Defaults to Playwright's own default (`data-testid`); when the app
10046
10227
  // uses something else (e.g. `data-test`), the generated spec must set
10047
10228
  // it via `test.use({ testIdAttribute })` or getByTestId finds nothing.
10048
- testIdAttribute: z.string().default("data-testid")
10229
+ testIdAttribute: z.string().default("data-testid"),
10230
+ // 0.5: DOM grounding. When the orchestrator has a baseUrl, it
10231
+ // captures the live page (same headless snapshot heal.locator uses)
10232
+ // and threads it in here so the writer generates locators against
10233
+ // REAL elements instead of guessing from the DesignSpec's text hints.
10234
+ // Optional: API tests and no-baseUrl runs omit it and the writer
10235
+ // behaves byte-for-byte as before. Same shape as HealLocatorInput's
10236
+ // pageSnapshot so both paths share the capture infrastructure.
10237
+ pageSnapshot: z.object({
10238
+ url: z.string(),
10239
+ html: z.string().optional(),
10240
+ accessibilityTree: z.unknown().optional()
10241
+ }).optional()
10049
10242
  }),
10050
10243
  testDir: z.string().default("tests")
10051
10244
  });
10245
+ var WRITE_WEB_HTML_MAX_CHARS = 5e4;
10052
10246
  var HEADER_PLACEHOLDERS = {
10053
10247
  workflowId: "<ulid will be filled by runtime>",
10054
10248
  generatedAt: "<iso-8601 will be filled by runtime>",
@@ -10090,7 +10284,39 @@ function detectDisallowedImports(contents) {
10090
10284
  }
10091
10285
  return [...specifiers].sort();
10092
10286
  }
10093
- function buildSystemPrompt12(projectMemory, locatorPreference, knownSelectors, testIdAttribute) {
10287
+ function truncateHtmlForWriting(html, maxChars = WRITE_WEB_HTML_MAX_CHARS) {
10288
+ if (html.length <= maxChars) return html;
10289
+ return html.slice(0, maxChars) + "\n<!-- truncated -->";
10290
+ }
10291
+ function buildPageGroundingBlock(snapshot) {
10292
+ if (snapshot === void 0) return "";
10293
+ const html = snapshot.html ? truncateHtmlForWriting(snapshot.html) : "(no HTML captured)";
10294
+ const a11y = snapshot.accessibilityTree !== void 0 ? JSON.stringify(snapshot.accessibilityTree, null, 2) : "(no accessibility tree captured)";
10295
+ return `
10296
+
10297
+ REAL PAGE ELEMENTS (captured live from ${snapshot.url}):
10298
+ The HTML and accessibility tree below are the ACTUAL target page. Treat them
10299
+ as ground truth, overriding any locator hint in the DesignSpec that conflicts.
10300
+ - Generate every locator against an element you can SEE here. Do NOT invent
10301
+ test-ids, roles, or text that do not appear below.
10302
+ - Prefer a locator that matches EXACTLY ONE element. If the obvious locator
10303
+ matches several (e.g. several elements share the same role+name), narrow it
10304
+ with a containing region (\`getByRole('navigation').getByRole('link', \u2026)\`),
10305
+ a more specific accessible name, or \`.filter({ hasText })\` \u2014 do NOT reach
10306
+ for \`.nth(n)\` or \`.first()\` to paper over an ambiguous match.
10307
+ - If a DesignSpec step needs an element that genuinely is NOT on this page,
10308
+ emit \`test.fixme(...)\` with a comment and add the gap to \`qaiosMdSuggestions\`
10309
+ \u2014 do not guess a locator.
10310
+
10311
+ Page HTML (truncated):
10312
+ \`\`\`html
10313
+ ${html}
10314
+ \`\`\`
10315
+
10316
+ Accessibility tree:
10317
+ ${a11y}`;
10318
+ }
10319
+ function buildSystemPrompt12(projectMemory, locatorPreference, knownSelectors, testIdAttribute, pageSnapshot) {
10094
10320
  const knownSelectorsRendered = knownSelectors.length === 0 ? "(none)" : knownSelectors.join(", ");
10095
10321
  const testIdBlock = !testIdAttribute || testIdAttribute === "data-testid" ? "" : `
10096
10322
  - TEST-ID ATTRIBUTE: this project's test ids use the \`${testIdAttribute}\` attribute,
@@ -10147,7 +10373,7 @@ Project context:
10147
10373
  ${projectMemory}
10148
10374
 
10149
10375
  Locator preference order: ${locatorPreference.join(", ")}
10150
- Known selectors (use these where applicable): ${knownSelectorsRendered}
10376
+ Known selectors (use these where applicable): ${knownSelectorsRendered}${buildPageGroundingBlock(pageSnapshot)}
10151
10377
 
10152
10378
  Output via the \`submit\` tool.`;
10153
10379
  }
@@ -10175,7 +10401,8 @@ var writeWebSkill = {
10175
10401
  ctx.projectMemory,
10176
10402
  input.appContext.locatorPreference,
10177
10403
  input.appContext.knownSelectors,
10178
- input.appContext.testIdAttribute
10404
+ input.appContext.testIdAttribute,
10405
+ input.appContext.pageSnapshot
10179
10406
  ),
10180
10407
  userPrompt: (_ctx, input) => buildUserPrompt12(input),
10181
10408
  confidenceFn: (output, ctx) => {
@@ -10187,7 +10414,21 @@ var writeWebSkill = {
10187
10414
  const scan = scanGeneratedTests(output.files, designedIds);
10188
10415
  const assertionsPresent = scan.specFileCount === 0 || scan.specFilesWithoutAssertions.length === 0 ? 1 : 0.6;
10189
10416
  const idsInContents = scan.unreferencedScenarioIds.length === 0 ? 1 : 0.7;
10190
- return Math.max(0, Math.min(1, reported * noSilentDrops * assertionsPresent * idsInContents));
10417
+ const snapshot = ctx.input.appContext.pageSnapshot;
10418
+ let locatorsVerified = 1;
10419
+ if (snapshot !== void 0) {
10420
+ const allSpecText = output.files.filter((f) => f.kind === "spec").map((f) => f.contents).join("\n");
10421
+ const v = verifyLocatorsAgainstSnapshot(allSpecText, snapshot);
10422
+ const checked = v.okCount + v.problems.length;
10423
+ if (checked > 0 && v.problems.length > 0) {
10424
+ const badRatio = v.problems.length / checked;
10425
+ locatorsVerified = Math.max(0.5, 1 - badRatio);
10426
+ }
10427
+ }
10428
+ return Math.max(
10429
+ 0,
10430
+ Math.min(1, reported * noSilentDrops * assertionsPresent * idsInContents * locatorsVerified)
10431
+ );
10191
10432
  }
10192
10433
  };
10193
10434
  var WriteApiInput = z.object({
@@ -14014,7 +14255,9 @@ function riskHotspotsFromMemory2(mem) {
14014
14255
  var GLYPH6 = {
14015
14256
  ok: "\u2713",
14016
14257
  failed: "\u2717",
14017
- blocked: "\u23F8"};
14258
+ blocked: "\u23F8",
14259
+ warn: "\u26A0"
14260
+ };
14018
14261
  function renderTextLine4(event) {
14019
14262
  switch (event.kind) {
14020
14263
  case "workflow_started":
@@ -14024,8 +14267,13 @@ function renderTextLine4(event) {
14024
14267
  const seconds = (event.durationMs / 1e3).toFixed(1);
14025
14268
  const conf = readConfidence2(event.output);
14026
14269
  const confSuffix = conf !== null ? ` (confidence ${conf.toFixed(2)})` : "";
14027
- return `\u25B8 ${event.skillId.padEnd(22)} ${GLYPH6.ok} ${seconds}s${confSuffix}
14270
+ let line = `\u25B8 ${event.skillId.padEnd(22)} ${GLYPH6.ok} ${seconds}s${confSuffix}
14271
+ `;
14272
+ if (Array.isArray(event.warnings) && event.warnings.length > 0) {
14273
+ line += ` ${GLYPH6.warn} ${event.warnings.join("\n ")}
14028
14274
  `;
14275
+ }
14276
+ return line;
14029
14277
  }
14030
14278
  case "gate_evaluated":
14031
14279
  return event.decision === "pass" ? `\u25B8 gate.${event.gateType.padEnd(16)} ${GLYPH6.ok} passed
@@ -14172,6 +14420,11 @@ ${epSummary}`;
14172
14420
  riskHotspots: riskHotspotsFromMemory2(memory),
14173
14421
  knownSelectors: []
14174
14422
  };
14423
+ if (testType === "web") {
14424
+ const groundEnabled = opts.noGround === true ? false : testCfg?.groundLocators ?? true;
14425
+ if (!groundEnabled) args["noGround"] = true;
14426
+ args["groundCaptureTimeoutMs"] = testCfg?.groundCaptureTimeoutMs ?? 15e3;
14427
+ }
14175
14428
  if (testType === "api" && parsedSpec !== null) {
14176
14429
  args["apiContext"] = {
14177
14430
  baseUrl: parsedSpec.baseUrl,
@@ -14465,7 +14718,10 @@ function buildProgram() {
14465
14718
  "--scope <scope>",
14466
14719
  "design scope: minimal (~3 scenarios) | standard (default) | exhaustive (~15-25 scenarios)",
14467
14720
  "standard"
14468
- ).option("--run", "execute tests immediately after generating").option("--out <path>", "override default test output directory").option("--feature-name <name>", "override auto-detected feature name").option("--yes", "auto-approve any blocking gate for this run (non-interactive)").option(
14721
+ ).option("--run", "execute tests immediately after generating").option(
14722
+ "--no-ground",
14723
+ "skip DOM grounding (do not capture the live page before writing locators)"
14724
+ ).option("--out <path>", "override default test output directory").option("--feature-name <name>", "override auto-detected feature name").option("--yes", "auto-approve any blocking gate for this run (non-interactive)").option(
14469
14725
  "--workflow <id>",
14470
14726
  "resume a prior workflow interrupted mid-run instead of starting fresh"
14471
14727
  ).action(async (description, cmdOpts, command) => {
@@ -14481,6 +14737,7 @@ function buildProgram() {
14481
14737
  };
14482
14738
  if (typeof cmdOpts["apiSpec"] === "string") opts.apiSpec = cmdOpts["apiSpec"];
14483
14739
  if (typeof cmdOpts["baseUrl"] === "string") opts.baseUrl = cmdOpts["baseUrl"];
14740
+ if (cmdOpts["ground"] === false) opts.noGround = true;
14484
14741
  if (typeof cmdOpts["out"] === "string") opts.out = cmdOpts["out"];
14485
14742
  if (typeof cmdOpts["featureName"] === "string") opts.featureName = cmdOpts["featureName"];
14486
14743
  if (cmdOpts["yes"] === true) opts.yes = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qatonic_innovations/qaios",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
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",