@mknrt/autotests-overkill 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +28 -0
  2. package/assets/overkill-logo.svg +0 -0
  3. package/assets/overkill-small.svg +0 -0
  4. package/bin/autotests-overkill.js +0 -0
  5. package/dist/src/mcp/cypressTools.d.ts +2 -0
  6. package/dist/src/mcp/cypressTools.js +66 -0
  7. package/dist/src/mcp/registerTools.js +2 -0
  8. package/dist/src/mcp/server.js +19 -3
  9. package/dist/src/runtime/cypressBrowser.d.ts +32 -0
  10. package/dist/src/runtime/cypressBrowser.js +280 -0
  11. package/dist/src/runtime/cypressProcess.d.ts +8 -0
  12. package/dist/src/runtime/cypressProcess.js +83 -0
  13. package/dist/src/runtime/cypressRuns.d.ts +36 -0
  14. package/dist/src/runtime/cypressRuns.js +419 -0
  15. package/dist/src/runtime/cypressRuntime.d.ts +24 -0
  16. package/dist/src/runtime/cypressRuntime.js +125 -0
  17. package/dist/src/runtime/cypressTap.d.ts +95 -0
  18. package/dist/src/runtime/cypressTap.js +175 -0
  19. package/dist/src/runtime/redact.d.ts +3 -0
  20. package/dist/src/runtime/redact.js +40 -0
  21. package/docs/architecture/overview.md +0 -0
  22. package/docs/consumer-integration.md +0 -0
  23. package/docs/operator-cookbook.md +0 -0
  24. package/docs/plugin-packaging.md +0 -0
  25. package/docs/superpowers/plans/2026-09-07-cypress-runtime.md +42 -0
  26. package/docs/tool-catalog.md +0 -0
  27. package/overkill.config.example.json +0 -0
  28. package/package.json +1 -1
  29. package/skills/overkill-debug-failure/SKILL.md +10 -5
  30. package/skills/overkill-find-gaps/SKILL.md +0 -0
  31. package/skills/overkill-generate-setup/SKILL.md +0 -0
  32. package/skills/overkill-generate-test/SKILL.md +0 -0
  33. package/skills/overkill-impact-analysis/SKILL.md +0 -0
  34. package/skills/overkill-onboard/SKILL.md +0 -0
  35. package/skills/overkill-prompt-builder/SKILL.md +0 -0
  36. package/skills/overkill-reuse-project-patterns/SKILL.md +0 -0
  37. package/skills/overkill-review-test-draft/SKILL.md +0 -0
  38. package/templates/prompts/debug-failure.md +0 -0
  39. package/templates/prompts/find-gaps.md +0 -0
  40. package/templates/prompts/generate-test.md +0 -0
  41. package/templates/prompts/impact-analysis.md +0 -0
  42. package/templates/spec-blueprints/default.md +0 -0
@@ -0,0 +1,175 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { z } from 'zod';
6
+ import { cypressEnvironment, cypressInstallation, cypressTapArguments } from './cypressProcess.js';
7
+ import { redactValue } from './redact.js';
8
+ const text = z.string().min(1).max(2000).refine((value) => !/[\0\r\n]/.test(value), 'Control characters are not allowed');
9
+ export const cypressTapInput = z.object({
10
+ action: z.enum(['sessions', 'status', 'specs', 'run', 'reporter', 'command', 'pin', 'dom', 'aria', 'inspect']),
11
+ sessionId: z.number().int().positive().optional(), requestId: z.string().uuid().optional(),
12
+ spec: text.optional(), testId: text.optional(), commandId: text.optional(),
13
+ attempt: z.number().int().min(1).max(100).optional(),
14
+ depth: z.union([z.number().int().min(0).max(20), z.literal('all')]).optional(),
15
+ selector: text.optional(), at: z.union([z.number().int().nonnegative(), text]).optional(), clear: z.boolean().optional(),
16
+ maxChars: z.number().int().min(1).max(100000).optional(), maxNodes: z.number().int().min(1).max(1000).optional(),
17
+ timeoutMs: z.number().int().min(1000).max(60000).optional(),
18
+ runTimeoutSeconds: z.number().int().min(1).max(7200).optional(),
19
+ }).strict().superRefine((input, context) => {
20
+ const required = { run: ['spec'], command: ['testId', 'commandId'], inspect: ['selector'] };
21
+ if (input.action === 'pin' && !input.clear)
22
+ required.pin = ['testId', 'commandId'];
23
+ for (const key of required[input.action] ?? []) {
24
+ if (input[key] === undefined)
25
+ context.addIssue({ code: 'custom', path: [key], message: `${key} is required for ${input.action}` });
26
+ }
27
+ const allowed = {
28
+ sessions: [], status: ['requestId'], specs: [], run: ['spec', 'runTimeoutSeconds'], reporter: ['testId', 'attempt'],
29
+ command: ['testId', 'commandId', 'attempt', 'depth'], pin: input.clear ? ['clear'] : ['testId', 'commandId', 'attempt', 'at'],
30
+ dom: ['selector', 'maxChars', 'at'], aria: ['selector', 'maxNodes', 'at'], inspect: ['selector', 'at'],
31
+ };
32
+ for (const key of Object.keys(input)) {
33
+ if (!['action', 'sessionId', 'timeoutMs', ...allowed[input.action]].includes(key))
34
+ context.addIssue({ code: 'custom', path: [key], message: `${key} is not valid for ${input.action}` });
35
+ }
36
+ if (['dom', 'aria', 'inspect'].includes(input.action) && input.at !== undefined && typeof input.at !== 'number') {
37
+ context.addIssue({ code: 'custom', path: ['at'], message: 'Element index must be a nonnegative integer' });
38
+ }
39
+ if (input.action === 'pin' && typeof input.at === 'number' && input.at < 1) {
40
+ context.addIssue({ code: 'custom', path: ['at'], message: 'Snapshot indices start at 1' });
41
+ }
42
+ if (input.attempt !== undefined && !input.testId)
43
+ context.addIssue({ code: 'custom', path: ['testId'], message: 'testId is required with attempt' });
44
+ });
45
+ function omitNetworkPayload(value, parent = '') {
46
+ if (Array.isArray(value))
47
+ return value.map((entry) => omitNetworkPayload(entry, parent));
48
+ if (!value || typeof value !== 'object')
49
+ return value;
50
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
51
+ const name = key.replace(/[^a-z]/gi, '').toLowerCase();
52
+ const sensitive = /^(request|response)(body|headers)$|^headers$/.test(name) || (/^(request|response)$/.test(parent) && name === 'body');
53
+ return [key, sensitive ? '[omitted network payload]' : omitNetworkPayload(entry, name)];
54
+ }));
55
+ }
56
+ export class CypressTap {
57
+ sanitize;
58
+ requests = new Map();
59
+ running = new Set();
60
+ root;
61
+ controller = new AbortController();
62
+ constructor(repoRoot, sanitize = (value) => value) {
63
+ this.sanitize = sanitize;
64
+ this.root = fs.realpathSync(repoRoot);
65
+ }
66
+ close() { this.controller.abort(); }
67
+ async execute(raw) {
68
+ const input = cypressTapInput.parse(raw);
69
+ if (this.controller.signal.aborted)
70
+ throw new Error('Cypress TAP is closed.');
71
+ const sessions = await this.sessions(input.timeoutMs);
72
+ if (input.action === 'sessions')
73
+ return redactValue(sessions, this.sanitize);
74
+ const session = sessions.find((item) => item.pid === input.sessionId)
75
+ ?? (input.sessionId === undefined && sessions.length === 1 ? sessions[0] : undefined);
76
+ if (!session)
77
+ throw new Error(input.sessionId === undefined
78
+ ? `Found ${sessions.length} open Cypress sessions for this project. Start open mode or choose sessionId from sessions.`
79
+ : `Cypress session ${input.sessionId} is not an open session for the configured project.`);
80
+ const args = ['--session', String(session.pid), '--timeout', String(input.timeoutMs ?? 30000)];
81
+ if (input.action === 'run') {
82
+ if (this.running.has(session.pid))
83
+ throw new Error('A TAP run request is already in progress for this session.');
84
+ let spec = input.spec.replaceAll('\\', '/');
85
+ if (!spec.startsWith('cypress/') || !/\.cy\.[cm]?[jt]sx?$/.test(spec))
86
+ throw new Error('spec must be an existing Cypress spec inside cypress/.');
87
+ const relative = path.relative(this.root, fs.realpathSync(path.resolve(this.root, spec)));
88
+ if (relative.startsWith('..') || path.isAbsolute(relative))
89
+ throw new Error('spec escapes the configured project.');
90
+ spec = relative.replaceAll('\\', '/');
91
+ this.running.add(session.pid);
92
+ try {
93
+ const before = await this.call('status', args, input.timeoutMs);
94
+ const response = await this.call('run', [spec, ...args], input.timeoutMs);
95
+ const request = { requestId: randomUUID(), spec, previousStartedAt: before.startedAt, deadline: Date.now() + (input.runTimeoutSeconds ?? 900) * 1000 };
96
+ this.requests.set(session.pid, request);
97
+ return { ...redactValue(response, this.sanitize), sessionId: session.pid, ...request,
98
+ note: 'Run requested, not completed. Poll status with requestId; a previous startedAt is a stale verdict.' };
99
+ }
100
+ finally {
101
+ this.running.delete(session.pid);
102
+ }
103
+ }
104
+ const flags = { testId: 'test-id', commandId: 'command-id', attempt: 'attempt', depth: 'depth', selector: 'selector', at: 'at', maxChars: 'max-chars', maxNodes: 'max-nodes' };
105
+ for (const [key, flag] of Object.entries(flags)) {
106
+ const value = input[key];
107
+ if (value !== undefined)
108
+ args.push(`--${flag}`, String(value));
109
+ }
110
+ if (input.clear)
111
+ args.push('--clear');
112
+ const output = await this.call(input.action, args, input.timeoutMs);
113
+ if (input.action !== 'status')
114
+ return redactValue(omitNetworkPayload(output), this.sanitize);
115
+ const request = this.requests.get(session.pid);
116
+ if (input.requestId && input.requestId !== request?.requestId)
117
+ throw new Error('Unknown or superseded TAP requestId for this session.');
118
+ const stale = Boolean(request && (!output.startedAt || output.startedAt === request.previousStartedAt || output.spec?.replaceAll('\\', '/') !== request.spec));
119
+ const terminal = ['passed', 'failed'].includes(output.status);
120
+ const counts = output.results;
121
+ const validCounts = counts && ['passed', 'failed', 'pending', 'skipped'].every((key) => Number.isInteger(counts[key]) && counts[key] >= 0)
122
+ && Number.isInteger(output.totalTests) && Object.values(counts).reduce((sum, count) => sum + Number(count), 0) === output.totalTests;
123
+ const testsExecuted = validCounts ? counts.passed + counts.failed : null;
124
+ const timedOut = Boolean(request && Date.now() > request.deadline && (!terminal || stale));
125
+ const validTimestamp = typeof output.startedAt === 'string' && Number.isFinite(Date.parse(output.startedAt));
126
+ const consistentStage = output.status === 'failed' ? counts?.failed > 0 : counts?.failed === 0;
127
+ const verdict = terminal && !stale && validCounts && validTimestamp && consistentStage
128
+ ? counts.failed > 0 ? 'failed' : testsExecuted > 0 ? 'passed' : 'no_tests'
129
+ : null;
130
+ return { ...redactValue(output, this.sanitize), sessionId: session.pid, requestId: request?.requestId,
131
+ stale, timedOut, verdict, testsExecuted,
132
+ note: timedOut ? 'The requested spec did not finish before its deadline; inspect the build/log or stop the session.'
133
+ : stale ? 'This is the previous run or another spec; keep polling for the requested startedAt.'
134
+ : verdict === 'no_tests' ? 'No tests executed; skipped/pending/empty specs are not validation.'
135
+ : terminal && (!validCounts || !validTimestamp || !consistentStage) ? 'Missing or inconsistent run timestamp/results; do not treat this as a verified pass.' : undefined };
136
+ }
137
+ async sessions(timeoutMs) {
138
+ const output = await this.call('sessions', ['--timeout', String(timeoutMs ?? 30000)], timeoutMs);
139
+ if (!Array.isArray(output))
140
+ throw new Error('Invalid Cypress TAP sessions JSON: expected an array.');
141
+ return output.filter((session) => {
142
+ if (!Number.isInteger(session?.pid) || session.pid <= 0 || typeof session.projectRoot !== 'string')
143
+ return false;
144
+ try {
145
+ return fs.realpathSync(session.projectRoot) === this.root;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ });
151
+ }
152
+ async call(action, args, timeoutMs = 30000) {
153
+ const { cli } = cypressInstallation(this.root, true);
154
+ const stdout = await new Promise((resolve, reject) => {
155
+ execFile(process.execPath, cypressTapArguments(cli, [action, '--json', ...args]), {
156
+ cwd: this.root, env: cypressEnvironment(), windowsHide: true, encoding: 'utf8',
157
+ timeout: timeoutMs + 2000, maxBuffer: 2 * 1024 * 1024, signal: this.controller.signal,
158
+ }, (error, stdout, stderr) => {
159
+ if (error)
160
+ reject(new Error(this.sanitize(`Cypress TAP ${action} failed: ${stderr || stdout || error.message}`).slice(0, 4000)));
161
+ else
162
+ resolve(stdout);
163
+ });
164
+ });
165
+ // Cypress 16 emits this one non-JSON success response before the first open session.
166
+ if (action === 'sessions' && stdout.trim() === 'No running Cypress session found. Start Cypress in open mode (e.g. `cypress open`) and select a testing type to get started.')
167
+ return [];
168
+ try {
169
+ return JSON.parse(stdout);
170
+ }
171
+ catch {
172
+ throw new Error(`Cypress TAP ${action} returned invalid JSON.`);
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,3 @@
1
+ export declare function createRedactor(values?: Record<string, unknown>): (text: string) => string;
2
+ export declare function projectRedactor(repoRoot: string): (text: string) => string;
3
+ export declare function redactValue(value: unknown, sanitize?: (text: string) => string): any;
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ const secretKey = /password|passwd|(?:^|_)pass(?:$|_)|token|secret|authorization|cookie|private.?key/i;
5
+ export function createRedactor(values = {}) {
6
+ const secrets = Object.entries(values)
7
+ .filter(([key, value]) => secretKey.test(key) && typeof value === 'string' && value.length > 0)
8
+ .map(([, value]) => value).sort((a, b) => b.length - a.length);
9
+ return (text) => {
10
+ let result = text;
11
+ for (const secret of secrets)
12
+ result = result.split(secret).join('[redacted]');
13
+ return result
14
+ .replace(/\bBearer\s+[^\s"']+/gi, 'Bearer [redacted]')
15
+ .replace(/\b(https?:\/\/[^\s?#"']+)[?#][^\s"']*/gi, '$1')
16
+ .replace(/((?:password|passwd|token|secret|authorization|cookie)\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1[redacted]');
17
+ };
18
+ }
19
+ export function projectRedactor(repoRoot) {
20
+ let local = {};
21
+ const envPath = path.join(repoRoot, '.env');
22
+ if (fs.existsSync(envPath)) {
23
+ // Reuse the consumer's dotenv parser without loading secrets into this process's environment.
24
+ const require = createRequire(path.join(repoRoot, 'package.json'));
25
+ local = require('dotenv').parse(fs.readFileSync(envPath));
26
+ }
27
+ return createRedactor({ ...local, ...process.env });
28
+ }
29
+ export function redactValue(value, sanitize = createRedactor()) {
30
+ if (typeof value === 'string')
31
+ return sanitize(value);
32
+ if (Array.isArray(value))
33
+ return value.map((entry) => redactValue(entry, sanitize));
34
+ if (value && typeof value === 'object') {
35
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
36
+ key, secretKey.test(key) ? '[redacted]' : redactValue(entry, sanitize),
37
+ ]));
38
+ }
39
+ return value;
40
+ }
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,42 @@
1
+ # Integrated Cypress runtime
2
+
3
+ **Goal:** one existing Overkill MCP connection can launch project tests, inspect the live Cypress browser, retain failure evidence, and compare repeat/retry outcomes.
4
+
5
+ **Architecture:** keep stdio and existing analysis tools. A run manager invokes the project's interactive wrapper with an opt-in companion Cypress config. Native Node WebSocket connects to the run's Chrome debugging port. The support bridge captures failure DOM before cleanup; reports and logs belong to a unique run directory. Codex edits test files with its existing editor and reruns the selected spec.
6
+
7
+ **Constraints:** preserve existing WSL wrapper edits; no commits, dependency installations, broad refactors, automatic publication, or changes to normal Cypress runs. One active managed run per checkout avoids collisions in legacy plugins. No secret-bearing config, headers, or bodies in default output.
8
+
9
+ - [x] Runner agent: implement and check start/status/stop, bounded repeats, timeouts, strict spec paths, per-run artifacts, and optional companion config/wrapper support.
10
+ - [x] Browser agent: implement and check CDP connection, AUT/runner contexts, DOM, console/network summaries, screenshots, evaluate, pause/resume, and cleanup.
11
+ - [x] Root: implement and check failure capture, sanitized MCP output, tool registration, flakiness comparison, single-command setup, and workflow instructions.
12
+ - [x] Integrate: build and run targeted protocol/lifecycle/security checks; execute an actual Cypress self-check and a suitable existing project spec; inspect real DOM, capture a deliberate failure and retry, then verify corrected rerun.
13
+ - [x] Review: cross-review shared behavior, preserve pre-existing changes, update usage docs and report verified capabilities and practical limits.
14
+
15
+ **Verification:** use installed TypeScript and test tools. If platform-specific test dependencies are unavailable, use Node's test runner for independent runtime checks and report the existing suite's limitation. Live testing must use the project wrapper and isolated diagnostic files.
16
+
17
+ ## Verification evidence
18
+
19
+ - TypeScript typecheck and build passed.
20
+ - 23 targeted Overkill checks passed: browser protocol, lifecycle/timeout/shutdown, capture/redaction, MCP registration and existing triage/tools; two project support capture checks passed; changed JavaScript passed formatting and targeted lint, and wrapper syntax passed `bash -n`.
21
+ - Existing `criticalPathTests/stage1/[0]loginAndLogout.cy.js`: two tests passed through the managed wrapper.
22
+ - `npm run mcp:check`: real MCP transport, pause gate/resume, live AUT DOM, screenshot image, browser evaluation, deliberate failed attempt, preserved DOM, successful retry classified flaky, and two independent corrected executions passed.
23
+ - A new MCP client read the completed two-repeat archive successfully. Setup verified all 19 tools and registered the updated local checkout in Codex.
24
+ - Review fixed actual Cypress `Your project`/`Your Spec` context selection, preserved filesystem paths when a credential coincides with a folder name, duplicate captures, shutdown/connect races, and moved generated self-check specs outside normal test discovery.
25
+ - The existing dependency tree contains Windows-only Rollup/esbuild binaries. Targeted Vitest checks used already installed Linux packages through a temporary test-process resolver, without installing dependencies. The full existing suite was not green: baseline indexer checks depend on sibling frontend content and tsx-based handshake checks also hit that platform mismatch. The compiled production MCP and its real handshake/self-check passed without that workaround.
26
+
27
+
28
+ ## Cypress 16 / TAP release extension (1.3.0)
29
+
30
+ The user approved preparing native Windows/Linux execution and TAP in parallel with the consumer Cypress 16 upgrade, including the necessary platform dependency installation. The original wrapper-only restriction above describes the first implementation, and is superseded for this release.
31
+
32
+ - The managed runner now invokes the consumer's Cypress Node CLI directly, preserving spec bracket escaping, ports, opt-in configuration, retries/repeats, artifacts and CDP. It strips inherited Electron Node mode and stops the native Windows process tree or POSIX process group.
33
+ - `mode: open` retains a managed session for TAP; one additional `cypress_tap` tool exposes sessions/status/specs/run/reporter/command/pin/dom/aria/inspect on the same stdio MCP. Run mode remains the path for independent repeats.
34
+ - Session selection stays within the configured project. Rerun status rejects stale timestamps, incomplete/inconsistent result counts and zero executed tests. CLI calls use JSON, bounded time/output and separated arguments; the exact Cypress 16 plain-text no-session response is treated as an empty list.
35
+ - Full verification is now green after making the six prior environment-dependent test failures deterministic with native paths and temporary CI/frontend fixtures. Production indexing and adapter behavior were unchanged.
36
+ - Real Cypress 16 Linux verification passed the managed debug/failure/retry/corrected-repeat check and the open TAP failure → reporter → command → historical pin → DOM/ARIA/inspect → clear → corrected fresh pass check.
37
+ - Native Windows Node 24.14 process-tree verification passed with actual `taskkill /T /F` and descendant PID checks. This verifies process handling; it is not a claim that every desktop/browser combination was exercised.
38
+ - Package version is 1.3.0. The npm artifact includes runtime and TAP code without a sibling checkout, local caches, secrets or node_modules. No Git commit, tag or publication was performed.
39
+
40
+ - Native Windows TAP initially exposed Node's `UV_HANDLE_CLOSING` shutdown assertion. Cypress 16 sends guest telemetry with `fetch` immediately before `process.exit`, matching nodejs/node#56645. Native A/B checks with piped, ignored and inherited stdin all failed before; all succeeded with `CYPRESS_DISABLE_GUEST_TELEMETRY=1`. Managed child environments now set that flag without changing the parent environment or ignoring CLI failures.
41
+
42
+ - A later native corrected-rerun check reproduced the same Node assertion after TAP's local GraphQL `fetch`, even with telemetry disabled. The Windows TAP invocation now loads the public CLI in a small CommonJS bootstrap that sets `process.exitCode` instead of forcing `process.exit`. This lets pending work drain under the existing timeout and preserves nonzero failures; it does not patch Cypress, retry crashes, or discard stderr. The regression checks real subprocess argv, pending-work completion and both success/failure exit codes.
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mknrt/autotests-overkill",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "AI-assisted testing intelligence platform for autotests2, caseplatform-web, and CI artifacts",
@@ -1,13 +1,18 @@
1
1
  # overkill-debug-failure
2
2
 
3
- Goal: triage failed runs using real artifacts and metadata.
3
+ Goal: debug Cypress through the integrated Overkill runtime using actual browser state, errors and artifacts.
4
4
 
5
5
  Workflow:
6
- 1. Run `summarize_ci_context`.
7
- 2. Run `triage_failed_run`.
8
- 3. Run `build_rerun_scope`.
9
- 4. If code changed recently, run `analyze_diff_impact`.
6
+ 0. For a local spec, use `start_cypress_run` and poll `get_cypress_run`. It launches the project's Cypress CLI natively on Windows or Linux with the opt-in adapter. Use `debug: true` for a pause before each test; use `inspect_cypress_browser` (`snapshot`, `screenshot`, `evaluate`, `pause`, `resume`) on that run. Live AUT context and the Cypress runner/spec contexts are distinct. Prefer captured failure DOM for pre-teardown state. Use bounded `repeats`/`retries` to investigate flakiness, and stop unused runs with `stop_cypress_run`.
7
+ 1. For completed Command Log and time travel, use `mode: "open"` (Cypress 15.21+, Chromium). Poll `get_cypress_run.tap` for `sessionId`/`requestId`, then `cypress_tap` status. Trust its fresh `verdict`, not the raw stage: `no_tests` is not a pass. Use reporter → command → pin → dom/aria/inspect; clear the pin afterward. TAP cannot inspect run mode. Open sessions stay alive until stop/timeout/disconnect; use `run` for a rerun and the new requestId, or managed run mode for independent repeats.
8
+ 2. Run `summarize_ci_context`.
9
+ 3. Run `triage_failed_run`.
10
+ 4. Run `build_rerun_scope`.
11
+ 5. If code changed recently, run `analyze_diff_impact`.
10
12
 
11
13
  Rules:
12
14
  - Separate artifact-backed hypotheses from generic guesses.
13
15
  - Mention screenshots, videos, or API captures explicitly when they inform the conclusion.
16
+ - A completed process may contain failed tests. Read errors and attempt states; skipped tests and incomplete launches are not passes. Report observed flakiness without claiming small samples prove stability.
17
+ - Search existing project helpers/selectors before changing a test. Codex edits files through its normal editor, then reruns the affected spec and checks lint. Do not weaken assertions to obtain a green result.
18
+ - Keep credentials out of diagnostics. Browser evaluation can change state. Runtime launch/inspection is local Windows or Linux/WSL and needs no separate browser MCP. Managed runs stop when the MCP connection closes; completed archives can be read afterward.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes