@quikit/quikpilot-runner 8.2.6 → 8.2.8

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/dist/index.js CHANGED
@@ -73,10 +73,11 @@ async function describeQuikTrackFailure(res) {
73
73
 
74
74
  // src/callback.ts
75
75
  async function reportBack(params) {
76
- const { callbackUrl, runToken, status, branch, warnings, targeting, baselineFindings } = params;
76
+ const { callbackUrl, runToken, status, branch, warnings, targeting, baselineFindings, unresolved } = params;
77
77
  const body = { status };
78
78
  if (branch) body.branch = branch;
79
79
  if (warnings && warnings.length > 0) body.warnings = warnings;
80
+ if (unresolved && unresolved.length > 0) body.unresolved = unresolved;
80
81
  if (targeting) body.targeting = targeting;
81
82
  if (baselineFindings) body.baselineFindings = baselineFindings;
82
83
  const res = await fetch(callbackUrl, {
@@ -250,6 +251,16 @@ function findDeniedContextPath(contextPaths, patterns) {
250
251
  return null;
251
252
  }
252
253
 
254
+ // src/runtimeReplyError.ts
255
+ var RuntimeReplyError = class extends Error {
256
+ summary;
257
+ constructor(message, summary) {
258
+ super(message);
259
+ this.name = "RuntimeReplyError";
260
+ this.summary = summary;
261
+ }
262
+ };
263
+
253
264
  // src/envelope.ts
254
265
  var HARD_FAILURE_CODES = /* @__PURE__ */ new Set(["SEMANTIC_MISMATCH", "CONTRACT_REJECTED"]);
255
266
  function checkHardFailure(envelope, useCase, target) {
@@ -257,17 +268,25 @@ function checkHardFailure(envelope, useCase, target) {
257
268
  if (typeof code === "string" && HARD_FAILURE_CODES.has(code)) {
258
269
  const detail = envelope.error ?? envelope.normalizedText ?? "no detail";
259
270
  const where = target ? ` for ${target}` : "";
260
- throw new Error(`${useCase} failed${where} (${code}, not retried): ${detail}`);
271
+ throw new RuntimeReplyError(
272
+ `${useCase} failed${where} (${code}, not retried): ${detail}`,
273
+ `${useCase} failed${where} (${code}, not retried)`
274
+ );
261
275
  }
262
276
  }
263
277
  function unwrapExecute(envelope, useCase) {
264
278
  if (envelope?.success === false) {
265
- throw new Error(
266
- `${useCase} failed: ${envelope.errorCode ?? "UNKNOWN"} \u2014 ${envelope.error ?? envelope.normalizedText ?? "no message"}`
279
+ const code = envelope.errorCode ?? "UNKNOWN";
280
+ throw new RuntimeReplyError(
281
+ `${useCase} failed: ${code} \u2014 ${envelope.error ?? envelope.normalizedText ?? "no message"}`,
282
+ `${useCase} failed: ${code}`
267
283
  );
268
284
  }
269
285
  if (!envelope?.structuredJson || typeof envelope.structuredJson !== "object") {
270
- throw new Error(`${useCase} returned no structuredJson: ${JSON.stringify(envelope)}`);
286
+ throw new RuntimeReplyError(
287
+ `${useCase} returned no structuredJson: ${JSON.stringify(envelope)}`,
288
+ `${useCase} returned no structuredJson`
289
+ );
271
290
  }
272
291
  return envelope.structuredJson;
273
292
  }
@@ -419,7 +438,7 @@ function narrowJudgedByForTarget(pool, targets, codeTargetPath) {
419
438
  function toWireJudgedBy(pool) {
420
439
  return pool.map(({ automationId: _automationId, ...rest }) => rest);
421
440
  }
422
- async function runCodegen(gateway, targets, workspaceListing, requiredTests, deniedPaths, judgedByPool, warnings, contextLog) {
441
+ async function runCodegen(gateway, targets, workspaceListing, requiredTests, deniedPaths, judgedByPool, warnings, contextLog, outOfScope = []) {
423
442
  const results = [];
424
443
  const listing = new Set(workspaceListing);
425
444
  for (const target of targets) {
@@ -468,6 +487,9 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
468
487
  if (deniedPaths.length > 0) {
469
488
  contextData.deniedPaths = deniedPaths;
470
489
  }
490
+ if (outOfScope.length > 0) {
491
+ contextData.outOfScope = outOfScope;
492
+ }
471
493
  if (target.automationIds && target.automationIds.length > 0) {
472
494
  const wanted = new Set(target.automationIds);
473
495
  contextData.requiredTests = requiredTests.filter((tc) => wanted.has(tc.automationId));
@@ -521,16 +543,23 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
521
543
  }
522
544
  if (typeof envelope?.errorCode === "string" && NOT_RETRIED_CODES.has(envelope.errorCode)) {
523
545
  const detail = envelope.error ? `: ${envelope.error}` : "";
524
- throw new Error(`Codegen refused ${target.path}: ${envelope.errorCode} (not retried)${detail}`);
546
+ throw new RuntimeReplyError(
547
+ `Codegen refused ${target.path}: ${envelope.errorCode} (not retried)${detail}`,
548
+ `Codegen refused ${target.path}: ${envelope.errorCode} (not retried)`
549
+ );
525
550
  }
526
551
  if (envelope?.errorCode === JUDGED_BY_TOO_LARGE_CODE) {
527
552
  const detail = envelope.error ? `: ${envelope.error}` : "";
528
- throw new Error(`Codegen refused ${target.path}: ${envelope.errorCode} (after narrowing)${detail}`);
553
+ throw new RuntimeReplyError(
554
+ `Codegen refused ${target.path}: ${envelope.errorCode} (after narrowing)${detail}`,
555
+ `Codegen refused ${target.path}: ${envelope.errorCode} (after narrowing)`
556
+ );
529
557
  }
530
558
  const body = unwrapExecute(envelope, "pilot.codegen");
531
559
  if (body.outcome !== "GENERATED" || typeof body.newContent !== "string") {
532
- throw new Error(
533
- `Codegen refused ${target.path}${body.refusalReason ? `: ${body.refusalReason}` : ""}`
560
+ throw new RuntimeReplyError(
561
+ `Codegen refused ${target.path}${body.refusalReason ? `: ${body.refusalReason}` : ""}`,
562
+ `Codegen refused ${target.path}`
534
563
  );
535
564
  }
536
565
  if (!isNewFile) {
@@ -1038,14 +1067,27 @@ var MAX_FAILURE_TEXT = 500;
1038
1067
  function unescapeXmlEntities(s) {
1039
1068
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
1040
1069
  }
1070
+ var FAILURE_FILE_LINE_RE = /^[ \t]*File:[ \t]*(\S[^\r\n]*?:\d+(?::\d+)?)[ \t]*$/m;
1071
+ var MAX_FAILURE_LOCATION = 300;
1072
+ function parseFailureLocation(body) {
1073
+ const found = FAILURE_FILE_LINE_RE.exec(unescapeXmlEntities(body));
1074
+ if (!found) return null;
1075
+ const path = normalizeSlashes(found[1]);
1076
+ const root = `${normalizeSlashes(process.cwd()).replace(/\/+$/, "")}/`;
1077
+ const shown = path.startsWith(root) ? path.slice(root.length) : path;
1078
+ return shown.length > MAX_FAILURE_LOCATION ? `${shown.slice(0, MAX_FAILURE_LOCATION)}\u2026` : shown;
1079
+ }
1041
1080
  function extractFailureText(testcaseBody) {
1042
1081
  const tag = FAILURE_TAG_RE.exec(testcaseBody);
1043
1082
  if (!tag) return null;
1044
1083
  const [, attrs = "", inner = ""] = tag;
1045
- const raw = (FAILURE_MESSAGE_ATTR_RE.exec(attrs)?.[1] ?? inner).trim();
1084
+ const messageAttr = FAILURE_MESSAGE_ATTR_RE.exec(attrs)?.[1];
1085
+ const raw = (messageAttr ?? inner).trim();
1046
1086
  if (!raw) return null;
1047
1087
  const text = unescapeXmlEntities(raw);
1048
- return text.length > MAX_FAILURE_TEXT ? `${text.slice(0, MAX_FAILURE_TEXT)}\u2026` : text;
1088
+ const shortened = text.length > MAX_FAILURE_TEXT ? `${text.slice(0, MAX_FAILURE_TEXT)}\u2026` : text;
1089
+ const location = messageAttr === void 0 ? null : parseFailureLocation(inner);
1090
+ return location ? `${shortened} [at ${location}]` : shortened;
1049
1091
  }
1050
1092
  var RESULT_RANK = { pass: 0, skipped: 1, fail: 2 };
1051
1093
  function parseJUnitOutcomes(xml) {
@@ -1347,6 +1389,36 @@ async function runBaselineCheck(params) {
1347
1389
  }
1348
1390
  }
1349
1391
 
1392
+ // src/failureReason.ts
1393
+ var MAX_FAILURE_REASON = 1e3;
1394
+ var MIN_SECRET_LENGTH = 8;
1395
+ var CREDENTIAL_PATTERNS = [
1396
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g,
1397
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
1398
+ /x-access-token:[^@\s'"]+/gi,
1399
+ // Only the header form ("Authorization: basic <token>"), or a bearer value that contains a digit: a bare
1400
+ // "basic authentication" or "bearer of the news" is prose, not a credential.
1401
+ /\bauthorization:\s*(?:basic|bearer|token)\s+[^\s'"]{8,}/gi,
1402
+ /\bbearer\s+(?=[A-Za-z0-9._~+/=-]*\d)[A-Za-z0-9._~+/=-]{12,}/gi,
1403
+ // A signed JWT is three dot-separated segments; QuikPilot's own run token is an encrypted JWT (alg dir) of five, the
1404
+ // second one empty. The run token is also matched by exact value, but one that has expired or rotated is not the
1405
+ // current value, so its shape has to catch it.
1406
+ /\beyJ[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]*){2,4}/g
1407
+ ];
1408
+ function failureSummary(err) {
1409
+ if (err instanceof RuntimeReplyError) return err.summary;
1410
+ return err instanceof Error ? err.message : String(err);
1411
+ }
1412
+ function describeFailureReason(message, secrets = []) {
1413
+ let text = message;
1414
+ for (const secret of secrets) {
1415
+ if (secret.length >= MIN_SECRET_LENGTH) text = text.split(secret).join("***");
1416
+ }
1417
+ for (const pattern of CREDENTIAL_PATTERNS) text = text.replace(pattern, "***");
1418
+ text = text.replace(/\s*\n\s*/g, " | ").trim();
1419
+ return text.length > MAX_FAILURE_REASON ? `${text.slice(0, MAX_FAILURE_REASON)}\u2026` : text;
1420
+ }
1421
+
1350
1422
  // src/scopeCheck.ts
1351
1423
  var import_node_fs7 = require("node:fs");
1352
1424
  var ROUTE_METHOD_RE = /\b(GET|POST|PUT|PATCH|DELETE)\b/i;
@@ -1461,7 +1533,7 @@ function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
1461
1533
  );
1462
1534
  }
1463
1535
  }
1464
- async function runTargeting(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery) {
1536
+ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope = []) {
1465
1537
  const contextData = {
1466
1538
  // NESTED, NOT FLATTENED — a second vocabulary for the design shape is exactly what
1467
1539
  // QUIKTR-1034 cost.
@@ -1476,6 +1548,9 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
1476
1548
  if (deniedPaths.length > 0) {
1477
1549
  contextData.deniedPaths = deniedPaths;
1478
1550
  }
1551
+ if (outOfScope.length > 0) {
1552
+ contextData.outOfScope = outOfScope;
1553
+ }
1479
1554
  contextData.testDiscovery = testDiscovery;
1480
1555
  const res = await fetchWithRetry(`${gateway.baseUrl}/ai/execute`, {
1481
1556
  method: "POST",
@@ -1498,7 +1573,10 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
1498
1573
  return { outcome: "NO_TARGET", reason: body.noTargetReason ?? null };
1499
1574
  }
1500
1575
  if (body.outcome !== "TARGETED" || !Array.isArray(body.targets)) {
1501
- throw new Error(`pilot.targeting returned an unexpected shape: ${JSON.stringify(body)}`);
1576
+ throw new RuntimeReplyError(
1577
+ `pilot.targeting returned an unexpected shape: ${JSON.stringify(body)}`,
1578
+ "pilot.targeting returned an unexpected shape"
1579
+ );
1502
1580
  }
1503
1581
  const listing = new Set(workspaceListing);
1504
1582
  for (const target of body.targets) {
@@ -1677,6 +1755,13 @@ async function run(env2) {
1677
1755
  let branch;
1678
1756
  let runToken;
1679
1757
  const warnings = [];
1758
+ const failureReasons = [];
1759
+ const recordFailure = (err) => {
1760
+ const message = err instanceof Error ? err.message : String(err);
1761
+ console.error(message);
1762
+ const reason = describeFailureReason(failureSummary(err), runToken ? [runToken] : []);
1763
+ if (reason) failureReasons.push(reason);
1764
+ };
1680
1765
  let targeted = false;
1681
1766
  let generatedFilePaths = [];
1682
1767
  let targetingResult;
@@ -1687,6 +1772,7 @@ async function run(env2) {
1687
1772
  packet = await fetchPacket(env2.packetUrl, runToken, env2.githubRunId);
1688
1773
  checkoutTargetBranch(packet.targetBranch);
1689
1774
  const workspaceListing = listWorkspaceFiles(packet.workspaceRoot);
1775
+ const outOfScope = (packet.outOfScope ?? []).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1690
1776
  const targeting = await runTargeting(
1691
1777
  packet.gateway,
1692
1778
  packet.design,
@@ -1694,7 +1780,8 @@ async function run(env2) {
1694
1780
  workspaceListing,
1695
1781
  packet.requiredTests,
1696
1782
  packet.guardrails.deniedPaths,
1697
- packet.testDiscovery
1783
+ packet.testDiscovery,
1784
+ outOfScope
1698
1785
  );
1699
1786
  targetingResult = targeting;
1700
1787
  if (targeting.outcome === "NO_TARGET") {
@@ -1742,7 +1829,8 @@ async function run(env2) {
1742
1829
  packet.guardrails.deniedPaths,
1743
1830
  judgedByPool,
1744
1831
  warnings,
1745
- contextLog
1832
+ contextLog,
1833
+ outOfScope
1746
1834
  )
1747
1835
  ]);
1748
1836
  const depCheck = checkImportDependencies(
@@ -1805,7 +1893,7 @@ async function run(env2) {
1805
1893
  }
1806
1894
  } catch (err) {
1807
1895
  hardFailure = true;
1808
- console.error(err instanceof Error ? err.message : String(err));
1896
+ recordFailure(err);
1809
1897
  }
1810
1898
  if (!runToken) {
1811
1899
  console.error("No run token \u2014 the OIDC exchange failed before QuikTrack could be reached.");
@@ -1842,7 +1930,7 @@ async function run(env2) {
1842
1930
  branch = result.branch;
1843
1931
  } catch (err) {
1844
1932
  hardFailure = true;
1845
- console.error(err instanceof Error ? err.message : String(err));
1933
+ recordFailure(err);
1846
1934
  }
1847
1935
  }
1848
1936
  await reportBack({
@@ -1853,7 +1941,8 @@ async function run(env2) {
1853
1941
  warnings,
1854
1942
  // Omitted when targeting itself never produced a result (nothing exists to record).
1855
1943
  targeting: targetingResult ? buildTargetingRecord(targetingResult, contextLog) : void 0,
1856
- baselineFindings
1944
+ baselineFindings,
1945
+ unresolved: failureReasons.length > 0 ? failureReasons : void 0
1857
1946
  });
1858
1947
  return { hardFailure };
1859
1948
  }