Repository navigation
fix(OMN-9868): repair main red CI — Typecheck/Type-Sync/CodeQL #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| #!/usr/bin/env bash | ||
| # SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. | ||
| # SPDX-License-Identifier: MIT | ||
| # | ||
| # OMN-3258: TypeScript Type Sync Check (shell wrapper) | ||
| # | ||
| # Verifies that TypeScript boundary types in omnidash/shared/ stay in sync | ||
| # with the corresponding Python Pydantic models in omnibase_core and | ||
| # omniintelligence. | ||
| # | ||
| # Usage: | ||
| # scripts/check-type-sync.sh [--omnibase-core PATH] [--omniintelligence PATH] | ||
| # | ||
| # Environment variables (override paths): | ||
| # OMNIBASE_CORE_PATH — path to omnibase_core repo root | ||
| # OMNIINTELLIGENCE_PATH — path to omniintelligence repo root | ||
| # | ||
| # In CI this script is invoked after the Python repos are checked out as | ||
| # sibling directories alongside omnidash. | ||
| # | ||
| # Exit codes: | ||
| # 0 — all TypeScript boundary types are in sync with Python models | ||
| # 1 — one or more TypeScript types are missing Python model fields | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| OMNIDASH_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
|
|
||
| # Allow overriding via env vars or CLI args | ||
| OMNIBASE_CORE_ARG="" | ||
| OMNIINTELLIGENCE_ARG="" | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --omnibase-core) | ||
| OMNIBASE_CORE_ARG="--omnibase-core $2" | ||
| shift 2 | ||
| ;; | ||
| --omniintelligence) | ||
| OMNIINTELLIGENCE_ARG="--omniintelligence $2" | ||
| shift 2 | ||
| ;; | ||
| *) | ||
| echo "Unknown argument: $1" >&2 | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| # Override from env vars if not set via CLI args | ||
| if [[ -z "$OMNIBASE_CORE_ARG" && -n "${OMNIBASE_CORE_PATH:-}" ]]; then | ||
| OMNIBASE_CORE_ARG="--omnibase-core $OMNIBASE_CORE_PATH" | ||
| fi | ||
| if [[ -z "$OMNIINTELLIGENCE_ARG" && -n "${OMNIINTELLIGENCE_PATH:-}" ]]; then | ||
| OMNIINTELLIGENCE_ARG="--omniintelligence $OMNIINTELLIGENCE_PATH" | ||
| fi | ||
|
|
||
| echo "Running TypeScript type sync check..." | ||
| echo "" | ||
|
|
||
| # Run the Python check script. | ||
| # Uses python3 directly (stdlib ast only — no uv or additional packages needed). | ||
| # shellcheck disable=SC2086 | ||
| python3 "$OMNIDASH_ROOT/scripts/check_type_sync.py" \ | ||
| --omnidash "$OMNIDASH_ROOT" \ | ||
| $OMNIBASE_CORE_ARG \ | ||
| $OMNIINTELLIGENCE_ARG | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,283 @@ | ||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||
| # SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. | ||||||||||||||||||||||||||||||||
| # SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| OMN-3258: TypeScript Type Sync Check | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Verifies that TypeScript boundary types in omnidash stay in sync with the | ||||||||||||||||||||||||||||||||
| corresponding Python Pydantic models in omnibase_core / omniintelligence. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Strategy: | ||||||||||||||||||||||||||||||||
| 1. Parse target Python model files with `ast` (stdlib — no install needed) | ||||||||||||||||||||||||||||||||
| 2. Extract field names from Pydantic `Field(...)` or annotated assignments | ||||||||||||||||||||||||||||||||
| 3. For each model, verify that every Python field name appears in the | ||||||||||||||||||||||||||||||||
| corresponding TypeScript file | ||||||||||||||||||||||||||||||||
| 4. Exit 1 if any field is missing from the TypeScript type | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| This approach avoids `datamodel-codegen` (which generates incompatible output | ||||||||||||||||||||||||||||||||
| for Zod-based schemas) and works with stdlib only — no uv/pip required. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Usage: | ||||||||||||||||||||||||||||||||
| python3 scripts/check_type_sync.py [--omnibase-core PATH] [--omniintelligence PATH] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Defaults assume the sibling repos are checked out alongside omnidash: | ||||||||||||||||||||||||||||||||
| omnibase_core/ (or set --omnibase-core) | ||||||||||||||||||||||||||||||||
| omniintelligence/ (or set --omniintelligence) | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||
| import ast | ||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # Target model descriptors | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||
| # Each entry maps: | ||||||||||||||||||||||||||||||||
| # python_path — path relative to the repo root (passed via args) | ||||||||||||||||||||||||||||||||
| # ts_path — path relative to omnidash repo root | ||||||||||||||||||||||||||||||||
| # model_class — Pydantic class name to extract fields from | ||||||||||||||||||||||||||||||||
| # ts_name — human-readable name for error messages | ||||||||||||||||||||||||||||||||
| # exclude_fields — fields intentionally absent in TS (base-class internals, etc.) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Descriptor docs and runtime keys are out of sync. Lines 42-46 document Suggested doc fix # Each entry maps:
-# python_path — path relative to the repo root (passed via args)
+# python_repo — one of: "omnibase_core" | "omniintelligence"
+# python_rel — path relative to the selected python repo root
# ts_path — path relative to omnidash repo root
# model_class — Pydantic class name to extract fields from
-# ts_name — human-readable name for error messages
+# name — human-readable name for log output
# exclude_fields — fields intentionally absent in TS (base-class internals, etc.)Also applies to: 225-231 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # omnidash-v2 uses a snapshot-projection architecture (shared/types/) rather than | ||||||||||||||||||||||||||||||||
| # the read-model consumer architecture of omnidash v1 (shared/schemas/, shared/intent-types.ts). | ||||||||||||||||||||||||||||||||
| # Concrete TARGETS will be added here as v2 TS boundary types stabilise and are mapped to | ||||||||||||||||||||||||||||||||
| # their corresponding Python Pydantic models (tracked in OMN-9868 follow-up). | ||||||||||||||||||||||||||||||||
| TARGETS: list[dict] = [] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # Python model field extraction | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def extract_pydantic_fields(path: Path, class_name: str) -> set[str]: | ||||||||||||||||||||||||||||||||
| """Parse a Python file with `ast` and extract annotated field names | ||||||||||||||||||||||||||||||||
| from the named Pydantic class. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Handles both: | ||||||||||||||||||||||||||||||||
| - `field: Type = Field(...)` style | ||||||||||||||||||||||||||||||||
| - `field: Type` bare annotations (without a default) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Skips: | ||||||||||||||||||||||||||||||||
| - `model_config` (Pydantic v2 ConfigDict assignment) | ||||||||||||||||||||||||||||||||
| - ClassVar annotations | ||||||||||||||||||||||||||||||||
| - Private fields starting with `_` | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||
| Set of field name strings found in the class body. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| source = path.read_text(encoding="utf-8") | ||||||||||||||||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||||||||||||||||
| print(f" ERROR: Python model file not found: {path}", file=sys.stderr) | ||||||||||||||||||||||||||||||||
| return set() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| tree = ast.parse(source) | ||||||||||||||||||||||||||||||||
| except SyntaxError as exc: | ||||||||||||||||||||||||||||||||
| print(f" ERROR: Cannot parse {path}: {exc}", file=sys.stderr) | ||||||||||||||||||||||||||||||||
| return set() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| fields: set[str] = set() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for node in ast.walk(tree): | ||||||||||||||||||||||||||||||||
| if not isinstance(node, ast.ClassDef) or node.name != class_name: | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| for item in node.body: | ||||||||||||||||||||||||||||||||
| # Annotated assignment: `field: Type = ...` or `field: Type` | ||||||||||||||||||||||||||||||||
| if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): | ||||||||||||||||||||||||||||||||
| name = item.target.id | ||||||||||||||||||||||||||||||||
| if name.startswith("_"): | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| if name == "model_config": | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| # Skip ClassVar[...] annotations | ||||||||||||||||||||||||||||||||
| ann = item.annotation | ||||||||||||||||||||||||||||||||
| if isinstance(ann, ast.Subscript): | ||||||||||||||||||||||||||||||||
| ann_name = ( | ||||||||||||||||||||||||||||||||
| getattr(ann.value, "id", "") | ||||||||||||||||||||||||||||||||
| or getattr( | ||||||||||||||||||||||||||||||||
| getattr(ann.value, "attr", None), "__str__", lambda: "" | ||||||||||||||||||||||||||||||||
| )() | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| if ann_name == "ClassVar": | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| fields.add(name) | ||||||||||||||||||||||||||||||||
| # Only process the first matching class definition | ||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return fields | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # TypeScript field presence check | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def check_fields_in_ts(ts_path: Path, required_fields: set[str]) -> list[str]: | ||||||||||||||||||||||||||||||||
| """Verify that each required field name appears somewhere in the TS file. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Uses a simple substring/word-boundary search. A field `foo_bar` is | ||||||||||||||||||||||||||||||||
| considered present if the pattern `foo_bar` appears in the file as | ||||||||||||||||||||||||||||||||
| a standalone identifier (not as a substring of a longer word). | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||
| List of missing field names (empty = all present). | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if not ts_path.exists(): | ||||||||||||||||||||||||||||||||
| print(f" ERROR: TypeScript file not found: {ts_path}", file=sys.stderr) | ||||||||||||||||||||||||||||||||
| return list(required_fields) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| content = ts_path.read_text(encoding="utf-8") | ||||||||||||||||||||||||||||||||
| missing: list[str] = [] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for field in sorted(required_fields): | ||||||||||||||||||||||||||||||||
| # Match field as a standalone identifier (word boundary on both sides) | ||||||||||||||||||||||||||||||||
| pattern = rf"\b{re.escape(field)}\b" | ||||||||||||||||||||||||||||||||
| if not re.search(pattern, content): | ||||||||||||||||||||||||||||||||
| missing.append(field) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return missing | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # Main | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def parse_args() -> argparse.Namespace: | ||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser( | ||||||||||||||||||||||||||||||||
| description="Check TypeScript boundary types stay in sync with Python Pydantic models." | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||
| "--omnibase-core", | ||||||||||||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||||||||||||
| help=( | ||||||||||||||||||||||||||||||||
| "Path to omnibase_core repo root. " | ||||||||||||||||||||||||||||||||
| "Defaults to ../omnibase_core relative to omnidash root." | ||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||
| "--omniintelligence", | ||||||||||||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||||||||||||
| help=( | ||||||||||||||||||||||||||||||||
| "Path to omniintelligence repo root. " | ||||||||||||||||||||||||||||||||
| "Defaults to ../omniintelligence relative to omnidash root." | ||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||
| "--omnidash", | ||||||||||||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||||||||||||
| help=( | ||||||||||||||||||||||||||||||||
| "Path to omnidash repo root. " | ||||||||||||||||||||||||||||||||
| "Defaults to the directory containing this script's parent." | ||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| return parser.parse_args() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def resolve_repo_path( | ||||||||||||||||||||||||||||||||
| arg_value: str | None, default_relative: str, omnidash_root: Path | ||||||||||||||||||||||||||||||||
| ) -> Path: | ||||||||||||||||||||||||||||||||
| if arg_value: | ||||||||||||||||||||||||||||||||
| return Path(arg_value).resolve() | ||||||||||||||||||||||||||||||||
| # Default: sibling directory relative to omnidash root | ||||||||||||||||||||||||||||||||
| return (omnidash_root / ".." / default_relative).resolve() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def main() -> int: | ||||||||||||||||||||||||||||||||
| args = parse_args() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Determine omnidash root | ||||||||||||||||||||||||||||||||
| script_dir = Path(__file__).resolve().parent | ||||||||||||||||||||||||||||||||
| omnidash_root = ( | ||||||||||||||||||||||||||||||||
| Path(args.omnidash).resolve() if args.omnidash else script_dir.parent | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| repo_roots = { | ||||||||||||||||||||||||||||||||
| "omnibase_core": resolve_repo_path( | ||||||||||||||||||||||||||||||||
| args.omnibase_core, "omnibase_core", omnidash_root | ||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| "omniintelligence": resolve_repo_path( | ||||||||||||||||||||||||||||||||
| args.omniintelligence, "omniintelligence", omnidash_root | ||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print("TypeScript Type Sync Check (OMN-3258)") | ||||||||||||||||||||||||||||||||
| print("=" * 60) | ||||||||||||||||||||||||||||||||
| print(f"Omnidash root: {omnidash_root}") | ||||||||||||||||||||||||||||||||
| for name, path in repo_roots.items(): | ||||||||||||||||||||||||||||||||
| status = "OK" if path.exists() else "MISSING" | ||||||||||||||||||||||||||||||||
| print(f" {name:<20} {path} [{status}]") | ||||||||||||||||||||||||||||||||
| print() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| overall_pass = True | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for target in TARGETS: | ||||||||||||||||||||||||||||||||
| print(f"Checking: {target['name']}") | ||||||||||||||||||||||||||||||||
| print(f" Python: {target['python_rel']}") | ||||||||||||||||||||||||||||||||
| print(f" TS: {target['ts_path']}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| python_repo_root = repo_roots[target["python_repo"]] | ||||||||||||||||||||||||||||||||
| python_path = python_repo_root / target["python_rel"] | ||||||||||||||||||||||||||||||||
| ts_path = omnidash_root / target["ts_path"] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --- Step 1: Extract Python fields --- | ||||||||||||||||||||||||||||||||
| all_fields = extract_pydantic_fields(python_path, target["model_class"]) | ||||||||||||||||||||||||||||||||
| if not all_fields: | ||||||||||||||||||||||||||||||||
| print( | ||||||||||||||||||||||||||||||||
| f" WARN: No fields extracted from {target['model_class']} — " | ||||||||||||||||||||||||||||||||
| "skipping (file missing or class not found)" | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| print() | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+235
to
+242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not silently skip when model extraction returns no fields. On Line 235, treating extraction failure as a warning and continuing can produce false-green CI when Suggested fix- if not all_fields:
- print(
- f" WARN: No fields extracted from {target['model_class']} — "
- "skipping (file missing or class not found)"
- )
- print()
- continue
+ if not all_fields:
+ print(
+ f" FAIL: No fields extracted from {target['model_class']} "
+ "(file missing, parse error, or class not found)"
+ )
+ overall_pass = False
+ print()
+ continue📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| print(f" Python fields extracted: {sorted(all_fields)}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --- Step 2: Determine required fields --- | ||||||||||||||||||||||||||||||||
| # Use explicit required_fields list if provided; otherwise use all | ||||||||||||||||||||||||||||||||
| # Python fields minus excluded ones. | ||||||||||||||||||||||||||||||||
| if target.get("required_fields"): | ||||||||||||||||||||||||||||||||
| required = set(target["required_fields"]) | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| required = all_fields - target.get("exclude_fields", set()) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+248
to
+252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, check if the file exists and examine the relevant section
head -260 scripts/check_type_sync.py | tail -20Repository: OmniNode-ai/omnidash-v2 Length of output: 820 🏁 Script executed: # Get more context around line 248-252
sed -n '240,260p' scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 840 🏁 Script executed: # Search for where exclude_fields is used to understand its type
rg "exclude_fields" scripts/check_type_sync.py -B 2 -A 2Repository: OmniNode-ai/omnidash-v2 Length of output: 490 🏁 Script executed: # Search for where target is populated to understand the structure
rg "target\[.*exclude_fields" scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 49 🏁 Script executed: # Look for where target is constructed/populated with exclude_fields
rg "exclude_fields" scripts/check_type_sync.py -B 5 -A 5Repository: OmniNode-ai/omnidash-v2 Length of output: 1160 🏁 Script executed: # Look at the beginning of the function to understand target structure
sed -n '1,100p' scripts/check_type_sync.py | head -80Repository: OmniNode-ai/omnidash-v2 Length of output: 3000 🏁 Script executed: # Search for type hints or schema validation on target
rg "target" scripts/check_type_sync.py | head -20Repository: OmniNode-ai/omnidash-v2 Length of output: 942 🏁 Script executed: # Look for where target comes from - search for the main function/loop
sed -n '200,240p' scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 1496 🏁 Script executed: # Check if TARGETS is populated elsewhere or has any examples
rg "TARGETS" scripts/check_type_sync.py -B 2 -A 2Repository: OmniNode-ai/omnidash-v2 Length of output: 592 🏁 Script executed: # Look for any test files or configuration that might populate TARGETS
fd -type f -name "*.py" | xargs rg "exclude_fields" 2>/dev/null | head -20Repository: OmniNode-ai/omnidash-v2 Length of output: 466 🏁 Script executed: # Check the full TARGETS definition more carefully
sed -n '50,80p' scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 1236 🏁 Script executed: # Look for type hints on target dict structure
sed -n '1,250p' scripts/check_type_sync.py | grep -A 20 "^TARGETS"Repository: OmniNode-ai/omnidash-v2 Length of output: 722 🏁 Script executed: # Check if there's any TypedDict or type hint for target structure
rg "TypedDict|dict\[" scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 49 🏁 Script executed: # Look at how required_fields is handled to see if similar defensive coding is used
sed -n '245,265p' scripts/check_type_sync.pyRepository: OmniNode-ai/omnidash-v2 Length of output: 907 🏁 Script executed: # Test what happens when we subtract a list from a set in Python
python3 << 'EOF'
# Test the actual behavior
all_fields = {"a", "b", "c"}
# Current code behavior - with set default
try:
result = all_fields - {"b"}
print(f"Set subtraction works: {result}")
except TypeError as e:
print(f"Set subtraction failed: {e}")
# What happens if exclude_fields is a list
try:
result = all_fields - ["b"]
print(f"List subtraction works: {result}")
except TypeError as e:
print(f"List subtraction failed: {e}")
# What happens if exclude_fields is a tuple
try:
result = all_fields - ("b",)
print(f"Tuple subtraction works: {result}")
except TypeError as e:
print(f"Tuple subtraction failed: {e}")
# Proposed fix - convert to set first
try:
excluded = set(["b", "c"])
result = all_fields - excluded
print(f"Defensive set conversion works: {result}")
except TypeError as e:
print(f"Defensive conversion failed: {e}")
EOFRepository: OmniNode-ai/omnidash-v2 Length of output: 295 Coerce Line 251 will fail with Suggested fix if target.get("required_fields"):
required = set(target["required_fields"])
else:
- required = all_fields - target.get("exclude_fields", set())
+ excluded = set(target.get("exclude_fields", []))
+ required = all_fields - excluded🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| print(f" Required in TS ({len(required)}): {sorted(required)}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --- Step 3: Check TS file --- | ||||||||||||||||||||||||||||||||
| missing = check_fields_in_ts(ts_path, required) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if missing: | ||||||||||||||||||||||||||||||||
| print(f" FAIL: {len(missing)} field(s) missing from TypeScript type:") | ||||||||||||||||||||||||||||||||
| for field in missing: | ||||||||||||||||||||||||||||||||
| print(f" - {field}") | ||||||||||||||||||||||||||||||||
| overall_pass = False | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print(f" PASS: all {len(required)} required fields present in TypeScript") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print("=" * 60) | ||||||||||||||||||||||||||||||||
| if overall_pass: | ||||||||||||||||||||||||||||||||
| print("RESULT: PASS — all TypeScript boundary types are in sync") | ||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("RESULT: FAIL — TypeScript types are out of sync with Python models") | ||||||||||||||||||||||||||||||||
| print() | ||||||||||||||||||||||||||||||||
| print( | ||||||||||||||||||||||||||||||||
| "To fix: update the TypeScript types in shared/ to include the missing fields." | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| print("See OMN-3258 for the type sync policy.") | ||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||
| sys.exit(main()) | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: OmniNode-ai/omnidash-v2
Length of output: 2571
Use arrays for argument passing to avoid word-splitting and enable explicit validation.
The current string-based approach (
"--omnibase-core $2") combined with unquoted expansion at line 66-67 will fail with paths containing spaces or special characters. Additionally, the script lacks validation that the second argument exists before accessing$2(lines 36, 40, 52, 55).Converting to arrays with explicit argument validation is the correct approach, especially since SC2086 is already being suppressed (line 63), indicating the original implementation acknowledged this concern.
Suggested fix
🤖 Prompt for AI Agents