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