Skip to content

Commit aa7dcbf

Browse files
conorluddyclaude
andcommitted
Add --raw flag for full model source dump
Progressive disclosure: summary first, then drill down with --raw ModelName to get the complete Swift source or Core Data XML for a specific model. Useful when the regex extraction isn't sufficient and the agent needs the unprocessed source. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cbfff2e commit aa7dcbf

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

ios-simulator-skill/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ Screenshots cost 1,600–6,300 tokens depending on size. The accessibility tree
128128
- Parse .xcdatamodeld packages (entities, attributes, relationships)
129129
- Detect model versions and current active version
130130
- Best-effort SwiftData @Model class extraction
131-
- Options: `--project-path`, `--core-data-only`, `--swiftdata-only`, `--show-versions`, `--verbose`, `--json`
131+
- Raw source dump for any model on demand (`--raw ModelName`)
132+
- Options: `--project-path`, `--core-data-only`, `--swiftdata-only`, `--show-versions`, `--raw`, `--verbose`, `--json`
132133

133134
### Advanced Testing & Permissions (4 scripts)
134135

ios-simulator-skill/scripts/model_inspector.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,83 @@ def execute(
6767
has_results = bool(results["core_data"]) or bool(results["swiftdata"])
6868
return has_results, results
6969

70+
# === RAW SOURCE EXTRACTION ===
71+
72+
def get_raw_source(self, model_name: str) -> tuple[bool, str]:
73+
"""Get raw source for a named model.
74+
75+
Searches SwiftData @Model classes first, then Core Data XML entities.
76+
77+
Args:
78+
model_name: Class or entity name to look up
79+
80+
Returns:
81+
(success, raw_source) tuple
82+
"""
83+
# Search SwiftData files
84+
swift_files = sorted(self.project_path.rglob("*.swift"))
85+
skip_dirs = {"DerivedData", "Pods", "Carthage"}
86+
87+
model_pattern = re.compile(
88+
r"@Model\s*\n\s*(?:final\s+)?class\s+(\w+)",
89+
re.MULTILINE,
90+
)
91+
92+
for swift_file in swift_files:
93+
if any(
94+
part in skip_dirs or part.startswith(".")
95+
for part in swift_file.relative_to(self.project_path).parts
96+
):
97+
continue
98+
99+
try:
100+
content = swift_file.read_text(encoding="utf-8")
101+
except (OSError, UnicodeDecodeError):
102+
continue
103+
104+
for match in model_pattern.finditer(content):
105+
if match.group(1) == model_name:
106+
class_start = match.start()
107+
body = self._extract_class_body(content, match.end())
108+
if body is None:
109+
continue
110+
# Find closing brace position
111+
brace_start = content.find("{", match.end())
112+
depth = 0
113+
end = brace_start
114+
for i in range(brace_start, len(content)):
115+
if content[i] == "{":
116+
depth += 1
117+
elif content[i] == "}":
118+
depth -= 1
119+
if depth == 0:
120+
end = i + 1
121+
break
122+
rel_path = swift_file.relative_to(self.project_path)
123+
raw = content[class_start:end]
124+
return True, f"// {rel_path}\n{raw}"
125+
126+
# Search Core Data XML
127+
for package in self._find_xcdatamodeld():
128+
current_version = self._detect_current_version(package)
129+
versions = [d for d in package.iterdir() if d.suffix == ".xcdatamodel"]
130+
target = current_version or (versions[0].name if versions else None)
131+
if not target:
132+
continue
133+
contents_path = package / target / "contents"
134+
if not contents_path.exists():
135+
continue
136+
try:
137+
tree = ElementTree.parse(contents_path)
138+
except ElementTree.ParseError:
139+
continue
140+
for entity_elem in tree.getroot().findall("entity"):
141+
if entity_elem.get("name") == model_name:
142+
raw_xml = ElementTree.tostring(entity_elem, encoding="unicode")
143+
return True, f"<!-- {package.name}/{target}/contents -->\n{raw_xml}"
144+
145+
return False, f"Model '{model_name}' not found"
146+
70147
# === CORE DATA PARSING ===
71148

72149
def _find_xcdatamodeld(self) -> list[Path]:
@@ -495,6 +572,7 @@ def main():
495572
python scripts/model_inspector.py --project-path . --json
496573
python scripts/model_inspector.py --project-path . --show-versions
497574
python scripts/model_inspector.py --project-path . --core-data-only
575+
python scripts/model_inspector.py --project-path . --raw TrainingSession
498576
""",
499577
)
500578

@@ -519,6 +597,11 @@ def main():
519597
action="store_true",
520598
help="List all model versions with current version highlighted",
521599
)
600+
inspection_group.add_argument(
601+
"--raw",
602+
metavar="MODEL_NAME",
603+
help="Dump raw source for a specific model (Swift class or Core Data entity)",
604+
)
522605

523606
output_group = parser.add_argument_group("Output Options")
524607
output_group.add_argument("--json", action="store_true", help="Output as JSON")
@@ -527,6 +610,16 @@ def main():
527610
args = parser.parse_args()
528611

529612
inspector = ModelInspector(project_path=args.project_path)
613+
614+
if args.raw:
615+
success, raw = inspector.get_raw_source(args.raw)
616+
if success:
617+
print(raw)
618+
else:
619+
print(f"Error: {raw}", file=sys.stderr)
620+
sys.exit(1)
621+
return
622+
530623
success, results = inspector.execute(
531624
core_data_only=args.core_data_only,
532625
swiftdata_only=args.swiftdata_only,

0 commit comments

Comments
 (0)