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