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.
Files changed (48) hide show
  1. package/README.md +229 -229
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/core/version.d.ts +3 -3
  4. package/dist/index.js +81 -24
  5. package/package.json +76 -76
  6. package/src/authoring/author.ts +81 -81
  7. package/src/authoring/generation-contract.ts +283 -283
  8. package/src/authoring/hybrid-author.ts +115 -115
  9. package/src/authoring/index.ts +7 -7
  10. package/src/authoring/llm-author.ts +282 -282
  11. package/src/authoring/naming/name-plan.ts +28 -28
  12. package/src/authoring/naming/name-planner.ts +339 -339
  13. package/src/authoring/naming/naming-linter.ts +292 -292
  14. package/src/cli/index.ts +1238 -1172
  15. package/src/collectors/git-diff-collector.ts +190 -190
  16. package/src/core/version.ts +25 -25
  17. package/src/diagnostics/diagnostic-report.ts +204 -204
  18. package/src/diagnostics/failure-classifier.ts +701 -701
  19. package/src/evaluation/repository-runner.ts +197 -197
  20. package/src/gate/quality.ts +101 -101
  21. package/src/generator/architecture-generator.ts +582 -582
  22. package/src/generator/assertion-quality-gate.ts +88 -88
  23. package/src/generator/candidate-writer.ts +288 -288
  24. package/src/generator/test-generator.ts +52 -52
  25. package/src/index.ts +107 -107
  26. package/src/intelligence/evidence-bundle.ts +300 -300
  27. package/src/intelligence/generation-agent.ts +561 -561
  28. package/src/intelligence/generation-verifier.ts +1115 -1115
  29. package/src/intelligence/locator-disambiguation-engine.ts +331 -331
  30. package/src/intelligence/project-intelligence.ts +140 -140
  31. package/src/intelligence/repository-profile.ts +796 -796
  32. package/src/intelligence/trace-proposal-workflow.ts +318 -318
  33. package/src/intelligence/workflow-planner.ts +966 -966
  34. package/src/mcp/server.ts +970 -970
  35. package/src/perception/app-model.ts +32 -32
  36. package/src/perception/crawler.ts +263 -263
  37. package/src/perception/index.ts +3 -3
  38. package/src/repair/assertion-hasher.ts +386 -386
  39. package/src/repair/diff-allowlist.ts +612 -612
  40. package/src/repair/intent-validator.ts +761 -761
  41. package/src/repair/repair-planner.ts +467 -467
  42. package/src/repair/statement-patcher.ts +544 -544
  43. package/src/reproduce/interactive-workflow-runner.ts +828 -828
  44. package/src/sandbox/execution-runner.ts +408 -408
  45. package/src/selector/abstention-policy.ts +175 -175
  46. package/src/selector/candidate-extractor.ts +343 -343
  47. package/src/selector/repair-candidate-ranker.ts +92 -92
  48. package/src/selector/selector-scorer.ts +344 -344
@@ -1,190 +1,190 @@
1
- import { execFileSync } from 'child_process';
2
- import { EvidenceCollector } from './collector-interface.js';
3
- import { UnifiedEvidenceRecord } from '../evidence/evidence-model.js';
4
- import { EvidenceRedactor } from '../evidence/evidence-redactor.js';
5
-
6
- export interface GitDiffInput {
7
- workspaceRoot: string;
8
- sinceRef?: string;
9
- executionId?: string;
10
- }
11
-
12
- export class GitDiffCollector implements EvidenceCollector<GitDiffInput> {
13
- public readonly name = 'GitDiffCollector';
14
- public readonly source = 'git-diff' as const;
15
-
16
- public async collect(input: GitDiffInput): Promise<UnifiedEvidenceRecord[]> {
17
- const executionId = input.executionId ?? `exec-${Date.now()}`;
18
- const timestamp = Date.now();
19
- const ref = input.sinceRef ?? 'HEAD~1';
20
-
21
- try {
22
- // 1. Get recent commits
23
- const logOutput = execFileSync(
24
- 'git',
25
- ['log', '-n', '5', '--pretty=format:%h|%an|%s'],
26
- { cwd: input.workspaceRoot, encoding: 'utf-8', timeout: 5000 }
27
- );
28
-
29
- const recentCommits = logOutput
30
- .split('\n')
31
- .filter(Boolean)
32
- .map((line) => {
33
- const [hash, author, subject] = line.split('|');
34
- return { hash, author, subject };
35
- });
36
-
37
- // 2. Get changed files
38
- let changedFiles: string[] = [];
39
- try {
40
- const filesOutput = execFileSync('git', ['diff', '--name-only', ref, 'HEAD'], {
41
- cwd: input.workspaceRoot,
42
- encoding: 'utf-8',
43
- timeout: 5000,
44
- });
45
- changedFiles = filesOutput.split('\n').filter(Boolean);
46
- } catch {
47
- // If ref doesn't exist, diff working tree
48
- const uncommitted = execFileSync('git', ['diff', '--name-only'], {
49
- cwd: input.workspaceRoot,
50
- encoding: 'utf-8',
51
- timeout: 5000,
52
- });
53
- changedFiles = uncommitted.split('\n').filter(Boolean);
54
- }
55
-
56
- // 3. Get diff summary with line changes
57
- let rawDiff = '';
58
- try {
59
- rawDiff = execFileSync('git', ['diff', '-U2', ref, 'HEAD'], {
60
- cwd: input.workspaceRoot,
61
- encoding: 'utf-8',
62
- timeout: 5000,
63
- maxBuffer: 4 * 1024 * 1024,
64
- });
65
- } catch {
66
- rawDiff = execFileSync('git', ['diff', '-U2'], {
67
- cwd: input.workspaceRoot,
68
- encoding: 'utf-8',
69
- timeout: 5000,
70
- maxBuffer: 4 * 1024 * 1024,
71
- });
72
- }
73
-
74
- const { redactedText: cleanDiff } = EvidenceRedactor.redactText(rawDiff);
75
-
76
- // Extract modified selectors or identifiers in diff
77
- const modifiedAttributes: string[] = [];
78
- const attrMatches = cleanDiff.matchAll(/[+-]\s*.*?(id|data-testid|class|name|role|aria-label|placeholder)=["']([^"']+)["']/g);
79
- for (const m of attrMatches) {
80
- modifiedAttributes.push(`${m[1]}="${m[2]}"`);
81
- }
82
-
83
- const diffHints = GitDiffCollector.extractDiffHints(cleanDiff, changedFiles);
84
-
85
- return [
86
- {
87
- evidenceId: `git-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
88
- executionId,
89
- source: 'git-diff',
90
- timestamp,
91
- evidenceType: 'git-diff',
92
- payload: {
93
- sinceRef: ref,
94
- recentCommits,
95
- changedFiles,
96
- modifiedAttributes: Array.from(new Set(modifiedAttributes)),
97
- diffHints,
98
- diffSnippet: cleanDiff.slice(0, 10000),
99
- },
100
- reliability: 'HIGH',
101
- provenance: {
102
- collector: this.name,
103
- collectedAt: timestamp,
104
- captureMethod: 'git-cli-diff',
105
- sourceUri: input.workspaceRoot,
106
- },
107
- redactionStatus: { isRedacted: false },
108
- },
109
- ];
110
- } catch (err: any) {
111
- return [
112
- {
113
- evidenceId: `git-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
114
- executionId,
115
- source: 'git-diff',
116
- timestamp,
117
- evidenceType: 'git-diff',
118
- payload: {
119
- error: err?.message || 'Failed to collect git diff',
120
- changedFiles: [],
121
- recentCommits: [],
122
- },
123
- reliability: 'LOW',
124
- provenance: {
125
- collector: this.name,
126
- collectedAt: timestamp,
127
- captureMethod: 'git-cli-diff',
128
- sourceUri: input.workspaceRoot,
129
- },
130
- redactionStatus: { isRedacted: false },
131
- },
132
- ];
133
- }
134
- }
135
-
136
- public static extractDiffHints(cleanDiff: string, changedFiles: string[]): {
137
- changedRoutes: string[];
138
- changedSymbols: string[];
139
- apiEndpoints: string[];
140
- validationHints: string[];
141
- existingTestsChanged: string[];
142
- } {
143
- const addedOrChangedLines = cleanDiff
144
- .split(/\r?\n/)
145
- .filter((line) => (line.startsWith('+') || line.startsWith('-')) && !line.startsWith('+++') && !line.startsWith('---'))
146
- .join('\n');
147
-
148
- const changedRoutes = new Set<string>();
149
- for (const file of changedFiles) {
150
- const appMatch = file.match(/(?:^|\/)(?:src\/)?app\/(.+?)\/page\.[tj]sx?$/i);
151
- if (appMatch?.[1]) changedRoutes.add(`/${appMatch[1]}`);
152
- const pagesMatch = file.match(/(?:^|\/)(?:src\/)?pages\/(.+?)\.[tj]sx?$/i);
153
- if (pagesMatch?.[1] && !pagesMatch[1].startsWith('api/')) {
154
- changedRoutes.add(pagesMatch[1] === 'index' ? '/' : `/${pagesMatch[1].replace(/\/index$/, '')}`);
155
- }
156
- }
157
- for (const m of addedOrChangedLines.matchAll(/path\s*[:=]\s*['"](\/[^'"]*)['"]/g)) {
158
- if (m[1]) changedRoutes.add(m[1]);
159
- }
160
-
161
- const changedSymbols = new Set<string>();
162
- for (const m of addedOrChangedLines.matchAll(/(?:function|class|const|export\s+(?:async\s+)?function)\s+([A-Za-z_][A-Za-z0-9_]*)/g)) {
163
- if (m[1]) changedSymbols.add(m[1]);
164
- }
165
-
166
- const apiEndpoints = new Set<string>();
167
- for (const m of addedOrChangedLines.matchAll(/['"`](\/api\/[A-Za-z0-9_/?=&{}:-]+)['"`]/g)) {
168
- if (m[1]) apiEndpoints.add(m[1]);
169
- }
170
-
171
- const validationHints = new Set<string>();
172
- for (const m of addedOrChangedLines.matchAll(/\.(min|max|email|regex|required)\s*\(([^)]*)\)/g)) {
173
- validationHints.add(`${m[1]}(${(m[2] ?? '').trim()})`);
174
- }
175
- for (const m of addedOrChangedLines.matchAll(/required(?:\s*=\s*\{?true\}?|\b)/g)) {
176
- validationHints.add(m[0].trim());
177
- }
178
-
179
- const existingTestsChanged = changedFiles.filter((f) => /\.(?:spec|test)\.[tj]sx?$/i.test(f));
180
-
181
- return {
182
- changedRoutes: [...changedRoutes],
183
- changedSymbols: [...changedSymbols].slice(0, 30),
184
- apiEndpoints: [...apiEndpoints].slice(0, 20),
185
- validationHints: [...validationHints].slice(0, 20),
186
- existingTestsChanged,
187
- };
188
- }
189
- }
190
-
1
+ import { execFileSync } from 'child_process';
2
+ import { EvidenceCollector } from './collector-interface.js';
3
+ import { UnifiedEvidenceRecord } from '../evidence/evidence-model.js';
4
+ import { EvidenceRedactor } from '../evidence/evidence-redactor.js';
5
+
6
+ export interface GitDiffInput {
7
+ workspaceRoot: string;
8
+ sinceRef?: string;
9
+ executionId?: string;
10
+ }
11
+
12
+ export class GitDiffCollector implements EvidenceCollector<GitDiffInput> {
13
+ public readonly name = 'GitDiffCollector';
14
+ public readonly source = 'git-diff' as const;
15
+
16
+ public async collect(input: GitDiffInput): Promise<UnifiedEvidenceRecord[]> {
17
+ const executionId = input.executionId ?? `exec-${Date.now()}`;
18
+ const timestamp = Date.now();
19
+ const ref = input.sinceRef ?? 'HEAD~1';
20
+
21
+ try {
22
+ // 1. Get recent commits
23
+ const logOutput = execFileSync(
24
+ 'git',
25
+ ['log', '-n', '5', '--pretty=format:%h|%an|%s'],
26
+ { cwd: input.workspaceRoot, encoding: 'utf-8', timeout: 5000 }
27
+ );
28
+
29
+ const recentCommits = logOutput
30
+ .split('\n')
31
+ .filter(Boolean)
32
+ .map((line) => {
33
+ const [hash, author, subject] = line.split('|');
34
+ return { hash, author, subject };
35
+ });
36
+
37
+ // 2. Get changed files
38
+ let changedFiles: string[] = [];
39
+ try {
40
+ const filesOutput = execFileSync('git', ['diff', '--name-only', ref, 'HEAD'], {
41
+ cwd: input.workspaceRoot,
42
+ encoding: 'utf-8',
43
+ timeout: 5000,
44
+ });
45
+ changedFiles = filesOutput.split('\n').filter(Boolean);
46
+ } catch {
47
+ // If ref doesn't exist, diff working tree
48
+ const uncommitted = execFileSync('git', ['diff', '--name-only'], {
49
+ cwd: input.workspaceRoot,
50
+ encoding: 'utf-8',
51
+ timeout: 5000,
52
+ });
53
+ changedFiles = uncommitted.split('\n').filter(Boolean);
54
+ }
55
+
56
+ // 3. Get diff summary with line changes
57
+ let rawDiff = '';
58
+ try {
59
+ rawDiff = execFileSync('git', ['diff', '-U2', ref, 'HEAD'], {
60
+ cwd: input.workspaceRoot,
61
+ encoding: 'utf-8',
62
+ timeout: 5000,
63
+ maxBuffer: 4 * 1024 * 1024,
64
+ });
65
+ } catch {
66
+ rawDiff = execFileSync('git', ['diff', '-U2'], {
67
+ cwd: input.workspaceRoot,
68
+ encoding: 'utf-8',
69
+ timeout: 5000,
70
+ maxBuffer: 4 * 1024 * 1024,
71
+ });
72
+ }
73
+
74
+ const { redactedText: cleanDiff } = EvidenceRedactor.redactText(rawDiff);
75
+
76
+ // Extract modified selectors or identifiers in diff
77
+ const modifiedAttributes: string[] = [];
78
+ const attrMatches = cleanDiff.matchAll(/[+-]\s*.*?(id|data-testid|class|name|role|aria-label|placeholder)=["']([^"']+)["']/g);
79
+ for (const m of attrMatches) {
80
+ modifiedAttributes.push(`${m[1]}="${m[2]}"`);
81
+ }
82
+
83
+ const diffHints = GitDiffCollector.extractDiffHints(cleanDiff, changedFiles);
84
+
85
+ return [
86
+ {
87
+ evidenceId: `git-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
88
+ executionId,
89
+ source: 'git-diff',
90
+ timestamp,
91
+ evidenceType: 'git-diff',
92
+ payload: {
93
+ sinceRef: ref,
94
+ recentCommits,
95
+ changedFiles,
96
+ modifiedAttributes: Array.from(new Set(modifiedAttributes)),
97
+ diffHints,
98
+ diffSnippet: cleanDiff.slice(0, 10000),
99
+ },
100
+ reliability: 'HIGH',
101
+ provenance: {
102
+ collector: this.name,
103
+ collectedAt: timestamp,
104
+ captureMethod: 'git-cli-diff',
105
+ sourceUri: input.workspaceRoot,
106
+ },
107
+ redactionStatus: { isRedacted: false },
108
+ },
109
+ ];
110
+ } catch (err: any) {
111
+ return [
112
+ {
113
+ evidenceId: `git-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
114
+ executionId,
115
+ source: 'git-diff',
116
+ timestamp,
117
+ evidenceType: 'git-diff',
118
+ payload: {
119
+ error: err?.message || 'Failed to collect git diff',
120
+ changedFiles: [],
121
+ recentCommits: [],
122
+ },
123
+ reliability: 'LOW',
124
+ provenance: {
125
+ collector: this.name,
126
+ collectedAt: timestamp,
127
+ captureMethod: 'git-cli-diff',
128
+ sourceUri: input.workspaceRoot,
129
+ },
130
+ redactionStatus: { isRedacted: false },
131
+ },
132
+ ];
133
+ }
134
+ }
135
+
136
+ public static extractDiffHints(cleanDiff: string, changedFiles: string[]): {
137
+ changedRoutes: string[];
138
+ changedSymbols: string[];
139
+ apiEndpoints: string[];
140
+ validationHints: string[];
141
+ existingTestsChanged: string[];
142
+ } {
143
+ const addedOrChangedLines = cleanDiff
144
+ .split(/\r?\n/)
145
+ .filter((line) => (line.startsWith('+') || line.startsWith('-')) && !line.startsWith('+++') && !line.startsWith('---'))
146
+ .join('\n');
147
+
148
+ const changedRoutes = new Set<string>();
149
+ for (const file of changedFiles) {
150
+ const appMatch = file.match(/(?:^|\/)(?:src\/)?app\/(.+?)\/page\.[tj]sx?$/i);
151
+ if (appMatch?.[1]) changedRoutes.add(`/${appMatch[1]}`);
152
+ const pagesMatch = file.match(/(?:^|\/)(?:src\/)?pages\/(.+?)\.[tj]sx?$/i);
153
+ if (pagesMatch?.[1] && !pagesMatch[1].startsWith('api/')) {
154
+ changedRoutes.add(pagesMatch[1] === 'index' ? '/' : `/${pagesMatch[1].replace(/\/index$/, '')}`);
155
+ }
156
+ }
157
+ for (const m of addedOrChangedLines.matchAll(/path\s*[:=]\s*['"](\/[^'"]*)['"]/g)) {
158
+ if (m[1]) changedRoutes.add(m[1]);
159
+ }
160
+
161
+ const changedSymbols = new Set<string>();
162
+ for (const m of addedOrChangedLines.matchAll(/(?:function|class|const|export\s+(?:async\s+)?function)\s+([A-Za-z_][A-Za-z0-9_]*)/g)) {
163
+ if (m[1]) changedSymbols.add(m[1]);
164
+ }
165
+
166
+ const apiEndpoints = new Set<string>();
167
+ for (const m of addedOrChangedLines.matchAll(/['"`](\/api\/[A-Za-z0-9_/?=&{}:-]+)['"`]/g)) {
168
+ if (m[1]) apiEndpoints.add(m[1]);
169
+ }
170
+
171
+ const validationHints = new Set<string>();
172
+ for (const m of addedOrChangedLines.matchAll(/\.(min|max|email|regex|required)\s*\(([^)]*)\)/g)) {
173
+ validationHints.add(`${m[1]}(${(m[2] ?? '').trim()})`);
174
+ }
175
+ for (const m of addedOrChangedLines.matchAll(/required(?:\s*=\s*\{?true\}?|\b)/g)) {
176
+ validationHints.add(m[0].trim());
177
+ }
178
+
179
+ const existingTestsChanged = changedFiles.filter((f) => /\.(?:spec|test)\.[tj]sx?$/i.test(f));
180
+
181
+ return {
182
+ changedRoutes: [...changedRoutes],
183
+ changedSymbols: [...changedSymbols].slice(0, 30),
184
+ apiEndpoints: [...apiEndpoints].slice(0, 20),
185
+ validationHints: [...validationHints].slice(0, 20),
186
+ existingTestsChanged,
187
+ };
188
+ }
189
+ }
190
+
@@ -1,25 +1,25 @@
1
- import { execFileSync } from 'node:child_process';
2
-
3
- /**
4
- * Product identity is kept in one place so the CLI, MCP server, and execution
5
- * proofs cannot silently advertise different versions.
6
- */
7
- export const PACKAGE_VERSION = '2.0.0';
8
- export const ENGINE_MILESTONE = '2.0.0';
9
- export const PROOF_VERSION = ENGINE_MILESTONE;
10
-
11
- export interface RuntimeProvenance {
12
- sourceRevision: string;
13
- workspaceDirty: boolean;
14
- }
15
-
16
- export function getRuntimeProvenance(root = process.cwd()): RuntimeProvenance {
17
- try {
18
- const sourceRevision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || 'unknown';
19
- const status = execFileSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
20
- return { sourceRevision, workspaceDirty: status.trim().length > 0 };
21
- } catch {
22
- return { sourceRevision: 'unknown', workspaceDirty: false };
23
- }
24
- }
25
-
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ /**
4
+ * Product identity is kept in one place so the CLI, MCP server, and execution
5
+ * proofs cannot silently advertise different versions.
6
+ */
7
+ export const PACKAGE_VERSION = '2.0.2';
8
+ export const ENGINE_MILESTONE = '2.0.2';
9
+ export const PROOF_VERSION = ENGINE_MILESTONE;
10
+
11
+ export interface RuntimeProvenance {
12
+ sourceRevision: string;
13
+ workspaceDirty: boolean;
14
+ }
15
+
16
+ export function getRuntimeProvenance(root = process.cwd()): RuntimeProvenance {
17
+ try {
18
+ const sourceRevision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || 'unknown';
19
+ const status = execFileSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
20
+ return { sourceRevision, workspaceDirty: status.trim().length > 0 };
21
+ } catch {
22
+ return { sourceRevision: 'unknown', workspaceDirty: false };
23
+ }
24
+ }
25
+