codeloop-mcp-server 0.1.56 → 0.1.60

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 (47) hide show
  1. package/dist/auth/critical_floors.d.ts.map +1 -1
  2. package/dist/auth/critical_floors.js +8 -0
  3. package/dist/auth/critical_floors.js.map +1 -1
  4. package/dist/evidence/backend_verification.d.ts +64 -0
  5. package/dist/evidence/backend_verification.d.ts.map +1 -0
  6. package/dist/evidence/backend_verification.js +94 -0
  7. package/dist/evidence/backend_verification.js.map +1 -0
  8. package/dist/evidence/cycle_issues.d.ts +8 -0
  9. package/dist/evidence/cycle_issues.d.ts.map +1 -1
  10. package/dist/evidence/cycle_issues.js +7 -2
  11. package/dist/evidence/cycle_issues.js.map +1 -1
  12. package/dist/evidence/deep_internal.d.ts +45 -0
  13. package/dist/evidence/deep_internal.d.ts.map +1 -0
  14. package/dist/evidence/deep_internal.js +99 -0
  15. package/dist/evidence/deep_internal.js.map +1 -0
  16. package/dist/evidence/runtime_log_scan.d.ts +43 -0
  17. package/dist/evidence/runtime_log_scan.d.ts.map +1 -0
  18. package/dist/evidence/runtime_log_scan.js +112 -0
  19. package/dist/evidence/runtime_log_scan.js.map +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +243 -17
  22. package/dist/index.js.map +1 -1
  23. package/dist/runners/backend_runtime.d.ts +40 -0
  24. package/dist/runners/backend_runtime.d.ts.map +1 -0
  25. package/dist/runners/backend_runtime.js +380 -0
  26. package/dist/runners/backend_runtime.js.map +1 -0
  27. package/dist/runners/coverage.d.ts +66 -0
  28. package/dist/runners/coverage.d.ts.map +1 -0
  29. package/dist/runners/coverage.js +380 -0
  30. package/dist/runners/coverage.js.map +1 -0
  31. package/dist/runners/modal_detector.d.ts.map +1 -1
  32. package/dist/runners/modal_detector.js +38 -7
  33. package/dist/runners/modal_detector.js.map +1 -1
  34. package/dist/runners/static_analysis.d.ts +72 -0
  35. package/dist/runners/static_analysis.d.ts.map +1 -0
  36. package/dist/runners/static_analysis.js +248 -0
  37. package/dist/runners/static_analysis.js.map +1 -0
  38. package/dist/tools/diagnose.d.ts.map +1 -1
  39. package/dist/tools/diagnose.js +143 -0
  40. package/dist/tools/diagnose.js.map +1 -1
  41. package/dist/tools/gate_check.d.ts.map +1 -1
  42. package/dist/tools/gate_check.js +116 -0
  43. package/dist/tools/gate_check.js.map +1 -1
  44. package/dist/tools/verify.d.ts.map +1 -1
  45. package/dist/tools/verify.js +83 -0
  46. package/dist/tools/verify.js.map +1 -1
  47. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -174,6 +174,35 @@ startUpdateCheck();
174
174
  * issues we've already written to disk continue to gate ready_for_review.
175
175
  */
176
176
  const modalPersistenceTracker = new Map();
177
+ /**
178
+ * 0.1.57 H2 — Throttle for the post-interact modal probe.
179
+ *
180
+ * 0.1.55 only ran the probe after SUCCESSFUL clicky/escape actions, so a
181
+ * file dialog that blocked the app (subsequent clicks fail, or the agent
182
+ * switches to wait / capture_screenshot / win_ui_automate) was never
183
+ * re-detected and the persistence counter never climbed — the modal sat
184
+ * open with zero cycle issues recorded (E2E #12). We now probe on the
185
+ * broader modal-related action set REGARDLESS of success, and use this map
186
+ * to keep the PowerShell cost bounded: clicky/escape always probe; other
187
+ * modal-related actions probe at most once per throttle window per app.
188
+ */
189
+ const modalProbeLastAt = new Map();
190
+ const MODAL_PROBE_THROTTLE_MS = 1500;
191
+ /**
192
+ * 0.1.57 H3 — auto-close stuck external file dialogs.
193
+ *
194
+ * Default ON. The user can opt out per-machine with
195
+ * CODELOOP_AUTO_CLOSE_STUCK_MODALS=0 (or "false"/"off") if a workflow
196
+ * legitimately keeps a file picker open across many interactions.
197
+ */
198
+ function autoCloseStuckModalsEnabled() {
199
+ const v = (process.env.CODELOOP_AUTO_CLOSE_STUCK_MODALS ?? "").trim().toLowerCase();
200
+ if (v === "0" || v === "false" || v === "off" || v === "no")
201
+ return false;
202
+ return true;
203
+ }
204
+ /** Consecutive same-dialog detections that mark a file dialog as genuinely stuck. */
205
+ const MODAL_AUTOCLOSE_THRESHOLD = 3;
177
206
  const server = new McpServer({
178
207
  name: "codeloop",
179
208
  version: "0.1.14",
@@ -1924,6 +1953,59 @@ After stopping, call codeloop_interaction_replay with the run_id to extract fram
1924
1953
  The response includes log_path if app logs were captured during the recording session.`, {
1925
1954
  recording_id: z.string().describe("The recording_id returned by codeloop_start_recording"),
1926
1955
  }, async (params) => {
1956
+ // 0.1.57 H4 — stuck-modal sweep. If a file dialog is still open when the
1957
+ // recording stops (E2E #12: the external folder picker that "kept opening
1958
+ // all the time"), close it automatically so it does not linger on the
1959
+ // user's desktop after the cycle. Best-effort and Windows-only; never
1960
+ // blocks the stop. Runs BEFORE finalising so the close is visible in the
1961
+ // final frames.
1962
+ let sweepNote;
1963
+ if (process.platform === "win32" && autoCloseStuckModalsEnabled()) {
1964
+ try {
1965
+ const { detectModal } = await import("./runners/modal_detector.js");
1966
+ const { closeModalWithStrategies } = await import("./runners/modal_close_strategies.js");
1967
+ const vrMod = await import("./runners/video_recorder.js");
1968
+ const appNameForModal = vrMod.getActiveRecordingAppName() || undefined;
1969
+ const detection = await detectModal({
1970
+ target_type: "desktop",
1971
+ app_name: appNameForModal,
1972
+ cwd: projectDir,
1973
+ });
1974
+ if (detection.is_modal_present && detection.modal_kind === "file_dialog") {
1975
+ const closeResult = await closeModalWithStrategies({
1976
+ initial_detection: detection,
1977
+ app_name: appNameForModal,
1978
+ cwd: projectDir,
1979
+ });
1980
+ const { recordCycleIssue } = await import("./evidence/cycle_issues.js");
1981
+ if (closeResult.closed) {
1982
+ const usedStrategy = closeResult.strategies_tried.find((s) => s.success)?.strategy ?? "ladder";
1983
+ sweepNote = `CodeLoop auto-closed a lingering file dialog (${detection.modal_description ?? "(unnamed)"}) at stop via ${usedStrategy}.`;
1984
+ await recordCycleIssue(projectDir, {
1985
+ kind: "modal_close_failed",
1986
+ modal_kind: detection.modal_kind ?? "file_dialog",
1987
+ modal_description: `${detection.modal_description ?? "(unnamed)"} (auto-closed by CodeLoop H4 stop sweep via ${usedStrategy})`,
1988
+ strategies_tried: closeResult.strategies_tried.filter((s) => s.success).map((s) => s.strategy),
1989
+ hwnd: detection.hwnd,
1990
+ auto_resolved: true,
1991
+ });
1992
+ }
1993
+ else {
1994
+ sweepNote = `A file dialog (${detection.modal_description ?? "(unnamed)"}) was still open at stop and the close ladder failed — call codeloop_kill_modal_window with hwnd ${detection.hwnd ?? "(see logs)"}.`;
1995
+ await recordCycleIssue(projectDir, {
1996
+ kind: "modal_close_failed",
1997
+ modal_kind: detection.modal_kind ?? "file_dialog",
1998
+ modal_description: detection.modal_description,
1999
+ strategies_tried: closeResult.strategies_tried.map((s) => s.strategy),
2000
+ hwnd: detection.hwnd ?? closeResult.hwnd,
2001
+ });
2002
+ }
2003
+ }
2004
+ }
2005
+ catch {
2006
+ /* best-effort sweep — never block the stop */
2007
+ }
2008
+ }
1927
2009
  const authResult = await withAuth(async () => {
1928
2010
  const { stopBackgroundRecording } = await import("./runners/video_recorder.js");
1929
2011
  return stopBackgroundRecording(params.recording_id);
@@ -1945,6 +2027,7 @@ The response includes log_path if app logs were captured during the recording se
1945
2027
  " 1. codeloop_interaction_replay — extract frames + app logs from the just-saved video. This populates the data the replay/journey gates score against.",
1946
2028
  " 2. codeloop_gate_check — confirm confidence ≥ 94%. If continue_fixing, fix the failing gate's next_step and re-record / re-capture.",
1947
2029
  "Do NOT skip step 1 — without replay frames the interaction_replay_evidence gate fails even when the video exists. Do NOT pause to ask the user 'should I run replay now?' — yes, always.",
2030
+ ...(sweepNote ? ["", `[CodeLoop H4] ${sweepNote}`] : []),
1948
2031
  ].join("\n");
1949
2032
  return {
1950
2033
  content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) + nextStepDirective }]),
@@ -2166,6 +2249,36 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
2166
2249
  .slice(0, 5)
2167
2250
  .map((i) => `(${i.kind}) ${summariseCycleIssue(i)}`)
2168
2251
  .join(" | ")}${ci.issues.length > 5 ? " | … (see logs/cycle_issues.jsonl for the full list)" : ""}`;
2252
+ // 0.1.58-0.1.60 — Deep Internal Verification aggregate. Pull the most
2253
+ // recent run that produced each evidence file so the dev report can
2254
+ // document static analysis, changed-line coverage, backend smoke,
2255
+ // API contract, DB schema, and runtime-log cleanliness.
2256
+ const { loadCodeQualityEvidence } = await import("./evidence/deep_internal.js");
2257
+ const { loadBackendVerification } = await import("./evidence/backend_verification.js");
2258
+ const { loadRuntimeLogScan } = await import("./evidence/runtime_log_scan.js");
2259
+ const deepInternal = { static_analysis: null, coverage: null, backend: null, runtime_log: null };
2260
+ for (const runId of [...runs].reverse()) {
2261
+ const runDir = getRunDir(runId, baseDir);
2262
+ if (deepInternal.static_analysis === null || deepInternal.coverage === null) {
2263
+ const cq = loadCodeQualityEvidence(runDir);
2264
+ if (cq) {
2265
+ if (deepInternal.static_analysis === null)
2266
+ deepInternal.static_analysis = cq.static_analysis;
2267
+ if (deepInternal.coverage === null)
2268
+ deepInternal.coverage = cq.coverage;
2269
+ }
2270
+ }
2271
+ if (deepInternal.backend === null) {
2272
+ const b = loadBackendVerification(runDir);
2273
+ if (b)
2274
+ deepInternal.backend = b;
2275
+ }
2276
+ if (deepInternal.runtime_log === null) {
2277
+ const s = loadRuntimeLogScan(runDir);
2278
+ if (s)
2279
+ deepInternal.runtime_log = s;
2280
+ }
2281
+ }
2169
2282
  const report = {
2170
2283
  project_name: params.project_name,
2171
2284
  project_description: params.project_description || "",
@@ -2190,6 +2303,7 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
2190
2303
  cycle_issues_summary: cycleSummary,
2191
2304
  cycle_issues: cycleIssuesEntries,
2192
2305
  cycle_issues_directive: cycleIssuesDirective,
2306
+ deep_internal_verification: deepInternal,
2193
2307
  };
2194
2308
  await trackUsage(apiKey, "verification_run");
2195
2309
  return report;
@@ -2255,6 +2369,19 @@ For EACH video recording session, document:
2255
2369
  Create a table with: | # | Bug Description | Severity | How Found | Fix Applied |
2256
2370
  List every issue discovered by CodeLoop during the development process.
2257
2371
 
2372
+ **7b. Deep Internal Verification** (0.1.58+)
2373
+ Document the internal (code / backend / terminal) verification evidence from the
2374
+ \`deep_internal_verification\` block above. Include a table:
2375
+ | Layer | Check | Result | Gate |
2376
+ |-------|-------|--------|------|
2377
+ | Static analysis | tsc / eslint / ruff / mypy / clippy / dotnet-format / flutter analyze | errors found / clean / n/a | static_analysis_passes |
2378
+ | Coverage | changed-line coverage % (covered/total) | pass / below threshold / n/a | code_coverage_threshold |
2379
+ | Backend smoke | health + route probes, 5xx count | healthy / failed / n/a | backend_smoke_evidence |
2380
+ | API contract | OpenAPI route conformance (violations) | pass / violations / n/a | api_contract_evidence |
2381
+ | DB schema | migration apply + drift | clean / drift / n/a | db_schema_evidence |
2382
+ | Runtime logs | exceptions / 5xx / fatal lines in logs | clean / errors / n/a | runtime_log_clean_evidence |
2383
+ State clearly which checks were applicable for this project's stack and which were n/a.
2384
+
2258
2385
  **8. Cross-Platform Coverage**
2259
2386
  Document which OS and platform combinations CodeLoop supports:
2260
2387
  | OS | App Type | Video Method | Interaction Method | Log Capture |
@@ -3538,15 +3665,40 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
3538
3665
  const isClosingIntent = /\b(close|dismiss|cancel|escape|exit)\b/.test(closingIntent);
3539
3666
  const isClickyAction = action === "click" || action === "double_click" || action === "right_click";
3540
3667
  const isEscapeKeystroke = action === "keystroke" && (params.key ?? "").toLowerCase() === "escape";
3541
- const shouldVerifyClickEffect = success && tt === "desktop" && (isClickyAction || isEscapeKeystroke);
3542
- if (shouldVerifyClickEffect) {
3668
+ // 0.1.57 H2 probe on the broader modal-related action set REGARDLESS
3669
+ // of success. A blocked / failed click against a stuck file dialog is
3670
+ // itself the signal we need (E2E #12: subsequent clicks failed, so the
3671
+ // 0.1.55 `success &&` gate never re-ran the probe and the picker stayed
3672
+ // open with zero cycle issues). Clicky/escape always probe; the wider
3673
+ // set (type/hotkey/win_ui_automate/sequence/wait) probes at most once
3674
+ // per throttle window so the PowerShell cost stays bounded.
3675
+ const trackerKey = (params.app_name || vr.getActiveRecordingAppName() || "<default>").toLowerCase();
3676
+ const clicky = isClickyAction || isEscapeKeystroke;
3677
+ const widerModalActions = new Set([
3678
+ "type",
3679
+ "type_and_submit",
3680
+ "type_and_tab",
3681
+ "keystroke",
3682
+ "hotkey",
3683
+ "win_ui_automate",
3684
+ "sequence",
3685
+ "wait",
3686
+ "hover",
3687
+ ]);
3688
+ const nowMs = Date.now();
3689
+ const lastProbeMs = modalProbeLastAt.get(trackerKey) ?? 0;
3690
+ const throttleOk = clicky || (widerModalActions.has(action) && nowMs - lastProbeMs > MODAL_PROBE_THROTTLE_MS);
3691
+ const shouldCheckModal = tt === "desktop" && throttleOk;
3692
+ if (shouldCheckModal) {
3543
3693
  try {
3694
+ modalProbeLastAt.set(trackerKey, nowMs);
3544
3695
  await new Promise((resolve) => setTimeout(resolve, 500));
3545
3696
  const { detectModal } = await import("./runners/modal_detector.js");
3546
- const trackerKey = (params.app_name || vr.getActiveRecordingAppName() || "<default>").toLowerCase();
3697
+ const { closeModalWithStrategies } = await import("./runners/modal_close_strategies.js");
3698
+ const appNameForModal = params.app_name || vr.getActiveRecordingAppName() || undefined;
3547
3699
  const detection = await detectModal({
3548
3700
  target_type: "desktop",
3549
- app_name: params.app_name || vr.getActiveRecordingAppName() || undefined,
3701
+ app_name: appNameForModal,
3550
3702
  cwd,
3551
3703
  });
3552
3704
  const { recordCycleIssue } = await import("./evidence/cycle_issues.js");
@@ -3605,11 +3757,79 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
3605
3757
  modal_kind: detection.modal_kind,
3606
3758
  });
3607
3759
  }
3608
- // 0.1.55 F3at 3+ consecutive persistences, treat the modal
3609
- // as actively stuck and write a modal_close_failed entry so
3610
- // the cycle_issues_acknowledged gate fails even when the
3611
- // agent never called codeloop_handle_modal.
3612
- if (consecutive >= 3) {
3760
+ // 0.1.57 H3AUTOMATIC close of stuck external file dialogs.
3761
+ //
3762
+ // E2E #12: the agent rationalised a recurring OpenFolderDialog as
3763
+ // a "test artifact" and NEVER called codeloop_handle_modal, so the
3764
+ // picker was left open and kept reopening. CodeLoop must close it
3765
+ // itself rather than only directing the agent.
3766
+ //
3767
+ // Trigger conditions (all required) keep this safe against legit
3768
+ // picker usage:
3769
+ // - it's a file_dialog (external OS picker — the exact class the
3770
+ // user reports; confirm/alert kinds may need a real decision
3771
+ // and are intentionally left to the agent),
3772
+ // - it has persisted across >= MODAL_AUTOCLOSE_THRESHOLD (3)
3773
+ // consecutive detections of the SAME dialog. A legitimate
3774
+ // open→confirm picker flow clears within 1-2 interactions and
3775
+ // resets the tracker, so reaching 3 means the agent is stuck.
3776
+ const isStuckFileDialog = detection.modal_kind === "file_dialog" &&
3777
+ consecutive >= MODAL_AUTOCLOSE_THRESHOLD;
3778
+ let autoClosed = false;
3779
+ if (isStuckFileDialog && autoCloseStuckModalsEnabled()) {
3780
+ try {
3781
+ const closeResult = await closeModalWithStrategies({
3782
+ initial_detection: detection,
3783
+ app_name: appNameForModal,
3784
+ cwd,
3785
+ });
3786
+ autoClosed = closeResult.closed;
3787
+ if (autoClosed) {
3788
+ modalPersistenceTracker.delete(trackerKey);
3789
+ const usedStrategy = closeResult.strategies_tried.find((s) => s.success)?.strategy ?? "ladder";
3790
+ detail = `${detail} | CodeLoop auto-closed stuck file dialog (${desc}) via ${usedStrategy}`;
3791
+ clickEffectVerification = {
3792
+ ...(clickEffectVerification ?? { intent: closingIntent, modal_still_present: false }),
3793
+ modal_still_present: false,
3794
+ modal_description: detection.modal_description,
3795
+ modal_kind: detection.modal_kind,
3796
+ consecutive_persistences: consecutive,
3797
+ };
3798
+ // Surface the auto-handling so the dev report shows it was
3799
+ // caught and resolved (not silently swallowed).
3800
+ await recordCycleIssue(cwd, {
3801
+ kind: "modal_close_failed",
3802
+ modal_kind: detection.modal_kind ?? "file_dialog",
3803
+ modal_description: `${desc} (auto-closed by CodeLoop H3 after ${consecutive} consecutive detections via ${usedStrategy})`,
3804
+ strategies_tried: closeResult.strategies_tried
3805
+ .filter((s) => s.success)
3806
+ .map((s) => s.strategy),
3807
+ hwnd: detection.hwnd,
3808
+ auto_resolved: true,
3809
+ });
3810
+ }
3811
+ else {
3812
+ // Ladder exhausted — record the unresolved failure and
3813
+ // escalate the directive to kill-window.
3814
+ await recordCycleIssue(cwd, {
3815
+ kind: "modal_close_failed",
3816
+ modal_kind: detection.modal_kind ?? "file_dialog",
3817
+ modal_description: desc,
3818
+ strategies_tried: closeResult.strategies_tried.map((s) => s.strategy),
3819
+ hwnd: detection.hwnd ?? closeResult.hwnd,
3820
+ });
3821
+ }
3822
+ }
3823
+ catch {
3824
+ /* best-effort auto-close; fall through to the F3/F4 path */
3825
+ }
3826
+ }
3827
+ // 0.1.55 F3 — at 3+ consecutive persistences, treat the modal as
3828
+ // actively stuck and write a modal_close_failed entry so the
3829
+ // cycle_issues_acknowledged gate fails even when the agent never
3830
+ // called codeloop_handle_modal. Skipped when H3 already recorded
3831
+ // the (auto-resolved or unresolved) outcome above.
3832
+ if (consecutive >= 3 && !isStuckFileDialog) {
3613
3833
  await recordCycleIssue(cwd, {
3614
3834
  kind: "modal_close_failed",
3615
3835
  modal_kind: detection.modal_kind ?? "custom",
@@ -3620,14 +3840,20 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
3620
3840
  hwnd: detection.hwnd,
3621
3841
  });
3622
3842
  }
3623
- // 0.1.55 F4 — HARD directive for the post-interact postscript.
3624
- modalPersistenceDirective =
3625
- `\n\n[CodeLoop F4] HARD: A ${detection.modal_kind ?? "modal"} dialog (${desc}) is STILL present after this interaction ` +
3626
- `(${consecutive} consecutive interactions have not cleared it). ` +
3627
- `Stop sending raw clicks / Escape keystrokes against it and call codeloop_handle_modal with decision: "cancel" or "dismiss" — ` +
3628
- `the multi-strategy ladder (Escape → Alt+F4 → UIA Invoke "Close" → EndDialog) handles file dialogs the keystroke path cannot. ` +
3629
- `If codeloop_handle_modal returns escalation: "kill_window_required", call codeloop_kill_modal_window with the returned hwnd. ` +
3630
- `Continuing to ignore this modal will fail the cycle_issues_acknowledged gate and block ready_for_review.`;
3843
+ // 0.1.55 F4 / 0.1.57 H3 — HARD directive for the post-interact
3844
+ // postscript. Suppressed when CodeLoop already auto-closed the
3845
+ // dialog; otherwise escalated to kill-window when an auto-close
3846
+ // attempt was made and failed.
3847
+ if (!autoClosed) {
3848
+ const killHint = isStuckFileDialog
3849
+ ? `CodeLoop already attempted the multi-strategy close ladder and it FAILED — call codeloop_kill_modal_window with hwnd ${detection.hwnd ?? "(from codeloop_handle_modal)"} now. `
3850
+ : `Stop sending raw clicks / Escape keystrokes against it and call codeloop_handle_modal with decision: "cancel" or "dismiss" — the multi-strategy ladder (Escape Alt+F4 → UIA Invoke "Close" → EndDialog) handles file dialogs the keystroke path cannot. If codeloop_handle_modal returns escalation: "kill_window_required", call codeloop_kill_modal_window with the returned hwnd. `;
3851
+ modalPersistenceDirective =
3852
+ `\n\n[CodeLoop F4] HARD: A ${detection.modal_kind ?? "modal"} dialog (${desc}) is STILL present after this interaction ` +
3853
+ `(${consecutive} consecutive interactions have not cleared it). ` +
3854
+ killHint +
3855
+ `Continuing to ignore this modal will fail the cycle_issues_acknowledged gate and block ready_for_review.`;
3856
+ }
3631
3857
  }
3632
3858
  else {
3633
3859
  // Modal cleared — reset the tracker for this app so the next