Imported from eoheat/AstroLens (
AGENTS.md). Install upstream withnpx skills add eoheat/AstroLens. Copyright stays with the author.
AGENTS.md — AstroLens
This file coordinates multiple agents working in parallel on AstroLens. Read this first before touching code. The point is to let several agents build independent modules without stepping on each other.
Project at a glance
AstroLens is a native iOS AR astronomy app (Swift + SwiftUI + ARKit). Point your phone at the sky, tap a star, get real astrophysics (spectral class, HR diagram position, lifecycle, spectra). Observations auto-log to a Journal. Full spec is in astrolens_master_prompt.md.
Stack: Swift 5.9+, SwiftUI, ARKit, CoreMotion, CoreLocation, SwiftData, GRDB (SQLite). Min target iOS 17.
Ground rules for all agents
- Stay in your lane. Each agent owns a directory (see ownership table). Don't edit files outside your lane without flagging it in your handoff notes.
- Contracts before code. The shared data models and protocols in
Shared/are the contract between modules. If you need to change a shared model, that's a coordination event — note it loudly, don't silently break the interface. - Mock what you don't own. If your module depends on another agent's module that isn't built yet, build against a mock/stub conforming to the agreed protocol. Don't block.
- No global state. Pass dependencies explicitly (init injection or environment). Makes parallel work testable in isolation.
- Leave handoff notes. End each work session with a short note in your section below: what you finished, what's stubbed, what the next agent needs to know.
- Test your module standalone. Each module should have a SwiftUI
#Previewor a small test harness that runs without the rest of the app.
Shared contracts (the interface between agents)
These live in Shared/ and StarData/StarModel.swift. Build these FIRST, by one agent, before parallel work begins. Everyone codes against them.
// The core data model every module reads
struct Star: Identifiable, Codable {
let id: Int // HYG catalog ID
let name: String? // common name (nil for most)
let bayer: String? // e.g. "α Ori"
let constellation: String?
let ra: Double // right ascension (hours)
let dec: Double // declination (degrees)
let magnitude: Double // apparent
let absMagnitude: Double
let spectralClass: String // e.g. "M2Iab"
let distanceLy: Double
let luminosity: Double? // in L☉
let temperatureK: Double? // derived from spectral class if absent
let radiusSolar: Double?
let massSolar: Double?
}
// Sky alignment → screen mapping (AR team owns impl, everyone uses output)
protocol SkyAlignmentProvider {
var currentPointing: (ra: Double, dec: Double) { get }
func screenPosition(for star: Star) -> CGPoint? // nil if off-screen
func starsInView() -> [Star]
}
// Star data access (data team owns impl)
protocol StarQuerying {
func star(id: Int) -> Star?
func starsInCone(ra: Double, dec: Double, radiusDeg: Double) -> [Star]
}
// Journal logging (journal team owns impl)
protocol ObservationLogging {
func log(_ star: Star, note: String?)
func sessions() -> [ObservationSession]
}
If you're the agent assigned to Shared contracts, finalize these models and merge them before anyone else starts. Everyone else: import these, don't redefine them.
Ownership table
| Agent | Owns directory | Depends on | Can start immediately? |
|---|---|---|---|
| Agent 0 — Foundation | Shared/, StarData/StarModel.swift |
nothing | YES — do this first |
| Agent 1 — AR/Alignment | ARView/ |
Star model, SkyAlignmentProvider protocol |
After Agent 0 |
| Agent 2 — Data Pipeline | StarData/ (except StarModel) |
Star model |
After Agent 0 |
| Agent 3 — Physics/Viz | Physics/ |
Star model |
After Agent 0 |
| Agent 4 — Journal | Journal/ |
Star model, ObservationLogging |
After Agent 0 |
| Agent 5 — AI Q&A | AI/ |
Star model |
After Agent 0 |
Agents 1–5 can all run in parallel once Agent 0 ships the contracts.
Per-agent briefs
Agent 0 — Foundation
Goal: Lock the shared contracts so everyone can build in parallel.
- Finalize
Star,ObservationSession,ObservationEntrymodels - Define the four protocols above
- Set up
DesignSystem.swift: color tokens (#0A0A0F bg, #4A9EFF accent, spectral class color map O→M), font tokens (SF Mono for data, SF Pro for labels), spacing scale - Build
StarDetailSheet.swiftshell — the tabbed container that other agents fill in (Overview / Physics / Spectra / Ask tabs as empty placeholders) Done when: Project compiles, contracts are merged, design tokens exist, detail sheet shell renders with empty tabs.
Agent 1 — AR / Sky Alignment
Goal: Camera view with stars overlaid in correct screen positions.
SkyARView.swift— ARKit camera session + SwiftUI overlay layerSkyAlignmentEngine.swift— implementSkyAlignmentProvider: fuse CoreMotion attitude + CoreLocation + Local Sidereal Time → map RA/Dec to screen pixelsStarOverlayRenderer.swift— draw star dots (size by magnitude, color by spectral class), constellation lines, tap targets- Build a manual calibration flow (tap a known bright star to correct compass drift)
Watch out for: magnetic declination correction, RA=0/24h wraparound, sensor noise (apply smoothing). This is the hardest module — budget accordingly.
Mock dependency: use a hardcoded
[Star]array until Agent 2's real query engine lands.
Agent 2 — Data Pipeline
Goal: Fast star catalog queries.
HYGDatabase.swift— on first launch, parse HYG CSV → SQLite (GRDB). Derive temperature/radius/mass from spectral class where the catalog is missing values.SkyQueryEngine.swift— implementStarQuerying. KD-tree or SQLite spatial index over RA/Dec for cone queries. Handle RA wraparound.- Benchmark: cone query must return < 16ms to keep AR at 60fps.
Watch out for: spectral class parsing is messy ("M2Iab", "G2V", "DA" for white dwarfs). Write a robust parser.
Deliver: a clean
StarQueryingimplementation Agent 1 can swap in for their mock.
Agent 3 — Physics / Visualization
Goal: The physics tabs that make this app special.
HRDiagramView.swift— custom SwiftUI Canvas. Temperature axis right-to-left (hot left), luminosity log scale (10⁻⁴ to 10⁶ L☉). Plot the main sequence band, then animate the tapped star's dot to its position.LifecycleView.swift— illustrated stellar lifecycle timeline; highlight current stage based on mass + spectral class (protostar → MS → subgiant → red giant → WD/NS/BH).SpectraView.swift— absorption line rendering across visible spectrum, lines labeled by element, line pattern driven by spectral class. Watch out for: get the HR axes correct before rendering anything — it's the easiest thing to get backwards. Mock dependency: test with a hardcodedStar(use Betelgeuse, Sirius, the Sun as fixtures).
Agent 4 — Journal
Goal: Observation logging + browsing.
ObservationLog.swift— SwiftData models (ObservationSession,ObservationEntry), implementObservationLoggingJournalView.swift— sessions grouped by date, scrollable logbook feelObservationDetailView.swift— re-open a logged star's full physics detail- Stats view: total logged, spectral class breakdown, top constellations
- Auto-log hook: fires when a star detail sheet opens Watch out for: capture location + timestamp + sky conditions at log time. Don't store the whole Star blob redundantly — store the catalog ID + a snapshot of what mattered. Mock dependency: use fixture Stars to populate a fake journal for UI work.
Agent 5 — AI Q&A
Goal: Ask freeform physics questions about a tapped star.
ClaudeQAView.swift— text input + response display, lives in the "Ask" tab of the detail sheet- Call the Claude API (claude-sonnet-4-6). Inject the full
Stardata as context in the system prompt so answers are grounded in that specific star's real values. - Stream the response for responsiveness. Watch out for: never hardcode an API key in the repo — read from a config/keychain. Keep the system prompt tight: "You are an astrophysics tutor. Here is data for [star]. Answer the user's question using real physics, reference the actual values." Mock dependency: stub the API call with a canned response so the UI works offline during dev.
Coordination events (need a sync)
These changes affect everyone — flag before doing them:
- Changing any field in the
Starmodel - Changing any of the four shared protocols
- Changing the
StarDetailSheettab structure - Changing
DesignSystemtokens
Handoff notes
Agents append status here at the end of each session.
Agent 0 — Foundation
- Done.
Starmodel + fixtures (StarData/StarModel.swift),SpectralTypeparser,ObservationSession/ObservationEntrySwiftData models, the four protocols (Shared/Protocols.swift),StarChatProviding,AppServicesDI container + mocks (Shared/AppServices.swift),DesignSystem.swifttokens, and theStarDetailSheetshell with the Overview tab built in. App entry/composition root inApp/AstroLensApp.swift. Xcode project viaproject.yml(XcodeGen) +Resources/Info.plist.
Agent 1 — AR / Alignment
- Done.
SkyAlignmentEngine(CoreMotion + CoreLocation + LST sensor fusion, gnomonic projection, RA wraparound, smoothing, manualcalibrate),StarOverlayRenderer(mag-sized glowing dots, spectral colors, night-legible labels, 44pt hit targets),SkyARView(ARKit camera + overlay, presentsStarDetailSheet). Simulator-safe fallback. True-north handled via.xTrueNorthZVertical+trueHeading.
Agent 2 — Data Pipeline
- Done.
HYGDatabase(GRDB SQLite, streaming CSV import, derives temp/radius/mass from spectral class),BrightStarCatalog(126 curated real bright stars as fallback),SkyQueryEngine: StarQuerying(non-blockinginit(), haversine cone query with RA 0/24h wraparound). DropResources/hygdata_v3.csvto enable the full 120k catalog — seeStarData/Resources/BrightStars.md.
Agent 3 — Physics / Viz
- Done.
HRDiagramView(log-log Canvas, temp axis hot-left/cool-right, animated star marker, luminosity-from-absmag fallback),LifecycleView(stage + endpoint from mass: <8→WD, 8–25→NS, ≥25→BH),SpectraView(absorption lines by class with element labels),PhysicsTabView(temp/lum/radius/mass + HR + lifecycle).
Agent 4 — Journal
- Done.
ObservationLog: ObservationLogging(init(modelContext:), 6h session rollover, one-shot CoreLocation stamp, 30s dedup),JournalView(sessions grouped by date, stats: total/spectral breakdown/top constellations),ObservationDetailView(re-resolvesStarviaservices.starQuery, re-opensStarDetailSheet).
Agent 5 — AI Q&A
- Done.
ClaudeClient: StarChatProviding(Claude Messages API, modelclaude-sonnet-4-6, SSE streaming, key from Keychain/env/Info.plist, offline canned fallback),Keychainhelper,ClaudeQAView(streaming Ask tab with suggested questions). No key hardcoded.