codeloop-mcp-server 0.1.50 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +4 -0
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/evidence/loop_state.d.ts +53 -0
- package/dist/evidence/loop_state.d.ts.map +1 -0
- package/dist/evidence/loop_state.js +147 -0
- package/dist/evidence/loop_state.js.map +1 -0
- package/dist/evidence/verify_staleness.d.ts +9 -0
- package/dist/evidence/verify_staleness.d.ts.map +1 -0
- package/dist/evidence/verify_staleness.js +180 -0
- package/dist/evidence/verify_staleness.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -17
- package/dist/index.js.map +1 -1
- package/dist/runners/maestro.d.ts +13 -0
- package/dist/runners/maestro.d.ts.map +1 -1
- package/dist/runners/maestro.js +37 -1
- package/dist/runners/maestro.js.map +1 -1
- package/dist/runners/modal_detector.d.ts +60 -0
- package/dist/runners/modal_detector.d.ts.map +1 -0
- package/dist/runners/modal_detector.js +160 -0
- package/dist/runners/modal_detector.js.map +1 -0
- package/dist/runners/python_tests.d.ts +26 -0
- package/dist/runners/python_tests.d.ts.map +1 -0
- package/dist/runners/python_tests.js +181 -0
- package/dist/runners/python_tests.js.map +1 -0
- package/dist/runners/rust_tests.d.ts +28 -0
- package/dist/runners/rust_tests.d.ts.map +1 -0
- package/dist/runners/rust_tests.js +76 -0
- package/dist/runners/rust_tests.js.map +1 -0
- package/dist/tools/diagnose.d.ts.map +1 -1
- package/dist/tools/diagnose.js +13 -0
- package/dist/tools/diagnose.js.map +1 -1
- package/dist/tools/gate_check.d.ts +2 -1
- package/dist/tools/gate_check.d.ts.map +1 -1
- package/dist/tools/gate_check.js +46 -32
- package/dist/tools/gate_check.js.map +1 -1
- package/dist/tools/is_ui_project.d.ts +23 -0
- package/dist/tools/is_ui_project.d.ts.map +1 -0
- package/dist/tools/is_ui_project.js +42 -0
- package/dist/tools/is_ui_project.js.map +1 -0
- package/dist/tools/verify.d.ts +28 -0
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +159 -7
- package/dist/tools/verify.js.map +1 -1
- package/package.json +1 -1
package/dist/runners/maestro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runCommand } from "./base.js";
|
|
1
|
+
import { runCommand, makeSkippedResult } from "./base.js";
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
const MAESTRO_FLOW_REL_DIRS = ["tests/maestro", ".maestro", "maestro"];
|
|
@@ -130,4 +130,40 @@ export async function runMaestroTests(cwd, logDir) {
|
|
|
130
130
|
logPath,
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* 0.1.51 H9 — Adapt `runMaestroTests` to the `RunnerResult` shape used
|
|
135
|
+
* by every other verify runner so it can be pushed into the `results`
|
|
136
|
+
* array in `runVerify` without special-casing. Returns a skipped
|
|
137
|
+
* result (`available: false`) when:
|
|
138
|
+
* - no `.maestro/` / `tests/maestro/` / `maestro/` flows are present;
|
|
139
|
+
* - the `maestro` CLI is not on PATH.
|
|
140
|
+
*
|
|
141
|
+
* `passed` flows count toward `passed`, `failed` flows toward `failed`,
|
|
142
|
+
* and `skipped` is always 0 (Maestro doesn't surface a skipped count).
|
|
143
|
+
*/
|
|
144
|
+
export async function runMaestroFlows(cwd, logPath) {
|
|
145
|
+
const flows = findMaestroFlows(cwd);
|
|
146
|
+
if (flows.length === 0) {
|
|
147
|
+
return makeSkippedResult("maestro_flows", "No Maestro flows found (looked under .maestro/, tests/maestro/, maestro/)");
|
|
148
|
+
}
|
|
149
|
+
if (!(await isMaestroInstalled())) {
|
|
150
|
+
return makeSkippedResult("maestro_flows", "maestro CLI not found on PATH. Install via `curl -Ls 'https://get.maestro.mobile.dev' | bash` (POSIX/macOS) and retry.");
|
|
151
|
+
}
|
|
152
|
+
const start = Date.now();
|
|
153
|
+
const logDir = join(logPath, "..");
|
|
154
|
+
const result = await runMaestroTests(cwd, logDir);
|
|
155
|
+
const duration_ms = Date.now() - start;
|
|
156
|
+
return {
|
|
157
|
+
runner_name: "maestro_flows",
|
|
158
|
+
available: true,
|
|
159
|
+
exit_code: result.passed ? 0 : 1,
|
|
160
|
+
passed: result.passedFlows,
|
|
161
|
+
failed: result.failedFlows,
|
|
162
|
+
skipped: 0,
|
|
163
|
+
log_path: result.logPath,
|
|
164
|
+
duration_ms,
|
|
165
|
+
stdout: "",
|
|
166
|
+
stderr: "",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
133
169
|
//# sourceMappingURL=maestro.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maestro.js","sourceRoot":"","sources":["../../src/runners/maestro.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"maestro.js","sourceRoot":"","sources":["../../src/runners/maestro.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CodeLoopConfig } from "@codelooptech/shared";
|
|
2
|
+
/**
|
|
3
|
+
* 0.1.51 H11 — Cross-platform modal detector.
|
|
4
|
+
*
|
|
5
|
+
* Detects whether the running app currently has a foreground modal
|
|
6
|
+
* dialog (alert / confirm / sheet / overlay) that the agent must
|
|
7
|
+
* interact with before proceeding. The plan exposes 4 enforcement
|
|
8
|
+
* layers; this module is layer 1 (the detector) plus the building
|
|
9
|
+
* block for layer 4 (`codeloop_handle_modal`).
|
|
10
|
+
*
|
|
11
|
+
* Detection strategy:
|
|
12
|
+
* - Browser: query Playwright's page for alert/confirm dialogs
|
|
13
|
+
* and visible overlay elements (role=dialog, aria-modal=true,
|
|
14
|
+
* CSS classes that match common modal libraries).
|
|
15
|
+
* - Windows desktop: PowerShell + UIAutomation — walk the desktop
|
|
16
|
+
* tree for ControlType.Window with WindowVisualState=Normal AND
|
|
17
|
+
* IsModal=true (or, when IsModal isn't reliable, look for the
|
|
18
|
+
* OWNED-window pattern + small-screen-fraction heuristic).
|
|
19
|
+
* - macOS desktop: AppleScript — `every window of process whose
|
|
20
|
+
* subrole is "AXDialog" or "AXSystemDialog"`.
|
|
21
|
+
* - Linux desktop: xdotool / xprop walk for windows whose WM_NAME
|
|
22
|
+
* contains "Dialog" / "Confirm" / "Alert" or which set
|
|
23
|
+
* _NET_WM_WINDOW_TYPE_DIALOG.
|
|
24
|
+
* - Android / iOS: best-effort via dumpsys / xcrun — these
|
|
25
|
+
* surfaces are noisy enough that we treat the detector as
|
|
26
|
+
* optional and let codeloop_handle_modal fall through to a
|
|
27
|
+
* manual prompt (see the H11 docs entry in the plan).
|
|
28
|
+
*
|
|
29
|
+
* The detector NEVER fails the call; on any error it returns
|
|
30
|
+
* `is_modal_present: false` plus a `detection_error` string so the
|
|
31
|
+
* agent can decide whether to retry. Agents must NOT treat a false
|
|
32
|
+
* negative as proof there's no modal — the post-interact directive
|
|
33
|
+
* (layer 3) covers that case by always nudging the agent to look.
|
|
34
|
+
*/
|
|
35
|
+
export interface ModalDetectionResult {
|
|
36
|
+
is_modal_present: boolean;
|
|
37
|
+
/** Human-readable description of the detected modal, when present. */
|
|
38
|
+
modal_description?: string;
|
|
39
|
+
/** Suggested action for the agent: "confirm" | "cancel" | "dismiss" | "inspect". */
|
|
40
|
+
suggested_action?: string;
|
|
41
|
+
/** Heuristic confidence 0-1; > 0.7 should be acted on. */
|
|
42
|
+
confidence?: number;
|
|
43
|
+
/** When the detector failed (e.g. UIA not installed), set so the agent doesn't double-fire. */
|
|
44
|
+
detection_error?: string;
|
|
45
|
+
/** target_type the detector ran against. */
|
|
46
|
+
target_type: "browser" | "desktop" | "android_emulator" | "ios_simulator" | "unknown";
|
|
47
|
+
}
|
|
48
|
+
export declare function detectModal(opts: {
|
|
49
|
+
target_type?: "browser" | "desktop" | "android_emulator" | "ios_simulator";
|
|
50
|
+
app_name?: string;
|
|
51
|
+
cwd: string;
|
|
52
|
+
config?: CodeLoopConfig;
|
|
53
|
+
}): Promise<ModalDetectionResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Heuristic for whether a given action is "modal-related" — used by
|
|
56
|
+
* layer 2 (pre-interact block) to decide whether an unrelated action
|
|
57
|
+
* should be refused while a modal is open.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isModalRelatedAction(action: string): boolean;
|
|
60
|
+
//# sourceMappingURL=modal_detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal_detector.d.ts","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,WAAW,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAC;CACvF;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiBhC;AA6ID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB5D"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { runCommand } from "./base.js";
|
|
2
|
+
export async function detectModal(opts) {
|
|
3
|
+
const target = opts.target_type ?? "desktop";
|
|
4
|
+
try {
|
|
5
|
+
if (target === "browser")
|
|
6
|
+
return await detectBrowserModal(opts);
|
|
7
|
+
if (target === "desktop")
|
|
8
|
+
return await detectDesktopModal(opts);
|
|
9
|
+
return {
|
|
10
|
+
is_modal_present: false,
|
|
11
|
+
target_type: target,
|
|
12
|
+
detection_error: `Modal detection on ${target} is best-effort. Use codeloop_handle_modal manually if a modal blocks the interaction.`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return {
|
|
17
|
+
is_modal_present: false,
|
|
18
|
+
target_type: target,
|
|
19
|
+
detection_error: e.message,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function detectBrowserModal(_opts) {
|
|
24
|
+
// Browser detection requires the Playwright page handle. We don't
|
|
25
|
+
// own the page from this module — the actual probe runs through
|
|
26
|
+
// browser_interaction.ts at call-time. This stub returns a
|
|
27
|
+
// structured "use Playwright at the call site" so the wrapper in
|
|
28
|
+
// index.ts can short-circuit instead of re-implementing the
|
|
29
|
+
// browser tree walk.
|
|
30
|
+
return {
|
|
31
|
+
is_modal_present: false,
|
|
32
|
+
target_type: "browser",
|
|
33
|
+
detection_error: "Browser modal detection is performed inline by codeloop_interact via Playwright (page.locator('[role=\"dialog\"][aria-modal=\"true\"], dialog[open], .modal:not(.hidden)')). This module returns a stub for the cross-platform contract.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function detectDesktopModal(opts) {
|
|
37
|
+
if (process.platform === "win32")
|
|
38
|
+
return detectWindowsModal(opts);
|
|
39
|
+
if (process.platform === "darwin")
|
|
40
|
+
return detectMacModal(opts);
|
|
41
|
+
return detectLinuxModal(opts);
|
|
42
|
+
}
|
|
43
|
+
async function detectWindowsModal(opts) {
|
|
44
|
+
const app = opts.app_name ?? "";
|
|
45
|
+
const filterClause = app
|
|
46
|
+
? `if ($name -notlike '*' + '${app.replace(/'/g, "''")}' + '*') { continue }`
|
|
47
|
+
: "";
|
|
48
|
+
// PowerShell UIA probe. Returns JSON the agent can parse.
|
|
49
|
+
const script = [
|
|
50
|
+
"Add-Type -AssemblyName UIAutomationClient",
|
|
51
|
+
"$root = [System.Windows.Automation.AutomationElement]::RootElement",
|
|
52
|
+
"$cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::Window)",
|
|
53
|
+
"$windows = $root.FindAll([System.Windows.Automation.TreeScope]::Children, $cond)",
|
|
54
|
+
"$result = @()",
|
|
55
|
+
"foreach ($w in $windows) {",
|
|
56
|
+
" $name = $w.Current.Name",
|
|
57
|
+
" $owned = $false",
|
|
58
|
+
" try {",
|
|
59
|
+
" $pattern = $w.GetCurrentPattern([System.Windows.Automation.WindowPattern]::Pattern)",
|
|
60
|
+
" $owned = $pattern.Current.IsModal",
|
|
61
|
+
" } catch { }",
|
|
62
|
+
" " + filterClause,
|
|
63
|
+
" if ($owned) {",
|
|
64
|
+
" $result += @{ name = $name; modal = $true }",
|
|
65
|
+
" }",
|
|
66
|
+
"}",
|
|
67
|
+
"$result | ConvertTo-Json -Compress",
|
|
68
|
+
].join("\n");
|
|
69
|
+
const r = await runCommand("powershell", ["-NoProfile", "-Command", script], opts.cwd, undefined, undefined, 8000);
|
|
70
|
+
if (r.exit_code !== 0) {
|
|
71
|
+
return {
|
|
72
|
+
is_modal_present: false,
|
|
73
|
+
target_type: "desktop",
|
|
74
|
+
detection_error: r.stderr.slice(0, 200),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const parsed = r.stdout.trim() ? JSON.parse(r.stdout) : null;
|
|
79
|
+
const arr = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
|
|
80
|
+
const modal = arr.find((w) => w.modal);
|
|
81
|
+
if (modal) {
|
|
82
|
+
return {
|
|
83
|
+
is_modal_present: true,
|
|
84
|
+
modal_description: `Windows modal: ${modal.name ?? "(unnamed)"}`,
|
|
85
|
+
suggested_action: "inspect",
|
|
86
|
+
confidence: 0.85,
|
|
87
|
+
target_type: "desktop",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* fall through */
|
|
93
|
+
}
|
|
94
|
+
return { is_modal_present: false, target_type: "desktop" };
|
|
95
|
+
}
|
|
96
|
+
async function detectMacModal(opts) {
|
|
97
|
+
const app = opts.app_name ?? "frontmost";
|
|
98
|
+
const script = app === "frontmost"
|
|
99
|
+
? `tell application "System Events" to get name of every window of (first process whose frontmost is true) whose subrole is "AXDialog"`
|
|
100
|
+
: `tell application "System Events" to get name of every window of process "${app}" whose subrole is "AXDialog"`;
|
|
101
|
+
const r = await runCommand("osascript", ["-e", script], opts.cwd, undefined, undefined, 8000);
|
|
102
|
+
if (r.exit_code !== 0) {
|
|
103
|
+
return {
|
|
104
|
+
is_modal_present: false,
|
|
105
|
+
target_type: "desktop",
|
|
106
|
+
detection_error: r.stderr.slice(0, 200),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const out = r.stdout.trim();
|
|
110
|
+
if (!out || out === "{}" || out === "missing value") {
|
|
111
|
+
return { is_modal_present: false, target_type: "desktop" };
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
is_modal_present: true,
|
|
115
|
+
modal_description: `macOS modal: ${out}`,
|
|
116
|
+
suggested_action: "inspect",
|
|
117
|
+
confidence: 0.8,
|
|
118
|
+
target_type: "desktop",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function detectLinuxModal(opts) {
|
|
122
|
+
// xdotool: search for windows tagged as dialog by EWMH.
|
|
123
|
+
const r = await runCommand("xdotool", ["search", "--onlyvisible", "--name", "."], opts.cwd, undefined, undefined, 8000);
|
|
124
|
+
if (r.exit_code !== 0) {
|
|
125
|
+
return {
|
|
126
|
+
is_modal_present: false,
|
|
127
|
+
target_type: "desktop",
|
|
128
|
+
detection_error: "xdotool unavailable; install xdotool via apt/dnf to enable Linux modal detection.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Best-effort. We don't pursue full xprop parsing here because the
|
|
132
|
+
// dominant Linux DE-set already surfaces modals via WM_HINTS
|
|
133
|
+
// transient_for; agents that need precision can call
|
|
134
|
+
// codeloop_handle_modal directly.
|
|
135
|
+
return { is_modal_present: false, target_type: "desktop" };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Heuristic for whether a given action is "modal-related" — used by
|
|
139
|
+
* layer 2 (pre-interact block) to decide whether an unrelated action
|
|
140
|
+
* should be refused while a modal is open.
|
|
141
|
+
*/
|
|
142
|
+
export function isModalRelatedAction(action) {
|
|
143
|
+
const modalActions = new Set([
|
|
144
|
+
"click",
|
|
145
|
+
"double_click",
|
|
146
|
+
"right_click",
|
|
147
|
+
"hover",
|
|
148
|
+
"type",
|
|
149
|
+
"type_and_submit",
|
|
150
|
+
"type_and_tab",
|
|
151
|
+
"keystroke",
|
|
152
|
+
"hotkey",
|
|
153
|
+
"fill_form",
|
|
154
|
+
"select_option",
|
|
155
|
+
"toggle",
|
|
156
|
+
"wait",
|
|
157
|
+
]);
|
|
158
|
+
return modalActions.has(action);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=modal_detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal_detector.js","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RunnerResult } from "./base.js";
|
|
2
|
+
/**
|
|
3
|
+
* 0.1.51 H5 — Python verify runner.
|
|
4
|
+
*
|
|
5
|
+
* Detects the project's Python test framework in this priority order:
|
|
6
|
+
* 1. `pytest` if `pytest.ini`, `pyproject.toml [tool.pytest.ini_options]`,
|
|
7
|
+
* `setup.cfg [tool:pytest]`, or `tox.ini [pytest]` is present, OR
|
|
8
|
+
* a `conftest.py` exists, OR `tests/` / `test/` directory exists
|
|
9
|
+
* with `test_*.py` files.
|
|
10
|
+
* 2. Django `manage.py test` if `manage.py` is present.
|
|
11
|
+
* 3. `python -m unittest discover` as a last-resort fallback.
|
|
12
|
+
*
|
|
13
|
+
* Cross-platform: pure CLI invocations work identically on macOS,
|
|
14
|
+
* Linux, Windows. Picks `python3` first then `python` (the Windows
|
|
15
|
+
* launcher convention), then `py` as a final Windows-only fallback.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runPythonTests(cwd: string, logPath: string): Promise<RunnerResult>;
|
|
18
|
+
type PythonFramework = "pytest" | "django" | "unittest";
|
|
19
|
+
export declare function detectPythonTestFramework(cwd: string): PythonFramework | null;
|
|
20
|
+
export declare function parsePythonOutput(output: string, framework: PythonFramework): {
|
|
21
|
+
passed: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
skipped: number;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=python_tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python_tests.d.ts","sourceRoot":"","sources":["../../src/runners/python_tests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAmEvB;AAED,KAAK,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAExD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA6D7E;AAOD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,GACzB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwCrD"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { runCommand, makeSkippedResult, checkToolAvailable } from "./base.js";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
/**
|
|
5
|
+
* 0.1.51 H5 — Python verify runner.
|
|
6
|
+
*
|
|
7
|
+
* Detects the project's Python test framework in this priority order:
|
|
8
|
+
* 1. `pytest` if `pytest.ini`, `pyproject.toml [tool.pytest.ini_options]`,
|
|
9
|
+
* `setup.cfg [tool:pytest]`, or `tox.ini [pytest]` is present, OR
|
|
10
|
+
* a `conftest.py` exists, OR `tests/` / `test/` directory exists
|
|
11
|
+
* with `test_*.py` files.
|
|
12
|
+
* 2. Django `manage.py test` if `manage.py` is present.
|
|
13
|
+
* 3. `python -m unittest discover` as a last-resort fallback.
|
|
14
|
+
*
|
|
15
|
+
* Cross-platform: pure CLI invocations work identically on macOS,
|
|
16
|
+
* Linux, Windows. Picks `python3` first then `python` (the Windows
|
|
17
|
+
* launcher convention), then `py` as a final Windows-only fallback.
|
|
18
|
+
*/
|
|
19
|
+
export async function runPythonTests(cwd, logPath) {
|
|
20
|
+
const framework = detectPythonTestFramework(cwd);
|
|
21
|
+
if (framework === null) {
|
|
22
|
+
return makeSkippedResult("python_tests", "No Python test signals found (no pytest config, manage.py, conftest.py, or tests/ directory)");
|
|
23
|
+
}
|
|
24
|
+
// Pick the python interpreter. Order matters: prefer python3 on
|
|
25
|
+
// POSIX (where `python` is sometimes Python 2), then python (the
|
|
26
|
+
// Windows convention via the py launcher), then `py` as a final
|
|
27
|
+
// Windows fallback.
|
|
28
|
+
const candidates = process.platform === "win32"
|
|
29
|
+
? ["python", "py", "python3"]
|
|
30
|
+
: ["python3", "python"];
|
|
31
|
+
let python = null;
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
if (await checkToolAvailable(candidate)) {
|
|
34
|
+
python = candidate;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (python === null) {
|
|
39
|
+
return makeSkippedResult("python_tests", "Python interpreter not found on PATH (tried: " + candidates.join(", ") + ")");
|
|
40
|
+
}
|
|
41
|
+
let cmd;
|
|
42
|
+
let args;
|
|
43
|
+
if (framework === "pytest") {
|
|
44
|
+
// Use `python -m pytest` so we don't depend on a `pytest`
|
|
45
|
+
// executable being on PATH (it sometimes isn't, e.g. when the
|
|
46
|
+
// project uses a virtualenv that hasn't been `activate`d).
|
|
47
|
+
cmd = python;
|
|
48
|
+
args = ["-m", "pytest", "--tb=short", "-q"];
|
|
49
|
+
}
|
|
50
|
+
else if (framework === "django") {
|
|
51
|
+
cmd = python;
|
|
52
|
+
args = ["manage.py", "test", "--noinput"];
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
cmd = python;
|
|
56
|
+
args = ["-m", "unittest", "discover"];
|
|
57
|
+
}
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
const result = await runCommand(cmd, args, cwd, logPath);
|
|
60
|
+
const duration_ms = Date.now() - start;
|
|
61
|
+
const { passed, failed, skipped } = parsePythonOutput(result.stdout + "\n" + result.stderr, framework);
|
|
62
|
+
return {
|
|
63
|
+
runner_name: `python_${framework}`,
|
|
64
|
+
available: true,
|
|
65
|
+
exit_code: result.exit_code,
|
|
66
|
+
passed,
|
|
67
|
+
failed,
|
|
68
|
+
skipped,
|
|
69
|
+
log_path: logPath,
|
|
70
|
+
duration_ms,
|
|
71
|
+
stdout: result.stdout,
|
|
72
|
+
stderr: result.stderr,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function detectPythonTestFramework(cwd) {
|
|
76
|
+
// Check for pytest signals first — this is the most common modern
|
|
77
|
+
// Python test setup and the most flexible (covers both unittest-
|
|
78
|
+
// style classes and pytest-native function-based tests).
|
|
79
|
+
if (existsSync(join(cwd, "pytest.ini")))
|
|
80
|
+
return "pytest";
|
|
81
|
+
if (existsSync(join(cwd, "pyproject.toml"))) {
|
|
82
|
+
try {
|
|
83
|
+
const pyproject = readFileSync(join(cwd, "pyproject.toml"), "utf-8");
|
|
84
|
+
if (/\[tool\.pytest\.ini_options\]/.test(pyproject))
|
|
85
|
+
return "pytest";
|
|
86
|
+
}
|
|
87
|
+
catch { /* fall through */ }
|
|
88
|
+
}
|
|
89
|
+
if (existsSync(join(cwd, "setup.cfg"))) {
|
|
90
|
+
try {
|
|
91
|
+
const setupCfg = readFileSync(join(cwd, "setup.cfg"), "utf-8");
|
|
92
|
+
if (/\[tool:pytest\]/.test(setupCfg))
|
|
93
|
+
return "pytest";
|
|
94
|
+
}
|
|
95
|
+
catch { /* fall through */ }
|
|
96
|
+
}
|
|
97
|
+
if (existsSync(join(cwd, "tox.ini"))) {
|
|
98
|
+
try {
|
|
99
|
+
const toxIni = readFileSync(join(cwd, "tox.ini"), "utf-8");
|
|
100
|
+
if (/\[pytest\]/.test(toxIni))
|
|
101
|
+
return "pytest";
|
|
102
|
+
}
|
|
103
|
+
catch { /* fall through */ }
|
|
104
|
+
}
|
|
105
|
+
// conftest.py at the root is a strong signal even without an
|
|
106
|
+
// explicit ini section — pytest auto-discovers from any conftest.
|
|
107
|
+
if (existsSync(join(cwd, "conftest.py")))
|
|
108
|
+
return "pytest";
|
|
109
|
+
// Look for a tests/ or test/ directory containing pytest-style
|
|
110
|
+
// test files. `test_*.py` is the pytest convention; pytest will
|
|
111
|
+
// discover them automatically once invoked.
|
|
112
|
+
for (const candidate of ["tests", "test"]) {
|
|
113
|
+
const testDir = join(cwd, candidate);
|
|
114
|
+
if (!existsSync(testDir))
|
|
115
|
+
continue;
|
|
116
|
+
try {
|
|
117
|
+
const files = readdirSync(testDir);
|
|
118
|
+
if (files.some((f) => /^test_.*\.py$/.test(f) || /_test\.py$/.test(f))) {
|
|
119
|
+
return "pytest";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch { /* keep searching */ }
|
|
123
|
+
}
|
|
124
|
+
// Django: manage.py + a configured project. Use `manage.py test`.
|
|
125
|
+
if (existsSync(join(cwd, "manage.py")))
|
|
126
|
+
return "django";
|
|
127
|
+
// Anything-Python at all without a pytest config falls back to
|
|
128
|
+
// unittest discover (works against any subclass of TestCase).
|
|
129
|
+
if (existsSync(join(cwd, "setup.py")) ||
|
|
130
|
+
existsSync(join(cwd, "pyproject.toml")) ||
|
|
131
|
+
existsSync(join(cwd, "requirements.txt")) ||
|
|
132
|
+
existsSync(join(cwd, "Pipfile")) ||
|
|
133
|
+
existsSync(join(cwd, "poetry.lock"))) {
|
|
134
|
+
return "unittest";
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function stripAnsi(str) {
|
|
139
|
+
// eslint-disable-next-line no-control-regex
|
|
140
|
+
return str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
141
|
+
}
|
|
142
|
+
export function parsePythonOutput(output, framework) {
|
|
143
|
+
const clean = stripAnsi(output);
|
|
144
|
+
if (framework === "pytest") {
|
|
145
|
+
// pytest summary line examples:
|
|
146
|
+
// "5 passed in 0.12s"
|
|
147
|
+
// "1 failed, 4 passed, 2 skipped in 0.34s"
|
|
148
|
+
// "===== 12 passed, 3 skipped, 1 deselected in 0.5s ====="
|
|
149
|
+
// Match each token independently against the summary section so
|
|
150
|
+
// the order doesn't matter.
|
|
151
|
+
const passedM = clean.match(/(\d+)\s+passed/);
|
|
152
|
+
const failedM = clean.match(/(\d+)\s+failed/);
|
|
153
|
+
const errorM = clean.match(/(\d+)\s+error(?:s|ed)?/);
|
|
154
|
+
const skippedM = clean.match(/(\d+)\s+skipped/);
|
|
155
|
+
return {
|
|
156
|
+
passed: passedM ? parseInt(passedM[1], 10) : 0,
|
|
157
|
+
failed: (failedM ? parseInt(failedM[1], 10) : 0) + (errorM ? parseInt(errorM[1], 10) : 0),
|
|
158
|
+
skipped: skippedM ? parseInt(skippedM[1], 10) : 0,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Django test runner and unittest discover both end with:
|
|
162
|
+
// "Ran N tests in 0.012s"
|
|
163
|
+
// "OK" (success)
|
|
164
|
+
// "OK (skipped=2)"
|
|
165
|
+
// "FAILED (failures=1, errors=2, skipped=3)"
|
|
166
|
+
const ranM = clean.match(/Ran\s+(\d+)\s+tests?/);
|
|
167
|
+
const ran = ranM ? parseInt(ranM[1], 10) : 0;
|
|
168
|
+
const failuresM = clean.match(/failures\s*=\s*(\d+)/);
|
|
169
|
+
const errorsM = clean.match(/errors\s*=\s*(\d+)/);
|
|
170
|
+
const skippedM = clean.match(/skipped\s*=\s*(\d+)/);
|
|
171
|
+
const failures = failuresM ? parseInt(failuresM[1], 10) : 0;
|
|
172
|
+
const errors = errorsM ? parseInt(errorsM[1], 10) : 0;
|
|
173
|
+
const skipped = skippedM ? parseInt(skippedM[1], 10) : 0;
|
|
174
|
+
// Whether the run ended with OK is the source of truth for pass/fail.
|
|
175
|
+
// If we matched neither failures= nor errors= and saw "OK", everything
|
|
176
|
+
// that ran (minus skipped) is a pass.
|
|
177
|
+
const failed = failures + errors;
|
|
178
|
+
const passed = Math.max(0, ran - failed - skipped);
|
|
179
|
+
return { passed, failed, skipped };
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=python_tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python_tests.js","sourceRoot":"","sources":["../../src/runners/python_tests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RunnerResult } from "./base.js";
|
|
2
|
+
/**
|
|
3
|
+
* 0.1.51 H5 — Rust verify runner.
|
|
4
|
+
*
|
|
5
|
+
* Detects via `Cargo.toml` at the project root and runs:
|
|
6
|
+
* cargo test --no-fail-fast
|
|
7
|
+
*
|
|
8
|
+
* `--no-fail-fast` mirrors the existing Flutter / generic runners in
|
|
9
|
+
* this package (collect every failure in one pass; the agent then
|
|
10
|
+
* has all the data it needs for `codeloop_diagnose`).
|
|
11
|
+
*
|
|
12
|
+
* Cross-platform: cargo is the same CLI on macOS / Linux / Windows.
|
|
13
|
+
* If cargo isn't on PATH, the runner returns a skipped result with a
|
|
14
|
+
* specific install hint instead of throwing.
|
|
15
|
+
*
|
|
16
|
+
* Tauri detection note: Tauri projects have BOTH a top-level
|
|
17
|
+
* `package.json` AND a `src-tauri/Cargo.toml`. We honour both — when
|
|
18
|
+
* the top-level Cargo.toml is missing but `src-tauri/Cargo.toml`
|
|
19
|
+
* exists, we run cargo test inside `src-tauri/` so the Rust backend
|
|
20
|
+
* tests are exercised on Tauri verify.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runRustTests(cwd: string, logPath: string): Promise<RunnerResult>;
|
|
23
|
+
export declare function parseCargoOutput(output: string): {
|
|
24
|
+
passed: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
skipped: number;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=rust_tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rust_tests.d.ts","sourceRoot":"","sources":["../../src/runners/rust_tests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAOD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GACb;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAgBrD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { runCommand, makeSkippedResult, checkToolAvailable } from "./base.js";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
/**
|
|
5
|
+
* 0.1.51 H5 — Rust verify runner.
|
|
6
|
+
*
|
|
7
|
+
* Detects via `Cargo.toml` at the project root and runs:
|
|
8
|
+
* cargo test --no-fail-fast
|
|
9
|
+
*
|
|
10
|
+
* `--no-fail-fast` mirrors the existing Flutter / generic runners in
|
|
11
|
+
* this package (collect every failure in one pass; the agent then
|
|
12
|
+
* has all the data it needs for `codeloop_diagnose`).
|
|
13
|
+
*
|
|
14
|
+
* Cross-platform: cargo is the same CLI on macOS / Linux / Windows.
|
|
15
|
+
* If cargo isn't on PATH, the runner returns a skipped result with a
|
|
16
|
+
* specific install hint instead of throwing.
|
|
17
|
+
*
|
|
18
|
+
* Tauri detection note: Tauri projects have BOTH a top-level
|
|
19
|
+
* `package.json` AND a `src-tauri/Cargo.toml`. We honour both — when
|
|
20
|
+
* the top-level Cargo.toml is missing but `src-tauri/Cargo.toml`
|
|
21
|
+
* exists, we run cargo test inside `src-tauri/` so the Rust backend
|
|
22
|
+
* tests are exercised on Tauri verify.
|
|
23
|
+
*/
|
|
24
|
+
export async function runRustTests(cwd, logPath) {
|
|
25
|
+
let cargoCwd = cwd;
|
|
26
|
+
if (!existsSync(join(cwd, "Cargo.toml"))) {
|
|
27
|
+
const tauriCargo = join(cwd, "src-tauri", "Cargo.toml");
|
|
28
|
+
if (existsSync(tauriCargo)) {
|
|
29
|
+
cargoCwd = join(cwd, "src-tauri");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return makeSkippedResult("rust_tests", "No Cargo.toml at the project root or src-tauri/");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!(await checkToolAvailable("cargo"))) {
|
|
36
|
+
return makeSkippedResult("rust_tests", "cargo not found on PATH. Install via https://rustup.rs (POSIX/macOS) or `winget install Rustlang.Rustup` (Windows), then re-run codeloop_verify.");
|
|
37
|
+
}
|
|
38
|
+
const start = Date.now();
|
|
39
|
+
const result = await runCommand("cargo", ["test", "--no-fail-fast"], cargoCwd, logPath);
|
|
40
|
+
const duration_ms = Date.now() - start;
|
|
41
|
+
const { passed, failed, skipped } = parseCargoOutput(result.stdout + "\n" + result.stderr);
|
|
42
|
+
return {
|
|
43
|
+
runner_name: "rust_tests",
|
|
44
|
+
available: true,
|
|
45
|
+
exit_code: result.exit_code,
|
|
46
|
+
passed,
|
|
47
|
+
failed,
|
|
48
|
+
skipped,
|
|
49
|
+
log_path: logPath,
|
|
50
|
+
duration_ms,
|
|
51
|
+
stdout: result.stdout,
|
|
52
|
+
stderr: result.stderr,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function stripAnsi(str) {
|
|
56
|
+
// eslint-disable-next-line no-control-regex
|
|
57
|
+
return str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
58
|
+
}
|
|
59
|
+
export function parseCargoOutput(output) {
|
|
60
|
+
const clean = stripAnsi(output);
|
|
61
|
+
// cargo test prints one summary line per test target:
|
|
62
|
+
// "test result: ok. 12 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.05s"
|
|
63
|
+
// "test result: FAILED. 8 passed; 4 failed; 0 ignored; 0 measured"
|
|
64
|
+
// A workspace with N crates emits N such lines, so sum across them.
|
|
65
|
+
const summaryRe = /test result:\s+(?:ok|FAILED)\.\s+(\d+)\s+passed;\s+(\d+)\s+failed;\s+(\d+)\s+ignored/g;
|
|
66
|
+
let passed = 0;
|
|
67
|
+
let failed = 0;
|
|
68
|
+
let skipped = 0;
|
|
69
|
+
for (const m of clean.matchAll(summaryRe)) {
|
|
70
|
+
passed += parseInt(m[1], 10);
|
|
71
|
+
failed += parseInt(m[2], 10);
|
|
72
|
+
skipped += parseInt(m[3], 10);
|
|
73
|
+
}
|
|
74
|
+
return { passed, failed, skipped };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=rust_tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rust_tests.js","sourceRoot":"","sources":["../../src/runners/rust_tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,OAAe;IAEf,IAAI,QAAQ,GAAG,GAAG,CAAC;IACnB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACxD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,iBAAiB,CACtB,YAAY,EACZ,iDAAiD,CAClD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACzC,OAAO,iBAAiB,CACtB,YAAY,EACZ,kJAAkJ,CACnJ,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,OAAO,EACP,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC1B,QAAQ,EACR,OAAO,CACR,CAAC;IACF,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IAEvC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAE3F,OAAO;QACL,WAAW,EAAE,YAAY;QACzB,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM;QACN,MAAM;QACN,OAAO;QACP,QAAQ,EAAE,OAAO;QACjB,WAAW;QACX,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,4CAA4C;IAC5C,OAAO,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAc;IAEd,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,sDAAsD;IACtD,qGAAqG;IACrG,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,SAAS,GAAG,uFAAuF,CAAC;IAC1G,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7B,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACrC,CAAC"}
|