ether-to-astro 1.0.0

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 (138) hide show
  1. package/.env.example +13 -0
  2. package/.github/pull_request_template.md +16 -0
  3. package/.github/workflows/release.yml +35 -0
  4. package/.github/workflows/test.yml +32 -0
  5. package/AGENTS.md +99 -0
  6. package/LICENSE +18 -0
  7. package/NOTICE.md +45 -0
  8. package/README.md +301 -0
  9. package/SETUP.md +70 -0
  10. package/TESTING_SUMMARY.md +238 -0
  11. package/TEST_SUITE_STATUS.md +218 -0
  12. package/biome.json +48 -0
  13. package/dist/astro-service.d.ts +98 -0
  14. package/dist/astro-service.js +496 -0
  15. package/dist/chart-types.d.ts +52 -0
  16. package/dist/chart-types.js +51 -0
  17. package/dist/charts.d.ts +125 -0
  18. package/dist/charts.js +324 -0
  19. package/dist/cli.d.ts +7 -0
  20. package/dist/cli.js +472 -0
  21. package/dist/constants.d.ts +81 -0
  22. package/dist/constants.js +76 -0
  23. package/dist/eclipses.d.ts +85 -0
  24. package/dist/eclipses.js +184 -0
  25. package/dist/ephemeris.d.ts +120 -0
  26. package/dist/ephemeris.js +379 -0
  27. package/dist/formatter.d.ts +2 -0
  28. package/dist/formatter.js +22 -0
  29. package/dist/houses.d.ts +82 -0
  30. package/dist/houses.js +169 -0
  31. package/dist/index.d.ts +14 -0
  32. package/dist/index.js +150 -0
  33. package/dist/loader.d.ts +2 -0
  34. package/dist/loader.js +31 -0
  35. package/dist/logger.d.ts +25 -0
  36. package/dist/logger.js +73 -0
  37. package/dist/profile-store.d.ts +48 -0
  38. package/dist/profile-store.js +156 -0
  39. package/dist/riseset.d.ts +82 -0
  40. package/dist/riseset.js +185 -0
  41. package/dist/storage.d.ts +10 -0
  42. package/dist/storage.js +40 -0
  43. package/dist/time-utils.d.ts +68 -0
  44. package/dist/time-utils.js +136 -0
  45. package/dist/tool-registry.d.ts +35 -0
  46. package/dist/tool-registry.js +307 -0
  47. package/dist/tool-result.d.ts +175 -0
  48. package/dist/tool-result.js +188 -0
  49. package/dist/transits.d.ts +108 -0
  50. package/dist/transits.js +263 -0
  51. package/dist/types.d.ts +450 -0
  52. package/dist/types.js +161 -0
  53. package/example-usage.md +131 -0
  54. package/natal-chart.json +187 -0
  55. package/package.json +61 -0
  56. package/scripts/download-ephemeris.js +115 -0
  57. package/setup.sh +21 -0
  58. package/src/astro-service.ts +710 -0
  59. package/src/chart-types.ts +125 -0
  60. package/src/charts.ts +399 -0
  61. package/src/cli.ts +694 -0
  62. package/src/constants.ts +89 -0
  63. package/src/eclipses.ts +226 -0
  64. package/src/ephemeris.ts +437 -0
  65. package/src/formatter.ts +25 -0
  66. package/src/houses.ts +202 -0
  67. package/src/index.ts +170 -0
  68. package/src/loader.ts +36 -0
  69. package/src/logger.ts +104 -0
  70. package/src/profile-store.ts +285 -0
  71. package/src/riseset.ts +229 -0
  72. package/src/time-utils.ts +167 -0
  73. package/src/tool-registry.ts +357 -0
  74. package/src/tool-result.ts +283 -0
  75. package/src/transits.ts +352 -0
  76. package/src/types.ts +547 -0
  77. package/tests/README.md +173 -0
  78. package/tests/TESTING_STRATEGY.md +178 -0
  79. package/tests/fixtures/bowen-yang-chart.ts +69 -0
  80. package/tests/fixtures/calculate-expected.ts +81 -0
  81. package/tests/fixtures/expected-results.ts +117 -0
  82. package/tests/fixtures/generate-expected-simple.ts +94 -0
  83. package/tests/helpers/date-fixtures.ts +15 -0
  84. package/tests/helpers/ephem.ts +11 -0
  85. package/tests/helpers/temp.ts +9 -0
  86. package/tests/setup.ts +11 -0
  87. package/tests/unit/astro-service.test.ts +323 -0
  88. package/tests/unit/chart-types.test.ts +18 -0
  89. package/tests/unit/charts-errors.test.ts +42 -0
  90. package/tests/unit/charts.test.ts +157 -0
  91. package/tests/unit/cli-commands.test.ts +82 -0
  92. package/tests/unit/cli-profiles.test.ts +128 -0
  93. package/tests/unit/cli.test.ts +191 -0
  94. package/tests/unit/constants.test.ts +26 -0
  95. package/tests/unit/correctness-critical.test.ts +408 -0
  96. package/tests/unit/eclipses.test.ts +108 -0
  97. package/tests/unit/ephemeris.test.ts +213 -0
  98. package/tests/unit/error-handling.test.ts +116 -0
  99. package/tests/unit/formatter.test.ts +29 -0
  100. package/tests/unit/houses-errors.test.ts +27 -0
  101. package/tests/unit/houses-validation.test.ts +164 -0
  102. package/tests/unit/houses.test.ts +205 -0
  103. package/tests/unit/profile-store.test.ts +163 -0
  104. package/tests/unit/real-user-charts.test.ts +148 -0
  105. package/tests/unit/riseset.test.ts +106 -0
  106. package/tests/unit/solver-edges.test.ts +197 -0
  107. package/tests/unit/time-utils-temporal.test.ts +303 -0
  108. package/tests/unit/time-utils.test.ts +173 -0
  109. package/tests/unit/tool-registry.test.ts +222 -0
  110. package/tests/unit/tool-result.test.ts +45 -0
  111. package/tests/unit/transit-correctness.test.ts +78 -0
  112. package/tests/unit/transits.test.ts +238 -0
  113. package/tests/validation/README.md +32 -0
  114. package/tests/validation/adapters/astrolog.ts +306 -0
  115. package/tests/validation/adapters/internal.ts +184 -0
  116. package/tests/validation/compare/eclipses.ts +47 -0
  117. package/tests/validation/compare/houses.ts +76 -0
  118. package/tests/validation/compare/positions.ts +104 -0
  119. package/tests/validation/compare/riseSet.ts +48 -0
  120. package/tests/validation/compare/roots.ts +90 -0
  121. package/tests/validation/compare/transits.ts +69 -0
  122. package/tests/validation/fixtures/astrolog-parity/core.ts +194 -0
  123. package/tests/validation/fixtures/eclipses/core.ts +14 -0
  124. package/tests/validation/fixtures/houses/core.ts +47 -0
  125. package/tests/validation/fixtures/positions/core.ts +159 -0
  126. package/tests/validation/fixtures/rise-set/core.ts +20 -0
  127. package/tests/validation/fixtures/roots/core.ts +47 -0
  128. package/tests/validation/fixtures/transits/core.ts +61 -0
  129. package/tests/validation/fixtures/transits/dst.ts +21 -0
  130. package/tests/validation/oracle.spec.ts +129 -0
  131. package/tests/validation/utils/denseRootOracle.ts +269 -0
  132. package/tests/validation/utils/fixtureTypes.ts +146 -0
  133. package/tests/validation/utils/report.ts +60 -0
  134. package/tests/validation/utils/tolerances.ts +23 -0
  135. package/tests/validation/validation.spec.ts +836 -0
  136. package/tools/color-picker.html +388 -0
  137. package/tsconfig.json +17 -0
  138. package/vitest.config.ts +31 -0
package/src/types.ts ADDED
@@ -0,0 +1,547 @@
1
+ // Type unions for domain constraints
2
+ export type HouseSystem =
3
+ | 'P'
4
+ | 'K'
5
+ | 'W'
6
+ | 'E'
7
+ | 'O'
8
+ | 'R'
9
+ | 'C'
10
+ | 'A'
11
+ | 'V'
12
+ | 'X'
13
+ | 'H'
14
+ | 'T'
15
+ | 'B';
16
+ export type SolarEclipseType = 'partial' | 'annular' | 'total' | 'annular-total';
17
+ export type LunarEclipseType = 'penumbral' | 'partial' | 'total';
18
+ export type PlanetName =
19
+ | 'Sun'
20
+ | 'Moon'
21
+ | 'Mercury'
22
+ | 'Venus'
23
+ | 'Mars'
24
+ | 'Jupiter'
25
+ | 'Saturn'
26
+ | 'Uranus'
27
+ | 'Neptune'
28
+ | 'Pluto'
29
+ | 'Chiron'
30
+ | 'North Node (Mean)'
31
+ | 'North Node (True)'
32
+ | 'Ceres'
33
+ | 'Pallas'
34
+ | 'Juno'
35
+ | 'Vesta';
36
+
37
+ /**
38
+ * Represents a complete natal birth chart with all necessary data for calculations
39
+ *
40
+ * @remarks
41
+ * This is the core data structure for the MCP server. All calculations (transits,
42
+ * houses, charts) are based on this chart data.
43
+ *
44
+ * The julianDay field is calculated during set_natal_chart and should always be
45
+ * present for charts created in the current session.
46
+ */
47
+ export interface NatalChart {
48
+ name: string;
49
+ /** Birth date and time in local timezone */
50
+ birthDate: {
51
+ /** Full year (e.g., 1990) */
52
+ year: number;
53
+ /** Month number (1-12) */
54
+ month: number;
55
+ /** Day of month (1-31) */
56
+ day: number;
57
+ /** Hour in 24-hour format (0-23) */
58
+ hour: number;
59
+ /** Minute (0-59) */
60
+ minute: number;
61
+ /** Optional seconds for precision */
62
+ second?: number;
63
+ };
64
+ /** Birth location coordinates and timezone */
65
+ location: {
66
+ /** Latitude in decimal degrees (-90 to 90, negative for South) */
67
+ latitude: number;
68
+ /** Longitude in decimal degrees (-180 to 180, negative for West) */
69
+ longitude: number;
70
+ /** IANA timezone identifier (e.g., 'America/New_York') */
71
+ timezone: string;
72
+ };
73
+ /** Optional pre-calculated planet positions (rarely used) */
74
+ planets?: PlanetPosition[];
75
+ /**
76
+ * Cached Julian Day for birth time (UTC)
77
+ * @remarks
78
+ * This is calculated during set_natal_chart and should always be present.
79
+ * Required for chart generation and transit calculations.
80
+ */
81
+ julianDay?: number;
82
+ /**
83
+ * Preferred house system for calculations
84
+ * @default 'P' (Placidus)
85
+ * @remarks
86
+ * For polar latitudes (>66°), Whole Sign ('W') may be used as fallback.
87
+ */
88
+ houseSystem?: HouseSystem;
89
+ /**
90
+ * UTC equivalent of birth time
91
+ * @remarks
92
+ * Calculated from local birth time using timezone conversion.
93
+ * Used for Julian Day calculation to avoid timezone bugs.
94
+ */
95
+ utcDateTime?: {
96
+ /** UTC year */
97
+ year: number;
98
+ /** UTC month */
99
+ month: number;
100
+ /** UTC day */
101
+ day: number;
102
+ /** UTC hour */
103
+ hour: number;
104
+ /** UTC minute */
105
+ minute: number;
106
+ /** Optional UTC seconds */
107
+ second?: number;
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Represents a planet's position at a specific time
113
+ *
114
+ * @remarks
115
+ * All angular measurements are in degrees. Longitude is along the ecliptic,
116
+ * latitude is celestial latitude (distance from ecliptic).
117
+ */
118
+ export interface PlanetPosition {
119
+ /** Swiss Ephemeris planet ID */
120
+ planetId: number;
121
+ /** Planet name (e.g., 'Sun', 'Moon', 'Mercury') */
122
+ planet: PlanetName;
123
+ /**
124
+ * Ecliptic longitude in degrees (0-360)
125
+ * @remarks
126
+ * 0° Aries, 90° Cancer, 180° Libra, 270° Capricorn
127
+ */
128
+ longitude: number;
129
+ /**
130
+ * Celestial latitude in degrees (-90 to 90)
131
+ * @remarks
132
+ * Positive is north of ecliptic, negative is south
133
+ */
134
+ latitude: number;
135
+ /** Distance from Earth in AU (Astronomical Units) */
136
+ distance: number;
137
+ /**
138
+ * Daily motion in degrees per day
139
+ * @remarks
140
+ * Positive = direct motion, Negative = retrograde
141
+ */
142
+ speed: number;
143
+ /** Zodiac sign the planet is in (e.g., 'Aries', 'Taurus') */
144
+ sign: string;
145
+ /** Degree within the sign (0-30) */
146
+ degree: number;
147
+ /**
148
+ * Whether the planet is in retrograde motion
149
+ * @remarks
150
+ * Retrograde means the planet appears to move backward from Earth's perspective
151
+ */
152
+ isRetrograde: boolean;
153
+ }
154
+
155
+ /**
156
+ * Base interface for all transit data
157
+ *
158
+ * @remarks
159
+ * Contains the core transit information shared between internal Transit
160
+ * and serialized TransitData types.
161
+ */
162
+ interface BaseTransit {
163
+ /** Planet currently transiting (moving) */
164
+ transitingPlanet: PlanetName;
165
+ /** Planet in the natal chart being aspected */
166
+ natalPlanet: PlanetName;
167
+ /** Type of aspect between the planets */
168
+ aspect: AspectType;
169
+ /**
170
+ * Angular distance from exact aspect in degrees
171
+ * @remarks
172
+ * Lower values indicate stronger aspects. 0° is exact.
173
+ */
174
+ orb: number;
175
+ /**
176
+ * Whether the aspect is applying (getting stronger) or separating (weakening)
177
+ * @remarks
178
+ * true = applying (getting closer to exact)
179
+ * false = separating (moving away from exact)
180
+ */
181
+ isApplying: boolean;
182
+ /** Status of exact time lookup for this transit */
183
+ exactTimeStatus?: 'within_preview' | 'outside_preview' | 'not_found' | 'unsupported_body';
184
+ /** Current longitude of transiting planet */
185
+ transitLongitude: number;
186
+ /** Longitude of natal planet at birth time */
187
+ natalLongitude: number;
188
+ }
189
+
190
+ /**
191
+ * Internal transit representation with Date object
192
+ *
193
+ * @remarks
194
+ * Used for calculations and internal storage. The exactTime is a Date object
195
+ * for precise time comparisons.
196
+ */
197
+ export interface Transit extends BaseTransit {
198
+ /**
199
+ * Exact time when aspect becomes perfect (0° orb)
200
+ * @remarks
201
+ * May be undefined if aspect is not within orb or exact time not calculated
202
+ */
203
+ exactTime?: Date;
204
+ }
205
+
206
+ /**
207
+ * Serialized transit representation for API responses
208
+ *
209
+ * @remarks
210
+ * Used when sending transit data to MCP clients. The exactTime is an ISO string
211
+ * for JSON serialization.
212
+ */
213
+ export interface TransitData extends BaseTransit {
214
+ /**
215
+ * Exact time when aspect becomes perfect (0° orb) as ISO string
216
+ * @remarks
217
+ * May be undefined if aspect is not within orb or exact time not calculated
218
+ */
219
+ exactTime?: string; // ISO timestamp
220
+ }
221
+
222
+ /**
223
+ * Response wrapper for transit data
224
+ *
225
+ * @remarks
226
+ * Contains all transits for a specific date along with metadata
227
+ * about the calculation context.
228
+ */
229
+ export interface TransitResponse {
230
+ /** ISO date of the transit calculation (YYYY-MM-DD) */
231
+ date: string;
232
+ /** Timezone used for the calculation */
233
+ timezone: string;
234
+ /** Array of all active transits for the date */
235
+ transits: TransitData[];
236
+ }
237
+
238
+ /**
239
+ * Response wrapper for planet position data
240
+ *
241
+ * @remarks
242
+ * Contains positions for all requested planets at a specific time.
243
+ */
244
+ export interface PlanetPositionResponse {
245
+ /** ISO date of the position calculation (YYYY-MM-DD) */
246
+ date: string;
247
+ /** Timezone used for the calculation */
248
+ timezone: string;
249
+ /** Array of planet positions */
250
+ positions: PlanetPosition[];
251
+ }
252
+
253
+ /**
254
+ * Types of astrological aspects
255
+ *
256
+ * @remarks
257
+ * Aspects are angular relationships between planets that indicate
258
+ * specific types of interactions and energies.
259
+ */
260
+ export type AspectType = 'conjunction' | 'opposition' | 'square' | 'trine' | 'sextile';
261
+
262
+ /**
263
+ * Aspect definitions with angles and default orbs
264
+ *
265
+ * @remarks
266
+ * Each aspect has a specific angular relationship and an orb (tolerance).
267
+ * The orb determines how far from exact the aspect can still be considered active.
268
+ */
269
+ export const ASPECTS: Array<{ name: AspectType; angle: number; orb: number }> = [
270
+ { name: 'conjunction', angle: 0, orb: 8 },
271
+ { name: 'opposition', angle: 180, orb: 8 },
272
+ { name: 'square', angle: 90, orb: 7 },
273
+ { name: 'trine', angle: 120, orb: 7 },
274
+ { name: 'sextile', angle: 60, orb: 6 },
275
+ ];
276
+
277
+ /**
278
+ * Swiss Ephemeris planet IDs
279
+ *
280
+ * @remarks
281
+ * These are the numeric IDs used by the Swiss Ephemeris library.
282
+ * The `as const` assertion ensures type safety when referencing planets.
283
+ */
284
+ export const PLANETS = {
285
+ /** Sun */
286
+ SUN: 0,
287
+ /** Moon */
288
+ MOON: 1,
289
+ /** Mercury */
290
+ MERCURY: 2,
291
+ /** Venus */
292
+ VENUS: 3,
293
+ /** Mars */
294
+ MARS: 4,
295
+ /** Jupiter */
296
+ JUPITER: 5,
297
+ /** Saturn */
298
+ SATURN: 6,
299
+ /** Uranus */
300
+ URANUS: 7,
301
+ /** Neptune */
302
+ NEPTUNE: 8,
303
+ /** Pluto */
304
+ PLUTO: 9,
305
+ /** Mean North Node (average position) */
306
+ MEAN_NODE: 10,
307
+ /** True North Node (actual position) */
308
+ TRUE_NODE: 11,
309
+ /** Chiron (comet/centaur) */
310
+ CHIRON: 15,
311
+ /** Ceres (dwarf planet/asteroid) */
312
+ CERES: 17,
313
+ /** Pallas (asteroid) */
314
+ PALLAS: 18,
315
+ /** Juno (asteroid) */
316
+ JUNO: 19,
317
+ /** Vesta (asteroid) */
318
+ VESTA: 20,
319
+ } as const;
320
+
321
+ /**
322
+ * Type derived from PLANETS constants
323
+ *
324
+ * @remarks
325
+ * Ensures type safety when working with planet IDs.
326
+ * Only values present in PLANETS are valid PlanetId values.
327
+ */
328
+ export type PlanetId = (typeof PLANETS)[keyof typeof PLANETS];
329
+
330
+ /**
331
+ * Mapping from planet IDs to human-readable names
332
+ *
333
+ * @remarks
334
+ * Used for display purposes and converting between numeric IDs
335
+ * and string names. Index signature allows number-based lookup.
336
+ */
337
+ export const PLANET_NAMES: { [key: number]: PlanetName } = {
338
+ 0: 'Sun',
339
+ 1: 'Moon',
340
+ 2: 'Mercury',
341
+ 3: 'Venus',
342
+ 4: 'Mars',
343
+ 5: 'Jupiter',
344
+ 6: 'Saturn',
345
+ 7: 'Uranus',
346
+ 8: 'Neptune',
347
+ 9: 'Pluto',
348
+ 10: 'North Node (Mean)',
349
+ 11: 'North Node (True)',
350
+ 15: 'Chiron',
351
+ 17: 'Ceres',
352
+ 18: 'Pallas',
353
+ 19: 'Juno',
354
+ 20: 'Vesta',
355
+ };
356
+
357
+ /**
358
+ * Personal planets (inner planets)
359
+ *
360
+ * @remarks
361
+ * These planets move quickly and represent personal, day-to-day concerns:
362
+ * - Sun: Identity, vitality
363
+ * - Moon: Emotions, instincts
364
+ * - Mercury: Communication, thinking
365
+ * - Venus: Values, relationships
366
+ * - Mars: Action, desire
367
+ */
368
+ export const PERSONAL_PLANETS = [
369
+ PLANETS.SUN,
370
+ PLANETS.MOON,
371
+ PLANETS.MERCURY,
372
+ PLANETS.VENUS,
373
+ PLANETS.MARS,
374
+ ];
375
+ /**
376
+ * Slow-moving planets (Jupiter through Pluto)
377
+ *
378
+ * @remarks
379
+ * These planets move slowly and represent generational, societal themes.
380
+ * Note: Includes Jupiter and Saturn (social planets) plus the true outer planets.
381
+ */
382
+ export const OUTER_PLANETS = [
383
+ PLANETS.JUPITER,
384
+ PLANETS.SATURN,
385
+ PLANETS.URANUS,
386
+ PLANETS.NEPTUNE,
387
+ PLANETS.PLUTO,
388
+ ];
389
+ /**
390
+ * Major asteroids
391
+ *
392
+ * @remarks
393
+ * The four main asteroids used in astrology, representing feminine archetypes:
394
+ * - Ceres: Nurturing, agriculture
395
+ * - Pallas: Wisdom, strategy
396
+ * - Juno: Partnership, commitment
397
+ * - Vesta: Devotion, service
398
+ */
399
+ export const ASTEROIDS = [
400
+ PLANETS.CHIRON,
401
+ PLANETS.CERES,
402
+ PLANETS.PALLAS,
403
+ PLANETS.JUNO,
404
+ PLANETS.VESTA,
405
+ ];
406
+ /**
407
+ * Lunar nodes
408
+ *
409
+ * @remarks
410
+ * The North and South Nodes represent points where the Moon's orbit
411
+ * crosses the ecliptic. They indicate life path and evolutionary direction.
412
+ */
413
+ export const NODES = [PLANETS.MEAN_NODE, PLANETS.TRUE_NODE];
414
+
415
+ /**
416
+ * Zodiac signs in order
417
+ *
418
+ * @remarks
419
+ * The 12 signs of the tropical zodiac, each spanning 30° of the ecliptic.
420
+ * Used for determining which sign a planet is in.
421
+ */
422
+ export const ZODIAC_SIGNS = [
423
+ 'Aries',
424
+ 'Taurus',
425
+ 'Gemini',
426
+ 'Cancer',
427
+ 'Leo',
428
+ 'Virgo',
429
+ 'Libra',
430
+ 'Scorpio',
431
+ 'Sagittarius',
432
+ 'Capricorn',
433
+ 'Aquarius',
434
+ 'Pisces',
435
+ ];
436
+
437
+ /**
438
+ * House cusps in Swiss Ephemeris 1-based format
439
+ *
440
+ * @remarks
441
+ * - Index 0: Unused (by convention)
442
+ * - Index 1-12: Houses 1-12
443
+ * - Length: 13
444
+ *
445
+ * The Swiss Ephemeris uses 1-based indexing for house cusps,
446
+ * with index 0 unused by convention.
447
+ */
448
+ export type HouseCusps = number[];
449
+
450
+ /**
451
+ * Complete house system calculation results
452
+ *
453
+ * @remarks
454
+ * Contains the angles (Ascendant, MC) and all house cusps for a given
455
+ * time and location. The cusps array follows Swiss Ephemeris 1-based format.
456
+ */
457
+ export interface HouseData {
458
+ /**
459
+ * Ascendant angle in degrees (0-360)
460
+ * @remarks
461
+ * The point where the ecliptic crosses the eastern horizon.
462
+ * Represents the self, identity, and personal appearance.
463
+ */
464
+ ascendant: number;
465
+ /**
466
+ * Midheaven (Medium Coeli) angle in degrees (0-360)
467
+ * @remarks
468
+ * The highest point in the sky at the time of birth.
469
+ * Represents career, public life, and reputation.
470
+ */
471
+ mc: number;
472
+ /**
473
+ * House cusps in Swiss 1-based format
474
+ * @remarks
475
+ * cusps[1] = House 1, cusps[2] = House 2, ..., cusps[12] = House 12
476
+ * cusps[0] is unused by convention
477
+ */
478
+ cusps: HouseCusps;
479
+ /**
480
+ * The house system actually used for calculation
481
+ * @remarks
482
+ * May differ from requested system if fallback was used
483
+ * (e.g., Whole Sign for polar latitudes)
484
+ */
485
+ system: HouseSystem;
486
+ }
487
+
488
+ /**
489
+ * Rise, set, and meridian transit times for a celestial body
490
+ *
491
+ * @remarks
492
+ * Contains the times when a planet rises above the horizon, sets below it,
493
+ * and crosses the upper and lower meridians. Times may be undefined for
494
+ * circumpolar objects (never rise/set) or at extreme latitudes.
495
+ */
496
+ export interface RiseSetTime {
497
+ /** Planet name */
498
+ planet: string;
499
+ /**
500
+ * Time when planet rises above eastern horizon
501
+ * @remarks
502
+ * Undefined for circumpolar objects (always visible)
503
+ */
504
+ rise?: Date;
505
+ /**
506
+ * Time when planet sets below western horizon
507
+ * @remarks
508
+ * Undefined for circumpolar objects (always visible)
509
+ */
510
+ set?: Date;
511
+ /**
512
+ * Time when planet crosses upper meridian (highest point)
513
+ * @remarks
514
+ * This is the planet's "culmination" or "upper transit"
515
+ */
516
+ upperMeridianTransit?: Date;
517
+ /**
518
+ * Time when planet crosses lower meridian (lowest point)
519
+ * @remarks
520
+ * This is the planet's "lower transit" or "anti-culmination"
521
+ */
522
+ lowerMeridianTransit?: Date;
523
+ }
524
+
525
+ /**
526
+ * Basic eclipse information
527
+ *
528
+ * @remarks
529
+ * TODO: This should be replaced with a discriminated union for solar vs lunar eclipses
530
+ * with richer phase timing data. See planning documents for details.
531
+ */
532
+ export interface EclipseInfo {
533
+ /** Type of eclipse: 'solar' or 'lunar' */
534
+ type: 'solar' | 'lunar';
535
+ /** Date of the eclipse */
536
+ date: Date;
537
+ /**
538
+ * Eclipse classification
539
+ * @remarks
540
+ * TODO: Should use constrained union types:
541
+ * - Solar: 'partial' | 'annular' | 'total' | 'annular-total'
542
+ * - Lunar: 'penumbral' | 'partial' | 'total'
543
+ */
544
+ eclipseType: string;
545
+ /** Time of maximum eclipse */
546
+ maxTime: Date;
547
+ }
@@ -0,0 +1,173 @@
1
+ # Astro Test Suite
2
+
3
+ Comprehensive unit and validation tests for the `e2a` CLI and `e2a-mcp` server, with an 80% global coverage target.
4
+
5
+ ## Test Structure
6
+
7
+ ```
8
+ tests/
9
+ ├── unit/ # Unit tests for runtime and domain modules
10
+ │ ├── astro-service.test.ts # Service orchestration and tool-facing behavior
11
+ │ ├── cli*.test.ts # CLI contracts, profiles, error handling
12
+ │ ├── tool-registry.test.ts # MCP tool spec mapping and execution wrappers
13
+ │ ├── ephemeris/transits/* # Core astrology math and solver behavior
14
+ │ ├── riseset/eclipses/* # Event calculators and edge flags
15
+ │ └── charts/houses/* # Rendering and house-system behavior
16
+ ├── helpers/ # Reusable test helpers/builders
17
+ ├── fixtures/ # Test data and fixtures
18
+ │ ├── bowen-yang-chart.ts # Bowen Yang's birth chart
19
+ │ └── expected-results.ts # Known calculation results
20
+ ├── setup.ts # Test environment setup
21
+ └── validation/ # End-to-end validation harness
22
+
23
+ Total: 150+ tests
24
+ ```
25
+
26
+ ## Running Tests
27
+
28
+ ```bash
29
+ # Run all tests
30
+ npm test
31
+
32
+ # Run tests with coverage
33
+ npm run test:coverage
34
+
35
+ # Run tests with UI
36
+ npm run test:ui
37
+
38
+ # Run specific test file
39
+ npm test tests/unit/ephemeris.test.ts
40
+
41
+ # Watch mode
42
+ npm test -- --watch
43
+ ```
44
+
45
+ ## Test Data
46
+
47
+ ### Bowen Yang's Birth Chart
48
+ - **Born:** November 6, 1990, 11:30 AM
49
+ - **Location:** Brisbane, Australia (27.4705°S, 153.0260°E)
50
+ - **Timezone:** Australia/Brisbane (UTC+10)
51
+
52
+ This real-world chart is used throughout the test suite to ensure calculations work with actual astrological data.
53
+
54
+ ## Test Philosophy
55
+
56
+ ### User Story Format
57
+ Tests are written as user stories to make them readable and maintainable:
58
+
59
+ ```typescript
60
+ describe('When an AI asks "What transits is Bowen experiencing today?"', () => {
61
+ it('should find transits between current planets and natal planets', () => {
62
+ // Test implementation
63
+ });
64
+ });
65
+ ```
66
+
67
+ ### Coverage Goals
68
+ - **Minimum 80%** across lines, functions, branches, and statements
69
+ - Preserve high-fidelity solver tests while adding deterministic orchestration tests
70
+ - Exclude logging/entrypoint-only modules from coverage gates
71
+
72
+ ### Testing Strategy
73
+ 1. **Core math lane:** Real ephemeris/solver behavior (minimal mocking)
74
+ 2. **Orchestration lane:** Fast deterministic tests with injected mocks/fakes
75
+ 3. **Filesystem/profile lane:** Temp-fs integration style for precedence and parsing
76
+ 4. **Validation lane:** Cross-check production outputs with oracle comparators
77
+
78
+ ## Current Status
79
+
80
+ ✅ **Completed:**
81
+ - Unit suites for service, CLI, registry, domain calculators, and profile store
82
+ - Validation harness with subsystem comparators and dense root oracle
83
+ - Deterministic time setup and fixture-driven real-world chart checks
84
+
85
+ ## Known Issues
86
+
87
+ ### Ephemeris in Tests
88
+ The project uses native `sweph` bindings in Node.js:
89
+ - **Setup:** `tests/setup.ts` fixes test time for deterministic output
90
+ - **Data:** tests use real ephemeris logic with local ephemeris files when available
91
+ - **Fallback:** Moshier mode remains available when ephemeris files are missing
92
+
93
+ ### AstroChart Browser Dependencies
94
+ The chart library expects browser-like globals:
95
+ - Use `jsdom` test environment
96
+ - Polyfill `self` in `tests/setup.ts`
97
+
98
+ ## Coverage Reports
99
+
100
+ After running `npm run test:coverage`, view reports at:
101
+ - **HTML:** `coverage/index.html`
102
+ - **LCOV:** `coverage/lcov.info` (for CI/CD)
103
+ - **JSON:** `coverage/coverage-final.json`
104
+
105
+ ## CI/CD Integration
106
+
107
+ Tests are designed to run in GitHub Actions:
108
+ ```yaml
109
+ - name: Run tests with coverage
110
+ run: npm run test:coverage
111
+ - name: Upload coverage
112
+ uses: codecov/codecov-action@v3
113
+ ```
114
+
115
+ ## Writing New Tests
116
+
117
+ ### Example Test Structure
118
+ ```typescript
119
+ import { describe, it, expect, beforeAll } from 'vitest';
120
+ import { ModuleToTest } from '../../src/module.js';
121
+ import { bowenYangChart } from '../fixtures/bowen-yang-chart.js';
122
+
123
+ describe('When a user wants to [action]', () => {
124
+ let instance: ModuleToTest;
125
+
126
+ beforeAll(async () => {
127
+ instance = new ModuleToTest();
128
+ await instance.init();
129
+ });
130
+
131
+ describe('Given [context]', () => {
132
+ it('should [expected behavior]', () => {
133
+ // Arrange
134
+ const input = bowenYangChart;
135
+
136
+ // Act
137
+ const result = instance.method(input);
138
+
139
+ // Assert
140
+ expect(result).toBeDefined();
141
+ expect(result.property).toBe(expectedValue);
142
+ });
143
+ });
144
+ });
145
+ ```
146
+
147
+ ### Best Practices
148
+ 1. Use descriptive test names that read like sentences
149
+ 2. Follow Arrange-Act-Assert pattern
150
+ 3. Use Bowen Yang's chart for realistic data
151
+ 4. Mock external dependencies (file I/O, network)
152
+ 5. Test edge cases (polar regions, retrograde planets, etc.)
153
+ 6. Keep tests fast (<30s total runtime)
154
+
155
+ ## Troubleshooting
156
+
157
+ ### Tests Timeout
158
+ Increase timeout in `vitest.config.ts`:
159
+ ```typescript
160
+ testTimeout: 30000 // 30 seconds
161
+ ```
162
+
163
+ ### Ephemeris Data Missing
164
+ Check that `data/ephemeris` exists or reinstall with `npm install` (runs postinstall downloader).
165
+
166
+ ### Coverage Too Low
167
+ Run with `--coverage` to see which lines aren't covered, then add targeted tests.
168
+
169
+ ## Future Enhancements
170
+
171
+ - [ ] Thread-level MCP request/response integration tests
172
+ - [ ] CI flake detector pass (repeat-run sampling on key suites)
173
+ - [ ] Additional chart rendering failure-path contract tests