playwright-intelligence-engine 2.0.4 → 2.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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/core/version.ts
2
2
  import { execFileSync } from "child_process";
3
- var PACKAGE_VERSION = "2.0.4";
4
- var ENGINE_MILESTONE = "2.0.4";
3
+ var PACKAGE_VERSION = "2.1.0";
4
+ var ENGINE_MILESTONE = "2.1.0";
5
5
  var PROOF_VERSION = ENGINE_MILESTONE;
6
6
  function getRuntimeProvenance(root = process.cwd()) {
7
7
  try {
@@ -153,8 +153,8 @@ var UnsupportedTraceVersionError = class extends PlaywrightIntelligenceError {
153
153
  }
154
154
  };
155
155
  var TraceCorruptedError = class extends PlaywrightIntelligenceError {
156
- constructor(path32, reason) {
157
- super(`Trace archive at ${path32} is corrupted: ${reason}`, "TRACE_CORRUPTED");
156
+ constructor(path33, reason) {
157
+ super(`Trace archive at ${path33} is corrupted: ${reason}`, "TRACE_CORRUPTED");
158
158
  this.name = "TraceCorruptedError";
159
159
  }
160
160
  };
@@ -244,16 +244,23 @@ var ArchiveReader = class _ArchiveReader {
244
244
  const text = entry.getData().toString("utf8");
245
245
  const lines = text.split(/\r?\n/);
246
246
  let failureScore = 0;
247
+ let hasRealError = false;
247
248
  for (const line of lines) {
248
249
  if (!line.includes('"type":"')) continue;
249
- if (line.includes('"type":"after"') && line.includes('"error"')) failureScore += 100;
250
- if (line.includes('"type":"error"') && (line.includes('"message"') || line.includes('"error"'))) failureScore += 80;
250
+ if (line.includes('"type":"after"') && line.includes('"error"')) {
251
+ failureScore += 100;
252
+ hasRealError = true;
253
+ }
254
+ if (line.includes('"type":"error"') && (line.includes('"message"') || line.includes('"error"'))) {
255
+ failureScore += 80;
256
+ hasRealError = true;
257
+ }
251
258
  if (line.includes('"type":"log"')) failureScore += 1;
252
259
  }
253
260
  if (path.basename(entry.entryName).toLowerCase() === "test.trace" && failureScore > 0) {
254
261
  failureScore += 10;
255
262
  }
256
- return { entry, failureScore };
263
+ return { entry, failureScore, hasRealError };
257
264
  }).sort((a, b) => b.failureScore - a.failureScore || b.entry.header.size - a.entry.header.size);
258
265
  const orderedTraceNames = traceCandidates.map(({ entry }) => entry.entryName);
259
266
  const bestTrace = orderedTraceNames[0];
@@ -272,11 +279,16 @@ var ArchiveReader = class _ArchiveReader {
272
279
  const prefixMatch = networkCandidates.find((e) => path.basename(e.entryName, ".network").startsWith(workerPrefix));
273
280
  bestNetwork = prefixMatch ? prefixMatch.entryName : networkCandidates.sort((a, b) => b.header.size - a.header.size)[0]?.entryName;
274
281
  }
282
+ const topCandidate = traceCandidates[0];
283
+ const hasFailure = Boolean(topCandidate?.hasRealError);
284
+ const topFailureScore = topCandidate?.failureScore ?? 0;
275
285
  return {
276
286
  traceEntryName: bestTrace,
277
287
  networkEntryName: bestNetwork,
278
288
  traceEntryNames: orderedTraceNames,
279
- networkEntryNames
289
+ networkEntryNames,
290
+ hasFailure,
291
+ failureScore: topFailureScore
280
292
  };
281
293
  } catch {
282
294
  return {};
@@ -519,13 +531,25 @@ var SnapshotResolver = class _SnapshotResolver {
519
531
  const role = attributes["role"] ?? _SnapshotResolver.inferRoleFromTag(tag, attributes);
520
532
  const inputValue = tag === "input" ? attributes["value"] ?? attributes["__playwright_value_"] : void 0;
521
533
  const accessibleName = attributes["aria-label"] ?? attributes["alt"] ?? attributes["title"] ?? attributes["placeholder"] ?? inputValue ?? textAccumulator.trim();
534
+ let boundingBox;
535
+ const boxAttr = attributes["data-bbox"] ?? attributes["__playwright_box_"] ?? attributes["data-box"];
536
+ if (boxAttr) {
537
+ try {
538
+ const parts = boxAttr.split(",").map(Number);
539
+ if (parts.length === 4 && parts.every((p) => !isNaN(p))) {
540
+ boundingBox = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
541
+ }
542
+ } catch {
543
+ }
544
+ }
522
545
  return {
523
546
  tag,
524
547
  attributes,
525
548
  text: textAccumulator.trim(),
526
549
  children,
527
550
  role,
528
- accessibleName: accessibleName || void 0
551
+ accessibleName: accessibleName || void 0,
552
+ boundingBox
529
553
  };
530
554
  }
531
555
  /**
@@ -598,13 +622,25 @@ var SnapshotResolver = class _SnapshotResolver {
598
622
  const role = attributes["role"] ?? _SnapshotResolver.inferRoleFromTag(tag, attributes);
599
623
  const inputValue = tag === "input" ? attributes["value"] ?? attributes["__playwright_value_"] : void 0;
600
624
  const accessibleName = attributes["aria-label"] ?? attributes["alt"] ?? attributes["title"] ?? attributes["placeholder"] ?? inputValue ?? textAccumulator.trim();
625
+ let boundingBox;
626
+ const boxAttr = attributes["data-bbox"] ?? attributes["__playwright_box_"] ?? attributes["data-box"];
627
+ if (boxAttr) {
628
+ try {
629
+ const parts = boxAttr.split(",").map(Number);
630
+ if (parts.length === 4 && parts.every((p) => !isNaN(p))) {
631
+ boundingBox = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
632
+ }
633
+ } catch {
634
+ }
635
+ }
601
636
  return {
602
637
  tag,
603
638
  attributes,
604
639
  text: textAccumulator.trim(),
605
640
  children,
606
641
  role,
607
- accessibleName: accessibleName || void 0
642
+ accessibleName: accessibleName || void 0,
643
+ boundingBox
608
644
  };
609
645
  }
610
646
  /**
@@ -3395,6 +3431,12 @@ var LocatorDisambiguationEngine = class {
3395
3431
  return landmarkResult;
3396
3432
  }
3397
3433
  }
3434
+ if (context.targetBoundingBox) {
3435
+ const spatialResult = this.attemptSpatialDisambiguation(candidates, context.targetBoundingBox, pageProperty);
3436
+ if (spatialResult) {
3437
+ return spatialResult;
3438
+ }
3439
+ }
3398
3440
  const hasExplicitPositionalIntent = this.verifyExplicitPositionalIntent(context);
3399
3441
  if (hasExplicitPositionalIntent) {
3400
3442
  const candidate = candidates[0];
@@ -3439,10 +3481,10 @@ var LocatorDisambiguationEngine = class {
3439
3481
  if (!context.rootDom) return null;
3440
3482
  const candidatePaths = candidates.map((c) => this.findPathToElement(context.rootDom, c));
3441
3483
  for (let cIdx = 0; cIdx < candidates.length; cIdx++) {
3442
- const path32 = candidatePaths[cIdx];
3443
- if (!path32 || path32.length < 2) continue;
3444
- for (let i = path32.length - 2; i >= 0; i--) {
3445
- const container = path32[i];
3484
+ const path33 = candidatePaths[cIdx];
3485
+ if (!path33 || path33.length < 2) continue;
3486
+ for (let i = path33.length - 2; i >= 0; i--) {
3487
+ const container = path33[i];
3446
3488
  const isContainer = container.tag === "tr" || container.tag === "li" || container.tag === "article" || container.tag === "section" || (container.attributes["data-testid"] ?? "").includes("card") || (container.attributes["data-testid"] ?? "").includes("item") || (container.attributes["class"] ?? "").includes("card") || (container.attributes["class"] ?? "").includes("item") || container.role === "row" || container.role === "listitem";
3447
3489
  if (isContainer) {
3448
3490
  const containerText = this.getCleanText(container);
@@ -3485,8 +3527,8 @@ var LocatorDisambiguationEngine = class {
3485
3527
  for (const landmark of landmarks) {
3486
3528
  if (textToCheck.includes(landmark)) {
3487
3529
  const candidatesInLandmark = candidates.filter((candidate) => {
3488
- const path32 = this.findPathToElement(context.rootDom, candidate);
3489
- return Boolean(path32 && path32.some((el) => el.tag === landmark || el.role === landmark));
3530
+ const path33 = this.findPathToElement(context.rootDom, candidate);
3531
+ return Boolean(path33 && path33.some((el) => el.tag === landmark || el.role === landmark));
3490
3532
  });
3491
3533
  if (candidatesInLandmark.length === 1) {
3492
3534
  const candidate = candidatesInLandmark[0];
@@ -3546,6 +3588,35 @@ var LocatorDisambiguationEngine = class {
3546
3588
  }
3547
3589
  return null;
3548
3590
  }
3591
+ static attemptSpatialDisambiguation(candidates, targetBox, pageProperty) {
3592
+ const candidatesWithBoxes = candidates.filter((c) => c.boundingBox !== void 0);
3593
+ if (candidatesWithBoxes.length === 0) return void 0;
3594
+ const targetCenterX = targetBox.x + targetBox.width / 2;
3595
+ const targetCenterY = targetBox.y + targetBox.height / 2;
3596
+ const scored = candidatesWithBoxes.map((c) => {
3597
+ const box = c.boundingBox;
3598
+ const candCenterX = box.x + box.width / 2;
3599
+ const candCenterY = box.y + box.height / 2;
3600
+ const distance = Math.hypot(targetCenterX - candCenterX, targetCenterY - candCenterY);
3601
+ const sizeDiff = Math.abs(targetBox.width - box.width) + Math.abs(targetBox.height - box.height);
3602
+ return { candidate: c, distance, sizeDiff, totalScore: distance + sizeDiff * 0.5 };
3603
+ }).sort((a, b) => a.totalScore - b.totalScore);
3604
+ const best = scored[0];
3605
+ const secondBest = scored[1];
3606
+ if (best && (!secondBest || secondBest.distance - best.distance > 20 || best.distance < 15)) {
3607
+ const baseExpr = CandidateExtractor.generateLocator(best.candidate, pageProperty);
3608
+ return {
3609
+ strategy: "SPATIAL_COORDINATE_MATCH",
3610
+ resolvedCandidate: best.candidate,
3611
+ resolvedExpression: baseExpr,
3612
+ matchCount: candidates.length,
3613
+ explanation: `Disambiguated via spatial layout coordinates: candidate element aligns with target coordinates within ${best.distance.toFixed(1)}px distance.`,
3614
+ confidence: 0.94,
3615
+ isUnique: true
3616
+ };
3617
+ }
3618
+ return void 0;
3619
+ }
3549
3620
  static getCleanText(el) {
3550
3621
  if (el.text && el.text.trim().length > 0) {
3551
3622
  return el.text.trim();
@@ -3562,7 +3633,7 @@ var LocatorDisambiguationEngine = class {
3562
3633
 
3563
3634
  // src/selector/repair-candidate-ranker.ts
3564
3635
  var RepairCandidateRanker = class {
3565
- static rank(candidates, originalSelector, intendedAction, intelligence, rootDom, testIntentText) {
3636
+ static rank(candidates, originalSelector, intendedAction, intelligence, rootDom, testIntentText, targetBoundingBox) {
3566
3637
  const projectContext = intelligence?.profile ? {
3567
3638
  preferredLocator: intelligence.profile.locatorConventions.preferred,
3568
3639
  locatorUsage: {
@@ -3575,7 +3646,7 @@ var RepairCandidateRanker = class {
3575
3646
  } : void 0;
3576
3647
  const disambiguation = candidates.length > 1 ? LocatorDisambiguationEngine.disambiguate(
3577
3648
  candidates,
3578
- { originalSelector, intendedAction, rootDom, testIntentText },
3649
+ { originalSelector, intendedAction, rootDom, testIntentText, targetBoundingBox },
3579
3650
  "page"
3580
3651
  ) : void 0;
3581
3652
  return candidates.flatMap((candidate) => {
@@ -4054,14 +4125,14 @@ var EvidenceRedactor = class _EvidenceRedactor {
4054
4125
  static redactPayload(payload) {
4055
4126
  const redactedFields = [];
4056
4127
  const rulesApplied = [];
4057
- function recurse(value, path32) {
4128
+ function recurse(value, path33) {
4058
4129
  if (value === null || value === void 0) {
4059
4130
  return value;
4060
4131
  }
4061
4132
  if (typeof value === "string") {
4062
4133
  const { redactedText, rulesApplied: applied } = _EvidenceRedactor.redactText(value);
4063
4134
  if (applied.length > 0) {
4064
- redactedFields.push(path32);
4135
+ redactedFields.push(path33);
4065
4136
  for (const rule of applied) {
4066
4137
  if (!rulesApplied.includes(rule)) rulesApplied.push(rule);
4067
4138
  }
@@ -4069,12 +4140,12 @@ var EvidenceRedactor = class _EvidenceRedactor {
4069
4140
  return redactedText;
4070
4141
  }
4071
4142
  if (Array.isArray(value)) {
4072
- return value.map((item, idx) => recurse(item, `${path32}[${idx}]`));
4143
+ return value.map((item, idx) => recurse(item, `${path33}[${idx}]`));
4073
4144
  }
4074
4145
  if (typeof value === "object") {
4075
4146
  const result = {};
4076
4147
  for (const [k, v] of Object.entries(value)) {
4077
- const currentPath = path32 ? `${path32}.${k}` : k;
4148
+ const currentPath = path33 ? `${path33}.${k}` : k;
4078
4149
  const isSensitiveField = _EvidenceRedactor.SENSITIVE_FIELD_PATTERNS.some((p) => p.test(k));
4079
4150
  if (isSensitiveField) {
4080
4151
  result[k] = "[REDACTED]";
@@ -7934,6 +8005,19 @@ async function listSourceFiles2(root, depth = 0) {
7934
8005
  function normalize(value) {
7935
8006
  return value.replace(/\s+/g, "").replace(/;$/, "");
7936
8007
  }
8008
+ function extractInternalSelectorValues(selector) {
8009
+ const values = [];
8010
+ if (!selector.startsWith("internal:")) return values;
8011
+ const quoted = selector.matchAll(/(?:name|text|label|testid|placeholder)=["']([^"']+)["']/gi);
8012
+ for (const m of quoted) {
8013
+ if (m[1]) values.push(m[1]);
8014
+ }
8015
+ const unquoted = selector.matchAll(/(?:name|text|label|testid|placeholder)=([^\]\s]+)/gi);
8016
+ for (const m of unquoted) {
8017
+ if (m[1] && !m[1].startsWith('"') && !m[1].startsWith("'")) values.push(m[1].replace(/i$/, ""));
8018
+ }
8019
+ return values;
8020
+ }
7937
8021
  function selectorMatches(selector, locator) {
7938
8022
  const wanted = normalize(selector);
7939
8023
  const source = normalize(locator.selectorText);
@@ -7941,6 +8025,13 @@ function selectorMatches(selector, locator) {
7941
8025
  if (source.includes(wanted)) {
7942
8026
  return { score: 100, evidence: ["Failed selector expression matched a Page Object locator expression"] };
7943
8027
  }
8028
+ const internalValues = extractInternalSelectorValues(selector);
8029
+ for (const val of internalValues) {
8030
+ const normVal = normalize(val);
8031
+ if (normVal && source.includes(normVal)) {
8032
+ return { score: 95, evidence: [`Failed internal selector value '${val}' matched a Page Object locator expression`] };
8033
+ }
8034
+ }
7944
8035
  const argumentMatch = source.match(/(?:locator|getByRole|getByText|getByTestId|getByLabel|getByPlaceholder|getByAltText|getByTitle)\((.*)\)/);
7945
8036
  if (argumentMatch?.[1] && (argumentMatch[1].includes(wanted) || wanted.includes(argumentMatch[1]))) {
7946
8037
  return { score: 80, evidence: ["Failed selector value matched a Page Object locator argument"] };
@@ -7964,8 +8055,8 @@ function resolveTestSourcePath(root, sourceFiles, sourceFile) {
7964
8055
  return wanted.endsWith(`/${candidate}`) || candidate.endsWith(`/${wanted}`) || candidate === wanted;
7965
8056
  });
7966
8057
  if (suffixMatches.length === 1 && suffixMatches[0]) return { path: suffixMatches[0], remapped: true };
7967
- const basename = path14.basename(sourceFile).toLowerCase();
7968
- const basenameMatches = sourceFiles.filter((file) => path14.basename(file).toLowerCase() === basename);
8058
+ const basename2 = path14.basename(sourceFile).toLowerCase();
8059
+ const basenameMatches = sourceFiles.filter((file) => path14.basename(file).toLowerCase() === basename2);
7969
8060
  return basenameMatches.length === 1 && basenameMatches[0] ? { path: basenameMatches[0], remapped: true } : { remapped: false };
7970
8061
  }
7971
8062
  async function discoverRepairSourceMapping(input) {
@@ -7984,7 +8075,9 @@ async function discoverRepairSourceMapping(input) {
7984
8075
  } catch {
7985
8076
  continue;
7986
8077
  }
7987
- if (!normalize(sourceText).includes(normalizedSelector)) continue;
8078
+ const internalValues = extractInternalSelectorValues(selector);
8079
+ const hasMatchInSource = normalize(sourceText).includes(normalizedSelector) || internalValues.some((val) => normalize(sourceText).includes(normalize(val)));
8080
+ if (!hasMatchInSource) continue;
7988
8081
  const result = await new StaticAstCollector().collect({ targetFilePath: filePath });
7989
8082
  const payload = result[0]?.payload;
7990
8083
  if (!payload?.isPageObject || !payload.locators) continue;
@@ -15265,6 +15358,353 @@ async function runAutonomousRepairWorkflow(input) {
15265
15358
  };
15266
15359
  }
15267
15360
 
15361
+ // src/intelligence/batch-repair-workflow.ts
15362
+ import fs27 from "fs/promises";
15363
+ import fsSync5 from "fs";
15364
+ import path26 from "path";
15365
+ import crypto9 from "crypto";
15366
+ function discoverFailingTraces(workspaceRoot) {
15367
+ const candidateDirs = [
15368
+ path26.resolve(workspaceRoot, "test-results"),
15369
+ path26.resolve(workspaceRoot, "playwright-report"),
15370
+ path26.resolve(workspaceRoot)
15371
+ ];
15372
+ const found = [];
15373
+ const seen = /* @__PURE__ */ new Set();
15374
+ for (const dir of candidateDirs) {
15375
+ if (!fsSync5.existsSync(dir)) continue;
15376
+ try {
15377
+ const entries = fsSync5.readdirSync(dir, { recursive: true });
15378
+ for (const entry of entries) {
15379
+ const fullPath = path26.join(dir, String(entry));
15380
+ if (fullPath.endsWith(".zip") && !seen.has(fullPath)) {
15381
+ try {
15382
+ const stat = fsSync5.statSync(fullPath);
15383
+ if (stat.isFile()) {
15384
+ seen.add(fullPath);
15385
+ let hasFailure = false;
15386
+ try {
15387
+ const discovery = ArchiveReader.findTraceFileEntries(fullPath);
15388
+ hasFailure = Boolean(discovery.hasFailure);
15389
+ } catch {
15390
+ }
15391
+ const isTestResults = fullPath.includes("test-results");
15392
+ found.push({ filePath: fullPath, mtime: stat.mtimeMs, hasFailure, isTestResults });
15393
+ }
15394
+ } catch {
15395
+ }
15396
+ }
15397
+ }
15398
+ } catch {
15399
+ }
15400
+ }
15401
+ const testResultsFailing = found.filter((f) => f.isTestResults && f.hasFailure);
15402
+ const failing = testResultsFailing.length > 0 ? testResultsFailing : found.filter((f) => f.hasFailure);
15403
+ const pool = failing.length > 0 ? failing : found;
15404
+ return pool.sort((a, b) => {
15405
+ if (a.hasFailure !== b.hasFailure) return a.hasFailure ? -1 : 1;
15406
+ if (a.isTestResults !== b.isTestResults) return a.isTestResults ? -1 : 1;
15407
+ return b.mtime - a.mtime;
15408
+ }).map((f) => f.filePath);
15409
+ }
15410
+ async function runBatchRepairWorkflow(input) {
15411
+ const workspaceRoot = path26.resolve(input.workspaceRoot ?? process.cwd());
15412
+ const tracePaths = input.tracePaths && input.tracePaths.length > 0 ? input.tracePaths.map((p) => path26.resolve(workspaceRoot, p)) : discoverFailingTraces(workspaceRoot);
15413
+ if (tracePaths.length === 0) {
15414
+ return {
15415
+ status: "ABSTAINED",
15416
+ totalTraces: 0,
15417
+ repairedCount: 0,
15418
+ abstainedCount: 0,
15419
+ failedCount: 0,
15420
+ items: [],
15421
+ clusters: [],
15422
+ executionPassed: true,
15423
+ promotedFiles: []
15424
+ };
15425
+ }
15426
+ const items = [];
15427
+ const failureObservations = [];
15428
+ const intelligence = await bootstrapProjectIntelligence(workspaceRoot);
15429
+ for (const tracePath of tracePaths) {
15430
+ try {
15431
+ const proposalRun = await runTraceProposalWorkflow({
15432
+ tracePath,
15433
+ workspaceRoot,
15434
+ enableBrowserInvestigation: input.enableBrowserInvestigation,
15435
+ browserUrl: input.browserUrl,
15436
+ browserAllowedHosts: input.browserAllowedHosts,
15437
+ enableTestExecution: input.enableTestExecution
15438
+ });
15439
+ const { correlated, agent } = proposalRun;
15440
+ const category = correlated.classification.category;
15441
+ const confidence = correlated.classification.confidence;
15442
+ const testTitle = correlated.context.failedAction.sourceFile ? path26.basename(correlated.context.failedAction.sourceFile) : "Unknown Spec";
15443
+ failureObservations.push({
15444
+ id: path26.basename(tracePath, ".zip"),
15445
+ repository: path26.basename(workspaceRoot),
15446
+ testTitle,
15447
+ sourceFile: correlated.context.failedAction.sourceFile,
15448
+ category,
15449
+ action: correlated.context.failedAction.action,
15450
+ selector: correlated.context.failedAction.selector,
15451
+ errorMessage: correlated.context.failedAction.errorStack?.split("\n")[0] ?? correlated.context.failedAction.action,
15452
+ errorStack: correlated.context.failedAction.errorStack,
15453
+ tracePath
15454
+ });
15455
+ if (correlated.classification.shouldAbstain) {
15456
+ items.push({
15457
+ tracePath,
15458
+ testTitle,
15459
+ status: "ABSTAINED",
15460
+ category,
15461
+ confidence,
15462
+ reason: correlated.classification.abstentionReason ?? correlated.classification.reason ?? "Abstained by diagnostic policy",
15463
+ failedTarget: correlated.context.failedAction.selector
15464
+ });
15465
+ continue;
15466
+ }
15467
+ if (!agent.proposal || agent.proposal.decision === "ABSTAIN") {
15468
+ items.push({
15469
+ tracePath,
15470
+ testTitle,
15471
+ status: "ABSTAINED",
15472
+ category,
15473
+ confidence,
15474
+ reason: agent.proposal?.reasoningSummary ?? agent.errors.join("; ") ?? "Proposal abstained",
15475
+ failedTarget: correlated.context.failedAction.selector
15476
+ });
15477
+ continue;
15478
+ }
15479
+ const selected = DiagnosticReportGenerator.selectRepairCandidate(correlated, intelligence);
15480
+ const proposedLocator = selected?.locatorExpression ?? agent.proposal.operations[0]?.locator;
15481
+ const failedTarget = correlated.context.failedAction.selector ?? agent.proposal.operations[0]?.target;
15482
+ if (!proposedLocator) {
15483
+ items.push({
15484
+ tracePath,
15485
+ testTitle,
15486
+ status: "ABSTAINED",
15487
+ category,
15488
+ confidence,
15489
+ reason: "No unique candidate locator survived ranking and abstention gates",
15490
+ failedTarget
15491
+ });
15492
+ continue;
15493
+ }
15494
+ const mapping = await discoverRepairSourceMapping({
15495
+ workspaceRoot,
15496
+ failedAction: correlated.context.failedAction
15497
+ });
15498
+ if (mapping) {
15499
+ items.push({
15500
+ tracePath,
15501
+ testTitle,
15502
+ status: "PROPOSAL_READY",
15503
+ category,
15504
+ confidence,
15505
+ reason: agent.proposal.reasoningSummary,
15506
+ failedTarget,
15507
+ healedLocator: proposedLocator,
15508
+ targetFilePath: mapping.pomFilePath,
15509
+ testFilePath: mapping.testFilePath,
15510
+ className: mapping.className,
15511
+ memberName: mapping.memberName,
15512
+ pattern: "page-object",
15513
+ proposal: agent.proposal
15514
+ });
15515
+ } else {
15516
+ const sourceFile = correlated.context.failedAction.sourceFile;
15517
+ const resolvedSpec = sourceFile ? path26.resolve(workspaceRoot, sourceFile) : void 0;
15518
+ if (resolvedSpec && fsSync5.existsSync(resolvedSpec) && (resolvedSpec.includes(".spec.") || resolvedSpec.includes(".test."))) {
15519
+ items.push({
15520
+ tracePath,
15521
+ testTitle,
15522
+ status: "PROPOSAL_READY",
15523
+ category,
15524
+ confidence,
15525
+ reason: agent.proposal.reasoningSummary,
15526
+ failedTarget,
15527
+ healedLocator: proposedLocator,
15528
+ targetFilePath: resolvedSpec,
15529
+ testFilePath: resolvedSpec,
15530
+ pattern: "inline-locator",
15531
+ proposal: agent.proposal
15532
+ });
15533
+ } else {
15534
+ items.push({
15535
+ tracePath,
15536
+ testTitle,
15537
+ status: "ABSTAINED",
15538
+ category,
15539
+ confidence,
15540
+ reason: "Source mapping could not locate a unique Page Object or test file to patch",
15541
+ failedTarget,
15542
+ healedLocator: proposedLocator
15543
+ });
15544
+ }
15545
+ }
15546
+ } catch (err) {
15547
+ items.push({
15548
+ tracePath,
15549
+ status: "FAILED",
15550
+ category: "INTERNAL_ERROR",
15551
+ confidence: 0,
15552
+ reason: err?.message ?? String(err)
15553
+ });
15554
+ }
15555
+ }
15556
+ const clusters = clusterFailures(failureObservations);
15557
+ const proposableItems = items.filter((item) => item.status === "PROPOSAL_READY");
15558
+ if (proposableItems.length === 0 || !input.execute && !input.autoPromote) {
15559
+ return {
15560
+ status: proposableItems.length > 0 ? "PARTIAL" : items.every((i) => i.status === "ABSTAINED") ? "ABSTAINED" : "FAILED",
15561
+ totalTraces: items.length,
15562
+ repairedCount: 0,
15563
+ abstainedCount: items.filter((i) => i.status === "ABSTAINED").length,
15564
+ failedCount: items.filter((i) => i.status === "FAILED").length,
15565
+ items,
15566
+ clusters
15567
+ };
15568
+ }
15569
+ const manager = new WorktreeManager(workspaceRoot);
15570
+ const batchWorktreeId = input.worktreeId ?? `batch-repair-${crypto9.randomUUID().slice(0, 8)}`;
15571
+ let worktree;
15572
+ try {
15573
+ worktree = await manager.createWorktree(batchWorktreeId);
15574
+ } catch (err) {
15575
+ return {
15576
+ status: "FAILED",
15577
+ totalTraces: items.length,
15578
+ repairedCount: 0,
15579
+ abstainedCount: items.filter((i) => i.status === "ABSTAINED").length,
15580
+ failedCount: items.length,
15581
+ items: items.map((i) => i.status === "PROPOSAL_READY" ? { ...i, status: "FAILED", reason: `Worktree creation failed: ${err.message}` } : i),
15582
+ clusters
15583
+ };
15584
+ }
15585
+ let executionPassed = false;
15586
+ const promotedFiles = [];
15587
+ const modifiedFileContents = /* @__PURE__ */ new Map();
15588
+ try {
15589
+ const byTargetFile = /* @__PURE__ */ new Map();
15590
+ for (const item of proposableItems) {
15591
+ if (!item.targetFilePath) continue;
15592
+ const list = byTargetFile.get(item.targetFilePath) ?? [];
15593
+ list.push(item);
15594
+ byTargetFile.set(item.targetFilePath, list);
15595
+ }
15596
+ const astPatcher = new AstPatcher();
15597
+ const statementPatcher = new StatementPatcher();
15598
+ for (const [targetFile, targetItems] of byTargetFile.entries()) {
15599
+ const relTarget = path26.relative(workspaceRoot, targetFile);
15600
+ const worktreeTarget = path26.resolve(worktree.worktreePath, relTarget);
15601
+ if (!fsSync5.existsSync(worktreeTarget)) {
15602
+ for (const it of targetItems) {
15603
+ it.status = "REJECTED";
15604
+ it.reason = `Worktree target file does not exist: '${relTarget}'`;
15605
+ }
15606
+ continue;
15607
+ }
15608
+ let currentSource = await fs27.readFile(worktreeTarget, "utf8");
15609
+ for (const item of targetItems) {
15610
+ try {
15611
+ if (item.pattern === "page-object" && item.className && item.memberName && item.healedLocator) {
15612
+ const patchRes = astPatcher.patchPageObjectInMemory(currentSource, {
15613
+ className: item.className,
15614
+ memberName: item.memberName,
15615
+ replacementExpression: item.healedLocator
15616
+ });
15617
+ currentSource = patchRes.patchedSource;
15618
+ item.fullDiff = patchRes.diff;
15619
+ } else if (item.pattern === "inline-locator" && item.failedTarget && item.healedLocator) {
15620
+ const patchRes = statementPatcher.patchStatementInMemory(currentSource, {
15621
+ kind: "replace-inline-locator",
15622
+ oldLocatorText: item.failedTarget,
15623
+ newLocatorExpression: item.healedLocator
15624
+ }, path26.basename(relTarget));
15625
+ currentSource = patchRes.patchedSource;
15626
+ item.fullDiff = patchRes.diff;
15627
+ }
15628
+ } catch (patchErr) {
15629
+ item.status = "REJECTED";
15630
+ item.reason = `AST patching rejected: ${patchErr?.message ?? String(patchErr)}`;
15631
+ }
15632
+ }
15633
+ let allAllowed = true;
15634
+ for (const item of targetItems) {
15635
+ if (item.healedLocator) {
15636
+ const checkGrammar = DiffAllowlistValidator.validateLocatorGrammar(item.healedLocator);
15637
+ if (!checkGrammar.isValid) {
15638
+ item.status = "REJECTED";
15639
+ item.reason = `Invalid locator grammar: ${checkGrammar.reason}`;
15640
+ allAllowed = false;
15641
+ }
15642
+ }
15643
+ }
15644
+ if (!allAllowed) continue;
15645
+ await fs27.writeFile(worktreeTarget, currentSource, "utf8");
15646
+ modifiedFileContents.set(relTarget, currentSource);
15647
+ }
15648
+ const impactedSpecs = [...new Set(
15649
+ proposableItems.filter((it) => it.status === "PROPOSAL_READY").map((it) => it.testFilePath).filter(Boolean)
15650
+ )];
15651
+ if (impactedSpecs.length > 0) {
15652
+ const relSpecs = impactedSpecs.map((s) => path26.relative(workspaceRoot, s).replace(/\\/g, "/"));
15653
+ const testArgs = ["playwright", "test", ...relSpecs, "--reporter=line"];
15654
+ if (input.configPath) {
15655
+ testArgs.push("--config", path26.relative(workspaceRoot, input.configPath).replace(/\\/g, "/"));
15656
+ }
15657
+ const execResult = await ExecutionRunner.run("npx", {
15658
+ cwd: worktree.worktreePath,
15659
+ args: testArgs,
15660
+ timeoutMs: 12e4
15661
+ });
15662
+ const passedMatch = execResult.stdout.match(/(\d+)\s+passed/i);
15663
+ const passedCount = passedMatch ? parseInt(passedMatch[1] ?? "0", 10) : 0;
15664
+ executionPassed = execResult.exitCode === 0 && passedCount > 0 && !execResult.timedOut;
15665
+ for (const it of proposableItems) {
15666
+ if (it.status === "PROPOSAL_READY") {
15667
+ it.status = executionPassed ? "VERIFIED" : "VERIFICATION_FAILED";
15668
+ if (!executionPassed) {
15669
+ it.reason = execResult.timedOut ? "Execution timed out in worktree" : `Worktree Playwright tests failed (exitCode=${execResult.exitCode})`;
15670
+ }
15671
+ }
15672
+ }
15673
+ if (executionPassed && input.autoPromote) {
15674
+ for (const [relPath, content] of modifiedFileContents.entries()) {
15675
+ const workspaceDest = path26.resolve(workspaceRoot, relPath);
15676
+ await fs27.mkdir(path26.dirname(workspaceDest), { recursive: true });
15677
+ await fs27.writeFile(workspaceDest, content, "utf8");
15678
+ promotedFiles.push(relPath);
15679
+ }
15680
+ for (const it of proposableItems) {
15681
+ if (it.status === "VERIFIED") {
15682
+ it.status = "PROMOTED";
15683
+ }
15684
+ }
15685
+ }
15686
+ }
15687
+ } finally {
15688
+ await manager.destroyWorktree(batchWorktreeId);
15689
+ }
15690
+ const repairedCount = items.filter((i) => i.status === "VERIFIED" || i.status === "PROMOTED").length;
15691
+ const abstainedCount = items.filter((i) => i.status === "ABSTAINED").length;
15692
+ const failedCount = items.filter((i) => i.status === "FAILED" || i.status === "VERIFICATION_FAILED" || i.status === "REJECTED").length;
15693
+ const status = repairedCount > 0 && failedCount === 0 ? "COMPLETED" : repairedCount > 0 ? "PARTIAL" : abstainedCount > 0 && repairedCount === 0 ? "ABSTAINED" : "FAILED";
15694
+ return {
15695
+ status,
15696
+ totalTraces: items.length,
15697
+ repairedCount,
15698
+ abstainedCount,
15699
+ failedCount,
15700
+ items,
15701
+ clusters,
15702
+ worktree,
15703
+ executionPassed,
15704
+ promotedFiles
15705
+ };
15706
+ }
15707
+
15268
15708
  // src/intelligence/generation-agent.ts
15269
15709
  import { z as z11 } from "zod";
15270
15710
 
@@ -15729,13 +16169,13 @@ async function runGenerationAgent(input) {
15729
16169
  }
15730
16170
 
15731
16171
  // src/intelligence/generation-verifier.ts
15732
- import fs27 from "fs/promises";
15733
- import fsSync5 from "fs";
15734
- import path26 from "path";
16172
+ import fs28 from "fs/promises";
16173
+ import fsSync6 from "fs";
16174
+ import path27 from "path";
15735
16175
  import { z as z13 } from "zod";
15736
16176
 
15737
16177
  // src/intelligence/workflow-planner.ts
15738
- import crypto9 from "crypto";
16178
+ import crypto10 from "crypto";
15739
16179
  import { z as z12 } from "zod";
15740
16180
  var WorkflowProvenanceSchema = z12.enum([
15741
16181
  "explicit-requirement",
@@ -15813,7 +16253,7 @@ var GenerationWorkflowPlanSchema = z12.object({
15813
16253
  }).optional()
15814
16254
  });
15815
16255
  function stableHash(value) {
15816
- return crypto9.createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 12);
16256
+ return crypto10.createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 12);
15817
16257
  }
15818
16258
  function computeWorkflowPlanHash(plan) {
15819
16259
  return stableHash({
@@ -16663,7 +17103,7 @@ export { expect } from '@playwright/test';
16663
17103
  });
16664
17104
  fixtureImportForSpec = `./fixtures/app.fixtures.js`;
16665
17105
  } else {
16666
- const relFromSpec = path26.posix.relative("tests", repoGraph.testImport).replace(/\.ts$/, ".js");
17106
+ const relFromSpec = path27.posix.relative("tests", repoGraph.testImport).replace(/\.ts$/, ".js");
16667
17107
  fixtureImportForSpec = relFromSpec.startsWith(".") ? relFromSpec : `./${relFromSpec}`;
16668
17108
  }
16669
17109
  const testBlocks = [];
@@ -16782,22 +17222,22 @@ export default defineConfig({
16782
17222
  }
16783
17223
  function resolveWorkspaceCli(workspaceRoot, tool) {
16784
17224
  if (tool === "tsc") {
16785
- const jsEntry = path26.join(workspaceRoot, "node_modules", "typescript", "bin", "tsc");
16786
- if (fsSync5.existsSync(jsEntry)) {
17225
+ const jsEntry = path27.join(workspaceRoot, "node_modules", "typescript", "bin", "tsc");
17226
+ if (fsSync6.existsSync(jsEntry)) {
16787
17227
  return { cmd: process.execPath, prefixArgs: [jsEntry] };
16788
17228
  }
16789
- const binEntry = path26.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
16790
- if (fsSync5.existsSync(binEntry)) {
17229
+ const binEntry = path27.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
17230
+ if (fsSync6.existsSync(binEntry)) {
16791
17231
  return { cmd: "npx", prefixArgs: ["tsc"] };
16792
17232
  }
16793
17233
  return void 0;
16794
17234
  }
16795
- const pwJs = path26.join(workspaceRoot, "node_modules", "@playwright", "test", "cli.js");
16796
- if (fsSync5.existsSync(pwJs)) {
17235
+ const pwJs = path27.join(workspaceRoot, "node_modules", "@playwright", "test", "cli.js");
17236
+ if (fsSync6.existsSync(pwJs)) {
16797
17237
  return { cmd: process.execPath, prefixArgs: [pwJs] };
16798
17238
  }
16799
- const pwBin = path26.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "playwright.cmd" : "playwright");
16800
- if (fsSync5.existsSync(pwBin)) {
17239
+ const pwBin = path27.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "playwright.cmd" : "playwright");
17240
+ if (fsSync6.existsSync(pwBin)) {
16801
17241
  return { cmd: "npx", prefixArgs: ["playwright"] };
16802
17242
  }
16803
17243
  return void 0;
@@ -16811,16 +17251,16 @@ async function runCommandInWorkspace(cmd, args, cwd, timeoutMs = 6e4) {
16811
17251
  return { exitCode: res.exitCode ?? 1, stdout: res.stdout, stderr: res.stderr };
16812
17252
  }
16813
17253
  async function findFirstTraceZip(root) {
16814
- const candidates = [path26.join(root, "test-results"), path26.join(root, ".playwright-results")];
17254
+ const candidates = [path27.join(root, "test-results"), path27.join(root, ".playwright-results")];
16815
17255
  for (const base of candidates) {
16816
- if (!fsSync5.existsSync(base)) continue;
16817
- const entries = await fs27.readdir(base, { withFileTypes: true }).catch(() => []);
17256
+ if (!fsSync6.existsSync(base)) continue;
17257
+ const entries = await fs28.readdir(base, { withFileTypes: true }).catch(() => []);
16818
17258
  for (const entry of entries) {
16819
17259
  if (entry.isDirectory()) {
16820
- const traceFile = path26.join(base, entry.name, "trace.zip");
16821
- if (fsSync5.existsSync(traceFile)) return traceFile;
17260
+ const traceFile = path27.join(base, entry.name, "trace.zip");
17261
+ if (fsSync6.existsSync(traceFile)) return traceFile;
16822
17262
  } else if (entry.name.endsWith("trace.zip")) {
16823
- return path26.join(base, entry.name);
17263
+ return path27.join(base, entry.name);
16824
17264
  }
16825
17265
  }
16826
17266
  }
@@ -16893,9 +17333,9 @@ function formatQaReportMarkdown(report) {
16893
17333
  `;
16894
17334
  }
16895
17335
  async function runEvidenceDrivenGeneration(input) {
16896
- const workspaceRoot = path26.resolve(input.workspaceRoot);
16897
- const artifactDir = path26.resolve(input.outArtifactDir ?? path26.join(workspaceRoot, ".pie"));
16898
- await fs27.mkdir(artifactDir, { recursive: true });
17336
+ const workspaceRoot = path27.resolve(input.workspaceRoot);
17337
+ const artifactDir = path27.resolve(input.outArtifactDir ?? path27.join(workspaceRoot, ".pie"));
17338
+ await fs28.mkdir(artifactDir, { recursive: true });
16899
17339
  const repoGraph = await buildBrownfieldRepositoryGraph(workspaceRoot).catch(() => void 0);
16900
17340
  let workflowPlan = buildWorkflowPlan({
16901
17341
  ...input,
@@ -16912,12 +17352,12 @@ async function runEvidenceDrivenGeneration(input) {
16912
17352
  featureText = input.editedFeatureText;
16913
17353
  }
16914
17354
  }
16915
- const workflowPlanPath = path26.join(artifactDir, "workflow-plan.json");
16916
- const featureFilePath = path26.join(artifactDir, `${workflowPlan.slug}.feature`);
16917
- await fs27.writeFile(workflowPlanPath, `${JSON.stringify(workflowPlan, null, 2)}
17355
+ const workflowPlanPath = path27.join(artifactDir, "workflow-plan.json");
17356
+ const featureFilePath = path27.join(artifactDir, `${workflowPlan.slug}.feature`);
17357
+ await fs28.writeFile(workflowPlanPath, `${JSON.stringify(workflowPlan, null, 2)}
16918
17358
  `, "utf8");
16919
- await fs27.writeFile(featureFilePath, featureText, "utf8");
16920
- const explorationBundlePath = path26.join(artifactDir, "exploration-evidence.json");
17359
+ await fs28.writeFile(featureFilePath, featureText, "utf8");
17360
+ const explorationBundlePath = path27.join(artifactDir, "exploration-evidence.json");
16921
17361
  const explorationBundle = await InteractiveWorkflowRunner.run({
16922
17362
  baseUrl: input.baseUrl,
16923
17363
  workspaceRoot,
@@ -17046,9 +17486,9 @@ async function runEvidenceDrivenGeneration(input) {
17046
17486
  modifiedFiles = writeRes.modified;
17047
17487
  }
17048
17488
  }
17049
- const targetTsconfig = path26.join(workspaceRoot, "tsconfig.json");
17489
+ const targetTsconfig = path27.join(workspaceRoot, "tsconfig.json");
17050
17490
  const tscTool = resolveWorkspaceCli(workspaceRoot, "tsc");
17051
- if (input.write !== false && staticViolations.length === 0 && fsSync5.existsSync(targetTsconfig) && tscTool) {
17491
+ if (input.write !== false && staticViolations.length === 0 && fsSync6.existsSync(targetTsconfig) && tscTool) {
17052
17492
  tscChecked = true;
17053
17493
  const tscRes = await runCommandInWorkspace(tscTool.cmd, [...tscTool.prefixArgs, "-p", targetTsconfig, "--noEmit"], workspaceRoot);
17054
17494
  tscPassed = tscRes.exitCode === 0;
@@ -17160,12 +17600,12 @@ async function runEvidenceDrivenGeneration(input) {
17160
17600
  if (input.runMutations !== false && execPassed && canRunPlaywright && pwTool) {
17161
17601
  mutationExecuted = true;
17162
17602
  for (const spec of specFiles) {
17163
- const targetPath = path26.resolve(workspaceRoot, spec.relativePath);
17164
- const original = await fs27.readFile(targetPath, "utf8");
17603
+ const targetPath = path27.resolve(workspaceRoot, spec.relativePath);
17604
+ const original = await fs28.readFile(targetPath, "utf8");
17165
17605
  const levelCMutants = buildAssertionMutants(spec.relativePath, original);
17166
17606
  for (const mut of levelCMutants) {
17167
17607
  try {
17168
- await fs27.writeFile(targetPath, mut.mutatedContent, "utf8");
17608
+ await fs28.writeFile(targetPath, mut.mutatedContent, "utf8");
17169
17609
  const res = await runCommandInWorkspace(pwTool.cmd, [...pwTool.prefixArgs, "test", spec.relativePath, "--reporter=line"], workspaceRoot, 6e4);
17170
17610
  mutationEntries.push({
17171
17611
  id: mut.id,
@@ -17184,7 +17624,7 @@ async function runEvidenceDrivenGeneration(input) {
17184
17624
  status: "INVALID"
17185
17625
  });
17186
17626
  } finally {
17187
- await fs27.writeFile(targetPath, original, "utf8");
17627
+ await fs28.writeFile(targetPath, original, "utf8");
17188
17628
  }
17189
17629
  }
17190
17630
  const hasApiEndpoints = explorationBundle.network.some((n) => n.url.includes("/api/"));
@@ -17198,7 +17638,7 @@ $1`) : original.replace(/(test\s*\()/, `${injected}
17198
17638
  $1`);
17199
17639
  if (mutatedApiContent !== original) {
17200
17640
  try {
17201
- await fs27.writeFile(targetPath, mutatedApiContent, "utf8");
17641
+ await fs28.writeFile(targetPath, mutatedApiContent, "utf8");
17202
17642
  const res = await runCommandInWorkspace(pwTool.cmd, [...pwTool.prefixArgs, "test", spec.relativePath, "--reporter=line"], workspaceRoot, 6e4);
17203
17643
  mutationEntries.push({
17204
17644
  id: "mut-b-api-500",
@@ -17209,7 +17649,7 @@ $1`);
17209
17649
  exitCode: res.exitCode
17210
17650
  });
17211
17651
  } finally {
17212
- await fs27.writeFile(targetPath, original, "utf8");
17652
+ await fs28.writeFile(targetPath, original, "utf8");
17213
17653
  }
17214
17654
  }
17215
17655
  }
@@ -17223,8 +17663,8 @@ $1`);
17223
17663
  const mutationScore = validTotal > 0 ? Number((killed / validTotal * 100).toFixed(2)) : null;
17224
17664
  const hasDefects = explorationBundle.findings.some((f) => f.category === "SPEC_VIOLATION" || f.category === "RUNTIME_ERROR");
17225
17665
  const finalStatus = staticViolations.length > 0 || executed && !execPassed ? "VERIFICATION_FAILED" : execPassed && (!hasDefects && (mutationScore === null || mutationScore >= 80)) ? "VERIFIED" : execPassed ? "VERIFIED_WITH_WARNINGS" : "GENERATED";
17226
- const qaReportJsonPath = path26.join(artifactDir, "qa-report.json");
17227
- const qaReportMarkdownPath = path26.join(artifactDir, "qa-report.md");
17666
+ const qaReportJsonPath = path27.join(artifactDir, "qa-report.json");
17667
+ const qaReportMarkdownPath = path27.join(artifactDir, "qa-report.md");
17228
17668
  const qaReport = {
17229
17669
  reportVersion: "1.0",
17230
17670
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17305,9 +17745,9 @@ $1`);
17305
17745
  mcpDependenciesUsed: "none"
17306
17746
  }
17307
17747
  };
17308
- await fs27.writeFile(qaReportJsonPath, `${JSON.stringify(qaReport, null, 2)}
17748
+ await fs28.writeFile(qaReportJsonPath, `${JSON.stringify(qaReport, null, 2)}
17309
17749
  `, "utf8");
17310
- await fs27.writeFile(qaReportMarkdownPath, formatQaReportMarkdown(qaReport), "utf8");
17750
+ await fs28.writeFile(qaReportMarkdownPath, formatQaReportMarkdown(qaReport), "utf8");
17311
17751
  return {
17312
17752
  workflowPlan,
17313
17753
  featureText,
@@ -17318,9 +17758,9 @@ $1`);
17318
17758
  }
17319
17759
 
17320
17760
  // src/intelligence/suite-intelligence.ts
17321
- import fs28 from "fs/promises";
17322
- import fsSync6 from "fs";
17323
- import path27 from "path";
17761
+ import fs29 from "fs/promises";
17762
+ import fsSync7 from "fs";
17763
+ import path28 from "path";
17324
17764
  function round(value) {
17325
17765
  return Number(value.toFixed(3));
17326
17766
  }
@@ -17374,14 +17814,14 @@ async function listTestFiles(root, depth = 0) {
17374
17814
  const ignored = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", "test-results", "playwright-report"]);
17375
17815
  let entries;
17376
17816
  try {
17377
- entries = await fs28.readdir(root, { withFileTypes: true });
17817
+ entries = await fs29.readdir(root, { withFileTypes: true });
17378
17818
  } catch {
17379
17819
  return [];
17380
17820
  }
17381
17821
  const files = [];
17382
17822
  for (const entry of entries) {
17383
17823
  if (ignored.has(entry.name)) continue;
17384
- const fullPath = path27.join(root, entry.name);
17824
+ const fullPath = path28.join(root, entry.name);
17385
17825
  if (entry.isDirectory()) files.push(...await listTestFiles(fullPath, depth + 1));
17386
17826
  else if (/\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/i.test(entry.name)) files.push(fullPath);
17387
17827
  }
@@ -17390,7 +17830,7 @@ async function listTestFiles(root, depth = 0) {
17390
17830
  function resolveImport(root, sourceFile, specifier, aliases) {
17391
17831
  let base;
17392
17832
  if (specifier.startsWith(".")) {
17393
- base = path27.resolve(path27.dirname(sourceFile), specifier);
17833
+ base = path28.resolve(path28.dirname(sourceFile), specifier);
17394
17834
  } else {
17395
17835
  const alias = aliases.find((candidate) => {
17396
17836
  if (candidate.pattern.includes("*")) {
@@ -17403,17 +17843,17 @@ function resolveImport(root, sourceFile, specifier, aliases) {
17403
17843
  const [prefix, suffix] = alias.pattern.split("*");
17404
17844
  const wildcard = alias.pattern.includes("*") ? specifier.slice((prefix ?? "").length, specifier.length - (suffix ?? "").length || void 0) : "";
17405
17845
  const target = alias.targets[0];
17406
- if (target) base = path27.resolve(root, target.replace("*", wildcard));
17846
+ if (target) base = path28.resolve(root, target.replace("*", wildcard));
17407
17847
  }
17408
17848
  }
17409
17849
  if (!base) return void 0;
17410
17850
  const candidates = [
17411
17851
  base,
17412
17852
  ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].map((extension) => `${base}${extension}`),
17413
- ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].map((extension) => path27.join(base, `index${extension}`))
17853
+ ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].map((extension) => path28.join(base, `index${extension}`))
17414
17854
  ];
17415
- const resolved = candidates.find((candidate) => fsSync6.existsSync(candidate) && fsSync6.statSync(candidate).isFile());
17416
- return resolved ? path27.relative(root, resolved).replace(/\\/g, "/") : void 0;
17855
+ const resolved = candidates.find((candidate) => fsSync7.existsSync(candidate) && fsSync7.statSync(candidate).isFile());
17856
+ return resolved ? path28.relative(root, resolved).replace(/\\/g, "/") : void 0;
17417
17857
  }
17418
17858
  function extractImportSpecifiers(source) {
17419
17859
  const imports = /* @__PURE__ */ new Set();
@@ -17430,27 +17870,27 @@ function extractImportSpecifiers(source) {
17430
17870
  return [...imports];
17431
17871
  }
17432
17872
  async function loadPathAliases(root) {
17433
- const tsconfigPath = path27.join(root, "tsconfig.json");
17873
+ const tsconfigPath = path28.join(root, "tsconfig.json");
17434
17874
  try {
17435
- const raw = await fs28.readFile(tsconfigPath, "utf8");
17875
+ const raw = await fs29.readFile(tsconfigPath, "utf8");
17436
17876
  const withoutComments = raw.replace(/^\s*\/\/.*$/gm, "");
17437
17877
  const parsed = JSON.parse(withoutComments);
17438
17878
  const baseUrl = parsed.compilerOptions?.baseUrl ?? ".";
17439
17879
  return Object.entries(parsed.compilerOptions?.paths ?? {}).map(([pattern, targets]) => ({
17440
17880
  pattern,
17441
- targets: targets.map((target) => path27.join(baseUrl, target))
17881
+ targets: targets.map((target) => path28.join(baseUrl, target))
17442
17882
  }));
17443
17883
  } catch {
17444
17884
  return [];
17445
17885
  }
17446
17886
  }
17447
17887
  async function collectTransitiveDependencies(root, entry, aliases, visited = /* @__PURE__ */ new Set()) {
17448
- const absoluteEntry = path27.resolve(entry);
17888
+ const absoluteEntry = path28.resolve(entry);
17449
17889
  if (visited.has(absoluteEntry)) return [];
17450
17890
  visited.add(absoluteEntry);
17451
17891
  let source;
17452
17892
  try {
17453
- source = await fs28.readFile(absoluteEntry, "utf8");
17893
+ source = await fs29.readFile(absoluteEntry, "utf8");
17454
17894
  } catch {
17455
17895
  return [];
17456
17896
  }
@@ -17459,25 +17899,25 @@ async function collectTransitiveDependencies(root, entry, aliases, visited = /*
17459
17899
  const dependency = resolveImport(root, absoluteEntry, specifier, aliases);
17460
17900
  if (!dependency) continue;
17461
17901
  dependencies.push(dependency);
17462
- const nested = await collectTransitiveDependencies(root, path27.resolve(root, dependency), aliases, visited);
17902
+ const nested = await collectTransitiveDependencies(root, path28.resolve(root, dependency), aliases, visited);
17463
17903
  dependencies.push(...nested);
17464
17904
  }
17465
17905
  return [...new Set(dependencies)];
17466
17906
  }
17467
17907
  async function discoverTestImpactCandidates(root) {
17468
- const projectRoot = path27.resolve(root);
17908
+ const projectRoot = path28.resolve(root);
17469
17909
  const files = await listTestFiles(projectRoot);
17470
17910
  const aliases = await loadPathAliases(projectRoot);
17471
17911
  const candidates = [];
17472
17912
  for (const file of files) {
17473
17913
  let source;
17474
17914
  try {
17475
- source = await fs28.readFile(file, "utf8");
17915
+ source = await fs29.readFile(file, "utf8");
17476
17916
  } catch {
17477
17917
  continue;
17478
17918
  }
17479
17919
  const dependencies = await collectTransitiveDependencies(projectRoot, file, aliases);
17480
- const relativeSource = path27.relative(projectRoot, file).replace(/\\/g, "/");
17920
+ const relativeSource = path28.relative(projectRoot, file).replace(/\\/g, "/");
17481
17921
  const tags = [...new Set([...source.matchAll(/@([A-Za-z][\w-]*)/g)].map((match) => match[1].toLowerCase()))].sort();
17482
17922
  candidates.push({
17483
17923
  testId: relativeSource,
@@ -17490,11 +17930,11 @@ async function discoverTestImpactCandidates(root) {
17490
17930
  return candidates.sort((left, right) => left.testId.localeCompare(right.testId));
17491
17931
  }
17492
17932
  async function analyzeRepositoryImpact(root, changedFiles) {
17493
- const projectRoot = path27.resolve(root);
17933
+ const projectRoot = path28.resolve(root);
17494
17934
  const candidates = await discoverTestImpactCandidates(projectRoot);
17495
17935
  const normalizedChanged = changedFiles.map((file) => {
17496
- const absolute = path27.isAbsolute(file) ? file : path27.resolve(projectRoot, file);
17497
- return path27.relative(projectRoot, absolute).replace(/\\/g, "/");
17936
+ const absolute = path28.isAbsolute(file) ? file : path28.resolve(projectRoot, file);
17937
+ return path28.relative(projectRoot, absolute).replace(/\\/g, "/");
17498
17938
  });
17499
17939
  return {
17500
17940
  root: projectRoot,
@@ -17957,7 +18397,7 @@ function buildTeamDashboard(runs, policy = {}) {
17957
18397
 
17958
18398
  // src/evaluation/repository-runner.ts
17959
18399
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
17960
- import path28 from "path";
18400
+ import path29 from "path";
17961
18401
  var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
17962
18402
  var SAFE_COMMANDS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "npx"]);
17963
18403
  function executableFor(manager) {
@@ -17992,7 +18432,7 @@ var RepositoryRunner = class {
17992
18432
  return this.runCommand(manifest, executable, args, options);
17993
18433
  }
17994
18434
  static runCommand(manifest, executable, args, options = {}) {
17995
- const normalizedExecutable = path28.basename(executable).replace(/\.cmd$/i, "").toLowerCase();
18435
+ const normalizedExecutable = path29.basename(executable).replace(/\.cmd$/i, "").toLowerCase();
17996
18436
  if (!SAFE_COMMANDS.has(normalizedExecutable)) {
17997
18437
  return Promise.resolve({
17998
18438
  repository: manifest.name,
@@ -18231,8 +18671,8 @@ function buildEvaluationScorecard(observations) {
18231
18671
  }
18232
18672
 
18233
18673
  // src/framework/config-adapter.ts
18234
- import fs29 from "fs/promises";
18235
- import path29 from "path";
18674
+ import fs30 from "fs/promises";
18675
+ import path30 from "path";
18236
18676
  import { Project as Project10, SyntaxKind as SyntaxKind6 } from "ts-morph";
18237
18677
  var ConfigAdapter = class _ConfigAdapter {
18238
18678
  static CONFIG_NAMES = [
@@ -18246,9 +18686,9 @@ var ConfigAdapter = class _ConfigAdapter {
18246
18686
  */
18247
18687
  static async findConfig(repoDir) {
18248
18688
  for (const name of _ConfigAdapter.CONFIG_NAMES) {
18249
- const fullPath = path29.resolve(repoDir, name);
18689
+ const fullPath = path30.resolve(repoDir, name);
18250
18690
  try {
18251
- await fs29.access(fullPath);
18691
+ await fs30.access(fullPath);
18252
18692
  return _ConfigAdapter.parseConfig(fullPath);
18253
18693
  } catch {
18254
18694
  continue;
@@ -18318,7 +18758,7 @@ var ConfigAdapter = class _ConfigAdapter {
18318
18758
  * Generates a temporary verification config string with retries disabled and tracing enabled.
18319
18759
  */
18320
18760
  static generateVerificationOverride(baseConfigPath) {
18321
- const importStatement = baseConfigPath ? `import baseConfig from './${path29.basename(baseConfigPath).replace(/\.ts$/, "")}';
18761
+ const importStatement = baseConfigPath ? `import baseConfig from './${path30.basename(baseConfigPath).replace(/\.ts$/, "")}';
18322
18762
  ` : `import { defineConfig } from '@playwright/test';
18323
18763
  `;
18324
18764
  return `${importStatement}
@@ -18340,13 +18780,13 @@ export default {
18340
18780
 
18341
18781
  // src/mcp/server.ts
18342
18782
  import readline from "readline";
18343
- import path30 from "path";
18344
- import fs30 from "fs";
18783
+ import path31 from "path";
18784
+ import fs31 from "fs";
18345
18785
  var McpServer = class {
18346
18786
  rl;
18347
18787
  workspaceRoot;
18348
18788
  constructor(workspaceRoot = process.cwd()) {
18349
- this.workspaceRoot = path30.resolve(workspaceRoot);
18789
+ this.workspaceRoot = path31.resolve(workspaceRoot);
18350
18790
  this.rl = readline.createInterface({
18351
18791
  input: process.stdin,
18352
18792
  output: process.stdout,
@@ -18366,26 +18806,26 @@ var McpServer = class {
18366
18806
  );
18367
18807
  }
18368
18808
  const normalizedTarget = process.platform === "win32" ? targetPath : targetPath.replace(/\\/g, "/");
18369
- const resolved = path30.resolve(this.workspaceRoot, normalizedTarget);
18370
- if (!fs30.existsSync(this.workspaceRoot)) {
18809
+ const resolved = path31.resolve(this.workspaceRoot, normalizedTarget);
18810
+ if (!fs31.existsSync(this.workspaceRoot)) {
18371
18811
  throw new Error(`Workspace root does not exist: '${this.workspaceRoot}'`);
18372
18812
  }
18373
- const canonicalWorkspace = fs30.realpathSync(this.workspaceRoot);
18813
+ const canonicalWorkspace = fs31.realpathSync(this.workspaceRoot);
18374
18814
  let canonicalTarget;
18375
- if (fs30.existsSync(resolved)) {
18376
- canonicalTarget = fs30.realpathSync(resolved);
18815
+ if (fs31.existsSync(resolved)) {
18816
+ canonicalTarget = fs31.realpathSync(resolved);
18377
18817
  } else {
18378
- const parent = path30.dirname(resolved);
18379
- if (fs30.existsSync(parent)) {
18380
- canonicalTarget = path30.join(fs30.realpathSync(parent), path30.basename(resolved));
18818
+ const parent = path31.dirname(resolved);
18819
+ if (fs31.existsSync(parent)) {
18820
+ canonicalTarget = path31.join(fs31.realpathSync(parent), path31.basename(resolved));
18381
18821
  } else {
18382
18822
  throw new Error(
18383
18823
  `Workspace Security Violation: Path '${targetPath}' cannot be resolved inside authorized workspace boundary '${canonicalWorkspace}'.`
18384
18824
  );
18385
18825
  }
18386
18826
  }
18387
- const rel = path30.relative(canonicalWorkspace, canonicalTarget);
18388
- if (rel.startsWith("..") || path30.isAbsolute(rel)) {
18827
+ const rel = path31.relative(canonicalWorkspace, canonicalTarget);
18828
+ if (rel.startsWith("..") || path31.isAbsolute(rel)) {
18389
18829
  throw new Error(
18390
18830
  `Workspace Security Violation: Path '${targetPath}' resolves to '${canonicalTarget}' outside authorized workspace boundary '${canonicalWorkspace}'.`
18391
18831
  );
@@ -18672,7 +19112,7 @@ var McpServer = class {
18672
19112
  };
18673
19113
  }
18674
19114
  const tracePath = this.assertPathInWorkspace(tracePathRaw);
18675
- if (!fs30.existsSync(tracePath)) {
19115
+ if (!fs31.existsSync(tracePath)) {
18676
19116
  throw new Error(`Trace file not found: '${tracePath}'`);
18677
19117
  }
18678
19118
  const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
@@ -18694,7 +19134,7 @@ var McpServer = class {
18694
19134
  let context = await adapter.parse(manifest, traceLines, networkLines);
18695
19135
  if (args?.errorContextPath) {
18696
19136
  const errorContextPath = this.assertPathInWorkspace(args.errorContextPath);
18697
- const errorContext = await fs30.promises.readFile(errorContextPath, "utf8");
19137
+ const errorContext = await fs31.promises.readFile(errorContextPath, "utf8");
18698
19138
  context = supplementDiagnosticContext(context, parseExternalFailureEvidence(errorContext));
18699
19139
  }
18700
19140
  const correlated = EvidenceCorrelator.correlate(context);
@@ -18915,14 +19355,14 @@ var McpServer = class {
18915
19355
  }
18916
19356
  const pomPath = this.assertPathInWorkspace(pomRaw);
18917
19357
  const testPath = this.assertPathInWorkspace(testRaw);
18918
- if (!fs30.existsSync(pomPath)) throw new Error(`Page Object file not found: '${pomPath}'`);
18919
- if (!fs30.existsSync(testPath)) throw new Error(`Test spec file not found: '${testPath}'`);
19358
+ if (!fs31.existsSync(pomPath)) throw new Error(`Page Object file not found: '${pomPath}'`);
19359
+ if (!fs31.existsSync(testPath)) throw new Error(`Test spec file not found: '${testPath}'`);
18920
19360
  const grammar = DiffAllowlistValidator.validateLocatorGrammar(replacementSelector);
18921
19361
  if (!grammar.isValid) {
18922
19362
  throw new Error(`Security Violation: Disallowed replacement expression '${replacementSelector}'. ${grammar.reason}`);
18923
19363
  }
18924
- const origPomSource = await fs30.promises.readFile(pomPath, "utf-8");
18925
- const origTestSource = await fs30.promises.readFile(testPath, "utf-8");
19364
+ const origPomSource = await fs31.promises.readFile(pomPath, "utf-8");
19365
+ const origTestSource = await fs31.promises.readFile(testPath, "utf-8");
18926
19366
  const initialFingerprint = AssertionHasher.computeFingerprint(testPath);
18927
19367
  const patcher = new AstPatcher();
18928
19368
  const inMemPatch = patcher.patchPageObjectInMemory(origPomSource, {
@@ -18973,7 +19413,7 @@ var McpServer = class {
18973
19413
  const postFingerprint = AssertionHasher.computeFingerprint(testPath);
18974
19414
  const assertionIntact = initialFingerprint === postFingerprint;
18975
19415
  if (!assertionIntact) {
18976
- await fs30.promises.writeFile(pomPath, origPomSource, "utf8");
19416
+ await fs31.promises.writeFile(pomPath, origPomSource, "utf8");
18977
19417
  return {
18978
19418
  jsonrpc: "2.0",
18979
19419
  id,
@@ -19099,7 +19539,7 @@ ${generated.specCode}`
19099
19539
  verify,
19100
19540
  runMutations
19101
19541
  } = args ?? {};
19102
- const targetWorkspace = typeof workspaceRoot === "string" && workspaceRoot.trim() ? path30.resolve(workspaceRoot) : this.workspaceRoot;
19542
+ const targetWorkspace = typeof workspaceRoot === "string" && workspaceRoot.trim() ? path31.resolve(workspaceRoot) : this.workspaceRoot;
19103
19543
  const resolvedAuthor = author === "template" || author === "hybrid" || author === "llm" ? author : void 0;
19104
19544
  const result = await runEvidenceDrivenGeneration({
19105
19545
  workspaceRoot: targetWorkspace,
@@ -19184,38 +19624,45 @@ ${generated.specCode}`
19184
19624
  };
19185
19625
 
19186
19626
  // src/cli/index.ts
19187
- import * as path31 from "path";
19188
- import * as fs31 from "fs";
19627
+ import * as path32 from "path";
19628
+ import * as fs32 from "fs";
19189
19629
  import { spawn as spawn3, execSync } from "child_process";
19190
19630
  function findTraceArchives(targetPath) {
19191
19631
  if (targetPath && !targetPath.startsWith("--")) {
19192
- const resolved = path31.resolve(targetPath);
19193
- if (fs31.existsSync(resolved)) {
19194
- if (fs31.statSync(resolved).isFile() && resolved.endsWith(".zip")) {
19632
+ const resolved = path32.resolve(targetPath);
19633
+ if (fs32.existsSync(resolved)) {
19634
+ if (fs32.statSync(resolved).isFile() && resolved.endsWith(".zip")) {
19195
19635
  return [resolved];
19196
19636
  }
19197
19637
  }
19198
19638
  }
19199
19639
  const candidateDirs = [
19200
- targetPath && !targetPath.startsWith("--") && fs31.existsSync(targetPath) && fs31.statSync(targetPath).isDirectory() ? path31.resolve(targetPath) : null,
19201
- path31.resolve("test-results"),
19202
- path31.resolve("playwright-report"),
19640
+ targetPath && !targetPath.startsWith("--") && fs32.existsSync(targetPath) && fs32.statSync(targetPath).isDirectory() ? path32.resolve(targetPath) : null,
19641
+ path32.resolve("test-results"),
19642
+ path32.resolve("playwright-report"),
19203
19643
  process.cwd()
19204
19644
  ].filter(Boolean);
19205
19645
  const found = [];
19206
19646
  const seen = /* @__PURE__ */ new Set();
19207
19647
  for (const dir of candidateDirs) {
19208
- if (!fs31.existsSync(dir)) continue;
19648
+ if (!fs32.existsSync(dir)) continue;
19209
19649
  try {
19210
- const entries = fs31.readdirSync(dir, { recursive: true });
19650
+ const entries = fs32.readdirSync(dir, { recursive: true });
19211
19651
  for (const entry of entries) {
19212
- const fullPath = path31.join(dir, String(entry));
19652
+ const fullPath = path32.join(dir, String(entry));
19213
19653
  if (fullPath.endsWith(".zip") && !seen.has(fullPath)) {
19214
19654
  try {
19215
- const stat = fs31.statSync(fullPath);
19655
+ const stat = fs32.statSync(fullPath);
19216
19656
  if (stat.isFile()) {
19217
19657
  seen.add(fullPath);
19218
- found.push({ filePath: fullPath, mtime: stat.mtimeMs });
19658
+ let hasFailure = false;
19659
+ try {
19660
+ const discovery = ArchiveReader.findTraceFileEntries(fullPath);
19661
+ hasFailure = Boolean(discovery.hasFailure);
19662
+ } catch {
19663
+ }
19664
+ const isTestResults = fullPath.includes("test-results");
19665
+ found.push({ filePath: fullPath, mtime: stat.mtimeMs, hasFailure, isTestResults });
19219
19666
  }
19220
19667
  } catch {
19221
19668
  }
@@ -19224,7 +19671,11 @@ function findTraceArchives(targetPath) {
19224
19671
  } catch {
19225
19672
  }
19226
19673
  }
19227
- return found.sort((a, b) => b.mtime - a.mtime).map((f) => f.filePath);
19674
+ return found.sort((a, b) => {
19675
+ if (a.hasFailure !== b.hasFailure) return a.hasFailure ? -1 : 1;
19676
+ if (a.isTestResults !== b.isTestResults) return a.isTestResults ? -1 : 1;
19677
+ return b.mtime - a.mtime;
19678
+ }).map((f) => f.filePath);
19228
19679
  }
19229
19680
  async function runCli(argv) {
19230
19681
  const command = argv[2];
@@ -19253,8 +19704,8 @@ async function runCli(argv) {
19253
19704
  const outputPath = outputPathIndex >= 0 ? argv[outputPathIndex + 1] : void 0;
19254
19705
  const serialized = JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), testbedPath, repositories: manifests }, null, 2);
19255
19706
  if (outputPath) {
19256
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19257
- await fs31.promises.writeFile(path31.resolve(outputPath), serialized, "utf8");
19707
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
19708
+ await fs32.promises.writeFile(path32.resolve(outputPath), serialized, "utf8");
19258
19709
  }
19259
19710
  if (argv.includes("--json") || outputPath) {
19260
19711
  console.log(serialized);
@@ -19299,8 +19750,8 @@ async function runCli(argv) {
19299
19750
  }
19300
19751
  const serialized = JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), testbedPath, results }, null, 2);
19301
19752
  if (outputPath) {
19302
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19303
- await fs31.promises.writeFile(path31.resolve(outputPath), serialized, "utf8");
19753
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
19754
+ await fs32.promises.writeFile(path32.resolve(outputPath), serialized, "utf8");
19304
19755
  }
19305
19756
  console.log(serialized);
19306
19757
  return;
@@ -19320,9 +19771,9 @@ async function runCli(argv) {
19320
19771
  }
19321
19772
  const tracePath = traces[0];
19322
19773
  if (traces.length > 1) {
19323
- console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Diagnosing most recent: ${path31.relative(process.cwd(), tracePath)}`);
19324
- } else if (!candidateArg || candidateArg.startsWith("--") || fs31.existsSync(candidateArg) && fs31.statSync(candidateArg).isDirectory()) {
19325
- console.log(`[pw-intel] Auto-discovered trace: ${path31.relative(process.cwd(), tracePath)}`);
19774
+ console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Diagnosing most recent: ${path32.relative(process.cwd(), tracePath)}`);
19775
+ } else if (!candidateArg || candidateArg.startsWith("--") || fs32.existsSync(candidateArg) && fs32.statSync(candidateArg).isDirectory()) {
19776
+ console.log(`[pw-intel] Auto-discovered trace: ${path32.relative(process.cwd(), tracePath)}`);
19326
19777
  }
19327
19778
  try {
19328
19779
  const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
@@ -19337,7 +19788,7 @@ async function runCli(argv) {
19337
19788
  const errorContextIndex = argv.indexOf("--error-context");
19338
19789
  const errorContextPath = errorContextIndex >= 0 ? argv[errorContextIndex + 1] : void 0;
19339
19790
  if (errorContextPath) {
19340
- const errorContext = await fs31.promises.readFile(path31.resolve(errorContextPath), "utf8");
19791
+ const errorContext = await fs32.promises.readFile(path32.resolve(errorContextPath), "utf8");
19341
19792
  context = supplementDiagnosticContext(context, parseExternalFailureEvidence(errorContext));
19342
19793
  }
19343
19794
  const correlated = EvidenceCorrelator.correlate(context);
@@ -19423,6 +19874,80 @@ async function runCli(argv) {
19423
19874
  return;
19424
19875
  }
19425
19876
  if (command === "autonomous-repair") {
19877
+ const option = (name) => {
19878
+ const index = argv.indexOf(name);
19879
+ return index >= 0 ? argv[index + 1] : void 0;
19880
+ };
19881
+ if (argv.includes("--all")) {
19882
+ try {
19883
+ const batchResult = await runBatchRepairWorkflow({
19884
+ workspaceRoot: option("--workspace") ?? process.cwd(),
19885
+ execute: argv.includes("--execute"),
19886
+ autoPromote: argv.includes("--promote"),
19887
+ configPath: option("--config"),
19888
+ worktreeId: option("--worktree-id"),
19889
+ enableBrowserInvestigation: argv.includes("--browser"),
19890
+ browserUrl: option("--browser-url"),
19891
+ browserAllowedHosts: (option("--allow-host") ?? "").split(",").map((h) => h.trim()).filter(Boolean),
19892
+ enableTestExecution: argv.includes("--run-tests")
19893
+ });
19894
+ const reportsDir = path32.resolve(process.cwd(), ".pie", "reports");
19895
+ await fs32.promises.mkdir(reportsDir, { recursive: true });
19896
+ const reportJsonPath = path32.resolve(reportsDir, "batch-repair-report.json");
19897
+ await fs32.promises.writeFile(reportJsonPath, `${JSON.stringify(batchResult, null, 2)}
19898
+ `, "utf8");
19899
+ if (argv.includes("--json")) {
19900
+ console.log(JSON.stringify(batchResult, null, 2));
19901
+ } else {
19902
+ const divider = "\u2500".repeat(72);
19903
+ console.log("");
19904
+ console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
19905
+ console.log("\u2551 PLAYWRIGHT INTELLIGENCE ENGINE: BATCH AUTO-HEALING \u2551");
19906
+ console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
19907
+ console.log("");
19908
+ console.log(`Discovered Traces : ${batchResult.totalTraces}`);
19909
+ console.log(`Repaired & Verified: ${batchResult.repairedCount}`);
19910
+ console.log(`Abstained : ${batchResult.abstainedCount}`);
19911
+ console.log(`Failed : ${batchResult.failedCount}`);
19912
+ console.log(`Batch Status : ${batchResult.status}`);
19913
+ console.log(divider);
19914
+ console.log("Failure Clusters:");
19915
+ if (batchResult.clusters.length > 0) {
19916
+ for (const c of batchResult.clusters) {
19917
+ console.log(` \u2022 [${c.severity.toUpperCase()}] ${c.fingerprint.category} (count: ${c.count})`);
19918
+ }
19919
+ } else {
19920
+ console.log(" \u2022 None");
19921
+ }
19922
+ console.log(divider);
19923
+ console.log("Test Repair Summary:");
19924
+ for (const item of batchResult.items) {
19925
+ const icon = item.status === "PROMOTED" || item.status === "VERIFIED" ? "\u2705" : item.status === "ABSTAINED" ? "\u26A0\uFE0F" : item.status === "PROPOSAL_READY" ? "\u{1F4A1}" : "\u274C";
19926
+ const spec = item.testTitle ?? path32.basename(item.tracePath);
19927
+ console.log(` ${icon} [${item.status}] ${spec}`);
19928
+ if (item.failedTarget) console.log(` Failed Target : ${item.failedTarget}`);
19929
+ if (item.healedLocator) console.log(` Healed Locator: ${item.healedLocator}`);
19930
+ if (item.targetFilePath) console.log(` Target File : ${path32.relative(process.cwd(), item.targetFilePath)}`);
19931
+ if (item.reason && item.status === "ABSTAINED") console.log(` Abstain Reason: ${item.reason}`);
19932
+ }
19933
+ console.log(divider);
19934
+ if (batchResult.promotedFiles && batchResult.promotedFiles.length > 0) {
19935
+ console.log("Promoted Files:");
19936
+ for (const file of batchResult.promotedFiles) {
19937
+ console.log(` \u{1F389} ${file}`);
19938
+ }
19939
+ console.log(divider);
19940
+ }
19941
+ console.log(`Report JSON : ${path32.relative(process.cwd(), reportJsonPath)}`);
19942
+ console.log("(Pass --json to view full machine-readable payload)");
19943
+ }
19944
+ if (batchResult.status === "FAILED") process.exitCode = 1;
19945
+ return;
19946
+ } catch (err) {
19947
+ console.error(`Batch repair failed: ${err?.message ?? String(err)}`);
19948
+ process.exit(1);
19949
+ }
19950
+ }
19426
19951
  const candidateArg = argv[3];
19427
19952
  const traces = findTraceArchives(candidateArg);
19428
19953
  if (traces.length === 0) {
@@ -19433,15 +19958,11 @@ async function runCli(argv) {
19433
19958
  }
19434
19959
  const tracePath = traces[0];
19435
19960
  if (traces.length > 1) {
19436
- console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Repairing most recent: ${path31.relative(process.cwd(), tracePath)}`);
19437
- } else if (!candidateArg || candidateArg.startsWith("--") || fs31.existsSync(candidateArg) && fs31.statSync(candidateArg).isDirectory()) {
19438
- console.log(`[pw-intel] Auto-discovered trace: ${path31.relative(process.cwd(), tracePath)}`);
19961
+ console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Repairing most recent: ${path32.relative(process.cwd(), tracePath)}`);
19962
+ } else if (!candidateArg || candidateArg.startsWith("--") || fs32.existsSync(candidateArg) && fs32.statSync(candidateArg).isDirectory()) {
19963
+ console.log(`[pw-intel] Auto-discovered trace: ${path32.relative(process.cwd(), tracePath)}`);
19439
19964
  }
19440
19965
  try {
19441
- const option = (name) => {
19442
- const index = argv.indexOf(name);
19443
- return index >= 0 ? argv[index + 1] : void 0;
19444
- };
19445
19966
  const result = await runAutonomousRepairWorkflow({
19446
19967
  tracePath,
19447
19968
  workspaceRoot: option("--workspace") ?? process.cwd(),
@@ -19459,21 +19980,83 @@ async function runCli(argv) {
19459
19980
  autoPromote: argv.includes("--promote"),
19460
19981
  execute: argv.includes("--execute")
19461
19982
  });
19462
- console.log(JSON.stringify({
19463
- provenance: getRuntimeProvenance(process.cwd()),
19464
- status: result.status,
19465
- reason: result.reason,
19466
- agent: {
19467
- state: result.proposalRun.agent.state,
19468
- modelInvoked: result.proposalRun.agent.modelInvoked,
19469
- classification: result.proposalRun.agent.classification,
19470
- proposal: result.proposal,
19471
- errors: result.proposalRun.agent.errors,
19472
- steps: result.proposalRun.agent.steps
19473
- },
19474
- verification: result.verification,
19475
- promotion: result.promotion
19476
- }, null, 2));
19983
+ if (argv.includes("--json")) {
19984
+ console.log(JSON.stringify({
19985
+ provenance: getRuntimeProvenance(process.cwd()),
19986
+ status: result.status,
19987
+ reason: result.reason,
19988
+ agent: {
19989
+ state: result.proposalRun.agent.state,
19990
+ modelInvoked: result.proposalRun.agent.modelInvoked,
19991
+ classification: result.proposalRun.agent.classification,
19992
+ proposal: result.proposal,
19993
+ errors: result.proposalRun.agent.errors,
19994
+ steps: result.proposalRun.agent.steps
19995
+ },
19996
+ verification: result.verification,
19997
+ promotion: result.promotion
19998
+ }, null, 2));
19999
+ } else {
20000
+ const divider = "\u2500".repeat(72);
20001
+ console.log("");
20002
+ console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
20003
+ console.log("\u2551 PLAYWRIGHT INTELLIGENCE ENGINE: AUTONOMOUS REPAIR \u2551");
20004
+ console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
20005
+ console.log("");
20006
+ console.log(`Target Trace : ${path32.relative(process.cwd(), tracePath)}`);
20007
+ console.log(`Repair Status : ${result.status}`);
20008
+ if (result.reason) {
20009
+ console.log(`Status Reason : ${result.reason}`);
20010
+ }
20011
+ console.log(divider);
20012
+ console.log("Agent Diagnostic & Proposal:");
20013
+ const classificationStr = typeof result.proposalRun.agent.classification === "object" && result.proposalRun.agent.classification !== null ? result.proposalRun.agent.classification.category ?? result.proposalRun.agent.classification.name ?? "N/A" : result.proposalRun.agent.classification ?? "N/A";
20014
+ console.log(` \u2022 Classification: ${classificationStr}`);
20015
+ console.log(` \u2022 Model Invoked : ${result.proposalRun.agent.modelInvoked}`);
20016
+ console.log(` \u2022 Steps / Turns : ${result.proposalRun.agent.steps?.length ?? 0}`);
20017
+ if (result.proposal) {
20018
+ const primaryOp = result.proposal.operations[0];
20019
+ console.log(` \u2022 Decision : ${result.proposal.decision}`);
20020
+ console.log(` \u2022 Confidence : ${(result.proposal.confidence * 100).toFixed(1)}%`);
20021
+ console.log(` \u2022 Reasoning : ${result.proposal.reasoningSummary}`);
20022
+ if (primaryOp) {
20023
+ console.log(` \u2022 Operation : ${primaryOp.kind}`);
20024
+ if (primaryOp.kind === "replace_locator") {
20025
+ console.log(` \u2022 Failed Target : ${primaryOp.target}`);
20026
+ console.log(` \u2022 Healed Locator: ${primaryOp.locator}`);
20027
+ }
20028
+ }
20029
+ } else {
20030
+ console.log(" \u2022 Repair Proposal: None (Abstained or No Proposal)");
20031
+ }
20032
+ console.log(divider);
20033
+ if (result.verification) {
20034
+ console.log("Isolated Worktree Verification:");
20035
+ if (result.verification.worktree) {
20036
+ console.log(` \u2022 Worktree ID : ${result.verification.worktree.id}`);
20037
+ }
20038
+ if (result.verification.verification) {
20039
+ const passed = result.verification.verification.executionResult.exitCode === 0;
20040
+ console.log(` \u2022 Test Outcome : ${passed ? "\u2705 PASSED" : "\u274C FAILED"}`);
20041
+ console.log(` \u2022 Duration : ${result.verification.verification.executionResult.durationMs}ms`);
20042
+ } else {
20043
+ console.log(` \u2022 Status : ${result.verification.status}`);
20044
+ }
20045
+ console.log(divider);
20046
+ }
20047
+ if (result.promotion) {
20048
+ console.log("Workspace Promotion:");
20049
+ console.log(` \u2022 Status : ${result.promotion.success ? "\u2705 PROMOTED TO REPOSITORY" : "FAILED"}`);
20050
+ console.log(` \u2022 Target File : ${result.promotion.targetFilePath}`);
20051
+ console.log(` \u2022 Bytes Written : ${result.promotion.bytesWritten}`);
20052
+ console.log(` \u2022 Content Hash : ${result.promotion.finalHash.slice(0, 16)}...`);
20053
+ console.log(divider);
20054
+ } else if (result.verification?.verification?.executionResult.exitCode === 0 && !argv.includes("--promote")) {
20055
+ console.log("Workspace Promotion:");
20056
+ console.log(" \u2022 Status : \u23F8 VERIFIED IN WORKTREE (pass --promote to apply to main workspace)");
20057
+ console.log(divider);
20058
+ }
20059
+ }
19477
20060
  if (result.status === "PROPOSAL_REJECTED" || result.status === "VERIFICATION_FAILED") process.exitCode = 1;
19478
20061
  } catch (err) {
19479
20062
  console.error(`Autonomous repair failed: ${err?.message ?? String(err)}`);
@@ -19508,21 +20091,21 @@ async function runCli(argv) {
19508
20091
  return;
19509
20092
  }
19510
20093
  if (command === "benchmark") {
19511
- const cliDir = path31.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"));
19512
- const packageRootDir = path31.resolve(cliDir, "..");
19513
- const packageBenchmarkScript = path31.resolve(packageRootDir, "benchmarks", "runner", "master-benchmark-runner.ts");
19514
- const localBenchmarkScript = path31.resolve(process.cwd(), "benchmarks", "runner", "master-benchmark-runner.ts");
19515
- const benchmarkScript = fs31.existsSync(packageBenchmarkScript) ? packageBenchmarkScript : fs31.existsSync(localBenchmarkScript) ? localBenchmarkScript : void 0;
20094
+ const cliDir = path32.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"));
20095
+ const packageRootDir = path32.resolve(cliDir, "..");
20096
+ const packageBenchmarkScript = path32.resolve(packageRootDir, "benchmarks", "runner", "master-benchmark-runner.ts");
20097
+ const localBenchmarkScript = path32.resolve(process.cwd(), "benchmarks", "runner", "master-benchmark-runner.ts");
20098
+ const benchmarkScript = fs32.existsSync(packageBenchmarkScript) ? packageBenchmarkScript : fs32.existsSync(localBenchmarkScript) ? localBenchmarkScript : void 0;
19516
20099
  if (!benchmarkScript) {
19517
20100
  console.log("Empirical benchmark harness is maintained in the research testbed repository.");
19518
20101
  console.log("See docs/BENCHMARK.md for methodology, metrics, and empirical validation results.");
19519
20102
  return;
19520
20103
  }
19521
20104
  const tsxCandidatePaths = [
19522
- path31.resolve(packageRootDir, "node_modules", "tsx", "dist", "cli.mjs"),
19523
- path31.resolve(process.cwd(), "node_modules", "tsx", "dist", "cli.mjs")
20105
+ path32.resolve(packageRootDir, "node_modules", "tsx", "dist", "cli.mjs"),
20106
+ path32.resolve(process.cwd(), "node_modules", "tsx", "dist", "cli.mjs")
19524
20107
  ];
19525
- const tsxCli = tsxCandidatePaths.find((p) => fs31.existsSync(p));
20108
+ const tsxCli = tsxCandidatePaths.find((p) => fs32.existsSync(p));
19526
20109
  let executable;
19527
20110
  let finalArgs;
19528
20111
  if (tsxCli) {
@@ -19557,7 +20140,7 @@ async function runCli(argv) {
19557
20140
  const endpointArg = option("--endpoint");
19558
20141
  const result = await initProjectConfig(workspaceRoot, { force: argv.includes("--force") });
19559
20142
  if (apiKeyArg) {
19560
- const envPath = path31.join(workspaceRoot, ".env");
20143
+ const envPath = path32.join(workspaceRoot, ".env");
19561
20144
  const envLines = [
19562
20145
  "# Playwright Intelligence Engine (PIE) Configuration",
19563
20146
  `PIE_LLM_API_KEY=${apiKeyArg}`,
@@ -19568,13 +20151,13 @@ async function runCli(argv) {
19568
20151
  } else if (apiKeyArg.startsWith("sk-proj-") || apiKeyArg.startsWith("sk-")) {
19569
20152
  envLines.push("PIE_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions");
19570
20153
  }
19571
- await fs31.promises.writeFile(envPath, `${envLines.join("\n")}
20154
+ await fs32.promises.writeFile(envPath, `${envLines.join("\n")}
19572
20155
  `, "utf8");
19573
- const gitignorePath = path31.join(workspaceRoot, ".gitignore");
20156
+ const gitignorePath = path32.join(workspaceRoot, ".gitignore");
19574
20157
  try {
19575
- const gitignoreContent = await fs31.promises.readFile(gitignorePath, "utf8").catch(() => "");
20158
+ const gitignoreContent = await fs32.promises.readFile(gitignorePath, "utf8").catch(() => "");
19576
20159
  if (!gitignoreContent.includes(".env")) {
19577
- await fs31.promises.appendFile(gitignorePath, "\n.env\n", "utf8");
20160
+ await fs32.promises.appendFile(gitignorePath, "\n.env\n", "utf8");
19578
20161
  }
19579
20162
  } catch {
19580
20163
  }
@@ -19609,8 +20192,8 @@ async function runCli(argv) {
19609
20192
  const outputIndex = argv.indexOf("--out");
19610
20193
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19611
20194
  if (outputPath) {
19612
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19613
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(profile, null, 2)}
20195
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20196
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(profile, null, 2)}
19614
20197
  `, "utf8");
19615
20198
  } else {
19616
20199
  await writeRepositoryProfile(workspaceRoot, profile);
@@ -19637,7 +20220,7 @@ async function runCli(argv) {
19637
20220
  process.exit(1);
19638
20221
  }
19639
20222
  try {
19640
- const parsed = JSON.parse(await fs31.promises.readFile(path31.resolve(inputPath), "utf8"));
20223
+ const parsed = JSON.parse(await fs32.promises.readFile(path32.resolve(inputPath), "utf8"));
19641
20224
  const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? [];
19642
20225
  const clusters = clusterFailures(observations);
19643
20226
  const report = {
@@ -19649,8 +20232,8 @@ async function runCli(argv) {
19649
20232
  const outputIndex = argv.indexOf("--out");
19650
20233
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19651
20234
  if (outputPath) {
19652
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19653
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20235
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20236
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19654
20237
  `, "utf8");
19655
20238
  }
19656
20239
  console.log(JSON.stringify(report, null, 2));
@@ -19667,15 +20250,15 @@ async function runCli(argv) {
19667
20250
  process.exit(1);
19668
20251
  }
19669
20252
  try {
19670
- const parsed = JSON.parse(await fs31.promises.readFile(path31.resolve(inputPath), "utf8"));
20253
+ const parsed = JSON.parse(await fs32.promises.readFile(path32.resolve(inputPath), "utf8"));
19671
20254
  const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? parsed.diagnosisResults ?? [];
19672
20255
  const scorecard = buildEvaluationScorecard(observations);
19673
- const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), inputPath: path31.resolve(inputPath), ...scorecard };
20256
+ const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), inputPath: path32.resolve(inputPath), ...scorecard };
19674
20257
  const outputIndex = argv.indexOf("--out");
19675
20258
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19676
20259
  if (outputPath) {
19677
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19678
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20260
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20261
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19679
20262
  `, "utf8");
19680
20263
  }
19681
20264
  console.log(JSON.stringify(report, null, 2));
@@ -19692,23 +20275,23 @@ async function runCli(argv) {
19692
20275
  process.exit(1);
19693
20276
  }
19694
20277
  try {
19695
- const requirementsRaw = JSON.parse(await fs31.promises.readFile(path31.resolve(requirementsPath), "utf8"));
20278
+ const requirementsRaw = JSON.parse(await fs32.promises.readFile(path32.resolve(requirementsPath), "utf8"));
19696
20279
  const requirements = Array.isArray(requirementsRaw) ? requirementsRaw : Array.isArray(requirementsRaw.requirements) ? requirementsRaw.requirements : typeof requirementsRaw.requirements === "string" ? [{ id: "requirement-1", title: requirementsRaw.title ?? requirementsRaw.requirements.slice(0, 100), description: requirementsRaw.requirements }] : [];
19697
20280
  if (requirements.length === 0) throw new Error("The requirements document contains no requirements");
19698
20281
  const testsIndex = argv.indexOf("--tests");
19699
20282
  const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : void 0;
19700
20283
  let existingTests = [];
19701
20284
  if (testsPath) {
19702
- const testsRaw = JSON.parse(await fs31.promises.readFile(path31.resolve(testsPath), "utf8"));
20285
+ const testsRaw = JSON.parse(await fs32.promises.readFile(path32.resolve(testsPath), "utf8"));
19703
20286
  existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
19704
20287
  }
19705
20288
  const plan = buildTestIntelligencePlan(requirements, existingTests);
19706
- const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), requirementsPath: path31.resolve(requirementsPath), testsPath: testsPath ? path31.resolve(testsPath) : void 0, ...plan };
20289
+ const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), requirementsPath: path32.resolve(requirementsPath), testsPath: testsPath ? path32.resolve(testsPath) : void 0, ...plan };
19707
20290
  const outputIndex = argv.indexOf("--out");
19708
20291
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19709
20292
  if (outputPath) {
19710
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19711
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20293
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20294
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19712
20295
  `, "utf8");
19713
20296
  }
19714
20297
  console.log(JSON.stringify(report, null, 2));
@@ -19725,7 +20308,7 @@ async function runCli(argv) {
19725
20308
  process.exit(1);
19726
20309
  }
19727
20310
  try {
19728
- const requirementsRaw = JSON.parse(await fs31.promises.readFile(path31.resolve(requirementsPath), "utf8"));
20311
+ const requirementsRaw = JSON.parse(await fs32.promises.readFile(path32.resolve(requirementsPath), "utf8"));
19729
20312
  const requirementsDocument = Array.isArray(requirementsRaw) ? void 0 : requirementsRaw;
19730
20313
  const requirements = Array.isArray(requirementsRaw) ? requirementsRaw : Array.isArray(requirementsRaw.requirements) ? requirementsRaw.requirements : typeof requirementsRaw.requirements === "string" ? [{ id: "requirement-1", title: requirementsRaw.title ?? requirementsRaw.requirements.slice(0, 100), description: requirementsRaw.requirements }] : [];
19731
20314
  if (requirements.length === 0) throw new Error("The requirements document contains no requirements");
@@ -19733,7 +20316,7 @@ async function runCli(argv) {
19733
20316
  const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : void 0;
19734
20317
  let existingTests = [];
19735
20318
  if (testsPath) {
19736
- const testsRaw = JSON.parse(await fs31.promises.readFile(path31.resolve(testsPath), "utf8"));
20319
+ const testsRaw = JSON.parse(await fs32.promises.readFile(path32.resolve(testsPath), "utf8"));
19737
20320
  existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
19738
20321
  }
19739
20322
  const modeIndex = argv.indexOf("--mode");
@@ -19756,7 +20339,41 @@ async function runCli(argv) {
19756
20339
  write: argv.includes("--write"),
19757
20340
  verify: argv.includes("--verify")
19758
20341
  });
19759
- console.log(JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), requirementsPath: path31.resolve(requirementsPath), ...result }, null, 2));
20342
+ const reportPayload = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), requirementsPath: path32.resolve(requirementsPath), ...result };
20343
+ const reportsDir = path32.resolve(process.cwd(), ".pie", "reports");
20344
+ await fs32.promises.mkdir(reportsDir, { recursive: true });
20345
+ const reportJsonPath = path32.resolve(reportsDir, "generation-report.json");
20346
+ await fs32.promises.writeFile(reportJsonPath, `${JSON.stringify(reportPayload, null, 2)}
20347
+ `, "utf8");
20348
+ if (argv.includes("--json")) {
20349
+ console.log(JSON.stringify(reportPayload, null, 2));
20350
+ } else {
20351
+ const divider = "\u2500".repeat(72);
20352
+ console.log("");
20353
+ console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
20354
+ console.log("\u2551 PLAYWRIGHT INTELLIGENCE ENGINE: GENERATION AGENT \u2551");
20355
+ console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
20356
+ console.log("");
20357
+ console.log(`Requirements File : ${path32.relative(process.cwd(), requirementsPath)}`);
20358
+ console.log(`Mode : ${mode} (${author})`);
20359
+ console.log(`Generation Status : ${result.status}`);
20360
+ if (result.reason) {
20361
+ console.log(`Status Reason : ${result.reason}`);
20362
+ }
20363
+ console.log(divider);
20364
+ const generatedList = result.generatedFiles ?? [];
20365
+ console.log(`Generated Files (${generatedList.length}):`);
20366
+ if (generatedList.length > 0) {
20367
+ for (const file of generatedList) {
20368
+ console.log(` \u2022 ${file}`);
20369
+ }
20370
+ } else {
20371
+ console.log(" \u2022 None (No files written)");
20372
+ }
20373
+ console.log(divider);
20374
+ console.log(`Report JSON : ${path32.relative(process.cwd(), reportJsonPath)}`);
20375
+ console.log("(Pass --json to view full machine-readable payload)");
20376
+ }
19760
20377
  if (result.status === "ABSTAINED" || result.status === "MODEL_NOT_CONFIGURED" || result.status === "NEEDS_INPUT" || result.status === "VERIFICATION_FAILED" || result.status === "AGENT_NO_PROGRESS") process.exitCode = 1;
19761
20378
  } catch (err) {
19762
20379
  console.error(`Generation agent failed: ${err?.message ?? String(err)}`);
@@ -19788,15 +20405,15 @@ async function runCli(argv) {
19788
20405
  process.exit(1);
19789
20406
  }
19790
20407
  try {
19791
- const parsed = JSON.parse(await fs31.promises.readFile(path31.resolve(inputPath), "utf8"));
20408
+ const parsed = JSON.parse(await fs32.promises.readFile(path32.resolve(inputPath), "utf8"));
19792
20409
  const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
19793
20410
  const summaries = analyzeTestStability(runs);
19794
20411
  const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), runCount: runs.length, testCount: summaries.length, flakyTestCount: summaries.filter((summary) => summary.flaky).length, tests: summaries };
19795
20412
  const outputIndex = argv.indexOf("--out");
19796
20413
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19797
20414
  if (outputPath) {
19798
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19799
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20415
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20416
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19800
20417
  `, "utf8");
19801
20418
  }
19802
20419
  console.log(JSON.stringify(report, null, 2));
@@ -19813,14 +20430,14 @@ async function runCli(argv) {
19813
20430
  process.exit(1);
19814
20431
  }
19815
20432
  try {
19816
- const parsed = JSON.parse(await fs31.promises.readFile(path31.resolve(inputPath), "utf8"));
20433
+ const parsed = JSON.parse(await fs32.promises.readFile(path32.resolve(inputPath), "utf8"));
19817
20434
  const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
19818
20435
  const report = buildTeamDashboard(runs);
19819
20436
  const outputIndex = argv.indexOf("--out");
19820
20437
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19821
20438
  if (outputPath) {
19822
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19823
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20439
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20440
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19824
20441
  `, "utf8");
19825
20442
  }
19826
20443
  console.log(JSON.stringify(report, null, 2));
@@ -19837,7 +20454,7 @@ async function runCli(argv) {
19837
20454
  process.exit(1);
19838
20455
  }
19839
20456
  try {
19840
- const parsed = JSON.parse(await fs31.promises.readFile(path31.resolve(changedPath), "utf8"));
20457
+ const parsed = JSON.parse(await fs32.promises.readFile(path32.resolve(changedPath), "utf8"));
19841
20458
  const changedFiles = Array.isArray(parsed) ? parsed : parsed.changedFiles ?? [];
19842
20459
  const rootIndex = argv.indexOf("--root");
19843
20460
  const root = (rootIndex >= 0 ? argv[rootIndex + 1] : void 0) ?? process.cwd();
@@ -19845,8 +20462,8 @@ async function runCli(argv) {
19845
20462
  const outputIndex = argv.indexOf("--out");
19846
20463
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19847
20464
  if (outputPath) {
19848
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19849
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20465
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20466
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19850
20467
  `, "utf8");
19851
20468
  }
19852
20469
  console.log(JSON.stringify(report, null, 2));
@@ -19868,15 +20485,15 @@ async function runCli(argv) {
19868
20485
  if (mode !== "greenfield" && mode !== "brownfield") throw new Error("--mode must be 'greenfield' or 'brownfield'");
19869
20486
  const requirementsIndex = argv.indexOf("--requirements");
19870
20487
  const requirementsPath = requirementsIndex >= 0 ? argv[requirementsIndex + 1] : void 0;
19871
- const requirements = requirementsPath ? JSON.parse(await fs31.promises.readFile(path31.resolve(requirementsPath), "utf8")) : {};
20488
+ const requirements = requirementsPath ? JSON.parse(await fs32.promises.readFile(path32.resolve(requirementsPath), "utf8")) : {};
19872
20489
  const baseUrlIndex = argv.indexOf("--baseUrl");
19873
20490
  const baseUrlFromArg = baseUrlIndex >= 0 ? argv[baseUrlIndex + 1] : void 0;
19874
20491
  const baseUrl = baseUrlFromArg ?? requirements.baseUrl;
19875
20492
  let appModel = requirements.appModel;
19876
20493
  const appModelIndex = argv.indexOf("--app-model");
19877
20494
  if (appModelIndex >= 0 && argv[appModelIndex + 1]) {
19878
- const modelPath = path31.resolve(argv[appModelIndex + 1]);
19879
- appModel = JSON.parse(await fs31.promises.readFile(modelPath, "utf8"));
20495
+ const modelPath = path32.resolve(argv[appModelIndex + 1]);
20496
+ appModel = JSON.parse(await fs32.promises.readFile(modelPath, "utf8"));
19880
20497
  } else if (baseUrl && (argv.includes("--crawl") || (!requirements.steps || requirements.steps.length === 0))) {
19881
20498
  try {
19882
20499
  appModel = await AppCrawler.crawl({ baseUrl });
@@ -19907,20 +20524,20 @@ async function runCli(argv) {
19907
20524
  });
19908
20525
  if (verification.exitCode === 0) {
19909
20526
  try {
19910
- const specFullPath = path31.resolve(process.cwd(), spec.relativePath);
19911
- const originalContent = await fs31.promises.readFile(specFullPath, "utf8");
20527
+ const specFullPath = path32.resolve(process.cwd(), spec.relativePath);
20528
+ const originalContent = await fs32.promises.readFile(specFullPath, "utf8");
19912
20529
  const mutatedContent = originalContent.replace(
19913
20530
  /expect\(page\.getByText\(['"][^'"]+['"]\)\)/,
19914
20531
  `expect(page.getByText('__UNMATCHED_FALSIFICATION_TARGET__'))`
19915
20532
  );
19916
20533
  if (mutatedContent !== originalContent) {
19917
- await fs31.promises.writeFile(specFullPath, mutatedContent, "utf8");
20534
+ await fs32.promises.writeFile(specFullPath, mutatedContent, "utf8");
19918
20535
  const mutatedRun = await ExecutionRunner.run("npx", {
19919
20536
  cwd: process.cwd(),
19920
20537
  args: ["playwright", "test", spec.relativePath, "--reporter=line"],
19921
20538
  timeoutMs: 6e4
19922
20539
  });
19923
- await fs31.promises.writeFile(specFullPath, originalContent, "utf8");
20540
+ await fs32.promises.writeFile(specFullPath, originalContent, "utf8");
19924
20541
  verification.falsifiable = mutatedRun.exitCode !== 0;
19925
20542
  }
19926
20543
  } catch {
@@ -19933,8 +20550,8 @@ async function runCli(argv) {
19933
20550
  const outputIndex = argv.indexOf("--out");
19934
20551
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19935
20552
  if (outputPath) {
19936
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19937
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(report, null, 2)}
20553
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20554
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(report, null, 2)}
19938
20555
  `, "utf8");
19939
20556
  }
19940
20557
  console.log(JSON.stringify(report, null, 2));
@@ -19950,7 +20567,7 @@ async function runCli(argv) {
19950
20567
  const idx = argv.indexOf(flag);
19951
20568
  return idx >= 0 && argv[idx + 1] && !argv[idx + 1].startsWith("--") ? argv[idx + 1] : void 0;
19952
20569
  };
19953
- const workspaceRoot = path31.resolve(readFlag("--workspace") ?? process.cwd());
20570
+ const workspaceRoot = path32.resolve(readFlag("--workspace") ?? process.cwd());
19954
20571
  const baseUrl = readFlag("--url") ?? readFlag("--base-url");
19955
20572
  const requirementsPath = readFlag("--requirements");
19956
20573
  const markdownPath = readFlag("--markdown");
@@ -19966,13 +20583,13 @@ async function runCli(argv) {
19966
20583
  const runMutations = !argv.includes("--no-mutate");
19967
20584
  const allowMutatingActions = argv.includes("--allow-mutating");
19968
20585
  try {
19969
- let markdown = markdownPath ? await fs31.promises.readFile(path31.resolve(markdownPath), "utf8") : void 0;
20586
+ let markdown = markdownPath ? await fs32.promises.readFile(path32.resolve(markdownPath), "utf8") : void 0;
19970
20587
  let jsonRequirements;
19971
20588
  let prompt = promptFlag;
19972
- let featureText = gherkinPath ? await fs31.promises.readFile(path31.resolve(gherkinPath), "utf8") : void 0;
20589
+ let featureText = gherkinPath ? await fs32.promises.readFile(path32.resolve(gherkinPath), "utf8") : void 0;
19973
20590
  if (requirementsPath) {
19974
- const resolvedReqPath = path31.resolve(requirementsPath);
19975
- const rawReq = await fs31.promises.readFile(resolvedReqPath, "utf8");
20591
+ const resolvedReqPath = path32.resolve(requirementsPath);
20592
+ const rawReq = await fs32.promises.readFile(resolvedReqPath, "utf8");
19976
20593
  if (resolvedReqPath.toLowerCase().endsWith(".json")) {
19977
20594
  jsonRequirements = JSON.parse(rawReq);
19978
20595
  } else if (resolvedReqPath.toLowerCase().endsWith(".feature")) {
@@ -20015,13 +20632,57 @@ async function runCli(argv) {
20015
20632
  verify,
20016
20633
  runMutations
20017
20634
  });
20635
+ const reportsDir = path32.resolve(process.cwd(), ".pie", "reports");
20636
+ await fs32.promises.mkdir(reportsDir, { recursive: true });
20637
+ const defaultReportPath = path32.resolve(reportsDir, "qa-report.json");
20018
20638
  const outFlag = readFlag("--out");
20019
- if (outFlag) {
20020
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outFlag)), { recursive: true });
20021
- await fs31.promises.writeFile(path31.resolve(outFlag), `${JSON.stringify(result.qaReport, null, 2)}
20639
+ const targetReportJsonPath = outFlag ? path32.resolve(outFlag) : defaultReportPath;
20640
+ await fs32.promises.mkdir(path32.dirname(targetReportJsonPath), { recursive: true });
20641
+ await fs32.promises.writeFile(targetReportJsonPath, `${JSON.stringify(result.qaReport, null, 2)}
20022
20642
  `, "utf8");
20643
+ if (argv.includes("--json")) {
20644
+ console.log(JSON.stringify(result.qaReport, null, 2));
20645
+ } else {
20646
+ const divider = "\u2500".repeat(72);
20647
+ console.log("");
20648
+ console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
20649
+ console.log("\u2551 PLAYWRIGHT INTELLIGENCE ENGINE: QA REPORT \u2551");
20650
+ console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
20651
+ console.log("");
20652
+ console.log(`Feature : ${result.qaReport.featureName}`);
20653
+ console.log(`Topology : ${result.qaReport.topology}`);
20654
+ console.log(`Status : ${result.qaReport.status}`);
20655
+ console.log(`Runtime Verified : ${result.qaReport.runtimeVerified ? "\u2705 YES" : "\u274C NO"}`);
20656
+ console.log(`Static Validation : ${result.qaReport.staticValidation.passed ? "\u2705 PASSED" : "\u274C FAILED"}`);
20657
+ console.log(divider);
20658
+ console.log("Findings & Diagnostics:");
20659
+ if (result.qaReport.findings && result.qaReport.findings.length > 0) {
20660
+ for (const finding of result.qaReport.findings) {
20661
+ const sev = finding.severity ? finding.severity.toUpperCase() : "INFO";
20662
+ console.log(` \u2022 [${sev}] ${finding.actualBehavior}`);
20663
+ }
20664
+ } else {
20665
+ console.log(" \u2022 No defects or runtime errors discovered.");
20666
+ }
20667
+ if (result.qaReport.staticValidation.violations.length > 0) {
20668
+ console.log("\nValidation Violations:");
20669
+ for (const viol of result.qaReport.staticValidation.violations) {
20670
+ console.log(` \u2022 ${viol}`);
20671
+ }
20672
+ }
20673
+ console.log(divider);
20674
+ console.log("Artifacts Generated:");
20675
+ console.log(` \u2022 Generated Files : ${result.qaReport.generatedFiles.length > 0 ? result.qaReport.generatedFiles.join(", ") : "None"}`);
20676
+ if (result.qaReport.artifacts?.featureFilePath) {
20677
+ console.log(` \u2022 Feature File : ${path32.relative(process.cwd(), result.qaReport.artifacts.featureFilePath)}`);
20678
+ }
20679
+ if (result.qaReport.artifacts?.explorationBundlePath) {
20680
+ console.log(` \u2022 Exploration Log : ${path32.relative(process.cwd(), result.qaReport.artifacts.explorationBundlePath)}`);
20681
+ }
20682
+ console.log(` \u2022 Report JSON : ${path32.relative(process.cwd(), targetReportJsonPath)}`);
20683
+ console.log(divider);
20684
+ console.log("(Pass --json to view full machine-readable report)");
20023
20685
  }
20024
- console.log(JSON.stringify(result.qaReport, null, 2));
20025
20686
  } catch (err) {
20026
20687
  console.error(`QA pipeline failed: ${err?.message ?? String(err)}`);
20027
20688
  process.exit(1);
@@ -20035,7 +20696,7 @@ async function runCli(argv) {
20035
20696
  process.exit(1);
20036
20697
  }
20037
20698
  try {
20038
- const report = JSON.parse(await fs31.promises.readFile(path31.resolve(reportPath), "utf8"));
20699
+ const report = JSON.parse(await fs32.promises.readFile(path32.resolve(reportPath), "utf8"));
20039
20700
  const repositoryIndex = argv.indexOf("--repository");
20040
20701
  const repository = repositoryIndex >= 0 ? argv[repositoryIndex + 1] : void 0;
20041
20702
  const evidence = buildCiEvidenceReport(report, { repository, runId: process.env.GITHUB_RUN_ID ?? process.env.CI_PIPELINE_ID });
@@ -20043,16 +20704,16 @@ async function runCli(argv) {
20043
20704
  const outputIndex = argv.indexOf("--out");
20044
20705
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
20045
20706
  if (outputPath) {
20046
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
20047
- await fs31.promises.writeFile(path31.resolve(outputPath), `${JSON.stringify(evidence, null, 2)}
20707
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(outputPath)), { recursive: true });
20708
+ await fs32.promises.writeFile(path32.resolve(outputPath), `${JSON.stringify(evidence, null, 2)}
20048
20709
  `, "utf8");
20049
20710
  }
20050
20711
  const sarifIndex = argv.indexOf("--sarif-out");
20051
20712
  const sarifPath = sarifIndex >= 0 ? argv[sarifIndex + 1] : void 0;
20052
20713
  if (sarifPath) {
20053
20714
  const sarif = buildSarifReport(evidence, PACKAGE_VERSION);
20054
- await fs31.promises.mkdir(path31.dirname(path31.resolve(sarifPath)), { recursive: true });
20055
- await fs31.promises.writeFile(path31.resolve(sarifPath), `${JSON.stringify(sarif, null, 2)}
20715
+ await fs32.promises.mkdir(path32.dirname(path32.resolve(sarifPath)), { recursive: true });
20716
+ await fs32.promises.writeFile(path32.resolve(sarifPath), `${JSON.stringify(sarif, null, 2)}
20056
20717
  `, "utf8");
20057
20718
  }
20058
20719
  console.log(evidence.markdown);
@@ -20180,9 +20841,9 @@ async function runDoctor() {
20180
20841
  }
20181
20842
  let pwFound = false;
20182
20843
  try {
20183
- const cwdPkg = path31.resolve(process.cwd(), "package.json");
20184
- if (fs31.existsSync(cwdPkg)) {
20185
- const pkg = JSON.parse(fs31.readFileSync(cwdPkg, "utf-8"));
20844
+ const cwdPkg = path32.resolve(process.cwd(), "package.json");
20845
+ if (fs32.existsSync(cwdPkg)) {
20846
+ const pkg = JSON.parse(fs32.readFileSync(cwdPkg, "utf-8"));
20186
20847
  const pwVer = pkg.devDependencies?.["@playwright/test"] || pkg.dependencies?.["@playwright/test"];
20187
20848
  if (pwVer) {
20188
20849
  console.log(` \u2713 Playwright Test : ${pwVer} (Found in package.json)`);
@@ -20205,7 +20866,7 @@ async function runDoctor() {
20205
20866
  }
20206
20867
  }
20207
20868
  try {
20208
- fs31.accessSync(process.cwd(), fs31.constants.W_OK);
20869
+ fs32.accessSync(process.cwd(), fs32.constants.W_OK);
20209
20870
  console.log(` \u2713 Workspace Access : Read/Write verified for '${process.cwd()}'`);
20210
20871
  } catch {
20211
20872
  console.log(` \u2717 Workspace Access : Permission denied writing to '${process.cwd()}'`);
@@ -20479,6 +21140,7 @@ export {
20479
21140
  renderGitHubAnnotation,
20480
21141
  renderLocator,
20481
21142
  runAutonomousRepairWorkflow,
21143
+ runBatchRepairWorkflow,
20482
21144
  runCli,
20483
21145
  runEvidenceDrivenGeneration,
20484
21146
  runGate,