playwright-intelligence-engine 2.0.0 → 2.0.2
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/README.md +229 -229
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/core/version.d.ts +3 -3
- package/dist/index.js +81 -24
- package/package.json +76 -76
- package/src/authoring/author.ts +81 -81
- package/src/authoring/generation-contract.ts +283 -283
- package/src/authoring/hybrid-author.ts +115 -115
- package/src/authoring/index.ts +7 -7
- package/src/authoring/llm-author.ts +282 -282
- package/src/authoring/naming/name-plan.ts +28 -28
- package/src/authoring/naming/name-planner.ts +339 -339
- package/src/authoring/naming/naming-linter.ts +292 -292
- package/src/cli/index.ts +1238 -1172
- package/src/collectors/git-diff-collector.ts +190 -190
- package/src/core/version.ts +25 -25
- package/src/diagnostics/diagnostic-report.ts +204 -204
- package/src/diagnostics/failure-classifier.ts +701 -701
- package/src/evaluation/repository-runner.ts +197 -197
- package/src/gate/quality.ts +101 -101
- package/src/generator/architecture-generator.ts +582 -582
- package/src/generator/assertion-quality-gate.ts +88 -88
- package/src/generator/candidate-writer.ts +288 -288
- package/src/generator/test-generator.ts +52 -52
- package/src/index.ts +107 -107
- package/src/intelligence/evidence-bundle.ts +300 -300
- package/src/intelligence/generation-agent.ts +561 -561
- package/src/intelligence/generation-verifier.ts +1115 -1115
- package/src/intelligence/locator-disambiguation-engine.ts +331 -331
- package/src/intelligence/project-intelligence.ts +140 -140
- package/src/intelligence/repository-profile.ts +796 -796
- package/src/intelligence/trace-proposal-workflow.ts +318 -318
- package/src/intelligence/workflow-planner.ts +966 -966
- package/src/mcp/server.ts +970 -970
- package/src/perception/app-model.ts +32 -32
- package/src/perception/crawler.ts +263 -263
- package/src/perception/index.ts +3 -3
- package/src/repair/assertion-hasher.ts +386 -386
- package/src/repair/diff-allowlist.ts +612 -612
- package/src/repair/intent-validator.ts +761 -761
- package/src/repair/repair-planner.ts +467 -467
- package/src/repair/statement-patcher.ts +544 -544
- package/src/reproduce/interactive-workflow-runner.ts +828 -828
- package/src/sandbox/execution-runner.ts +408 -408
- package/src/selector/abstention-policy.ts +175 -175
- package/src/selector/candidate-extractor.ts +343 -343
- package/src/selector/repair-candidate-ranker.ts +92 -92
- package/src/selector/selector-scorer.ts +344 -344
package/src/cli/index.ts
CHANGED
|
@@ -1,1172 +1,1238 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import { spawn, execSync } from 'node:child_process';
|
|
4
|
-
import { ArchiveReader } from '../trace/archive-reader.js';
|
|
5
|
-
import { TraceParser } from '../trace/trace-parser.js';
|
|
6
|
-
import { EvidenceCorrelator } from '../diagnostics/evidence-correlator.js';
|
|
7
|
-
import { DiagnosticReportGenerator } from '../diagnostics/diagnostic-report.js';
|
|
8
|
-
import { McpServer } from '../mcp/server.js';
|
|
9
|
-
import { TestGenerator } from '../generator/test-generator.js';
|
|
10
|
-
import { DiagnosticRerunner } from '../reproduce/diagnostic-rerunner.js';
|
|
11
|
-
import { ApprovalService } from '../repair/approval-service.js';
|
|
12
|
-
import { WorkspacePromoter } from '../repair/workspace-promoter.js';
|
|
13
|
-
import { getRuntimeProvenance, PACKAGE_VERSION } from '../core/version.js';
|
|
14
|
-
import { inspectTestbed } from '../evaluation/repository-inspector.js';
|
|
15
|
-
import { RepositoryRunner } from '../evaluation/repository-runner.js';
|
|
16
|
-
import { initProjectConfig } from '../config/project-config.js';
|
|
17
|
-
import { buildRepositoryProfile, writeRepositoryProfile } from '../intelligence/repository-profile.js';
|
|
18
|
-
import { bootstrapProjectIntelligence, recordProjectFailure, recordProjectRepairAccepted, recordProjectRepairFeedback } from '../intelligence/project-intelligence.js';
|
|
19
|
-
import { clusterFailures, FailureObservation } from '../intelligence/failure-clustering.js';
|
|
20
|
-
import { analyzeTestStability, analyzeRepositoryImpact, TestRunObservation } from '../intelligence/suite-intelligence.js';
|
|
21
|
-
import { planArchitecture, writeArchitecturePlan, ArchitectureMode, ArchitectureGenerationInput } from '../generator/architecture-generator.js';
|
|
22
|
-
import { AppCrawler } from '../perception/crawler.js';
|
|
23
|
-
import type { AppModel } from '../perception/app-model.js';
|
|
24
|
-
import { ExecutionRunner } from '../sandbox/execution-runner.js';
|
|
25
|
-
import { buildCiEvidenceReport } from '../integrations/playwright-report-adapter.js';
|
|
26
|
-
import { buildSarifReport } from '../integrations/sarif-report.js';
|
|
27
|
-
import { recordArchitectureWorkflow, recordCiWorkflow } from '../governance/workflow-audit.js';
|
|
28
|
-
import { buildTeamDashboard, TeamEvidenceRun } from '../governance/team-dashboard.js';
|
|
29
|
-
import { parseExternalFailureEvidence, supplementDiagnosticContext } from '../diagnostics/external-failure-evidence.js';
|
|
30
|
-
import { runRepairWorkflow } from '../repair/repair-workflow.js';
|
|
31
|
-
import { runAutonomousRepairWorkflow } from '../intelligence/autonomous-repair-workflow.js';
|
|
32
|
-
import { normalizeFailureCategory } from '../diagnostics/failure-category.js';
|
|
33
|
-
import { buildEvaluationScorecard, EvaluationObservation } from '../evaluation/evaluation-scorecard.js';
|
|
34
|
-
import { buildTestIntelligencePlan, ExistingTestInput, RequirementInput } from '../intelligence/test-intelligence.js';
|
|
35
|
-
import { runGenerationAgent } from '../intelligence/generation-agent.js';
|
|
36
|
-
import { runEvidenceDrivenGeneration } from '../intelligence/generation-verifier.js';
|
|
37
|
-
import { GitDiffCollector } from '../collectors/git-diff-collector.js';
|
|
38
|
-
import type { AuthorKind } from '../authoring/index.js';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (command === '
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (outputPath) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
await fs.promises.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
console.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
requirements
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
console.error(
|
|
637
|
-
process.exit(1);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
console.error(
|
|
712
|
-
process.exit(1);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
console.
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
console.log(
|
|
1019
|
-
console.log(
|
|
1020
|
-
console.log(`
|
|
1021
|
-
console.log(`
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
console.log(`
|
|
1100
|
-
|
|
1101
|
-
console.log(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { spawn, execSync } from 'node:child_process';
|
|
4
|
+
import { ArchiveReader } from '../trace/archive-reader.js';
|
|
5
|
+
import { TraceParser } from '../trace/trace-parser.js';
|
|
6
|
+
import { EvidenceCorrelator } from '../diagnostics/evidence-correlator.js';
|
|
7
|
+
import { DiagnosticReportGenerator } from '../diagnostics/diagnostic-report.js';
|
|
8
|
+
import { McpServer } from '../mcp/server.js';
|
|
9
|
+
import { TestGenerator } from '../generator/test-generator.js';
|
|
10
|
+
import { DiagnosticRerunner } from '../reproduce/diagnostic-rerunner.js';
|
|
11
|
+
import { ApprovalService } from '../repair/approval-service.js';
|
|
12
|
+
import { WorkspacePromoter } from '../repair/workspace-promoter.js';
|
|
13
|
+
import { getRuntimeProvenance, PACKAGE_VERSION } from '../core/version.js';
|
|
14
|
+
import { inspectTestbed } from '../evaluation/repository-inspector.js';
|
|
15
|
+
import { RepositoryRunner } from '../evaluation/repository-runner.js';
|
|
16
|
+
import { initProjectConfig } from '../config/project-config.js';
|
|
17
|
+
import { buildRepositoryProfile, writeRepositoryProfile } from '../intelligence/repository-profile.js';
|
|
18
|
+
import { bootstrapProjectIntelligence, recordProjectFailure, recordProjectRepairAccepted, recordProjectRepairFeedback } from '../intelligence/project-intelligence.js';
|
|
19
|
+
import { clusterFailures, FailureObservation } from '../intelligence/failure-clustering.js';
|
|
20
|
+
import { analyzeTestStability, analyzeRepositoryImpact, TestRunObservation } from '../intelligence/suite-intelligence.js';
|
|
21
|
+
import { planArchitecture, writeArchitecturePlan, ArchitectureMode, ArchitectureGenerationInput } from '../generator/architecture-generator.js';
|
|
22
|
+
import { AppCrawler } from '../perception/crawler.js';
|
|
23
|
+
import type { AppModel } from '../perception/app-model.js';
|
|
24
|
+
import { ExecutionRunner } from '../sandbox/execution-runner.js';
|
|
25
|
+
import { buildCiEvidenceReport } from '../integrations/playwright-report-adapter.js';
|
|
26
|
+
import { buildSarifReport } from '../integrations/sarif-report.js';
|
|
27
|
+
import { recordArchitectureWorkflow, recordCiWorkflow } from '../governance/workflow-audit.js';
|
|
28
|
+
import { buildTeamDashboard, TeamEvidenceRun } from '../governance/team-dashboard.js';
|
|
29
|
+
import { parseExternalFailureEvidence, supplementDiagnosticContext } from '../diagnostics/external-failure-evidence.js';
|
|
30
|
+
import { runRepairWorkflow } from '../repair/repair-workflow.js';
|
|
31
|
+
import { runAutonomousRepairWorkflow } from '../intelligence/autonomous-repair-workflow.js';
|
|
32
|
+
import { normalizeFailureCategory } from '../diagnostics/failure-category.js';
|
|
33
|
+
import { buildEvaluationScorecard, EvaluationObservation } from '../evaluation/evaluation-scorecard.js';
|
|
34
|
+
import { buildTestIntelligencePlan, ExistingTestInput, RequirementInput } from '../intelligence/test-intelligence.js';
|
|
35
|
+
import { runGenerationAgent } from '../intelligence/generation-agent.js';
|
|
36
|
+
import { runEvidenceDrivenGeneration } from '../intelligence/generation-verifier.js';
|
|
37
|
+
import { GitDiffCollector } from '../collectors/git-diff-collector.js';
|
|
38
|
+
import type { AuthorKind } from '../authoring/index.js';
|
|
39
|
+
|
|
40
|
+
function findTraceArchives(targetPath?: string): string[] {
|
|
41
|
+
if (targetPath && !targetPath.startsWith('--')) {
|
|
42
|
+
const resolved = path.resolve(targetPath);
|
|
43
|
+
if (fs.existsSync(resolved)) {
|
|
44
|
+
if (fs.statSync(resolved).isFile() && resolved.endsWith('.zip')) {
|
|
45
|
+
return [resolved];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const candidateDirs = [
|
|
51
|
+
targetPath && !targetPath.startsWith('--') && fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()
|
|
52
|
+
? path.resolve(targetPath)
|
|
53
|
+
: null,
|
|
54
|
+
path.resolve('test-results'),
|
|
55
|
+
path.resolve('playwright-report'),
|
|
56
|
+
process.cwd(),
|
|
57
|
+
].filter(Boolean) as string[];
|
|
58
|
+
|
|
59
|
+
const found: { filePath: string; mtime: number }[] = [];
|
|
60
|
+
const seen = new Set<string>();
|
|
61
|
+
|
|
62
|
+
for (const dir of candidateDirs) {
|
|
63
|
+
if (!fs.existsSync(dir)) continue;
|
|
64
|
+
try {
|
|
65
|
+
const entries = fs.readdirSync(dir, { recursive: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const fullPath = path.join(dir, String(entry));
|
|
68
|
+
if (fullPath.endsWith('.zip') && !seen.has(fullPath)) {
|
|
69
|
+
try {
|
|
70
|
+
const stat = fs.statSync(fullPath);
|
|
71
|
+
if (stat.isFile()) {
|
|
72
|
+
seen.add(fullPath);
|
|
73
|
+
found.push({ filePath: fullPath, mtime: stat.mtimeMs });
|
|
74
|
+
}
|
|
75
|
+
} catch {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return found.sort((a, b) => b.mtime - a.mtime).map((f) => f.filePath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
85
|
+
const command = argv[2];
|
|
86
|
+
|
|
87
|
+
if (!command || command === '--help' || command === '-h') {
|
|
88
|
+
printHelp();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (command === '--version' || command === '-v') {
|
|
93
|
+
console.log(PACKAGE_VERSION);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (command === 'mcp') {
|
|
98
|
+
const server = new McpServer();
|
|
99
|
+
server.start();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (command === 'inspect-testbed') {
|
|
104
|
+
const testbedPath = argv[3];
|
|
105
|
+
if (!testbedPath) {
|
|
106
|
+
console.error('Error: testbed directory required. Usage: pw-intel inspect-testbed <directory> [--json] [--out <manifest.json>]');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const manifests = await inspectTestbed(testbedPath);
|
|
112
|
+
const outputPathIndex = argv.indexOf('--out');
|
|
113
|
+
const outputPath = outputPathIndex >= 0 ? argv[outputPathIndex + 1] : undefined;
|
|
114
|
+
const serialized = JSON.stringify({ generatedAt: new Date().toISOString(), testbedPath, repositories: manifests }, null, 2);
|
|
115
|
+
|
|
116
|
+
if (outputPath) {
|
|
117
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
118
|
+
await fs.promises.writeFile(path.resolve(outputPath), serialized, 'utf8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (argv.includes('--json') || outputPath) {
|
|
122
|
+
console.log(serialized);
|
|
123
|
+
} else {
|
|
124
|
+
console.log(`Testbed: ${testbedPath}`);
|
|
125
|
+
for (const manifest of manifests) {
|
|
126
|
+
console.log(`- ${manifest.name}: ${manifest.status}; ${manifest.testFiles} test files; ${manifest.pageObjectFiles} Page Object files; ${manifest.packageManager}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
} catch (err: any) {
|
|
131
|
+
console.error(`Testbed inspection failed: ${err?.message ?? String(err)}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (command === 'run-testbed') {
|
|
137
|
+
const testbedPath = argv[3];
|
|
138
|
+
if (!testbedPath) {
|
|
139
|
+
console.error('Error: testbed directory required. Usage: pw-intel run-testbed <directory> [--script <name>]');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const manifests = await inspectTestbed(testbedPath);
|
|
145
|
+
const scriptIndex = argv.indexOf('--script');
|
|
146
|
+
const script = scriptIndex >= 0 ? argv[scriptIndex + 1] : undefined;
|
|
147
|
+
const repositoryIndex = argv.indexOf('--repository');
|
|
148
|
+
const repository = repositoryIndex >= 0 ? argv[repositoryIndex + 1] : undefined;
|
|
149
|
+
const timeoutIndex = argv.indexOf('--timeout');
|
|
150
|
+
const timeoutValue = timeoutIndex >= 0 ? Number(argv[timeoutIndex + 1]) : undefined;
|
|
151
|
+
if (timeoutValue !== undefined && (!Number.isFinite(timeoutValue) || timeoutValue <= 0)) {
|
|
152
|
+
throw new Error('--timeout must be a positive number of milliseconds');
|
|
153
|
+
}
|
|
154
|
+
const outputPathIndex = argv.indexOf('--out');
|
|
155
|
+
const outputPath = outputPathIndex >= 0 ? argv[outputPathIndex + 1] : undefined;
|
|
156
|
+
const selectedManifests = repository ? manifests.filter((manifest) => manifest.name === repository) : manifests;
|
|
157
|
+
if (repository && selectedManifests.length === 0) {
|
|
158
|
+
throw new Error(`Repository '${repository}' was not found in the testbed manifest`);
|
|
159
|
+
}
|
|
160
|
+
const results = [];
|
|
161
|
+
for (const manifest of selectedManifests) {
|
|
162
|
+
results.push(await RepositoryRunner.runBaseline(manifest, { script, timeoutMs: timeoutValue }));
|
|
163
|
+
}
|
|
164
|
+
const serialized = JSON.stringify({ generatedAt: new Date().toISOString(), testbedPath, results }, null, 2);
|
|
165
|
+
if (outputPath) {
|
|
166
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
167
|
+
await fs.promises.writeFile(path.resolve(outputPath), serialized, 'utf8');
|
|
168
|
+
}
|
|
169
|
+
console.log(serialized);
|
|
170
|
+
return;
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
console.error(`Testbed execution failed: ${err?.message ?? String(err)}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (command === 'diagnose') {
|
|
178
|
+
const candidateArg = argv[3];
|
|
179
|
+
const traces = findTraceArchives(candidateArg);
|
|
180
|
+
if (traces.length === 0) {
|
|
181
|
+
console.error(
|
|
182
|
+
'Error: No trace.zip files found in test-results/ or current directory.\n' +
|
|
183
|
+
'Please run your Playwright tests with tracing enabled: npx playwright test --trace on\n' +
|
|
184
|
+
'Or pass the path directly: pw-intel diagnose <path-to-trace.zip>'
|
|
185
|
+
);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const tracePath = traces[0]!;
|
|
189
|
+
if (traces.length > 1) {
|
|
190
|
+
console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Diagnosing most recent: ${path.relative(process.cwd(), tracePath)}`);
|
|
191
|
+
} else if (!candidateArg || candidateArg.startsWith('--') || (fs.existsSync(candidateArg) && fs.statSync(candidateArg).isDirectory())) {
|
|
192
|
+
console.log(`[pw-intel] Auto-discovered trace: ${path.relative(process.cwd(), tracePath)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
|
|
197
|
+
if (!traceEntryName) {
|
|
198
|
+
throw new Error(`No .trace event stream found in archive '${tracePath}'`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const traceText = await ArchiveReader.readEntriesAsText(tracePath, traceEntryNames ?? [traceEntryName]);
|
|
202
|
+
const networkText = (networkEntryNames ?? (networkEntryName ? [networkEntryName] : [])).length > 0
|
|
203
|
+
? await ArchiveReader.readEntriesAsText(tracePath, networkEntryNames ?? [networkEntryName!])
|
|
204
|
+
: '';
|
|
205
|
+
|
|
206
|
+
const traceLines = traceText.split('\n');
|
|
207
|
+
const networkLines = networkText.split('\n');
|
|
208
|
+
let context = TraceParser.assembleDiagnosticContext(traceLines, networkLines);
|
|
209
|
+
const errorContextIndex = argv.indexOf('--error-context');
|
|
210
|
+
const errorContextPath = errorContextIndex >= 0 ? argv[errorContextIndex + 1] : undefined;
|
|
211
|
+
if (errorContextPath) {
|
|
212
|
+
const errorContext = await fs.promises.readFile(path.resolve(errorContextPath), 'utf8');
|
|
213
|
+
context = supplementDiagnosticContext(context, parseExternalFailureEvidence(errorContext));
|
|
214
|
+
}
|
|
215
|
+
const correlated = EvidenceCorrelator.correlate(context);
|
|
216
|
+
const intelligence = await bootstrapProjectIntelligence(process.cwd());
|
|
217
|
+
await recordProjectFailure(process.cwd(), context.failedAction.selector, correlated.classification.category);
|
|
218
|
+
|
|
219
|
+
if (argv.includes('--json')) {
|
|
220
|
+
const proposedLocator = DiagnosticReportGenerator.extractProposedLocator(correlated, intelligence);
|
|
221
|
+
const shouldAbstain = correlated.classification.shouldAbstain || !proposedLocator;
|
|
222
|
+
const actionOutcome = shouldAbstain ? 'ABSTAIN' : 'REPAIR';
|
|
223
|
+
const canonicalCategory = normalizeFailureCategory(correlated.classification.category);
|
|
224
|
+
const abstentionReason =
|
|
225
|
+
correlated.classification.abstentionReason ??
|
|
226
|
+
(!proposedLocator ? 'No safe, unique replacement locator candidate discovered in DOM snapshot.' : undefined);
|
|
227
|
+
|
|
228
|
+
console.log(JSON.stringify({
|
|
229
|
+
provenance: getRuntimeProvenance(process.cwd()),
|
|
230
|
+
classification: canonicalCategory,
|
|
231
|
+
rawClassification: correlated.classification.category,
|
|
232
|
+
confidence: correlated.classification.confidence,
|
|
233
|
+
shouldAbstain,
|
|
234
|
+
actionOutcome,
|
|
235
|
+
reason: correlated.classification.reason,
|
|
236
|
+
abstentionReason,
|
|
237
|
+
failedAction: correlated.context.failedAction,
|
|
238
|
+
failureFingerprint: correlated.failureFingerprint,
|
|
239
|
+
proposedLocator,
|
|
240
|
+
}, null, 2));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const report = DiagnosticReportGenerator.generateTerminalReport(correlated, undefined, intelligence);
|
|
245
|
+
|
|
246
|
+
console.log(report);
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
console.error(`Diagnostic execution failed: ${err.message}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (command === 'repair') {
|
|
255
|
+
const tracePath = argv[3];
|
|
256
|
+
if (!tracePath) {
|
|
257
|
+
console.error('Error: trace.zip path required. Usage: pw-intel repair <trace.zip> --test <spec> --pom <page-object> --class <Class> --member <member> [--execute]');
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const option = (name: string): string | undefined => {
|
|
262
|
+
const index = argv.indexOf(name);
|
|
263
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
264
|
+
};
|
|
265
|
+
const operationText = option('--operation');
|
|
266
|
+
const operation = operationText ? JSON.parse(operationText) as unknown : undefined;
|
|
267
|
+
const result = await runRepairWorkflow({
|
|
268
|
+
tracePath,
|
|
269
|
+
workspaceRoot: option('--workspace') ?? process.cwd(),
|
|
270
|
+
errorContextPath: option('--error-context'),
|
|
271
|
+
testFilePath: option('--test'),
|
|
272
|
+
pomFilePath: option('--pom'),
|
|
273
|
+
className: option('--class'),
|
|
274
|
+
memberName: option('--member'),
|
|
275
|
+
configPath: option('--config'),
|
|
276
|
+
project: option('--project'),
|
|
277
|
+
execute: argv.includes('--execute'),
|
|
278
|
+
worktreeId: option('--worktree-id'),
|
|
279
|
+
operation,
|
|
280
|
+
});
|
|
281
|
+
const canonicalClassification = {
|
|
282
|
+
...result.correlated.classification,
|
|
283
|
+
category: normalizeFailureCategory(result.correlated.classification.category),
|
|
284
|
+
};
|
|
285
|
+
console.log(JSON.stringify({
|
|
286
|
+
provenance: getRuntimeProvenance(process.cwd()),
|
|
287
|
+
status: result.status,
|
|
288
|
+
classification: canonicalClassification,
|
|
289
|
+
failureFingerprint: result.correlated.failureFingerprint,
|
|
290
|
+
proposedLocator: result.proposedLocator,
|
|
291
|
+
candidate: result.candidate,
|
|
292
|
+
proposalId: result.stagedProposal?.proposalId,
|
|
293
|
+
worktree: result.worktree,
|
|
294
|
+
verification: result.verification,
|
|
295
|
+
reason: result.reason,
|
|
296
|
+
}, null, 2));
|
|
297
|
+
if (result.status === 'REJECTED') process.exitCode = 1;
|
|
298
|
+
} catch (err: any) {
|
|
299
|
+
console.error(`Repair workflow failed: ${err?.message ?? String(err)}`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (command === 'autonomous-repair') {
|
|
306
|
+
const candidateArg = argv[3];
|
|
307
|
+
const traces = findTraceArchives(candidateArg);
|
|
308
|
+
if (traces.length === 0) {
|
|
309
|
+
console.error(
|
|
310
|
+
'Error: No trace.zip files found in test-results/ or current directory.\n' +
|
|
311
|
+
'Please run your Playwright tests with tracing enabled: npx playwright test --trace on\n' +
|
|
312
|
+
'Or pass the path directly: pw-intel autonomous-repair <trace.zip> [--execute]'
|
|
313
|
+
);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
const tracePath = traces[0]!;
|
|
317
|
+
if (traces.length > 1) {
|
|
318
|
+
console.log(`[pw-intel] Auto-discovered ${traces.length} trace files. Repairing most recent: ${path.relative(process.cwd(), tracePath)}`);
|
|
319
|
+
} else if (!candidateArg || candidateArg.startsWith('--') || (fs.existsSync(candidateArg) && fs.statSync(candidateArg).isDirectory())) {
|
|
320
|
+
console.log(`[pw-intel] Auto-discovered trace: ${path.relative(process.cwd(), tracePath)}`);
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const option = (name: string): string | undefined => {
|
|
324
|
+
const index = argv.indexOf(name);
|
|
325
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
326
|
+
};
|
|
327
|
+
const result = await runAutonomousRepairWorkflow({
|
|
328
|
+
tracePath,
|
|
329
|
+
workspaceRoot: option('--workspace') ?? process.cwd(),
|
|
330
|
+
errorContextPath: option('--error-context'),
|
|
331
|
+
testFilePath: option('--test'),
|
|
332
|
+
pomFilePath: option('--pom'),
|
|
333
|
+
className: option('--class'),
|
|
334
|
+
memberName: option('--member'),
|
|
335
|
+
configPath: option('--config'),
|
|
336
|
+
worktreeId: option('--worktree-id'),
|
|
337
|
+
enableBrowserInvestigation: argv.includes('--browser'),
|
|
338
|
+
browserUrl: option('--browser-url'),
|
|
339
|
+
browserAllowedHosts: (option('--allow-host') ?? '').split(',').map((host) => host.trim()).filter(Boolean),
|
|
340
|
+
enableTestExecution: argv.includes('--run-tests'),
|
|
341
|
+
autoPromote: argv.includes('--promote'),
|
|
342
|
+
execute: argv.includes('--execute'),
|
|
343
|
+
});
|
|
344
|
+
console.log(JSON.stringify({
|
|
345
|
+
provenance: getRuntimeProvenance(process.cwd()),
|
|
346
|
+
status: result.status,
|
|
347
|
+
reason: result.reason,
|
|
348
|
+
agent: {
|
|
349
|
+
state: result.proposalRun.agent.state,
|
|
350
|
+
modelInvoked: result.proposalRun.agent.modelInvoked,
|
|
351
|
+
classification: result.proposalRun.agent.classification,
|
|
352
|
+
proposal: result.proposal,
|
|
353
|
+
errors: result.proposalRun.agent.errors,
|
|
354
|
+
steps: result.proposalRun.agent.steps,
|
|
355
|
+
},
|
|
356
|
+
verification: result.verification,
|
|
357
|
+
promotion: result.promotion,
|
|
358
|
+
}, null, 2));
|
|
359
|
+
if (result.status === 'PROPOSAL_REJECTED' || result.status === 'VERIFICATION_FAILED') process.exitCode = 1;
|
|
360
|
+
} catch (err: any) {
|
|
361
|
+
console.error(`Autonomous repair failed: ${err?.message ?? String(err)}`);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (command === 'reproduce') {
|
|
368
|
+
const specFile = argv[3];
|
|
369
|
+
if (!specFile) {
|
|
370
|
+
console.error('Error: spec file path required. Usage: pw-intel reproduce <specFilePath>');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const option = (name: string): string | undefined => {
|
|
376
|
+
const index = argv.indexOf(name);
|
|
377
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
378
|
+
};
|
|
379
|
+
console.log(`Running diagnostic reproduction for '${specFile}'...`);
|
|
380
|
+
const result = await DiagnosticRerunner.rerunAndDiagnose({
|
|
381
|
+
specFilePath: specFile,
|
|
382
|
+
workspaceRoot: option('--workspace') ?? process.cwd(),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
console.log(result.report);
|
|
386
|
+
if (!result.passed) {
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
} catch (err: any) {
|
|
390
|
+
console.error(`Reproduction execution failed: ${err.message}`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (command === 'benchmark') {
|
|
397
|
+
// Resolve benchmark script from package directory first, falling back to process.cwd()
|
|
398
|
+
const cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'));
|
|
399
|
+
const packageRootDir = path.resolve(cliDir, '..');
|
|
400
|
+
const packageBenchmarkScript = path.resolve(packageRootDir, 'benchmarks', 'runner', 'master-benchmark-runner.ts');
|
|
401
|
+
const localBenchmarkScript = path.resolve(process.cwd(), 'benchmarks', 'runner', 'master-benchmark-runner.ts');
|
|
402
|
+
const benchmarkScript = fs.existsSync(packageBenchmarkScript)
|
|
403
|
+
? packageBenchmarkScript
|
|
404
|
+
: (fs.existsSync(localBenchmarkScript) ? localBenchmarkScript : undefined);
|
|
405
|
+
|
|
406
|
+
if (!benchmarkScript) {
|
|
407
|
+
console.log('Empirical benchmark harness is maintained in the research testbed repository.');
|
|
408
|
+
console.log('See docs/BENCHMARK.md for methodology, metrics, and empirical validation results.');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Locate tsx CLI entry point
|
|
413
|
+
const tsxCandidatePaths = [
|
|
414
|
+
path.resolve(packageRootDir, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
415
|
+
path.resolve(process.cwd(), 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
416
|
+
];
|
|
417
|
+
const tsxCli = tsxCandidatePaths.find((p) => fs.existsSync(p));
|
|
418
|
+
|
|
419
|
+
let executable: string;
|
|
420
|
+
let finalArgs: string[];
|
|
421
|
+
|
|
422
|
+
if (tsxCli) {
|
|
423
|
+
executable = process.execPath;
|
|
424
|
+
finalArgs = [tsxCli, benchmarkScript, ...argv.slice(3)];
|
|
425
|
+
} else {
|
|
426
|
+
executable = process.platform === 'win32' ? 'cmd.exe' : 'npx';
|
|
427
|
+
finalArgs = process.platform === 'win32'
|
|
428
|
+
? ['/d', '/s', '/c', 'npx', 'tsx', benchmarkScript, ...argv.slice(3)]
|
|
429
|
+
: ['tsx', benchmarkScript, ...argv.slice(3)];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const child = spawn(executable, finalArgs, {
|
|
433
|
+
cwd: process.cwd(),
|
|
434
|
+
stdio: 'inherit',
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
child.on('close', (code) => {
|
|
438
|
+
process.exit(code ?? 0);
|
|
439
|
+
});
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (command === 'doctor') {
|
|
444
|
+
await runDoctor();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (command === 'init') {
|
|
449
|
+
const workspaceRoot = argv[3] && !argv[3].startsWith('--') ? argv[3] : process.cwd();
|
|
450
|
+
try {
|
|
451
|
+
const option = (name: string): string | undefined => {
|
|
452
|
+
const index = argv.indexOf(name);
|
|
453
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
454
|
+
};
|
|
455
|
+
const apiKeyArg = option('--api-key');
|
|
456
|
+
const modelArg = option('--model') ?? (apiKeyArg?.startsWith('sk-') ? 'gpt-4o' : 'deepseek-chat');
|
|
457
|
+
const endpointArg = option('--endpoint');
|
|
458
|
+
|
|
459
|
+
const result = await initProjectConfig(workspaceRoot, { force: argv.includes('--force') });
|
|
460
|
+
|
|
461
|
+
// Handle optional LLM configuration
|
|
462
|
+
if (apiKeyArg) {
|
|
463
|
+
const envPath = path.join(workspaceRoot, '.env');
|
|
464
|
+
const envLines = [
|
|
465
|
+
'# Playwright Intelligence Engine (PIE) Configuration',
|
|
466
|
+
`PIE_LLM_API_KEY=${apiKeyArg}`,
|
|
467
|
+
`PIE_LLM_MODEL=${modelArg}`,
|
|
468
|
+
];
|
|
469
|
+
if (endpointArg) {
|
|
470
|
+
envLines.push(`PIE_LLM_ENDPOINT=${endpointArg}`);
|
|
471
|
+
} else if (apiKeyArg.startsWith('sk-proj-') || apiKeyArg.startsWith('sk-')) {
|
|
472
|
+
envLines.push('PIE_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions');
|
|
473
|
+
}
|
|
474
|
+
await fs.promises.writeFile(envPath, `${envLines.join('\n')}\n`, 'utf8');
|
|
475
|
+
|
|
476
|
+
// Ensure .env is in .gitignore
|
|
477
|
+
const gitignorePath = path.join(workspaceRoot, '.gitignore');
|
|
478
|
+
try {
|
|
479
|
+
const gitignoreContent = await fs.promises.readFile(gitignorePath, 'utf8').catch(() => '');
|
|
480
|
+
if (!gitignoreContent.includes('.env')) {
|
|
481
|
+
await fs.promises.appendFile(gitignorePath, '\n.env\n', 'utf8');
|
|
482
|
+
}
|
|
483
|
+
} catch {}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const activeApiKey = apiKeyArg ?? process.env.PIE_LLM_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.DEEPSEEK_API_KEY;
|
|
487
|
+
|
|
488
|
+
if (argv.includes('--json')) {
|
|
489
|
+
console.log(JSON.stringify({ ...result, llmConfigured: Boolean(activeApiKey) }, null, 2));
|
|
490
|
+
} else {
|
|
491
|
+
console.log(`${result.created ? 'Created' : 'Found'} PIE project configuration: ${result.configPath}`);
|
|
492
|
+
console.log(` Package manager : ${result.config.test.packageManager}`);
|
|
493
|
+
console.log(` Test command : ${result.config.test.command ?? 'not detected'}`);
|
|
494
|
+
console.log(` Fail-closed : ${result.config.diagnostics.failClosed ? 'enabled' : 'disabled'}`);
|
|
495
|
+
console.log(` LLM Provider : ${activeApiKey ? `configured (${apiKeyArg ? 'written to .env' : 'detected in environment'})` : 'not configured (100% offline deterministic mode)'}`);
|
|
496
|
+
if (!activeApiKey) {
|
|
497
|
+
console.log(` Note : Core diagnosis and repair are 100% offline. To enable AI test generation, run: pw-intel init --api-key <key>`);
|
|
498
|
+
}
|
|
499
|
+
console.log('\nQuickstart Commands:');
|
|
500
|
+
console.log(' Diagnose a trace : pw-intel diagnose ./test-results/trace.zip');
|
|
501
|
+
console.log(' Repair a failure : pw-intel autonomous-repair ./test-results/trace.zip --execute');
|
|
502
|
+
console.log(' Generate tests : pw-intel generate-agent ./requirements.json --mode brownfield --write');
|
|
503
|
+
}
|
|
504
|
+
} catch (err: any) {
|
|
505
|
+
console.error(`PIE initialization failed: ${err?.message ?? String(err)}`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (command === 'profile') {
|
|
512
|
+
const workspaceRoot = argv[3] && !argv[3].startsWith('--') ? argv[3] : process.cwd();
|
|
513
|
+
try {
|
|
514
|
+
const profile = await buildRepositoryProfile(workspaceRoot);
|
|
515
|
+
const outputIndex = argv.indexOf('--out');
|
|
516
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
517
|
+
if (outputPath) {
|
|
518
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
519
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(profile, null, 2)}\n`, 'utf8');
|
|
520
|
+
} else {
|
|
521
|
+
await writeRepositoryProfile(workspaceRoot, profile);
|
|
522
|
+
}
|
|
523
|
+
if (argv.includes('--json') || outputPath) console.log(JSON.stringify(profile, null, 2));
|
|
524
|
+
else {
|
|
525
|
+
console.log(`Repository profile generated for ${profile.repository.name}`);
|
|
526
|
+
console.log(` Tests : ${profile.structure.testFiles}`);
|
|
527
|
+
console.log(` Page Objects : ${profile.structure.pageObjectFiles}`);
|
|
528
|
+
console.log(` Fixtures : ${profile.structure.fixtureFiles}`);
|
|
529
|
+
console.log(` Preferred loc. : ${profile.locatorConventions.preferred}`);
|
|
530
|
+
console.log(` Domain terms : ${profile.domainTerms.slice(0, 10).join(', ') || 'none detected'}`);
|
|
531
|
+
}
|
|
532
|
+
} catch (err: any) {
|
|
533
|
+
console.error(`Repository profiling failed: ${err?.message ?? String(err)}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (command === 'cluster-failures') {
|
|
540
|
+
const inputPath = argv[3];
|
|
541
|
+
if (!inputPath) {
|
|
542
|
+
console.error('Error: input JSON path required. Usage: pw-intel cluster-failures <observations.json> [--out <report.json>]');
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const parsed = JSON.parse(await fs.promises.readFile(path.resolve(inputPath), 'utf8')) as { observations?: FailureObservation[] } | FailureObservation[];
|
|
547
|
+
const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? [];
|
|
548
|
+
const clusters = clusterFailures(observations);
|
|
549
|
+
const report = {
|
|
550
|
+
generatedAt: new Date().toISOString(),
|
|
551
|
+
observationCount: observations.length,
|
|
552
|
+
clusterCount: clusters.length,
|
|
553
|
+
clusters,
|
|
554
|
+
};
|
|
555
|
+
const outputIndex = argv.indexOf('--out');
|
|
556
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
557
|
+
if (outputPath) {
|
|
558
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
559
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
560
|
+
}
|
|
561
|
+
console.log(JSON.stringify(report, null, 2));
|
|
562
|
+
} catch (err: any) {
|
|
563
|
+
console.error(`Failure clustering failed: ${err?.message ?? String(err)}`);
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (command === 'evaluation-scorecard') {
|
|
570
|
+
const inputPath = argv[3];
|
|
571
|
+
if (!inputPath) {
|
|
572
|
+
console.error('Error: evaluation JSON path required. Usage: pw-intel evaluation-scorecard <results.json> [--out <scorecard.json>]');
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const parsed = JSON.parse(await fs.promises.readFile(path.resolve(inputPath), 'utf8')) as EvaluationObservation[] | { observations?: EvaluationObservation[]; diagnosisResults?: EvaluationObservation[] };
|
|
577
|
+
const observations = Array.isArray(parsed) ? parsed : parsed.observations ?? parsed.diagnosisResults ?? [];
|
|
578
|
+
const scorecard = buildEvaluationScorecard(observations);
|
|
579
|
+
const report = { generatedAt: new Date().toISOString(), inputPath: path.resolve(inputPath), ...scorecard };
|
|
580
|
+
const outputIndex = argv.indexOf('--out');
|
|
581
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
582
|
+
if (outputPath) {
|
|
583
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
584
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
585
|
+
}
|
|
586
|
+
console.log(JSON.stringify(report, null, 2));
|
|
587
|
+
} catch (err: any) {
|
|
588
|
+
console.error(`Evaluation scorecard failed: ${err?.message ?? String(err)}`);
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (command === 'plan-test-intelligence') {
|
|
595
|
+
const requirementsPath = argv[3];
|
|
596
|
+
if (!requirementsPath) {
|
|
597
|
+
console.error('Error: requirements JSON path required. Usage: pw-intel plan-test-intelligence <requirements.json> [--tests <tests.json>] [--out <plan.json>]');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
try {
|
|
601
|
+
const requirementsRaw = JSON.parse(await fs.promises.readFile(path.resolve(requirementsPath), 'utf8')) as RequirementInput[] | { requirements?: RequirementInput[] | string; title?: string; steps?: ArchitectureGenerationInput['steps']; baseUrl?: string; includeCi?: boolean };
|
|
602
|
+
const requirements = Array.isArray(requirementsRaw)
|
|
603
|
+
? requirementsRaw
|
|
604
|
+
: Array.isArray(requirementsRaw.requirements)
|
|
605
|
+
? requirementsRaw.requirements
|
|
606
|
+
: typeof requirementsRaw.requirements === 'string'
|
|
607
|
+
? [{ id: 'requirement-1', title: requirementsRaw.title ?? requirementsRaw.requirements.slice(0, 100), description: requirementsRaw.requirements }]
|
|
608
|
+
: [];
|
|
609
|
+
if (requirements.length === 0) throw new Error('The requirements document contains no requirements');
|
|
610
|
+
const testsIndex = argv.indexOf('--tests');
|
|
611
|
+
const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : undefined;
|
|
612
|
+
let existingTests: ExistingTestInput[] = [];
|
|
613
|
+
if (testsPath) {
|
|
614
|
+
const testsRaw = JSON.parse(await fs.promises.readFile(path.resolve(testsPath), 'utf8')) as ExistingTestInput[] | { tests?: ExistingTestInput[] };
|
|
615
|
+
existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
|
|
616
|
+
}
|
|
617
|
+
const plan = buildTestIntelligencePlan(requirements, existingTests);
|
|
618
|
+
const report = { generatedAt: new Date().toISOString(), requirementsPath: path.resolve(requirementsPath), testsPath: testsPath ? path.resolve(testsPath) : undefined, ...plan };
|
|
619
|
+
const outputIndex = argv.indexOf('--out');
|
|
620
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
621
|
+
if (outputPath) {
|
|
622
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
623
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
624
|
+
}
|
|
625
|
+
console.log(JSON.stringify(report, null, 2));
|
|
626
|
+
} catch (err: any) {
|
|
627
|
+
console.error(`Test intelligence planning failed: ${err?.message ?? String(err)}`);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (command === 'generate-agent') {
|
|
634
|
+
const requirementsPath = argv[3];
|
|
635
|
+
if (!requirementsPath) {
|
|
636
|
+
console.error('Error: requirements JSON path required. Usage: pw-intel generate-agent <requirements.json> [--mode greenfield|brownfield] [--tests <tests.json>] [--write]');
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
const requirementsRaw = JSON.parse(await fs.promises.readFile(path.resolve(requirementsPath), 'utf8')) as RequirementInput[] | { requirements?: RequirementInput[] | string; title?: string; steps?: ArchitectureGenerationInput['steps']; baseUrl?: string; includeCi?: boolean };
|
|
641
|
+
const requirementsDocument = Array.isArray(requirementsRaw) ? undefined : requirementsRaw;
|
|
642
|
+
const requirements = Array.isArray(requirementsRaw)
|
|
643
|
+
? requirementsRaw
|
|
644
|
+
: Array.isArray(requirementsRaw.requirements)
|
|
645
|
+
? requirementsRaw.requirements
|
|
646
|
+
: typeof requirementsRaw.requirements === 'string'
|
|
647
|
+
? [{ id: 'requirement-1', title: requirementsRaw.title ?? requirementsRaw.requirements.slice(0, 100), description: requirementsRaw.requirements }]
|
|
648
|
+
: [];
|
|
649
|
+
if (requirements.length === 0) throw new Error('The requirements document contains no requirements');
|
|
650
|
+
const testsIndex = argv.indexOf('--tests');
|
|
651
|
+
const testsPath = testsIndex >= 0 ? argv[testsIndex + 1] : undefined;
|
|
652
|
+
let existingTests: ExistingTestInput[] = [];
|
|
653
|
+
if (testsPath) {
|
|
654
|
+
const testsRaw = JSON.parse(await fs.promises.readFile(path.resolve(testsPath), 'utf8')) as ExistingTestInput[] | { tests?: ExistingTestInput[] };
|
|
655
|
+
existingTests = Array.isArray(testsRaw) ? testsRaw : testsRaw.tests ?? [];
|
|
656
|
+
}
|
|
657
|
+
const modeIndex = argv.indexOf('--mode');
|
|
658
|
+
const mode = (modeIndex >= 0 ? argv[modeIndex + 1] : 'brownfield') as 'greenfield' | 'brownfield';
|
|
659
|
+
if (mode !== 'greenfield' && mode !== 'brownfield') throw new Error("--mode must be 'greenfield' or 'brownfield'");
|
|
660
|
+
const baseUrlIndex = argv.indexOf('--base-url');
|
|
661
|
+
const baseUrl = baseUrlIndex >= 0 ? argv[baseUrlIndex + 1] : requirementsDocument?.baseUrl;
|
|
662
|
+
const authorArg = argv.find((arg) => arg.startsWith('--author='));
|
|
663
|
+
const authorIndex = argv.indexOf('--author');
|
|
664
|
+
const author = authorArg
|
|
665
|
+
? (authorArg.slice('--author='.length) as 'template' | 'hybrid' | 'llm')
|
|
666
|
+
: authorIndex >= 0
|
|
667
|
+
? (argv[authorIndex + 1] as 'template' | 'hybrid' | 'llm')
|
|
668
|
+
: 'hybrid';
|
|
669
|
+
const result = await runGenerationAgent({
|
|
670
|
+
requirements,
|
|
671
|
+
existingTests,
|
|
672
|
+
mode,
|
|
673
|
+
author,
|
|
674
|
+
workspaceRoot: (argv.indexOf('--workspace') >= 0 ? argv[argv.indexOf('--workspace') + 1] : undefined) ?? process.cwd(),
|
|
675
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
676
|
+
...(requirementsDocument?.steps ? { suggestedSteps: requirementsDocument.steps } : {}),
|
|
677
|
+
...(requirementsDocument?.includeCi !== undefined ? { includeCi: requirementsDocument.includeCi } : {}),
|
|
678
|
+
write: argv.includes('--write'),
|
|
679
|
+
verify: argv.includes('--verify'),
|
|
680
|
+
});
|
|
681
|
+
console.log(JSON.stringify({ generatedAt: new Date().toISOString(), requirementsPath: path.resolve(requirementsPath), ...result }, null, 2));
|
|
682
|
+
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;
|
|
683
|
+
} catch (err: any) {
|
|
684
|
+
console.error(`Generation agent failed: ${err?.message ?? String(err)}`);
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (command === 'stability-feedback') {
|
|
691
|
+
const selector = argv[3];
|
|
692
|
+
const outcomeIndex = argv.indexOf('--outcome');
|
|
693
|
+
const outcome = outcomeIndex >= 0 ? argv[outcomeIndex + 1] : undefined;
|
|
694
|
+
if (!selector || (outcome !== 'accepted' && outcome !== 'rejected')) {
|
|
695
|
+
console.error('Error: selector and outcome are required. Usage: pw-intel stability-feedback <selector> --outcome accepted|rejected');
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
await recordProjectRepairFeedback(process.cwd(), selector, outcome);
|
|
700
|
+
console.log(JSON.stringify({ selector, outcome, recorded: true }));
|
|
701
|
+
} catch (err: any) {
|
|
702
|
+
console.error(`Stability feedback failed: ${err?.message ?? String(err)}`);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (command === 'suite-health') {
|
|
709
|
+
const inputPath = argv[3];
|
|
710
|
+
if (!inputPath) {
|
|
711
|
+
console.error('Error: run-record JSON path required. Usage: pw-intel suite-health <runs.json> [--out <report.json>]');
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
const parsed = JSON.parse(await fs.promises.readFile(path.resolve(inputPath), 'utf8')) as { runs?: TestRunObservation[] } | TestRunObservation[];
|
|
716
|
+
const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
|
|
717
|
+
const summaries = analyzeTestStability(runs);
|
|
718
|
+
const report = { generatedAt: new Date().toISOString(), runCount: runs.length, testCount: summaries.length, flakyTestCount: summaries.filter((summary) => summary.flaky).length, tests: summaries };
|
|
719
|
+
const outputIndex = argv.indexOf('--out');
|
|
720
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
721
|
+
if (outputPath) {
|
|
722
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
723
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
724
|
+
}
|
|
725
|
+
console.log(JSON.stringify(report, null, 2));
|
|
726
|
+
} catch (err: any) {
|
|
727
|
+
console.error(`Suite health analysis failed: ${err?.message ?? String(err)}`);
|
|
728
|
+
process.exit(1);
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (command === 'team-dashboard') {
|
|
734
|
+
const inputPath = argv[3];
|
|
735
|
+
if (!inputPath) {
|
|
736
|
+
console.error('Error: team evidence JSON path required. Usage: pw-intel team-dashboard <evidence.json> [--out <report.json>]');
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const parsed = JSON.parse(await fs.promises.readFile(path.resolve(inputPath), 'utf8')) as { runs?: TeamEvidenceRun[] } | TeamEvidenceRun[];
|
|
741
|
+
const runs = Array.isArray(parsed) ? parsed : parsed.runs ?? [];
|
|
742
|
+
const report = buildTeamDashboard(runs);
|
|
743
|
+
const outputIndex = argv.indexOf('--out');
|
|
744
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
745
|
+
if (outputPath) {
|
|
746
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
747
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
748
|
+
}
|
|
749
|
+
console.log(JSON.stringify(report, null, 2));
|
|
750
|
+
} catch (err: any) {
|
|
751
|
+
console.error(`Team dashboard generation failed: ${err?.message ?? String(err)}`);
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (command === 'impact') {
|
|
758
|
+
const changedPath = argv[3];
|
|
759
|
+
if (!changedPath) {
|
|
760
|
+
console.error('Error: changed-files JSON path required. Usage: pw-intel impact <changed-files.json> [--root <directory>] [--out <report.json>]');
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
const parsed = JSON.parse(await fs.promises.readFile(path.resolve(changedPath), 'utf8')) as { changedFiles?: string[] } | string[];
|
|
765
|
+
const changedFiles = Array.isArray(parsed) ? parsed : parsed.changedFiles ?? [];
|
|
766
|
+
const rootIndex = argv.indexOf('--root');
|
|
767
|
+
const root = (rootIndex >= 0 ? argv[rootIndex + 1] : undefined) ?? process.cwd();
|
|
768
|
+
const report = await analyzeRepositoryImpact(root, changedFiles);
|
|
769
|
+
const outputIndex = argv.indexOf('--out');
|
|
770
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
771
|
+
if (outputPath) {
|
|
772
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
773
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
774
|
+
}
|
|
775
|
+
console.log(JSON.stringify(report, null, 2));
|
|
776
|
+
} catch (err: any) {
|
|
777
|
+
console.error(`Impact analysis failed: ${err?.message ?? String(err)}`);
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (command === 'architect') {
|
|
784
|
+
const featureName = argv[3];
|
|
785
|
+
if (!featureName || featureName.startsWith('--')) {
|
|
786
|
+
console.error('Error: feature name required. Usage: pw-intel architect <featureName> [--mode greenfield|brownfield] [--baseUrl <url>] [--crawl] [--app-model <path>] [--requirements <json>] [--write] [--verify]');
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
const modeIndex = argv.indexOf('--mode');
|
|
791
|
+
const mode = (modeIndex >= 0 ? argv[modeIndex + 1] : 'greenfield') as ArchitectureMode;
|
|
792
|
+
if (mode !== 'greenfield' && mode !== 'brownfield') throw new Error("--mode must be 'greenfield' or 'brownfield'");
|
|
793
|
+
const requirementsIndex = argv.indexOf('--requirements');
|
|
794
|
+
const requirementsPath = requirementsIndex >= 0 ? argv[requirementsIndex + 1] : undefined;
|
|
795
|
+
const requirements = requirementsPath
|
|
796
|
+
? JSON.parse(await fs.promises.readFile(path.resolve(requirementsPath), 'utf8')) as Partial<ArchitectureGenerationInput>
|
|
797
|
+
: {};
|
|
798
|
+
const baseUrlIndex = argv.indexOf('--baseUrl');
|
|
799
|
+
const baseUrlFromArg = baseUrlIndex >= 0 ? argv[baseUrlIndex + 1] : undefined;
|
|
800
|
+
const baseUrl = baseUrlFromArg ?? requirements.baseUrl;
|
|
801
|
+
|
|
802
|
+
let appModel = requirements.appModel;
|
|
803
|
+
const appModelIndex = argv.indexOf('--app-model');
|
|
804
|
+
if (appModelIndex >= 0 && argv[appModelIndex + 1]) {
|
|
805
|
+
const modelPath = path.resolve(argv[appModelIndex + 1]!);
|
|
806
|
+
appModel = JSON.parse(await fs.promises.readFile(modelPath, 'utf8')) as AppModel;
|
|
807
|
+
} else if (baseUrl && (argv.includes('--crawl') || (!requirements.steps || requirements.steps.length === 0))) {
|
|
808
|
+
try {
|
|
809
|
+
appModel = await AppCrawler.crawl({ baseUrl });
|
|
810
|
+
} catch {
|
|
811
|
+
// Graceful fallback if live crawl cannot connect
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const profile = mode === 'brownfield' ? await buildRepositoryProfile(process.cwd()) : undefined;
|
|
816
|
+
const plan = planArchitecture({
|
|
817
|
+
featureName,
|
|
818
|
+
mode,
|
|
819
|
+
profile,
|
|
820
|
+
baseUrl,
|
|
821
|
+
requirements: requirements.requirements,
|
|
822
|
+
steps: requirements.steps,
|
|
823
|
+
includeCi: requirements.includeCi,
|
|
824
|
+
appModel,
|
|
825
|
+
});
|
|
826
|
+
let writeResult;
|
|
827
|
+
if (argv.includes('--write')) writeResult = await writeArchitecturePlan(process.cwd(), plan);
|
|
828
|
+
let verification;
|
|
829
|
+
if (argv.includes('--verify') && writeResult && writeResult.written.length > 0) {
|
|
830
|
+
const spec = plan.files.find((file) => file.relativePath.endsWith('.spec.ts'));
|
|
831
|
+
if (spec) {
|
|
832
|
+
verification = await ExecutionRunner.run('npx', {
|
|
833
|
+
cwd: process.cwd(),
|
|
834
|
+
args: ['playwright', 'test', spec.relativePath, '--reporter=line'],
|
|
835
|
+
timeoutMs: 60_000,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// Falsifiability Prover (Mutation Verification)
|
|
839
|
+
if (verification.exitCode === 0) {
|
|
840
|
+
try {
|
|
841
|
+
const specFullPath = path.resolve(process.cwd(), spec.relativePath);
|
|
842
|
+
const originalContent = await fs.promises.readFile(specFullPath, 'utf8');
|
|
843
|
+
const mutatedContent = originalContent.replace(
|
|
844
|
+
/expect\(page\.getByText\(['"][^'"]+['"]\)\)/,
|
|
845
|
+
`expect(page.getByText('__UNMATCHED_FALSIFICATION_TARGET__'))`
|
|
846
|
+
);
|
|
847
|
+
if (mutatedContent !== originalContent) {
|
|
848
|
+
await fs.promises.writeFile(specFullPath, mutatedContent, 'utf8');
|
|
849
|
+
const mutatedRun = await ExecutionRunner.run('npx', {
|
|
850
|
+
cwd: process.cwd(),
|
|
851
|
+
args: ['playwright', 'test', spec.relativePath, '--reporter=line'],
|
|
852
|
+
timeoutMs: 60_000,
|
|
853
|
+
});
|
|
854
|
+
await fs.promises.writeFile(specFullPath, originalContent, 'utf8');
|
|
855
|
+
(verification as any).falsifiable = mutatedRun.exitCode !== 0;
|
|
856
|
+
}
|
|
857
|
+
} catch {
|
|
858
|
+
// Non-fatal if mutation check cannot run
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
await recordArchitectureWorkflow(process.cwd(), plan, writeResult);
|
|
864
|
+
const report = { generatedAt: new Date().toISOString(), plan, writeResult, ...(verification ? { verification } : {}) };
|
|
865
|
+
const outputIndex = argv.indexOf('--out');
|
|
866
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
867
|
+
if (outputPath) {
|
|
868
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
869
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
870
|
+
}
|
|
871
|
+
console.log(JSON.stringify(report, null, 2));
|
|
872
|
+
} catch (err: any) {
|
|
873
|
+
console.error(`Architecture generation failed: ${err?.message ?? String(err)}`);
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (command === 'qa') {
|
|
880
|
+
const featureName = argv[3] && !argv[3].startsWith('--') ? argv[3] : undefined;
|
|
881
|
+
const readFlag = (flag: string): string | undefined => {
|
|
882
|
+
const idx = argv.indexOf(flag);
|
|
883
|
+
return idx >= 0 && argv[idx + 1] && !argv[idx + 1]!.startsWith('--') ? argv[idx + 1] : undefined;
|
|
884
|
+
};
|
|
885
|
+
const workspaceRoot = path.resolve(readFlag('--workspace') ?? process.cwd());
|
|
886
|
+
const baseUrl = readFlag('--url') ?? readFlag('--base-url');
|
|
887
|
+
const requirementsPath = readFlag('--requirements');
|
|
888
|
+
const markdownPath = readFlag('--markdown');
|
|
889
|
+
const gherkinPath = readFlag('--gherkin');
|
|
890
|
+
const promptFlag = readFlag('--prompt');
|
|
891
|
+
const outArtifactDir = readFlag('--out-dir');
|
|
892
|
+
const storageStatePath = readFlag('--storage-state');
|
|
893
|
+
const authorFlag = readFlag('--author') as AuthorKind | undefined;
|
|
894
|
+
const author: AuthorKind | undefined = authorFlag === 'template' || authorFlag === 'hybrid' || authorFlag === 'llm' ? authorFlag : undefined;
|
|
895
|
+
const useDiff = argv.includes('--diff');
|
|
896
|
+
const write = !argv.includes('--no-write');
|
|
897
|
+
const verify = !argv.includes('--no-verify');
|
|
898
|
+
const runMutations = !argv.includes('--no-mutate');
|
|
899
|
+
const allowMutatingActions = argv.includes('--allow-mutating');
|
|
900
|
+
|
|
901
|
+
try {
|
|
902
|
+
let markdown = markdownPath ? await fs.promises.readFile(path.resolve(markdownPath), 'utf8') : undefined;
|
|
903
|
+
let jsonRequirements: unknown;
|
|
904
|
+
let prompt = promptFlag;
|
|
905
|
+
let featureText = gherkinPath ? await fs.promises.readFile(path.resolve(gherkinPath), 'utf8') : undefined;
|
|
906
|
+
|
|
907
|
+
if (requirementsPath) {
|
|
908
|
+
const resolvedReqPath = path.resolve(requirementsPath);
|
|
909
|
+
const rawReq = await fs.promises.readFile(resolvedReqPath, 'utf8');
|
|
910
|
+
if (resolvedReqPath.toLowerCase().endsWith('.json')) {
|
|
911
|
+
jsonRequirements = JSON.parse(rawReq);
|
|
912
|
+
} else if (resolvedReqPath.toLowerCase().endsWith('.feature')) {
|
|
913
|
+
featureText = featureText ?? rawReq;
|
|
914
|
+
} else {
|
|
915
|
+
markdown = markdown ?? rawReq;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (!featureName && !markdown && !jsonRequirements && !featureText && !prompt && !useDiff) {
|
|
920
|
+
console.error(
|
|
921
|
+
'Usage: pw-intel qa <featureName> [--workspace <dir>] [--url <baseUrl>] [--requirements <path>] [--markdown <path>] [--gherkin <path>] [--prompt <text>] [--diff] [--author template|hybrid|llm] [--out-dir <dir>] [--storage-state <path>] [--allow-mutating] [--no-write] [--no-verify] [--no-mutate]',
|
|
922
|
+
);
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
let gitDiff: { changedFiles?: string[]; changedRoutes?: string[]; changedSymbols?: string[]; apiEndpoints?: string[]; validationHints?: string[] } | undefined;
|
|
927
|
+
if (useDiff) {
|
|
928
|
+
const collected = await new GitDiffCollector().collect({ workspaceRoot });
|
|
929
|
+
const payload = collected[0]?.payload as { changedFiles?: string[]; diffHints?: { changedRoutes?: string[]; changedSymbols?: string[]; apiEndpoints?: string[]; validationHints?: string[] } } | undefined;
|
|
930
|
+
if (payload?.changedFiles?.length) {
|
|
931
|
+
gitDiff = {
|
|
932
|
+
changedFiles: payload.changedFiles,
|
|
933
|
+
...(payload.diffHints ?? {}),
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const result = await runEvidenceDrivenGeneration({
|
|
939
|
+
workspaceRoot,
|
|
940
|
+
featureName,
|
|
941
|
+
baseUrl,
|
|
942
|
+
prompt,
|
|
943
|
+
markdown,
|
|
944
|
+
jsonRequirements,
|
|
945
|
+
featureText,
|
|
946
|
+
gitDiff,
|
|
947
|
+
author,
|
|
948
|
+
outArtifactDir,
|
|
949
|
+
storageStatePath,
|
|
950
|
+
allowMutatingActions,
|
|
951
|
+
write,
|
|
952
|
+
verify,
|
|
953
|
+
runMutations,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const outFlag = readFlag('--out');
|
|
957
|
+
if (outFlag) {
|
|
958
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outFlag)), { recursive: true });
|
|
959
|
+
await fs.promises.writeFile(path.resolve(outFlag), `${JSON.stringify(result.qaReport, null, 2)}\n`, 'utf8');
|
|
960
|
+
}
|
|
961
|
+
console.log(JSON.stringify(result.qaReport, null, 2));
|
|
962
|
+
} catch (err: any) {
|
|
963
|
+
console.error(`QA pipeline failed: ${err?.message ?? String(err)}`);
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (command === 'ci-report') {
|
|
970
|
+
const reportPath = argv[3];
|
|
971
|
+
if (!reportPath) {
|
|
972
|
+
console.error('Error: Playwright JSON report path required. Usage: pw-intel ci-report <playwright-report.json> [--repository <name>] [--out <report.json>]');
|
|
973
|
+
process.exit(1);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const report = JSON.parse(await fs.promises.readFile(path.resolve(reportPath), 'utf8')) as unknown;
|
|
977
|
+
const repositoryIndex = argv.indexOf('--repository');
|
|
978
|
+
const repository = repositoryIndex >= 0 ? argv[repositoryIndex + 1] : undefined;
|
|
979
|
+
const evidence = buildCiEvidenceReport(report, { repository, runId: process.env.GITHUB_RUN_ID ?? process.env.CI_PIPELINE_ID });
|
|
980
|
+
await recordCiWorkflow(process.cwd(), evidence);
|
|
981
|
+
const outputIndex = argv.indexOf('--out');
|
|
982
|
+
const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : undefined;
|
|
983
|
+
if (outputPath) {
|
|
984
|
+
await fs.promises.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
985
|
+
await fs.promises.writeFile(path.resolve(outputPath), `${JSON.stringify(evidence, null, 2)}\n`, 'utf8');
|
|
986
|
+
}
|
|
987
|
+
const sarifIndex = argv.indexOf('--sarif-out');
|
|
988
|
+
const sarifPath = sarifIndex >= 0 ? argv[sarifIndex + 1] : undefined;
|
|
989
|
+
if (sarifPath) {
|
|
990
|
+
const sarif = buildSarifReport(evidence, PACKAGE_VERSION);
|
|
991
|
+
await fs.promises.mkdir(path.dirname(path.resolve(sarifPath)), { recursive: true });
|
|
992
|
+
await fs.promises.writeFile(path.resolve(sarifPath), `${JSON.stringify(sarif, null, 2)}\n`, 'utf8');
|
|
993
|
+
}
|
|
994
|
+
console.log(evidence.markdown);
|
|
995
|
+
for (const annotation of evidence.annotations) console.log(annotation);
|
|
996
|
+
if (evidence.qualityGate.status === 'FAIL') process.exitCode = 1;
|
|
997
|
+
} catch (err: any) {
|
|
998
|
+
console.error(`CI report ingestion failed: ${err?.message ?? String(err)}`);
|
|
999
|
+
process.exit(1);
|
|
1000
|
+
}
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (command === 'approve') {
|
|
1005
|
+
const proposalId = argv[3];
|
|
1006
|
+
if (!proposalId) {
|
|
1007
|
+
console.error('Error: proposalId required. Usage: pw-intel approve <proposalId>');
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const proposal = ApprovalService.getInstance().getProposal(proposalId);
|
|
1012
|
+
if (!proposal) {
|
|
1013
|
+
console.error(`Error: Proposal '${proposalId}' not found or expired.`);
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
console.log('========================================================================');
|
|
1018
|
+
console.log(' PLAYWRIGHT INTELLIGENCE ENGINE — OPERATOR REPAIR APPROVAL');
|
|
1019
|
+
console.log('========================================================================\n');
|
|
1020
|
+
console.log(`Proposal ID : ${proposal.proposalId}`);
|
|
1021
|
+
console.log(`Target File : ${proposal.targetFilePath}`);
|
|
1022
|
+
console.log(`Target Class : ${proposal.targetClassName}`);
|
|
1023
|
+
console.log(`Target Member : ${proposal.targetMemberName}`);
|
|
1024
|
+
console.log(`Original Hash : ${proposal.originalSourceHash.slice(0, 16)}...`);
|
|
1025
|
+
console.log(`Patched Hash : ${proposal.patchedSourceHash.slice(0, 16)}...`);
|
|
1026
|
+
console.log(`Worktree Status : ${proposal.worktreeExecutionVerified ? 'VERIFIED (Target test passed in isolated worktree)' : 'UNVERIFIED'}`);
|
|
1027
|
+
console.log('\n--- Proposed Unified Diff ---');
|
|
1028
|
+
console.log(proposal.fullDiff || '(No diff recorded)');
|
|
1029
|
+
console.log('-----------------------------\n');
|
|
1030
|
+
|
|
1031
|
+
if (!proposal.worktreeExecutionVerified) {
|
|
1032
|
+
console.error('Security Violation: Cannot authorize proposal. Worktree test execution was not verified.');
|
|
1033
|
+
process.exit(1);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
const tokenPayload = ApprovalService.getInstance().authorizeProposal({ proposalId });
|
|
1038
|
+
console.log('PROPOSAL AUTHORIZED.');
|
|
1039
|
+
console.log(`Single-Use Approval Token: ${tokenPayload.tokenId}\n`);
|
|
1040
|
+
console.log(`To atomically apply this patch to your workspace, run:`);
|
|
1041
|
+
console.log(` pw-intel promote --token ${tokenPayload.tokenId}\n`);
|
|
1042
|
+
} catch (err: any) {
|
|
1043
|
+
console.error(`Authorization failed: ${err.message}`);
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (command === 'promote') {
|
|
1050
|
+
const tokenIndex = argv.indexOf('--token');
|
|
1051
|
+
const token = tokenIndex !== -1 ? argv[tokenIndex + 1] : undefined;
|
|
1052
|
+
if (!token) {
|
|
1053
|
+
console.error('Error: --token argument required. Usage: pw-intel promote --token <approvalToken>');
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const tokenPayload = ApprovalService.getInstance().getTokenPayload(token);
|
|
1058
|
+
if (!tokenPayload) {
|
|
1059
|
+
console.error(`Error: Invalid or expired approval token '${token}'.`);
|
|
1060
|
+
process.exit(1);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const proposal = ApprovalService.getInstance().getProposal(tokenPayload.proposalId);
|
|
1064
|
+
if (!proposal) {
|
|
1065
|
+
console.error(`Error: Staged proposal '${tokenPayload.proposalId}' no longer available.`);
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
console.log(`Applying authorized repair to '${tokenPayload.targetFilePath}'...`);
|
|
1070
|
+
const promoResult = await WorkspacePromoter.promote({
|
|
1071
|
+
targetFilePath: tokenPayload.targetFilePath,
|
|
1072
|
+
workspaceRoot: process.cwd(),
|
|
1073
|
+
patchedContent: proposal.patchedSource,
|
|
1074
|
+
approvalTokenPayload: tokenPayload,
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
if (!promoResult.success) {
|
|
1078
|
+
console.error(`Promotion failed: ${promoResult.error}`);
|
|
1079
|
+
process.exit(1);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
await recordProjectRepairAccepted(process.cwd(), proposal.replacementSelector).catch(() => {});
|
|
1083
|
+
|
|
1084
|
+
console.log(`Successfully promoted verified repair!`);
|
|
1085
|
+
console.log(`Target File : ${promoResult.targetFilePath}`);
|
|
1086
|
+
console.log(`Bytes Written : ${promoResult.bytesWritten}`);
|
|
1087
|
+
console.log(`Final Hash : ${promoResult.finalHash.slice(0, 16)}...`);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (command === 'generate') {
|
|
1092
|
+
const featureName = argv[3] ?? 'Dashboard';
|
|
1093
|
+
const suite = TestGenerator.generateSuite(featureName, [
|
|
1094
|
+
{ action: 'fill', name: 'Search', value: 'query' },
|
|
1095
|
+
{ action: 'click', role: 'button', name: 'Submit' },
|
|
1096
|
+
{ action: 'assert-visible', role: 'heading', name: 'Results' },
|
|
1097
|
+
]);
|
|
1098
|
+
|
|
1099
|
+
console.log(`Generated Page Object (${suite.pageObjectName}.ts):`);
|
|
1100
|
+
console.log(suite.pageObjectCode);
|
|
1101
|
+
console.log('─────────────────────────────────────────────────────────────────');
|
|
1102
|
+
console.log(`Generated Spec:`);
|
|
1103
|
+
console.log(suite.specCode);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
console.error(`Unknown command: ${command}`);
|
|
1108
|
+
printHelp();
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
async function runDoctor(): Promise<void> {
|
|
1113
|
+
console.log('Running Playwright Intelligence Engine Diagnostic Healthcheck (pw-intel doctor)...\n');
|
|
1114
|
+
let healthy = true;
|
|
1115
|
+
|
|
1116
|
+
// 1. Node.js version
|
|
1117
|
+
const nodeVer = process.versions.node;
|
|
1118
|
+
const major = parseInt(nodeVer.split('.')[0] || '0', 10);
|
|
1119
|
+
if (major >= 20) {
|
|
1120
|
+
console.log(` ✓ Node.js Runtime : v${nodeVer} (Supported >= 20.0.0)`);
|
|
1121
|
+
} else {
|
|
1122
|
+
console.log(` ✗ Node.js Runtime : v${nodeVer} (Unsupported, requires Node >= 20.0.0)`);
|
|
1123
|
+
healthy = false;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// 2. Git CLI availability
|
|
1127
|
+
try {
|
|
1128
|
+
const gitVer = execSync('git --version', { encoding: 'utf-8' }).trim();
|
|
1129
|
+
console.log(` ✓ Git Version Control : ${gitVer}`);
|
|
1130
|
+
} catch {
|
|
1131
|
+
console.log(` ✗ Git Version Control : Not found in PATH (Required for isolated worktrees)`);
|
|
1132
|
+
healthy = false;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// 3. Playwright availability
|
|
1136
|
+
let pwFound = false;
|
|
1137
|
+
try {
|
|
1138
|
+
const cwdPkg = path.resolve(process.cwd(), 'package.json');
|
|
1139
|
+
if (fs.existsSync(cwdPkg)) {
|
|
1140
|
+
const pkg = JSON.parse(fs.readFileSync(cwdPkg, 'utf-8'));
|
|
1141
|
+
const pwVer = pkg.devDependencies?.['@playwright/test'] || pkg.dependencies?.['@playwright/test'];
|
|
1142
|
+
if (pwVer) {
|
|
1143
|
+
console.log(` ✓ Playwright Test : ${pwVer} (Found in package.json)`);
|
|
1144
|
+
pwFound = true;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
} catch {}
|
|
1148
|
+
if (!pwFound) {
|
|
1149
|
+
try {
|
|
1150
|
+
const pwCliVer = execSync('npx --no-install playwright --version', {
|
|
1151
|
+
encoding: 'utf-8',
|
|
1152
|
+
timeout: 4000,
|
|
1153
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1154
|
+
}).trim();
|
|
1155
|
+
console.log(` ✓ Playwright CLI : ${pwCliVer}`);
|
|
1156
|
+
pwFound = true;
|
|
1157
|
+
} catch {
|
|
1158
|
+
console.log(` ! Playwright Test : Not detected in current working directory (Optional for offline trace analysis)`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// 4. Workspace write access
|
|
1163
|
+
try {
|
|
1164
|
+
fs.accessSync(process.cwd(), fs.constants.W_OK);
|
|
1165
|
+
console.log(` ✓ Workspace Access : Read/Write verified for '${process.cwd()}'`);
|
|
1166
|
+
} catch {
|
|
1167
|
+
console.log(` ✗ Workspace Access : Permission denied writing to '${process.cwd()}'`);
|
|
1168
|
+
healthy = false;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// 5. Host platform info
|
|
1172
|
+
console.log(` ✓ Host Platform : ${process.platform} (${process.arch})`);
|
|
1173
|
+
|
|
1174
|
+
// 6. Optional LLM status
|
|
1175
|
+
const apiKey = process.env.PIE_LLM_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.DEEPSEEK_API_KEY;
|
|
1176
|
+
if (apiKey) {
|
|
1177
|
+
console.log(` ✓ LLM Provider : Configured (AI test generation enabled)`);
|
|
1178
|
+
} else {
|
|
1179
|
+
console.log(` ℹ LLM Provider : None configured (100% offline deterministic mode active; run 'pw-intel init --api-key <key>' to enable AI generation)`);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
console.log('\nHealthcheck Verdict: ' + (healthy ? 'HEALTHY (Ready for diagnostic and repair workflows)' : 'DEGRADED (Check failed items above)'));
|
|
1183
|
+
if (!healthy) {
|
|
1184
|
+
process.exit(1);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function printHelp(): void {
|
|
1189
|
+
console.log(`
|
|
1190
|
+
Playwright Intelligence Engine CLI (pw-intel)
|
|
1191
|
+
|
|
1192
|
+
Usage:
|
|
1193
|
+
pw-intel init [dir] [--api-key <k>] [--model <m>] Initializes PIE config & optional LLM credentials
|
|
1194
|
+
pw-intel profile [directory] Builds the project repository convention profile
|
|
1195
|
+
pw-intel cluster-failures <json> Groups failure observations by deterministic fingerprints
|
|
1196
|
+
pw-intel evaluation-scorecard <json> Reconciles expected/predicted categories, repairs, abstentions, and latency
|
|
1197
|
+
pw-intel plan-test-intelligence <requirements.json> Plans coverage, missing scenarios, and duplicate tests
|
|
1198
|
+
pw-intel generate-agent <requirements.json> Uses structured LLM planning, then deterministic Playwright generation
|
|
1199
|
+
--mode greenfield|brownfield --tests <tests.json> --base-url <url> --write --verify
|
|
1200
|
+
pw-intel stability-feedback <selector> --outcome accepted|rejected Records operator repair feedback
|
|
1201
|
+
pw-intel suite-health <json> Calculates flaky-test and duration summaries
|
|
1202
|
+
pw-intel team-dashboard <json> Aggregates repository CI evidence into a team dashboard
|
|
1203
|
+
pw-intel impact <json> Selects impacted tests from changed files and repository imports
|
|
1204
|
+
pw-intel architect <featureName> Plans greenfield/brownfield Playwright architecture; add --write to create missing files
|
|
1205
|
+
pw-intel qa <featureName> Runs 4-phase evidence-driven QA workflow (plan, explore, generate, verify & falsify)
|
|
1206
|
+
--workspace <dir> --url <baseUrl> --requirements <path> --gherkin <path> --diff --author template|hybrid|llm --out-dir <dir>
|
|
1207
|
+
pw-intel ci-report <playwright-report.json> Ingests Playwright JSON and emits clusters, stability, gate, and CI annotations
|
|
1208
|
+
--sarif-out <file> writes GitHub/code-scanning SARIF output
|
|
1209
|
+
pw-intel doctor Inspects host environment, Node, Git, Playwright & workspace readiness
|
|
1210
|
+
pw-intel diagnose <trace.zip> Diagnoses a Playwright trace archive and outputs root cause
|
|
1211
|
+
--error-context <file> supplements traces with Playwright error-context.md
|
|
1212
|
+
pw-intel repair <trace.zip> Creates a proposal; add --execute for isolated verification
|
|
1213
|
+
--test <spec> --pom <page-object> --class <Class> --member <member>
|
|
1214
|
+
--config <playwright.config> --error-context <file> --worktree-id <id>
|
|
1215
|
+
--operation <JSON> for an allowlisted frame/shadow/locator operation
|
|
1216
|
+
pw-intel autonomous-repair <trace.zip> Runs the bounded LLM agent and deterministic isolated verifier
|
|
1217
|
+
--execute applies the proposal only in a disposable worktree
|
|
1218
|
+
--browser --browser-url <url> --allow-host <hostname[,hostname]>
|
|
1219
|
+
--run-tests allows the agent to run an explicitly requested target test
|
|
1220
|
+
--promote promotes only an independently verified repair; implies execution
|
|
1221
|
+
pw-intel reproduce <spec.ts> Reruns a failing test with live multi-source diagnostic instrumentation
|
|
1222
|
+
pw-intel inspect-testbed <dir> Inspects external Playwright repositories and emits a reproducible manifest
|
|
1223
|
+
pw-intel run-testbed <dir> Runs bounded baseline commands for discovered repositories
|
|
1224
|
+
--repository <name> --script <name> --timeout <ms> --out <file>
|
|
1225
|
+
pw-intel approve <proposalId> Human review and cryptographic authorization of staged repair proposal
|
|
1226
|
+
pw-intel promote --token <token> Atomically applies authorized repair proposal to active workspace
|
|
1227
|
+
pw-intel benchmark [options] Executes empirical real browser benchmark suite against Controlled App Server
|
|
1228
|
+
pw-intel mcp Starts the Model Context Protocol (MCP) JSON-RPC server on stdio
|
|
1229
|
+
pw-intel generate <featureName> Generates verified Page Object and Spec scaffolding
|
|
1230
|
+
pw-intel --help Shows this help message
|
|
1231
|
+
|
|
1232
|
+
Benchmark Options:
|
|
1233
|
+
--family <name> Filter mutation scenarios by family name (e.g. "Dynamic IDs")
|
|
1234
|
+
--scenario <id> Filter negative control scenarios by category or name
|
|
1235
|
+
--browser <browser> Target browser (default: chromium)
|
|
1236
|
+
`);
|
|
1237
|
+
}
|
|
1238
|
+
|