codeloop-mcp-server 0.1.49 → 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 +8 -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 +377 -61
- 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/resolve_project_dir.d.ts +67 -0
- package/dist/runners/resolve_project_dir.d.ts.map +1 -0
- package/dist/runners/resolve_project_dir.js +82 -0
- package/dist/runners/resolve_project_dir.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/runners/screenshot.d.ts.map +1 -1
- package/dist/runners/screenshot.js +17 -2
- package/dist/runners/screenshot.js.map +1 -1
- package/dist/runners/uia_resolver.d.ts +70 -0
- package/dist/runners/uia_resolver.d.ts.map +1 -0
- package/dist/runners/uia_resolver.js +210 -0
- package/dist/runners/uia_resolver.js.map +1 -0
- package/dist/runners/window_manager.d.ts +28 -0
- package/dist/runners/window_manager.d.ts.map +1 -1
- package/dist/runners/window_manager.js +119 -4
- package/dist/runners/window_manager.js.map +1 -1
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +71 -33
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/diagnose.d.ts.map +1 -1
- package/dist/tools/diagnose.js +45 -1
- package/dist/tools/diagnose.js.map +1 -1
- package/dist/tools/discover_screens.d.ts.map +1 -1
- package/dist/tools/discover_screens.js +94 -2
- package/dist/tools/discover_screens.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,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.1.50 H4 — single source of truth for the `project_dir` precedence
|
|
3
|
+
* ladder used across every CodeLoop tool handler.
|
|
4
|
+
*
|
|
5
|
+
* Photometry-DB E2E logs 2 / 7 surfaced the recurrence path:
|
|
6
|
+
* codeloop_capture_screenshot was called WITHOUT project_dir on a
|
|
7
|
+
* Cursor-launched MCP server whose cwd was C:\Users\<user>. The
|
|
8
|
+
* handler defaulted to `params.project_dir || params.workspace_root || projectDir`,
|
|
9
|
+
* `projectDir` resolved to the home folder, and screenshots ended
|
|
10
|
+
* up under C:\Users\<user>\artifacts\... — invisible to every
|
|
11
|
+
* subsequent gate_check / design_compare run that looked under
|
|
12
|
+
* the actual workspace.
|
|
13
|
+
*
|
|
14
|
+
* The fix: every capture / interact / record / replay handler runs
|
|
15
|
+
* the same ladder:
|
|
16
|
+
* 1. Explicit `params.project_dir`
|
|
17
|
+
* 2. Explicit `params.workspace_root` (alias)
|
|
18
|
+
* 3. Active recording session's project_dir (when one is in flight)
|
|
19
|
+
* 4. `process.env.CODELOOP_PROJECT_DIR` (the workspace pin)
|
|
20
|
+
* 5. Walked-up `.codeloop/config.json` ancestor of `defaultDir`
|
|
21
|
+
* (catches the case where the agent's cwd is a sub-folder)
|
|
22
|
+
* 6. `defaultDir` (the discovery-time projectDir from index.ts)
|
|
23
|
+
*
|
|
24
|
+
* Step 5 is the new bit — it walks UP from the supplied default to
|
|
25
|
+
* find the nearest ancestor with `.codeloop/config.json`. That way,
|
|
26
|
+
* even if the MCP server boots with a stale projectDir (e.g. the
|
|
27
|
+
* pin became invalid because the workspace was renamed), as long
|
|
28
|
+
* as the cwd lives under a real project root we still land on it.
|
|
29
|
+
*
|
|
30
|
+
* Step 6 is the existing behaviour — preserved so callers that
|
|
31
|
+
* pre-flight their inputs aren't surprised.
|
|
32
|
+
*/
|
|
33
|
+
export interface ResolveProjectDirInput {
|
|
34
|
+
/** Explicit param from the tool call. */
|
|
35
|
+
project_dir?: string;
|
|
36
|
+
/** Alias for project_dir. */
|
|
37
|
+
workspace_root?: string;
|
|
38
|
+
/** Active recording project_dir, if known. */
|
|
39
|
+
recording_project_dir?: string;
|
|
40
|
+
/** The discovery-time projectDir to fall back to. */
|
|
41
|
+
default_dir: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Result of `resolveProjectDir` — includes the chosen path AND
|
|
45
|
+
* the source so callers can log it for diagnostic purposes.
|
|
46
|
+
*/
|
|
47
|
+
export interface ResolvedProjectDir {
|
|
48
|
+
path: string;
|
|
49
|
+
source: "explicit" | "workspace_root" | "recording" | "env" | "walked_up" | "default";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the effective project_dir for a tool call using the H4
|
|
53
|
+
* precedence ladder. ALWAYS returns a non-empty path.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveProjectDir(input: ResolveProjectDirInput): ResolvedProjectDir;
|
|
56
|
+
/**
|
|
57
|
+
* Cheaper variant that returns just the path. Use when you don't
|
|
58
|
+
* care about the source (most callers).
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveProjectDirPath(input: ResolveProjectDirInput): string;
|
|
61
|
+
/**
|
|
62
|
+
* Defensive guard — refuse paths that look like the user's home
|
|
63
|
+
* folder when we're about to scaffold side-effects (artifacts/,
|
|
64
|
+
* .codeloop/). Used by handlers that write to disk.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isLikelyHomeDir(p: string): boolean;
|
|
67
|
+
//# sourceMappingURL=resolve_project_dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve_project_dir.d.ts","sourceRoot":"","sources":["../../src/runners/resolve_project_dir.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,sBAAsB;IACrC,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,SAAS,CAAC;CACvF;AA4BD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,kBAAkB,CAwBnF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAMlD"}
|