diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5cf093..b040985 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -135,11 +135,10 @@ jobs: # ── Typecheck: mypy ─────────────────────────────────────────────────────── # Codebase already has type hints across most of the surface (~70+ typed - # functions). Mypy runs in lenient mode (--ignore-missing-imports for - # untyped third-party deps; no strict-optional) so the gate isn't a wall - # of false positives. The transitional `continue-on-error: true` was - # removed in #29 once mypy reached zero errors on this repo — type - # failures now block merges. + # functions). Mypy runs with --ignore-missing-imports for untyped + # third-party deps; strict-optional is enabled (mypy default). The + # transitional `continue-on-error: true` was removed in #29 once mypy + # reached zero errors on this repo — type failures now block merges. typecheck: name: Typecheck (mypy) runs-on: ubuntu-latest @@ -162,7 +161,7 @@ jobs: - name: Run mypy # No `continue-on-error` — mypy now exits zero on this repo (closes #29), # so type errors must fail the job from here on. - run: mypy --ignore-missing-imports --no-strict-optional --pretty . + run: mypy --ignore-missing-imports --pretty . # ── Secret scan: gitleaks ───────────────────────────────────────────────── # Catches accidentally committed credentials. Runs over full git history diff --git a/pyproject.toml b/pyproject.toml index cfe50b4..2c4226b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,6 @@ include = [ # and CI produce identical results. [tool.mypy] ignore_missing_imports = true -no_strict_optional = true pretty = true # Exclude virtual-env and build artefact directories so that `mypy .` from the # repo root matches CI behaviour (CI runs in a clean runner without a local venv). diff --git a/services/workspace_listing.py b/services/workspace_listing.py index dafb9e0..e3235c1 100644 --- a/services/workspace_listing.py +++ b/services/workspace_listing.py @@ -115,9 +115,11 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list: headers = cd.get("fullConversationHeadersOnly") or [] has_bubbles = any( - bubble_map.get(h.get("bubbleId")) + bubble_map.get(bubble_id) for h in headers if isinstance(h, dict) + for bubble_id in [h.get("bubbleId")] + if isinstance(bubble_id, str) ) if not has_bubbles: continue diff --git a/services/workspace_resolver.py b/services/workspace_resolver.py index cf34be1..e76a8c0 100644 --- a/services/workspace_resolver.py +++ b/services/workspace_resolver.py @@ -89,12 +89,10 @@ def _infer_workspace_name_from_context(workspace_path: str, workspace_id: str) - except sqlite3.Error: continue for row in rows: + if not row or row[0] is None: + continue try: - # sqlite3.Row supports string-key access at runtime when - # row_factory = sqlite3.Row is set on the connection (see - # _open_global_db); mypy's stub types Row as tuple[Any, ...] - # which only accepts SupportsIndex, hence the ignore. - ctx = json.loads(row["value"]) # type: ignore[call-overload] + ctx = json.loads(row[0]) except Exception: continue layouts = ctx.get("projectLayouts") diff --git a/services/workspace_tabs.py b/services/workspace_tabs.py index 36b7143..05d8dd6 100644 --- a/services/workspace_tabs.py +++ b/services/workspace_tabs.py @@ -260,6 +260,8 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list: if not isinstance(header, dict): continue bubble_id = header.get("bubbleId") + if not isinstance(bubble_id, str): + continue bubble = bubble_map.get(bubble_id) if not bubble: continue @@ -526,10 +528,11 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list: if model_name_from_config and model_name_from_config != "default": if not tab_meta: tab_meta = {} - if not tab_meta.get("modelsUsed"): + models_used = tab_meta.get("modelsUsed") + if not isinstance(models_used, list): tab_meta["modelsUsed"] = [model_name_from_config] - elif model_name_from_config not in tab_meta["modelsUsed"]: - tab_meta["modelsUsed"].insert(0, model_name_from_config) + elif model_name_from_config not in models_used: + models_used.insert(0, model_name_from_config) tab = { "id": composer_id, diff --git a/tests/test_workspace_tabs_malformed_nested.py b/tests/test_workspace_tabs_malformed_nested.py index 74c2d9a..f811949 100644 --- a/tests/test_workspace_tabs_malformed_nested.py +++ b/tests/test_workspace_tabs_malformed_nested.py @@ -131,6 +131,7 @@ def test_diffs_appear_in_code_block_diffs_field(self) -> None: tab = next((t for t in payload["tabs"] if t["id"] == "cmp-d"), None) self.assertIsNotNone(tab) + assert tab is not None self.assertTrue(tab["codeBlockDiffs"], "expected diffs on tab.codeBlockDiffs") def test_diffs_do_not_appear_as_synthetic_bubbles(self) -> None: