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.
- package/.env.example +13 -0
- package/.github/pull_request_template.md +16 -0
- package/.github/workflows/release.yml +35 -0
- package/.github/workflows/test.yml +32 -0
- package/AGENTS.md +99 -0
- package/LICENSE +18 -0
- package/NOTICE.md +45 -0
- package/README.md +301 -0
- package/SETUP.md +70 -0
- package/TESTING_SUMMARY.md +238 -0
- package/TEST_SUITE_STATUS.md +218 -0
- package/biome.json +48 -0
- package/dist/astro-service.d.ts +98 -0
- package/dist/astro-service.js +496 -0
- package/dist/chart-types.d.ts +52 -0
- package/dist/chart-types.js +51 -0
- package/dist/charts.d.ts +125 -0
- package/dist/charts.js +324 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +472 -0
- package/dist/constants.d.ts +81 -0
- package/dist/constants.js +76 -0
- package/dist/eclipses.d.ts +85 -0
- package/dist/eclipses.js +184 -0
- package/dist/ephemeris.d.ts +120 -0
- package/dist/ephemeris.js +379 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +22 -0
- package/dist/houses.d.ts +82 -0
- package/dist/houses.js +169 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +150 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +31 -0
- package/dist/logger.d.ts +25 -0
- package/dist/logger.js +73 -0
- package/dist/profile-store.d.ts +48 -0
- package/dist/profile-store.js +156 -0
- package/dist/riseset.d.ts +82 -0
- package/dist/riseset.js +185 -0
- package/dist/storage.d.ts +10 -0
- package/dist/storage.js +40 -0
- package/dist/time-utils.d.ts +68 -0
- package/dist/time-utils.js +136 -0
- package/dist/tool-registry.d.ts +35 -0
- package/dist/tool-registry.js +307 -0
- package/dist/tool-result.d.ts +175 -0
- package/dist/tool-result.js +188 -0
- package/dist/transits.d.ts +108 -0
- package/dist/transits.js +263 -0
- package/dist/types.d.ts +450 -0
- package/dist/types.js +161 -0
- package/example-usage.md +131 -0
- package/natal-chart.json +187 -0
- package/package.json +61 -0
- package/scripts/download-ephemeris.js +115 -0
- package/setup.sh +21 -0
- package/src/astro-service.ts +710 -0
- package/src/chart-types.ts +125 -0
- package/src/charts.ts +399 -0
- package/src/cli.ts +694 -0
- package/src/constants.ts +89 -0
- package/src/eclipses.ts +226 -0
- package/src/ephemeris.ts +437 -0
- package/src/formatter.ts +25 -0
- package/src/houses.ts +202 -0
- package/src/index.ts +170 -0
- package/src/loader.ts +36 -0
- package/src/logger.ts +104 -0
- package/src/profile-store.ts +285 -0
- package/src/riseset.ts +229 -0
- package/src/time-utils.ts +167 -0
- package/src/tool-registry.ts +357 -0
- package/src/tool-result.ts +283 -0
- package/src/transits.ts +352 -0
- package/src/types.ts +547 -0
- package/tests/README.md +173 -0
- package/tests/TESTING_STRATEGY.md +178 -0
- package/tests/fixtures/bowen-yang-chart.ts +69 -0
- package/tests/fixtures/calculate-expected.ts +81 -0
- package/tests/fixtures/expected-results.ts +117 -0
- package/tests/fixtures/generate-expected-simple.ts +94 -0
- package/tests/helpers/date-fixtures.ts +15 -0
- package/tests/helpers/ephem.ts +11 -0
- package/tests/helpers/temp.ts +9 -0
- package/tests/setup.ts +11 -0
- package/tests/unit/astro-service.test.ts +323 -0
- package/tests/unit/chart-types.test.ts +18 -0
- package/tests/unit/charts-errors.test.ts +42 -0
- package/tests/unit/charts.test.ts +157 -0
- package/tests/unit/cli-commands.test.ts +82 -0
- package/tests/unit/cli-profiles.test.ts +128 -0
- package/tests/unit/cli.test.ts +191 -0
- package/tests/unit/constants.test.ts +26 -0
- package/tests/unit/correctness-critical.test.ts +408 -0
- package/tests/unit/eclipses.test.ts +108 -0
- package/tests/unit/ephemeris.test.ts +213 -0
- package/tests/unit/error-handling.test.ts +116 -0
- package/tests/unit/formatter.test.ts +29 -0
- package/tests/unit/houses-errors.test.ts +27 -0
- package/tests/unit/houses-validation.test.ts +164 -0
- package/tests/unit/houses.test.ts +205 -0
- package/tests/unit/profile-store.test.ts +163 -0
- package/tests/unit/real-user-charts.test.ts +148 -0
- package/tests/unit/riseset.test.ts +106 -0
- package/tests/unit/solver-edges.test.ts +197 -0
- package/tests/unit/time-utils-temporal.test.ts +303 -0
- package/tests/unit/time-utils.test.ts +173 -0
- package/tests/unit/tool-registry.test.ts +222 -0
- package/tests/unit/tool-result.test.ts +45 -0
- package/tests/unit/transit-correctness.test.ts +78 -0
- package/tests/unit/transits.test.ts +238 -0
- package/tests/validation/README.md +32 -0
- package/tests/validation/adapters/astrolog.ts +306 -0
- package/tests/validation/adapters/internal.ts +184 -0
- package/tests/validation/compare/eclipses.ts +47 -0
- package/tests/validation/compare/houses.ts +76 -0
- package/tests/validation/compare/positions.ts +104 -0
- package/tests/validation/compare/riseSet.ts +48 -0
- package/tests/validation/compare/roots.ts +90 -0
- package/tests/validation/compare/transits.ts +69 -0
- package/tests/validation/fixtures/astrolog-parity/core.ts +194 -0
- package/tests/validation/fixtures/eclipses/core.ts +14 -0
- package/tests/validation/fixtures/houses/core.ts +47 -0
- package/tests/validation/fixtures/positions/core.ts +159 -0
- package/tests/validation/fixtures/rise-set/core.ts +20 -0
- package/tests/validation/fixtures/roots/core.ts +47 -0
- package/tests/validation/fixtures/transits/core.ts +61 -0
- package/tests/validation/fixtures/transits/dst.ts +21 -0
- package/tests/validation/oracle.spec.ts +129 -0
- package/tests/validation/utils/denseRootOracle.ts +269 -0
- package/tests/validation/utils/fixtureTypes.ts +146 -0
- package/tests/validation/utils/report.ts +60 -0
- package/tests/validation/utils/tolerances.ts +23 -0
- package/tests/validation/validation.spec.ts +836 -0
- package/tools/color-picker.html +388 -0
- package/tsconfig.json +17 -0
- 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
|
+
}
|
package/tests/README.md
ADDED
|
@@ -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
|