playwright-intelligence-engine 2.0.5 → 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.5";
4
- var ENGINE_MILESTONE = "2.0.5";
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,13 +279,15 @@ 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
  }
275
- const topFailureScore = traceCandidates[0]?.failureScore ?? 0;
282
+ const topCandidate = traceCandidates[0];
283
+ const hasFailure = Boolean(topCandidate?.hasRealError);
284
+ const topFailureScore = topCandidate?.failureScore ?? 0;
276
285
  return {
277
286
  traceEntryName: bestTrace,
278
287
  networkEntryName: bestNetwork,
279
288
  traceEntryNames: orderedTraceNames,
280
289
  networkEntryNames,
281
- hasFailure: topFailureScore > 0,
290
+ hasFailure,
282
291
  failureScore: topFailureScore
283
292
  };
284
293
  } catch {
@@ -522,13 +531,25 @@ var SnapshotResolver = class _SnapshotResolver {
522
531
  const role = attributes["role"] ?? _SnapshotResolver.inferRoleFromTag(tag, attributes);
523
532
  const inputValue = tag === "input" ? attributes["value"] ?? attributes["__playwright_value_"] : void 0;
524
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
+ }
525
545
  return {
526
546
  tag,
527
547
  attributes,
528
548
  text: textAccumulator.trim(),
529
549
  children,
530
550
  role,
531
- accessibleName: accessibleName || void 0
551
+ accessibleName: accessibleName || void 0,
552
+ boundingBox
532
553
  };
533
554
  }
534
555
  /**
@@ -601,13 +622,25 @@ var SnapshotResolver = class _SnapshotResolver {
601
622
  const role = attributes["role"] ?? _SnapshotResolver.inferRoleFromTag(tag, attributes);
602
623
  const inputValue = tag === "input" ? attributes["value"] ?? attributes["__playwright_value_"] : void 0;
603
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
+ }
604
636
  return {
605
637
  tag,
606
638
  attributes,
607
639
  text: textAccumulator.trim(),
608
640
  children,
609
641
  role,
610
- accessibleName: accessibleName || void 0
642
+ accessibleName: accessibleName || void 0,
643
+ boundingBox
611
644
  };
612
645
  }
613
646
  /**
@@ -3398,6 +3431,12 @@ var LocatorDisambiguationEngine = class {
3398
3431
  return landmarkResult;
3399
3432
  }
3400
3433
  }
3434
+ if (context.targetBoundingBox) {
3435
+ const spatialResult = this.attemptSpatialDisambiguation(candidates, context.targetBoundingBox, pageProperty);
3436
+ if (spatialResult) {
3437
+ return spatialResult;
3438
+ }
3439
+ }
3401
3440
  const hasExplicitPositionalIntent = this.verifyExplicitPositionalIntent(context);
3402
3441
  if (hasExplicitPositionalIntent) {
3403
3442
  const candidate = candidates[0];
@@ -3442,10 +3481,10 @@ var LocatorDisambiguationEngine = class {
3442
3481
  if (!context.rootDom) return null;
3443
3482
  const candidatePaths = candidates.map((c) => this.findPathToElement(context.rootDom, c));
3444
3483
  for (let cIdx = 0; cIdx < candidates.length; cIdx++) {
3445
- const path32 = candidatePaths[cIdx];
3446
- if (!path32 || path32.length < 2) continue;
3447
- for (let i = path32.length - 2; i >= 0; i--) {
3448
- 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];
3449
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";
3450
3489
  if (isContainer) {
3451
3490
  const containerText = this.getCleanText(container);
@@ -3488,8 +3527,8 @@ var LocatorDisambiguationEngine = class {
3488
3527
  for (const landmark of landmarks) {
3489
3528
  if (textToCheck.includes(landmark)) {
3490
3529
  const candidatesInLandmark = candidates.filter((candidate) => {
3491
- const path32 = this.findPathToElement(context.rootDom, candidate);
3492
- 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));
3493
3532
  });
3494
3533
  if (candidatesInLandmark.length === 1) {
3495
3534
  const candidate = candidatesInLandmark[0];
@@ -3549,6 +3588,35 @@ var LocatorDisambiguationEngine = class {
3549
3588
  }
3550
3589
  return null;
3551
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
+ }
3552
3620
  static getCleanText(el) {
3553
3621
  if (el.text && el.text.trim().length > 0) {
3554
3622
  return el.text.trim();
@@ -3565,7 +3633,7 @@ var LocatorDisambiguationEngine = class {
3565
3633
 
3566
3634
  // src/selector/repair-candidate-ranker.ts
3567
3635
  var RepairCandidateRanker = class {
3568
- static rank(candidates, originalSelector, intendedAction, intelligence, rootDom, testIntentText) {
3636
+ static rank(candidates, originalSelector, intendedAction, intelligence, rootDom, testIntentText, targetBoundingBox) {
3569
3637
  const projectContext = intelligence?.profile ? {
3570
3638
  preferredLocator: intelligence.profile.locatorConventions.preferred,
3571
3639
  locatorUsage: {
@@ -3578,7 +3646,7 @@ var RepairCandidateRanker = class {
3578
3646
  } : void 0;
3579
3647
  const disambiguation = candidates.length > 1 ? LocatorDisambiguationEngine.disambiguate(
3580
3648
  candidates,
3581
- { originalSelector, intendedAction, rootDom, testIntentText },
3649
+ { originalSelector, intendedAction, rootDom, testIntentText, targetBoundingBox },
3582
3650
  "page"
3583
3651
  ) : void 0;
3584
3652
  return candidates.flatMap((candidate) => {
@@ -4057,14 +4125,14 @@ var EvidenceRedactor = class _EvidenceRedactor {
4057
4125
  static redactPayload(payload) {
4058
4126
  const redactedFields = [];
4059
4127
  const rulesApplied = [];
4060
- function recurse(value, path32) {
4128
+ function recurse(value, path33) {
4061
4129
  if (value === null || value === void 0) {
4062
4130
  return value;
4063
4131
  }
4064
4132
  if (typeof value === "string") {
4065
4133
  const { redactedText, rulesApplied: applied } = _EvidenceRedactor.redactText(value);
4066
4134
  if (applied.length > 0) {
4067
- redactedFields.push(path32);
4135
+ redactedFields.push(path33);
4068
4136
  for (const rule of applied) {
4069
4137
  if (!rulesApplied.includes(rule)) rulesApplied.push(rule);
4070
4138
  }
@@ -4072,12 +4140,12 @@ var EvidenceRedactor = class _EvidenceRedactor {
4072
4140
  return redactedText;
4073
4141
  }
4074
4142
  if (Array.isArray(value)) {
4075
- return value.map((item, idx) => recurse(item, `${path32}[${idx}]`));
4143
+ return value.map((item, idx) => recurse(item, `${path33}[${idx}]`));
4076
4144
  }
4077
4145
  if (typeof value === "object") {
4078
4146
  const result = {};
4079
4147
  for (const [k, v] of Object.entries(value)) {
4080
- const currentPath = path32 ? `${path32}.${k}` : k;
4148
+ const currentPath = path33 ? `${path33}.${k}` : k;
4081
4149
  const isSensitiveField = _EvidenceRedactor.SENSITIVE_FIELD_PATTERNS.some((p) => p.test(k));
4082
4150
  if (isSensitiveField) {
4083
4151
  result[k] = "[REDACTED]";
@@ -7937,6 +8005,19 @@ async function listSourceFiles2(root, depth = 0) {
7937
8005
  function normalize(value) {
7938
8006
  return value.replace(/\s+/g, "").replace(/;$/, "");
7939
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
+ }
7940
8021
  function selectorMatches(selector, locator) {
7941
8022
  const wanted = normalize(selector);
7942
8023
  const source = normalize(locator.selectorText);
@@ -7944,6 +8025,13 @@ function selectorMatches(selector, locator) {
7944
8025
  if (source.includes(wanted)) {
7945
8026
  return { score: 100, evidence: ["Failed selector expression matched a Page Object locator expression"] };
7946
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
+ }
7947
8035
  const argumentMatch = source.match(/(?:locator|getByRole|getByText|getByTestId|getByLabel|getByPlaceholder|getByAltText|getByTitle)\((.*)\)/);
7948
8036
  if (argumentMatch?.[1] && (argumentMatch[1].includes(wanted) || wanted.includes(argumentMatch[1]))) {
7949
8037
  return { score: 80, evidence: ["Failed selector value matched a Page Object locator argument"] };
@@ -7967,8 +8055,8 @@ function resolveTestSourcePath(root, sourceFiles, sourceFile) {
7967
8055
  return wanted.endsWith(`/${candidate}`) || candidate.endsWith(`/${wanted}`) || candidate === wanted;
7968
8056
  });
7969
8057
  if (suffixMatches.length === 1 && suffixMatches[0]) return { path: suffixMatches[0], remapped: true };
7970
- const basename = path14.basename(sourceFile).toLowerCase();
7971
- 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);
7972
8060
  return basenameMatches.length === 1 && basenameMatches[0] ? { path: basenameMatches[0], remapped: true } : { remapped: false };
7973
8061
  }
7974
8062
  async function discoverRepairSourceMapping(input) {
@@ -7987,7 +8075,9 @@ async function discoverRepairSourceMapping(input) {
7987
8075
  } catch {
7988
8076
  continue;
7989
8077
  }
7990
- 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;
7991
8081
  const result = await new StaticAstCollector().collect({ targetFilePath: filePath });
7992
8082
  const payload = result[0]?.payload;
7993
8083
  if (!payload?.isPageObject || !payload.locators) continue;
@@ -15268,6 +15358,353 @@ async function runAutonomousRepairWorkflow(input) {
15268
15358
  };
15269
15359
  }
15270
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
+
15271
15708
  // src/intelligence/generation-agent.ts
15272
15709
  import { z as z11 } from "zod";
15273
15710
 
@@ -15732,13 +16169,13 @@ async function runGenerationAgent(input) {
15732
16169
  }
15733
16170
 
15734
16171
  // src/intelligence/generation-verifier.ts
15735
- import fs27 from "fs/promises";
15736
- import fsSync5 from "fs";
15737
- import path26 from "path";
16172
+ import fs28 from "fs/promises";
16173
+ import fsSync6 from "fs";
16174
+ import path27 from "path";
15738
16175
  import { z as z13 } from "zod";
15739
16176
 
15740
16177
  // src/intelligence/workflow-planner.ts
15741
- import crypto9 from "crypto";
16178
+ import crypto10 from "crypto";
15742
16179
  import { z as z12 } from "zod";
15743
16180
  var WorkflowProvenanceSchema = z12.enum([
15744
16181
  "explicit-requirement",
@@ -15816,7 +16253,7 @@ var GenerationWorkflowPlanSchema = z12.object({
15816
16253
  }).optional()
15817
16254
  });
15818
16255
  function stableHash(value) {
15819
- 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);
15820
16257
  }
15821
16258
  function computeWorkflowPlanHash(plan) {
15822
16259
  return stableHash({
@@ -16666,7 +17103,7 @@ export { expect } from '@playwright/test';
16666
17103
  });
16667
17104
  fixtureImportForSpec = `./fixtures/app.fixtures.js`;
16668
17105
  } else {
16669
- const relFromSpec = path26.posix.relative("tests", repoGraph.testImport).replace(/\.ts$/, ".js");
17106
+ const relFromSpec = path27.posix.relative("tests", repoGraph.testImport).replace(/\.ts$/, ".js");
16670
17107
  fixtureImportForSpec = relFromSpec.startsWith(".") ? relFromSpec : `./${relFromSpec}`;
16671
17108
  }
16672
17109
  const testBlocks = [];
@@ -16785,22 +17222,22 @@ export default defineConfig({
16785
17222
  }
16786
17223
  function resolveWorkspaceCli(workspaceRoot, tool) {
16787
17224
  if (tool === "tsc") {
16788
- const jsEntry = path26.join(workspaceRoot, "node_modules", "typescript", "bin", "tsc");
16789
- if (fsSync5.existsSync(jsEntry)) {
17225
+ const jsEntry = path27.join(workspaceRoot, "node_modules", "typescript", "bin", "tsc");
17226
+ if (fsSync6.existsSync(jsEntry)) {
16790
17227
  return { cmd: process.execPath, prefixArgs: [jsEntry] };
16791
17228
  }
16792
- const binEntry = path26.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
16793
- if (fsSync5.existsSync(binEntry)) {
17229
+ const binEntry = path27.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
17230
+ if (fsSync6.existsSync(binEntry)) {
16794
17231
  return { cmd: "npx", prefixArgs: ["tsc"] };
16795
17232
  }
16796
17233
  return void 0;
16797
17234
  }
16798
- const pwJs = path26.join(workspaceRoot, "node_modules", "@playwright", "test", "cli.js");
16799
- if (fsSync5.existsSync(pwJs)) {
17235
+ const pwJs = path27.join(workspaceRoot, "node_modules", "@playwright", "test", "cli.js");
17236
+ if (fsSync6.existsSync(pwJs)) {
16800
17237
  return { cmd: process.execPath, prefixArgs: [pwJs] };
16801
17238
  }
16802
- const pwBin = path26.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "playwright.cmd" : "playwright");
16803
- if (fsSync5.existsSync(pwBin)) {
17239
+ const pwBin = path27.join(workspaceRoot, "node_modules", ".bin", process.platform === "win32" ? "playwright.cmd" : "playwright");
17240
+ if (fsSync6.existsSync(pwBin)) {
16804
17241
  return { cmd: "npx", prefixArgs: ["playwright"] };
16805
17242
  }
16806
17243
  return void 0;
@@ -16814,16 +17251,16 @@ async function runCommandInWorkspace(cmd, args, cwd, timeoutMs = 6e4) {
16814
17251
  return { exitCode: res.exitCode ?? 1, stdout: res.stdout, stderr: res.stderr };
16815
17252
  }
16816
17253
  async function findFirstTraceZip(root) {
16817
- 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")];
16818
17255
  for (const base of candidates) {
16819
- if (!fsSync5.existsSync(base)) continue;
16820
- 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(() => []);
16821
17258
  for (const entry of entries) {
16822
17259
  if (entry.isDirectory()) {
16823
- const traceFile = path26.join(base, entry.name, "trace.zip");
16824
- if (fsSync5.existsSync(traceFile)) return traceFile;
17260
+ const traceFile = path27.join(base, entry.name, "trace.zip");
17261
+ if (fsSync6.existsSync(traceFile)) return traceFile;
16825
17262
  } else if (entry.name.endsWith("trace.zip")) {
16826
- return path26.join(base, entry.name);
17263
+ return path27.join(base, entry.name);
16827
17264
  }
16828
17265
  }
16829
17266
  }
@@ -16896,9 +17333,9 @@ function formatQaReportMarkdown(report) {
16896
17333
  `;
16897
17334
  }
16898
17335
  async function runEvidenceDrivenGeneration(input) {
16899
- const workspaceRoot = path26.resolve(input.workspaceRoot);
16900
- const artifactDir = path26.resolve(input.outArtifactDir ?? path26.join(workspaceRoot, ".pie"));
16901
- 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 });
16902
17339
  const repoGraph = await buildBrownfieldRepositoryGraph(workspaceRoot).catch(() => void 0);
16903
17340
  let workflowPlan = buildWorkflowPlan({
16904
17341
  ...input,
@@ -16915,12 +17352,12 @@ async function runEvidenceDrivenGeneration(input) {
16915
17352
  featureText = input.editedFeatureText;
16916
17353
  }
16917
17354
  }
16918
- const workflowPlanPath = path26.join(artifactDir, "workflow-plan.json");
16919
- const featureFilePath = path26.join(artifactDir, `${workflowPlan.slug}.feature`);
16920
- 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)}
16921
17358
  `, "utf8");
16922
- await fs27.writeFile(featureFilePath, featureText, "utf8");
16923
- const explorationBundlePath = path26.join(artifactDir, "exploration-evidence.json");
17359
+ await fs28.writeFile(featureFilePath, featureText, "utf8");
17360
+ const explorationBundlePath = path27.join(artifactDir, "exploration-evidence.json");
16924
17361
  const explorationBundle = await InteractiveWorkflowRunner.run({
16925
17362
  baseUrl: input.baseUrl,
16926
17363
  workspaceRoot,
@@ -17049,9 +17486,9 @@ async function runEvidenceDrivenGeneration(input) {
17049
17486
  modifiedFiles = writeRes.modified;
17050
17487
  }
17051
17488
  }
17052
- const targetTsconfig = path26.join(workspaceRoot, "tsconfig.json");
17489
+ const targetTsconfig = path27.join(workspaceRoot, "tsconfig.json");
17053
17490
  const tscTool = resolveWorkspaceCli(workspaceRoot, "tsc");
17054
- if (input.write !== false && staticViolations.length === 0 && fsSync5.existsSync(targetTsconfig) && tscTool) {
17491
+ if (input.write !== false && staticViolations.length === 0 && fsSync6.existsSync(targetTsconfig) && tscTool) {
17055
17492
  tscChecked = true;
17056
17493
  const tscRes = await runCommandInWorkspace(tscTool.cmd, [...tscTool.prefixArgs, "-p", targetTsconfig, "--noEmit"], workspaceRoot);
17057
17494
  tscPassed = tscRes.exitCode === 0;
@@ -17163,12 +17600,12 @@ async function runEvidenceDrivenGeneration(input) {
17163
17600
  if (input.runMutations !== false && execPassed && canRunPlaywright && pwTool) {
17164
17601
  mutationExecuted = true;
17165
17602
  for (const spec of specFiles) {
17166
- const targetPath = path26.resolve(workspaceRoot, spec.relativePath);
17167
- const original = await fs27.readFile(targetPath, "utf8");
17603
+ const targetPath = path27.resolve(workspaceRoot, spec.relativePath);
17604
+ const original = await fs28.readFile(targetPath, "utf8");
17168
17605
  const levelCMutants = buildAssertionMutants(spec.relativePath, original);
17169
17606
  for (const mut of levelCMutants) {
17170
17607
  try {
17171
- await fs27.writeFile(targetPath, mut.mutatedContent, "utf8");
17608
+ await fs28.writeFile(targetPath, mut.mutatedContent, "utf8");
17172
17609
  const res = await runCommandInWorkspace(pwTool.cmd, [...pwTool.prefixArgs, "test", spec.relativePath, "--reporter=line"], workspaceRoot, 6e4);
17173
17610
  mutationEntries.push({
17174
17611
  id: mut.id,
@@ -17187,7 +17624,7 @@ async function runEvidenceDrivenGeneration(input) {
17187
17624
  status: "INVALID"
17188
17625
  });
17189
17626
  } finally {
17190
- await fs27.writeFile(targetPath, original, "utf8");
17627
+ await fs28.writeFile(targetPath, original, "utf8");
17191
17628
  }
17192
17629
  }
17193
17630
  const hasApiEndpoints = explorationBundle.network.some((n) => n.url.includes("/api/"));
@@ -17201,7 +17638,7 @@ $1`) : original.replace(/(test\s*\()/, `${injected}
17201
17638
  $1`);
17202
17639
  if (mutatedApiContent !== original) {
17203
17640
  try {
17204
- await fs27.writeFile(targetPath, mutatedApiContent, "utf8");
17641
+ await fs28.writeFile(targetPath, mutatedApiContent, "utf8");
17205
17642
  const res = await runCommandInWorkspace(pwTool.cmd, [...pwTool.prefixArgs, "test", spec.relativePath, "--reporter=line"], workspaceRoot, 6e4);
17206
17643
  mutationEntries.push({
17207
17644
  id: "mut-b-api-500",
@@ -17212,7 +17649,7 @@ $1`);
17212
17649
  exitCode: res.exitCode
17213
17650
  });
17214
17651
  } finally {
17215
- await fs27.writeFile(targetPath, original, "utf8");
17652
+ await fs28.writeFile(targetPath, original, "utf8");
17216
17653
  }
17217
17654
  }
17218
17655
  }
@@ -17226,8 +17663,8 @@ $1`);
17226
17663
  const mutationScore = validTotal > 0 ? Number((killed / validTotal * 100).toFixed(2)) : null;
17227
17664
  const hasDefects = explorationBundle.findings.some((f) => f.category === "SPEC_VIOLATION" || f.category === "RUNTIME_ERROR");
17228
17665
  const finalStatus = staticViolations.length > 0 || executed && !execPassed ? "VERIFICATION_FAILED" : execPassed && (!hasDefects && (mutationScore === null || mutationScore >= 80)) ? "VERIFIED" : execPassed ? "VERIFIED_WITH_WARNINGS" : "GENERATED";
17229
- const qaReportJsonPath = path26.join(artifactDir, "qa-report.json");
17230
- 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");
17231
17668
  const qaReport = {
17232
17669
  reportVersion: "1.0",
17233
17670
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17308,9 +17745,9 @@ $1`);
17308
17745
  mcpDependenciesUsed: "none"
17309
17746
  }
17310
17747
  };
17311
- await fs27.writeFile(qaReportJsonPath, `${JSON.stringify(qaReport, null, 2)}
17748
+ await fs28.writeFile(qaReportJsonPath, `${JSON.stringify(qaReport, null, 2)}
17312
17749
  `, "utf8");
17313
- await fs27.writeFile(qaReportMarkdownPath, formatQaReportMarkdown(qaReport), "utf8");
17750
+ await fs28.writeFile(qaReportMarkdownPath, formatQaReportMarkdown(qaReport), "utf8");
17314
17751
  return {
17315
17752
  workflowPlan,
17316
17753
  featureText,
@@ -17321,9 +17758,9 @@ $1`);
17321
17758
  }
17322
17759
 
17323
17760
  // src/intelligence/suite-intelligence.ts
17324
- import fs28 from "fs/promises";
17325
- import fsSync6 from "fs";
17326
- import path27 from "path";
17761
+ import fs29 from "fs/promises";
17762
+ import fsSync7 from "fs";
17763
+ import path28 from "path";
17327
17764
  function round(value) {
17328
17765
  return Number(value.toFixed(3));
17329
17766
  }
@@ -17377,14 +17814,14 @@ async function listTestFiles(root, depth = 0) {
17377
17814
  const ignored = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", "test-results", "playwright-report"]);
17378
17815
  let entries;
17379
17816
  try {
17380
- entries = await fs28.readdir(root, { withFileTypes: true });
17817
+ entries = await fs29.readdir(root, { withFileTypes: true });
17381
17818
  } catch {
17382
17819
  return [];
17383
17820
  }
17384
17821
  const files = [];
17385
17822
  for (const entry of entries) {
17386
17823
  if (ignored.has(entry.name)) continue;
17387
- const fullPath = path27.join(root, entry.name);
17824
+ const fullPath = path28.join(root, entry.name);
17388
17825
  if (entry.isDirectory()) files.push(...await listTestFiles(fullPath, depth + 1));
17389
17826
  else if (/\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/i.test(entry.name)) files.push(fullPath);
17390
17827
  }
@@ -17393,7 +17830,7 @@ async function listTestFiles(root, depth = 0) {
17393
17830
  function resolveImport(root, sourceFile, specifier, aliases) {
17394
17831
  let base;
17395
17832
  if (specifier.startsWith(".")) {
17396
- base = path27.resolve(path27.dirname(sourceFile), specifier);
17833
+ base = path28.resolve(path28.dirname(sourceFile), specifier);
17397
17834
  } else {
17398
17835
  const alias = aliases.find((candidate) => {
17399
17836
  if (candidate.pattern.includes("*")) {
@@ -17406,17 +17843,17 @@ function resolveImport(root, sourceFile, specifier, aliases) {
17406
17843
  const [prefix, suffix] = alias.pattern.split("*");
17407
17844
  const wildcard = alias.pattern.includes("*") ? specifier.slice((prefix ?? "").length, specifier.length - (suffix ?? "").length || void 0) : "";
17408
17845
  const target = alias.targets[0];
17409
- if (target) base = path27.resolve(root, target.replace("*", wildcard));
17846
+ if (target) base = path28.resolve(root, target.replace("*", wildcard));
17410
17847
  }
17411
17848
  }
17412
17849
  if (!base) return void 0;
17413
17850
  const candidates = [
17414
17851
  base,
17415
17852
  ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].map((extension) => `${base}${extension}`),
17416
- ...[".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}`))
17417
17854
  ];
17418
- const resolved = candidates.find((candidate) => fsSync6.existsSync(candidate) && fsSync6.statSync(candidate).isFile());
17419
- 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;
17420
17857
  }
17421
17858
  function extractImportSpecifiers(source) {
17422
17859
  const imports = /* @__PURE__ */ new Set();
@@ -17433,27 +17870,27 @@ function extractImportSpecifiers(source) {
17433
17870
  return [...imports];
17434
17871
  }
17435
17872
  async function loadPathAliases(root) {
17436
- const tsconfigPath = path27.join(root, "tsconfig.json");
17873
+ const tsconfigPath = path28.join(root, "tsconfig.json");
17437
17874
  try {
17438
- const raw = await fs28.readFile(tsconfigPath, "utf8");
17875
+ const raw = await fs29.readFile(tsconfigPath, "utf8");
17439
17876
  const withoutComments = raw.replace(/^\s*\/\/.*$/gm, "");
17440
17877
  const parsed = JSON.parse(withoutComments);
17441
17878
  const baseUrl = parsed.compilerOptions?.baseUrl ?? ".";
17442
17879
  return Object.entries(parsed.compilerOptions?.paths ?? {}).map(([pattern, targets]) => ({
17443
17880
  pattern,
17444
- targets: targets.map((target) => path27.join(baseUrl, target))
17881
+ targets: targets.map((target) => path28.join(baseUrl, target))
17445
17882
  }));
17446
17883
  } catch {
17447
17884
  return [];
17448
17885
  }
17449
17886
  }
17450
17887
  async function collectTransitiveDependencies(root, entry, aliases, visited = /* @__PURE__ */ new Set()) {
17451
- const absoluteEntry = path27.resolve(entry);
17888
+ const absoluteEntry = path28.resolve(entry);
17452
17889
  if (visited.has(absoluteEntry)) return [];
17453
17890
  visited.add(absoluteEntry);
17454
17891
  let source;
17455
17892
  try {
17456
- source = await fs28.readFile(absoluteEntry, "utf8");
17893
+ source = await fs29.readFile(absoluteEntry, "utf8");
17457
17894
  } catch {
17458
17895
  return [];
17459
17896
  }
@@ -17462,25 +17899,25 @@ async function collectTransitiveDependencies(root, entry, aliases, visited = /*
17462
17899
  const dependency = resolveImport(root, absoluteEntry, specifier, aliases);
17463
17900
  if (!dependency) continue;
17464
17901
  dependencies.push(dependency);
17465
- const nested = await collectTransitiveDependencies(root, path27.resolve(root, dependency), aliases, visited);
17902
+ const nested = await collectTransitiveDependencies(root, path28.resolve(root, dependency), aliases, visited);
17466
17903
  dependencies.push(...nested);
17467
17904
  }
17468
17905
  return [...new Set(dependencies)];
17469
17906
  }
17470
17907
  async function discoverTestImpactCandidates(root) {
17471
- const projectRoot = path27.resolve(root);
17908
+ const projectRoot = path28.resolve(root);
17472
17909
  const files = await listTestFiles(projectRoot);
17473
17910
  const aliases = await loadPathAliases(projectRoot);
17474
17911
  const candidates = [];
17475
17912
  for (const file of files) {
17476
17913
  let source;
17477
17914
  try {
17478
- source = await fs28.readFile(file, "utf8");
17915
+ source = await fs29.readFile(file, "utf8");
17479
17916
  } catch {
17480
17917
  continue;
17481
17918
  }
17482
17919
  const dependencies = await collectTransitiveDependencies(projectRoot, file, aliases);
17483
- const relativeSource = path27.relative(projectRoot, file).replace(/\\/g, "/");
17920
+ const relativeSource = path28.relative(projectRoot, file).replace(/\\/g, "/");
17484
17921
  const tags = [...new Set([...source.matchAll(/@([A-Za-z][\w-]*)/g)].map((match) => match[1].toLowerCase()))].sort();
17485
17922
  candidates.push({
17486
17923
  testId: relativeSource,
@@ -17493,11 +17930,11 @@ async function discoverTestImpactCandidates(root) {
17493
17930
  return candidates.sort((left, right) => left.testId.localeCompare(right.testId));
17494
17931
  }
17495
17932
  async function analyzeRepositoryImpact(root, changedFiles) {
17496
- const projectRoot = path27.resolve(root);
17933
+ const projectRoot = path28.resolve(root);
17497
17934
  const candidates = await discoverTestImpactCandidates(projectRoot);
17498
17935
  const normalizedChanged = changedFiles.map((file) => {
17499
- const absolute = path27.isAbsolute(file) ? file : path27.resolve(projectRoot, file);
17500
- 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, "/");
17501
17938
  });
17502
17939
  return {
17503
17940
  root: projectRoot,
@@ -17960,7 +18397,7 @@ function buildTeamDashboard(runs, policy = {}) {
17960
18397
 
17961
18398
  // src/evaluation/repository-runner.ts
17962
18399
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
17963
- import path28 from "path";
18400
+ import path29 from "path";
17964
18401
  var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
17965
18402
  var SAFE_COMMANDS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "npx"]);
17966
18403
  function executableFor(manager) {
@@ -17995,7 +18432,7 @@ var RepositoryRunner = class {
17995
18432
  return this.runCommand(manifest, executable, args, options);
17996
18433
  }
17997
18434
  static runCommand(manifest, executable, args, options = {}) {
17998
- const normalizedExecutable = path28.basename(executable).replace(/\.cmd$/i, "").toLowerCase();
18435
+ const normalizedExecutable = path29.basename(executable).replace(/\.cmd$/i, "").toLowerCase();
17999
18436
  if (!SAFE_COMMANDS.has(normalizedExecutable)) {
18000
18437
  return Promise.resolve({
18001
18438
  repository: manifest.name,
@@ -18234,8 +18671,8 @@ function buildEvaluationScorecard(observations) {
18234
18671
  }
18235
18672
 
18236
18673
  // src/framework/config-adapter.ts
18237
- import fs29 from "fs/promises";
18238
- import path29 from "path";
18674
+ import fs30 from "fs/promises";
18675
+ import path30 from "path";
18239
18676
  import { Project as Project10, SyntaxKind as SyntaxKind6 } from "ts-morph";
18240
18677
  var ConfigAdapter = class _ConfigAdapter {
18241
18678
  static CONFIG_NAMES = [
@@ -18249,9 +18686,9 @@ var ConfigAdapter = class _ConfigAdapter {
18249
18686
  */
18250
18687
  static async findConfig(repoDir) {
18251
18688
  for (const name of _ConfigAdapter.CONFIG_NAMES) {
18252
- const fullPath = path29.resolve(repoDir, name);
18689
+ const fullPath = path30.resolve(repoDir, name);
18253
18690
  try {
18254
- await fs29.access(fullPath);
18691
+ await fs30.access(fullPath);
18255
18692
  return _ConfigAdapter.parseConfig(fullPath);
18256
18693
  } catch {
18257
18694
  continue;
@@ -18321,7 +18758,7 @@ var ConfigAdapter = class _ConfigAdapter {
18321
18758
  * Generates a temporary verification config string with retries disabled and tracing enabled.
18322
18759
  */
18323
18760
  static generateVerificationOverride(baseConfigPath) {
18324
- const importStatement = baseConfigPath ? `import baseConfig from './${path29.basename(baseConfigPath).replace(/\.ts$/, "")}';
18761
+ const importStatement = baseConfigPath ? `import baseConfig from './${path30.basename(baseConfigPath).replace(/\.ts$/, "")}';
18325
18762
  ` : `import { defineConfig } from '@playwright/test';
18326
18763
  `;
18327
18764
  return `${importStatement}
@@ -18343,13 +18780,13 @@ export default {
18343
18780
 
18344
18781
  // src/mcp/server.ts
18345
18782
  import readline from "readline";
18346
- import path30 from "path";
18347
- import fs30 from "fs";
18783
+ import path31 from "path";
18784
+ import fs31 from "fs";
18348
18785
  var McpServer = class {
18349
18786
  rl;
18350
18787
  workspaceRoot;
18351
18788
  constructor(workspaceRoot = process.cwd()) {
18352
- this.workspaceRoot = path30.resolve(workspaceRoot);
18789
+ this.workspaceRoot = path31.resolve(workspaceRoot);
18353
18790
  this.rl = readline.createInterface({
18354
18791
  input: process.stdin,
18355
18792
  output: process.stdout,
@@ -18369,26 +18806,26 @@ var McpServer = class {
18369
18806
  );
18370
18807
  }
18371
18808
  const normalizedTarget = process.platform === "win32" ? targetPath : targetPath.replace(/\\/g, "/");
18372
- const resolved = path30.resolve(this.workspaceRoot, normalizedTarget);
18373
- if (!fs30.existsSync(this.workspaceRoot)) {
18809
+ const resolved = path31.resolve(this.workspaceRoot, normalizedTarget);
18810
+ if (!fs31.existsSync(this.workspaceRoot)) {
18374
18811
  throw new Error(`Workspace root does not exist: '${this.workspaceRoot}'`);
18375
18812
  }
18376
- const canonicalWorkspace = fs30.realpathSync(this.workspaceRoot);
18813
+ const canonicalWorkspace = fs31.realpathSync(this.workspaceRoot);
18377
18814
  let canonicalTarget;
18378
- if (fs30.existsSync(resolved)) {
18379
- canonicalTarget = fs30.realpathSync(resolved);
18815
+ if (fs31.existsSync(resolved)) {
18816
+ canonicalTarget = fs31.realpathSync(resolved);
18380
18817
  } else {
18381
- const parent = path30.dirname(resolved);
18382
- if (fs30.existsSync(parent)) {
18383
- 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));
18384
18821
  } else {
18385
18822
  throw new Error(
18386
18823
  `Workspace Security Violation: Path '${targetPath}' cannot be resolved inside authorized workspace boundary '${canonicalWorkspace}'.`
18387
18824
  );
18388
18825
  }
18389
18826
  }
18390
- const rel = path30.relative(canonicalWorkspace, canonicalTarget);
18391
- if (rel.startsWith("..") || path30.isAbsolute(rel)) {
18827
+ const rel = path31.relative(canonicalWorkspace, canonicalTarget);
18828
+ if (rel.startsWith("..") || path31.isAbsolute(rel)) {
18392
18829
  throw new Error(
18393
18830
  `Workspace Security Violation: Path '${targetPath}' resolves to '${canonicalTarget}' outside authorized workspace boundary '${canonicalWorkspace}'.`
18394
18831
  );
@@ -18675,7 +19112,7 @@ var McpServer = class {
18675
19112
  };
18676
19113
  }
18677
19114
  const tracePath = this.assertPathInWorkspace(tracePathRaw);
18678
- if (!fs30.existsSync(tracePath)) {
19115
+ if (!fs31.existsSync(tracePath)) {
18679
19116
  throw new Error(`Trace file not found: '${tracePath}'`);
18680
19117
  }
18681
19118
  const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
@@ -18697,7 +19134,7 @@ var McpServer = class {
18697
19134
  let context = await adapter.parse(manifest, traceLines, networkLines);
18698
19135
  if (args?.errorContextPath) {
18699
19136
  const errorContextPath = this.assertPathInWorkspace(args.errorContextPath);
18700
- const errorContext = await fs30.promises.readFile(errorContextPath, "utf8");
19137
+ const errorContext = await fs31.promises.readFile(errorContextPath, "utf8");
18701
19138
  context = supplementDiagnosticContext(context, parseExternalFailureEvidence(errorContext));
18702
19139
  }
18703
19140
  const correlated = EvidenceCorrelator.correlate(context);
@@ -18918,14 +19355,14 @@ var McpServer = class {
18918
19355
  }
18919
19356
  const pomPath = this.assertPathInWorkspace(pomRaw);
18920
19357
  const testPath = this.assertPathInWorkspace(testRaw);
18921
- if (!fs30.existsSync(pomPath)) throw new Error(`Page Object file not found: '${pomPath}'`);
18922
- 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}'`);
18923
19360
  const grammar = DiffAllowlistValidator.validateLocatorGrammar(replacementSelector);
18924
19361
  if (!grammar.isValid) {
18925
19362
  throw new Error(`Security Violation: Disallowed replacement expression '${replacementSelector}'. ${grammar.reason}`);
18926
19363
  }
18927
- const origPomSource = await fs30.promises.readFile(pomPath, "utf-8");
18928
- 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");
18929
19366
  const initialFingerprint = AssertionHasher.computeFingerprint(testPath);
18930
19367
  const patcher = new AstPatcher();
18931
19368
  const inMemPatch = patcher.patchPageObjectInMemory(origPomSource, {
@@ -18976,7 +19413,7 @@ var McpServer = class {
18976
19413
  const postFingerprint = AssertionHasher.computeFingerprint(testPath);
18977
19414
  const assertionIntact = initialFingerprint === postFingerprint;
18978
19415
  if (!assertionIntact) {
18979
- await fs30.promises.writeFile(pomPath, origPomSource, "utf8");
19416
+ await fs31.promises.writeFile(pomPath, origPomSource, "utf8");
18980
19417
  return {
18981
19418
  jsonrpc: "2.0",
18982
19419
  id,
@@ -19102,7 +19539,7 @@ ${generated.specCode}`
19102
19539
  verify,
19103
19540
  runMutations
19104
19541
  } = args ?? {};
19105
- 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;
19106
19543
  const resolvedAuthor = author === "template" || author === "hybrid" || author === "llm" ? author : void 0;
19107
19544
  const result = await runEvidenceDrivenGeneration({
19108
19545
  workspaceRoot: targetWorkspace,
@@ -19187,41 +19624,41 @@ ${generated.specCode}`
19187
19624
  };
19188
19625
 
19189
19626
  // src/cli/index.ts
19190
- import * as path31 from "path";
19191
- import * as fs31 from "fs";
19627
+ import * as path32 from "path";
19628
+ import * as fs32 from "fs";
19192
19629
  import { spawn as spawn3, execSync } from "child_process";
19193
19630
  function findTraceArchives(targetPath) {
19194
19631
  if (targetPath && !targetPath.startsWith("--")) {
19195
- const resolved = path31.resolve(targetPath);
19196
- if (fs31.existsSync(resolved)) {
19197
- 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")) {
19198
19635
  return [resolved];
19199
19636
  }
19200
19637
  }
19201
19638
  }
19202
19639
  const candidateDirs = [
19203
- targetPath && !targetPath.startsWith("--") && fs31.existsSync(targetPath) && fs31.statSync(targetPath).isDirectory() ? path31.resolve(targetPath) : null,
19204
- path31.resolve("test-results"),
19205
- 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"),
19206
19643
  process.cwd()
19207
19644
  ].filter(Boolean);
19208
19645
  const found = [];
19209
19646
  const seen = /* @__PURE__ */ new Set();
19210
19647
  for (const dir of candidateDirs) {
19211
- if (!fs31.existsSync(dir)) continue;
19648
+ if (!fs32.existsSync(dir)) continue;
19212
19649
  try {
19213
- const entries = fs31.readdirSync(dir, { recursive: true });
19650
+ const entries = fs32.readdirSync(dir, { recursive: true });
19214
19651
  for (const entry of entries) {
19215
- const fullPath = path31.join(dir, String(entry));
19652
+ const fullPath = path32.join(dir, String(entry));
19216
19653
  if (fullPath.endsWith(".zip") && !seen.has(fullPath)) {
19217
19654
  try {
19218
- const stat = fs31.statSync(fullPath);
19655
+ const stat = fs32.statSync(fullPath);
19219
19656
  if (stat.isFile()) {
19220
19657
  seen.add(fullPath);
19221
19658
  let hasFailure = false;
19222
19659
  try {
19223
19660
  const discovery = ArchiveReader.findTraceFileEntries(fullPath);
19224
- hasFailure = Boolean(discovery.hasFailure || discovery.failureScore && discovery.failureScore > 0);
19661
+ hasFailure = Boolean(discovery.hasFailure);
19225
19662
  } catch {
19226
19663
  }
19227
19664
  const isTestResults = fullPath.includes("test-results");
@@ -19267,8 +19704,8 @@ async function runCli(argv) {
19267
19704
  const outputPath = outputPathIndex >= 0 ? argv[outputPathIndex + 1] : void 0;
19268
19705
  const serialized = JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), testbedPath, repositories: manifests }, null, 2);
19269
19706
  if (outputPath) {
19270
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19271
- 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");
19272
19709
  }
19273
19710
  if (argv.includes("--json") || outputPath) {
19274
19711
  console.log(serialized);
@@ -19313,8 +19750,8 @@ async function runCli(argv) {
19313
19750
  }
19314
19751
  const serialized = JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), testbedPath, results }, null, 2);
19315
19752
  if (outputPath) {
19316
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19317
- 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");
19318
19755
  }
19319
19756
  console.log(serialized);
19320
19757
  return;
@@ -19334,9 +19771,9 @@ async function runCli(argv) {
19334
19771
  }
19335
19772
  const tracePath = traces[0];
19336
19773
  if (traces.length > 1) {
19337
- console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Diagnosing most recent: ${path31.relative(process.cwd(), tracePath)}`);
19338
- } else if (!candidateArg || candidateArg.startsWith("--") || fs31.existsSync(candidateArg) && fs31.statSync(candidateArg).isDirectory()) {
19339
- 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)}`);
19340
19777
  }
19341
19778
  try {
19342
19779
  const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
@@ -19351,7 +19788,7 @@ async function runCli(argv) {
19351
19788
  const errorContextIndex = argv.indexOf("--error-context");
19352
19789
  const errorContextPath = errorContextIndex >= 0 ? argv[errorContextIndex + 1] : void 0;
19353
19790
  if (errorContextPath) {
19354
- const errorContext = await fs31.promises.readFile(path31.resolve(errorContextPath), "utf8");
19791
+ const errorContext = await fs32.promises.readFile(path32.resolve(errorContextPath), "utf8");
19355
19792
  context = supplementDiagnosticContext(context, parseExternalFailureEvidence(errorContext));
19356
19793
  }
19357
19794
  const correlated = EvidenceCorrelator.correlate(context);
@@ -19437,6 +19874,80 @@ async function runCli(argv) {
19437
19874
  return;
19438
19875
  }
19439
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
+ }
19440
19951
  const candidateArg = argv[3];
19441
19952
  const traces = findTraceArchives(candidateArg);
19442
19953
  if (traces.length === 0) {
@@ -19447,15 +19958,11 @@ async function runCli(argv) {
19447
19958
  }
19448
19959
  const tracePath = traces[0];
19449
19960
  if (traces.length > 1) {
19450
- console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Repairing most recent: ${path31.relative(process.cwd(), tracePath)}`);
19451
- } else if (!candidateArg || candidateArg.startsWith("--") || fs31.existsSync(candidateArg) && fs31.statSync(candidateArg).isDirectory()) {
19452
- 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)}`);
19453
19964
  }
19454
19965
  try {
19455
- const option = (name) => {
19456
- const index = argv.indexOf(name);
19457
- return index >= 0 ? argv[index + 1] : void 0;
19458
- };
19459
19966
  const result = await runAutonomousRepairWorkflow({
19460
19967
  tracePath,
19461
19968
  workspaceRoot: option("--workspace") ?? process.cwd(),
@@ -19496,14 +20003,15 @@ async function runCli(argv) {
19496
20003
  console.log("\u2551 PLAYWRIGHT INTELLIGENCE ENGINE: AUTONOMOUS REPAIR \u2551");
19497
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");
19498
20005
  console.log("");
19499
- console.log(`Target Trace : ${path31.relative(process.cwd(), tracePath)}`);
20006
+ console.log(`Target Trace : ${path32.relative(process.cwd(), tracePath)}`);
19500
20007
  console.log(`Repair Status : ${result.status}`);
19501
20008
  if (result.reason) {
19502
20009
  console.log(`Status Reason : ${result.reason}`);
19503
20010
  }
19504
20011
  console.log(divider);
19505
20012
  console.log("Agent Diagnostic & Proposal:");
19506
- console.log(` \u2022 Classification: ${result.proposalRun.agent.classification ?? "N/A"}`);
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}`);
19507
20015
  console.log(` \u2022 Model Invoked : ${result.proposalRun.agent.modelInvoked}`);
19508
20016
  console.log(` \u2022 Steps / Turns : ${result.proposalRun.agent.steps?.length ?? 0}`);
19509
20017
  if (result.proposal) {
@@ -19583,21 +20091,21 @@ async function runCli(argv) {
19583
20091
  return;
19584
20092
  }
19585
20093
  if (command === "benchmark") {
19586
- const cliDir = path31.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"));
19587
- const packageRootDir = path31.resolve(cliDir, "..");
19588
- const packageBenchmarkScript = path31.resolve(packageRootDir, "benchmarks", "runner", "master-benchmark-runner.ts");
19589
- const localBenchmarkScript = path31.resolve(process.cwd(), "benchmarks", "runner", "master-benchmark-runner.ts");
19590
- 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;
19591
20099
  if (!benchmarkScript) {
19592
20100
  console.log("Empirical benchmark harness is maintained in the research testbed repository.");
19593
20101
  console.log("See docs/BENCHMARK.md for methodology, metrics, and empirical validation results.");
19594
20102
  return;
19595
20103
  }
19596
20104
  const tsxCandidatePaths = [
19597
- path31.resolve(packageRootDir, "node_modules", "tsx", "dist", "cli.mjs"),
19598
- 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")
19599
20107
  ];
19600
- const tsxCli = tsxCandidatePaths.find((p) => fs31.existsSync(p));
20108
+ const tsxCli = tsxCandidatePaths.find((p) => fs32.existsSync(p));
19601
20109
  let executable;
19602
20110
  let finalArgs;
19603
20111
  if (tsxCli) {
@@ -19632,7 +20140,7 @@ async function runCli(argv) {
19632
20140
  const endpointArg = option("--endpoint");
19633
20141
  const result = await initProjectConfig(workspaceRoot, { force: argv.includes("--force") });
19634
20142
  if (apiKeyArg) {
19635
- const envPath = path31.join(workspaceRoot, ".env");
20143
+ const envPath = path32.join(workspaceRoot, ".env");
19636
20144
  const envLines = [
19637
20145
  "# Playwright Intelligence Engine (PIE) Configuration",
19638
20146
  `PIE_LLM_API_KEY=${apiKeyArg}`,
@@ -19643,13 +20151,13 @@ async function runCli(argv) {
19643
20151
  } else if (apiKeyArg.startsWith("sk-proj-") || apiKeyArg.startsWith("sk-")) {
19644
20152
  envLines.push("PIE_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions");
19645
20153
  }
19646
- await fs31.promises.writeFile(envPath, `${envLines.join("\n")}
20154
+ await fs32.promises.writeFile(envPath, `${envLines.join("\n")}
19647
20155
  `, "utf8");
19648
- const gitignorePath = path31.join(workspaceRoot, ".gitignore");
20156
+ const gitignorePath = path32.join(workspaceRoot, ".gitignore");
19649
20157
  try {
19650
- const gitignoreContent = await fs31.promises.readFile(gitignorePath, "utf8").catch(() => "");
20158
+ const gitignoreContent = await fs32.promises.readFile(gitignorePath, "utf8").catch(() => "");
19651
20159
  if (!gitignoreContent.includes(".env")) {
19652
- await fs31.promises.appendFile(gitignorePath, "\n.env\n", "utf8");
20160
+ await fs32.promises.appendFile(gitignorePath, "\n.env\n", "utf8");
19653
20161
  }
19654
20162
  } catch {
19655
20163
  }
@@ -19684,8 +20192,8 @@ async function runCli(argv) {
19684
20192
  const outputIndex = argv.indexOf("--out");
19685
20193
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19686
20194
  if (outputPath) {
19687
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19688
- 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)}
19689
20197
  `, "utf8");
19690
20198
  } else {
19691
20199
  await writeRepositoryProfile(workspaceRoot, profile);
@@ -19712,7 +20220,7 @@ async function runCli(argv) {
19712
20220
  process.exit(1);
19713
20221
  }
19714
20222
  try {
19715
- 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"));
19716
20224
  const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? [];
19717
20225
  const clusters = clusterFailures(observations);
19718
20226
  const report = {
@@ -19724,8 +20232,8 @@ async function runCli(argv) {
19724
20232
  const outputIndex = argv.indexOf("--out");
19725
20233
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19726
20234
  if (outputPath) {
19727
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19728
- 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)}
19729
20237
  `, "utf8");
19730
20238
  }
19731
20239
  console.log(JSON.stringify(report, null, 2));
@@ -19742,15 +20250,15 @@ async function runCli(argv) {
19742
20250
  process.exit(1);
19743
20251
  }
19744
20252
  try {
19745
- 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"));
19746
20254
  const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? parsed.diagnosisResults ?? [];
19747
20255
  const scorecard = buildEvaluationScorecard(observations);
19748
- 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 };
19749
20257
  const outputIndex = argv.indexOf("--out");
19750
20258
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19751
20259
  if (outputPath) {
19752
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19753
- 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)}
19754
20262
  `, "utf8");
19755
20263
  }
19756
20264
  console.log(JSON.stringify(report, null, 2));
@@ -19767,23 +20275,23 @@ async function runCli(argv) {
19767
20275
  process.exit(1);
19768
20276
  }
19769
20277
  try {
19770
- 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"));
19771
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 }] : [];
19772
20280
  if (requirements.length === 0) throw new Error("The requirements document contains no requirements");
19773
20281
  const testsIndex = argv.indexOf("--tests");
19774
20282
  const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : void 0;
19775
20283
  let existingTests = [];
19776
20284
  if (testsPath) {
19777
- 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"));
19778
20286
  existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
19779
20287
  }
19780
20288
  const plan = buildTestIntelligencePlan(requirements, existingTests);
19781
- 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 };
19782
20290
  const outputIndex = argv.indexOf("--out");
19783
20291
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19784
20292
  if (outputPath) {
19785
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19786
- 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)}
19787
20295
  `, "utf8");
19788
20296
  }
19789
20297
  console.log(JSON.stringify(report, null, 2));
@@ -19800,7 +20308,7 @@ async function runCli(argv) {
19800
20308
  process.exit(1);
19801
20309
  }
19802
20310
  try {
19803
- 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"));
19804
20312
  const requirementsDocument = Array.isArray(requirementsRaw) ? void 0 : requirementsRaw;
19805
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 }] : [];
19806
20314
  if (requirements.length === 0) throw new Error("The requirements document contains no requirements");
@@ -19808,7 +20316,7 @@ async function runCli(argv) {
19808
20316
  const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : void 0;
19809
20317
  let existingTests = [];
19810
20318
  if (testsPath) {
19811
- 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"));
19812
20320
  existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
19813
20321
  }
19814
20322
  const modeIndex = argv.indexOf("--mode");
@@ -19831,7 +20339,41 @@ async function runCli(argv) {
19831
20339
  write: argv.includes("--write"),
19832
20340
  verify: argv.includes("--verify")
19833
20341
  });
19834
- 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
+ }
19835
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;
19836
20378
  } catch (err) {
19837
20379
  console.error(`Generation agent failed: ${err?.message ?? String(err)}`);
@@ -19863,15 +20405,15 @@ async function runCli(argv) {
19863
20405
  process.exit(1);
19864
20406
  }
19865
20407
  try {
19866
- 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"));
19867
20409
  const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
19868
20410
  const summaries = analyzeTestStability(runs);
19869
20411
  const report = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), runCount: runs.length, testCount: summaries.length, flakyTestCount: summaries.filter((summary) => summary.flaky).length, tests: summaries };
19870
20412
  const outputIndex = argv.indexOf("--out");
19871
20413
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19872
20414
  if (outputPath) {
19873
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19874
- 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)}
19875
20417
  `, "utf8");
19876
20418
  }
19877
20419
  console.log(JSON.stringify(report, null, 2));
@@ -19888,14 +20430,14 @@ async function runCli(argv) {
19888
20430
  process.exit(1);
19889
20431
  }
19890
20432
  try {
19891
- 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"));
19892
20434
  const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
19893
20435
  const report = buildTeamDashboard(runs);
19894
20436
  const outputIndex = argv.indexOf("--out");
19895
20437
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19896
20438
  if (outputPath) {
19897
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19898
- 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)}
19899
20441
  `, "utf8");
19900
20442
  }
19901
20443
  console.log(JSON.stringify(report, null, 2));
@@ -19912,7 +20454,7 @@ async function runCli(argv) {
19912
20454
  process.exit(1);
19913
20455
  }
19914
20456
  try {
19915
- 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"));
19916
20458
  const changedFiles = Array.isArray(parsed) ? parsed : parsed.changedFiles ?? [];
19917
20459
  const rootIndex = argv.indexOf("--root");
19918
20460
  const root = (rootIndex >= 0 ? argv[rootIndex + 1] : void 0) ?? process.cwd();
@@ -19920,8 +20462,8 @@ async function runCli(argv) {
19920
20462
  const outputIndex = argv.indexOf("--out");
19921
20463
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
19922
20464
  if (outputPath) {
19923
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
19924
- 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)}
19925
20467
  `, "utf8");
19926
20468
  }
19927
20469
  console.log(JSON.stringify(report, null, 2));
@@ -19943,15 +20485,15 @@ async function runCli(argv) {
19943
20485
  if (mode !== "greenfield" && mode !== "brownfield") throw new Error("--mode must be 'greenfield' or 'brownfield'");
19944
20486
  const requirementsIndex = argv.indexOf("--requirements");
19945
20487
  const requirementsPath = requirementsIndex >= 0 ? argv[requirementsIndex + 1] : void 0;
19946
- 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")) : {};
19947
20489
  const baseUrlIndex = argv.indexOf("--baseUrl");
19948
20490
  const baseUrlFromArg = baseUrlIndex >= 0 ? argv[baseUrlIndex + 1] : void 0;
19949
20491
  const baseUrl = baseUrlFromArg ?? requirements.baseUrl;
19950
20492
  let appModel = requirements.appModel;
19951
20493
  const appModelIndex = argv.indexOf("--app-model");
19952
20494
  if (appModelIndex >= 0 && argv[appModelIndex + 1]) {
19953
- const modelPath = path31.resolve(argv[appModelIndex + 1]);
19954
- 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"));
19955
20497
  } else if (baseUrl && (argv.includes("--crawl") || (!requirements.steps || requirements.steps.length === 0))) {
19956
20498
  try {
19957
20499
  appModel = await AppCrawler.crawl({ baseUrl });
@@ -19982,20 +20524,20 @@ async function runCli(argv) {
19982
20524
  });
19983
20525
  if (verification.exitCode === 0) {
19984
20526
  try {
19985
- const specFullPath = path31.resolve(process.cwd(), spec.relativePath);
19986
- 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");
19987
20529
  const mutatedContent = originalContent.replace(
19988
20530
  /expect\(page\.getByText\(['"][^'"]+['"]\)\)/,
19989
20531
  `expect(page.getByText('__UNMATCHED_FALSIFICATION_TARGET__'))`
19990
20532
  );
19991
20533
  if (mutatedContent !== originalContent) {
19992
- await fs31.promises.writeFile(specFullPath, mutatedContent, "utf8");
20534
+ await fs32.promises.writeFile(specFullPath, mutatedContent, "utf8");
19993
20535
  const mutatedRun = await ExecutionRunner.run("npx", {
19994
20536
  cwd: process.cwd(),
19995
20537
  args: ["playwright", "test", spec.relativePath, "--reporter=line"],
19996
20538
  timeoutMs: 6e4
19997
20539
  });
19998
- await fs31.promises.writeFile(specFullPath, originalContent, "utf8");
20540
+ await fs32.promises.writeFile(specFullPath, originalContent, "utf8");
19999
20541
  verification.falsifiable = mutatedRun.exitCode !== 0;
20000
20542
  }
20001
20543
  } catch {
@@ -20008,8 +20550,8 @@ async function runCli(argv) {
20008
20550
  const outputIndex = argv.indexOf("--out");
20009
20551
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
20010
20552
  if (outputPath) {
20011
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
20012
- 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)}
20013
20555
  `, "utf8");
20014
20556
  }
20015
20557
  console.log(JSON.stringify(report, null, 2));
@@ -20025,7 +20567,7 @@ async function runCli(argv) {
20025
20567
  const idx = argv.indexOf(flag);
20026
20568
  return idx >= 0 && argv[idx + 1] && !argv[idx + 1].startsWith("--") ? argv[idx + 1] : void 0;
20027
20569
  };
20028
- const workspaceRoot = path31.resolve(readFlag("--workspace") ?? process.cwd());
20570
+ const workspaceRoot = path32.resolve(readFlag("--workspace") ?? process.cwd());
20029
20571
  const baseUrl = readFlag("--url") ?? readFlag("--base-url");
20030
20572
  const requirementsPath = readFlag("--requirements");
20031
20573
  const markdownPath = readFlag("--markdown");
@@ -20041,13 +20583,13 @@ async function runCli(argv) {
20041
20583
  const runMutations = !argv.includes("--no-mutate");
20042
20584
  const allowMutatingActions = argv.includes("--allow-mutating");
20043
20585
  try {
20044
- 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;
20045
20587
  let jsonRequirements;
20046
20588
  let prompt = promptFlag;
20047
- 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;
20048
20590
  if (requirementsPath) {
20049
- const resolvedReqPath = path31.resolve(requirementsPath);
20050
- const rawReq = await fs31.promises.readFile(resolvedReqPath, "utf8");
20591
+ const resolvedReqPath = path32.resolve(requirementsPath);
20592
+ const rawReq = await fs32.promises.readFile(resolvedReqPath, "utf8");
20051
20593
  if (resolvedReqPath.toLowerCase().endsWith(".json")) {
20052
20594
  jsonRequirements = JSON.parse(rawReq);
20053
20595
  } else if (resolvedReqPath.toLowerCase().endsWith(".feature")) {
@@ -20090,13 +20632,57 @@ async function runCli(argv) {
20090
20632
  verify,
20091
20633
  runMutations
20092
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");
20093
20638
  const outFlag = readFlag("--out");
20094
- if (outFlag) {
20095
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outFlag)), { recursive: true });
20096
- 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)}
20097
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)");
20098
20685
  }
20099
- console.log(JSON.stringify(result.qaReport, null, 2));
20100
20686
  } catch (err) {
20101
20687
  console.error(`QA pipeline failed: ${err?.message ?? String(err)}`);
20102
20688
  process.exit(1);
@@ -20110,7 +20696,7 @@ async function runCli(argv) {
20110
20696
  process.exit(1);
20111
20697
  }
20112
20698
  try {
20113
- 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"));
20114
20700
  const repositoryIndex = argv.indexOf("--repository");
20115
20701
  const repository = repositoryIndex >= 0 ? argv[repositoryIndex + 1] : void 0;
20116
20702
  const evidence = buildCiEvidenceReport(report, { repository, runId: process.env.GITHUB_RUN_ID ?? process.env.CI_PIPELINE_ID });
@@ -20118,16 +20704,16 @@ async function runCli(argv) {
20118
20704
  const outputIndex = argv.indexOf("--out");
20119
20705
  const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : void 0;
20120
20706
  if (outputPath) {
20121
- await fs31.promises.mkdir(path31.dirname(path31.resolve(outputPath)), { recursive: true });
20122
- 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)}
20123
20709
  `, "utf8");
20124
20710
  }
20125
20711
  const sarifIndex = argv.indexOf("--sarif-out");
20126
20712
  const sarifPath = sarifIndex >= 0 ? argv[sarifIndex + 1] : void 0;
20127
20713
  if (sarifPath) {
20128
20714
  const sarif = buildSarifReport(evidence, PACKAGE_VERSION);
20129
- await fs31.promises.mkdir(path31.dirname(path31.resolve(sarifPath)), { recursive: true });
20130
- 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)}
20131
20717
  `, "utf8");
20132
20718
  }
20133
20719
  console.log(evidence.markdown);
@@ -20255,9 +20841,9 @@ async function runDoctor() {
20255
20841
  }
20256
20842
  let pwFound = false;
20257
20843
  try {
20258
- const cwdPkg = path31.resolve(process.cwd(), "package.json");
20259
- if (fs31.existsSync(cwdPkg)) {
20260
- 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"));
20261
20847
  const pwVer = pkg.devDependencies?.["@playwright/test"] || pkg.dependencies?.["@playwright/test"];
20262
20848
  if (pwVer) {
20263
20849
  console.log(` \u2713 Playwright Test : ${pwVer} (Found in package.json)`);
@@ -20280,7 +20866,7 @@ async function runDoctor() {
20280
20866
  }
20281
20867
  }
20282
20868
  try {
20283
- fs31.accessSync(process.cwd(), fs31.constants.W_OK);
20869
+ fs32.accessSync(process.cwd(), fs32.constants.W_OK);
20284
20870
  console.log(` \u2713 Workspace Access : Read/Write verified for '${process.cwd()}'`);
20285
20871
  } catch {
20286
20872
  console.log(` \u2717 Workspace Access : Permission denied writing to '${process.cwd()}'`);
@@ -20554,6 +21140,7 @@ export {
20554
21140
  renderGitHubAnnotation,
20555
21141
  renderLocator,
20556
21142
  runAutonomousRepairWorkflow,
21143
+ runBatchRepairWorkflow,
20557
21144
  runCli,
20558
21145
  runEvidenceDrivenGeneration,
20559
21146
  runGate,