ether-to-astro 1.2.0 → 1.3.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/README.md +15 -5
- package/dist/astro-service/chart-output-service.d.ts +44 -0
- package/dist/astro-service/chart-output-service.js +110 -0
- package/dist/astro-service/date-input.d.ts +14 -0
- package/dist/astro-service/date-input.js +30 -0
- package/dist/astro-service/electional-service.d.ts +45 -0
- package/dist/astro-service/electional-service.js +305 -0
- package/dist/astro-service/natal-service.d.ts +41 -0
- package/dist/astro-service/natal-service.js +179 -0
- package/dist/astro-service/rising-sign-service.d.ts +37 -0
- package/dist/astro-service/rising-sign-service.js +137 -0
- package/dist/astro-service/service-types.d.ts +82 -0
- package/dist/astro-service/service-types.js +1 -0
- package/dist/astro-service/shared.d.ts +65 -0
- package/dist/astro-service/shared.js +98 -0
- package/dist/astro-service/sky-service.d.ts +48 -0
- package/dist/astro-service/sky-service.js +144 -0
- package/dist/astro-service/transit-service.d.ts +82 -0
- package/dist/astro-service/transit-service.js +353 -0
- package/dist/astro-service.d.ts +101 -89
- package/dist/astro-service.js +162 -1042
- package/dist/tool-registry.js +1 -1
- package/docs/product/architecture-boundaries.md +8 -0
- package/docs/releases/1.3.0.md +51 -0
- package/docs/releases/README.md +17 -0
- package/package.json +4 -1
- package/src/astro-service/chart-output-service.ts +155 -0
- package/src/astro-service/date-input.ts +40 -0
- package/src/astro-service/electional-service.ts +395 -0
- package/src/astro-service/natal-service.ts +235 -0
- package/src/astro-service/rising-sign-service.ts +181 -0
- package/src/astro-service/service-types.ts +90 -0
- package/src/astro-service/shared.ts +128 -0
- package/src/astro-service/sky-service.ts +191 -0
- package/src/astro-service/transit-service.ts +507 -0
- package/src/astro-service.ts +177 -1386
- package/src/tool-registry.ts +1 -1
- package/tests/README.md +15 -0
- package/tests/property/electional-service.property.test.ts +67 -0
- package/tests/property/helpers/arbitraries.ts +126 -0
- package/tests/property/helpers/config.ts +52 -0
- package/tests/property/helpers/runtime.ts +12 -0
- package/tests/property/houses.property.test.ts +74 -0
- package/tests/property/rising-sign-service.property.test.ts +255 -0
- package/tests/property/service-transits.property.test.ts +154 -0
- package/tests/property/time-utils.property.test.ts +91 -0
- package/tests/property/transits.property.test.ts +113 -0
- package/tests/unit/astro-service/chart-output-service.test.ts +102 -0
- package/tests/unit/astro-service/electional-service.test.ts +182 -0
- package/tests/unit/astro-service/natal-service.test.ts +126 -0
- package/tests/unit/astro-service/rising-sign-service.test.ts +145 -0
- package/tests/unit/astro-service/sky-service.test.ts +130 -0
- package/tests/unit/astro-service/transit-service.test.ts +312 -0
- package/tests/unit/astro-service.test.ts +136 -781
- package/tests/unit/rising-sign-windows.test.ts +93 -0
- package/tests/unit/tool-registry.test.ts +11 -0
- package/tests/validation/README.md +14 -0
- package/tests/validation/adapters/internal.ts +234 -4
- package/tests/validation/compare/electional.ts +151 -0
- package/tests/validation/compare/rising-sign-windows.ts +347 -0
- package/tests/validation/compare/service-transits.ts +205 -0
- package/tests/validation/fixtures/electional/core.ts +88 -0
- package/tests/validation/fixtures/rising-sign-windows/core.ts +57 -0
- package/tests/validation/fixtures/service-transits/core.ts +89 -0
- package/tests/validation/utils/fixtureTypes.ts +139 -1
- package/tests/validation/validation.spec.ts +82 -0
package/dist/tool-registry.js
CHANGED
|
@@ -175,7 +175,7 @@ export const MCP_TOOL_SPECS = [
|
|
|
175
175
|
},
|
|
176
176
|
include_mundane: {
|
|
177
177
|
type: 'boolean',
|
|
178
|
-
description: 'Include
|
|
178
|
+
description: 'Include deterministic mundane baseline data for the requested window. Output includes planetary positions, transit-to-transit mundane aspects, and non-narrative weather grouping metadata; forecast windows also include per-day mundane.days entries. Defaults to false.',
|
|
179
179
|
},
|
|
180
180
|
days_ahead: {
|
|
181
181
|
type: 'number',
|
|
@@ -195,6 +195,14 @@ Do not add a new MCP primitive when:
|
|
|
195
195
|
|
|
196
196
|
Based on the current product direction:
|
|
197
197
|
|
|
198
|
+
### Shared behavior boundary
|
|
199
|
+
|
|
200
|
+
- `src/astro-service.ts` remains the shared CLI/MCP behavior boundary.
|
|
201
|
+
- `AstroService` should stay a thin facade and orchestration layer.
|
|
202
|
+
- Shared implementation should usually live in the internal domain modules under `src/astro-service/`.
|
|
203
|
+
- New shared behavior should be added to the appropriate extracted service first, not piled back into the facade file.
|
|
204
|
+
- Public types may be re-exported from `src/astro-service.ts`, but domain logic should stay in the internal service modules unless there is a clear contract reason not to.
|
|
205
|
+
|
|
198
206
|
### Strong candidates for MCP
|
|
199
207
|
|
|
200
208
|
- range-aware transit forecast data
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# ether-to-astro v1.3.0
|
|
2
|
+
|
|
3
|
+
Suggested release type: `minor`
|
|
4
|
+
|
|
5
|
+
This release builds on `1.2.0` with a friendlier binary name, a cleaner internal service architecture, stronger validation/property coverage, and a small but explicit rising-sign payload correction. It also finishes the public contract alignment for the already-shipped mundane aspect and weather output.
|
|
6
|
+
|
|
7
|
+
## 1.3.0 (2026-03-29)
|
|
8
|
+
|
|
9
|
+
### New feature
|
|
10
|
+
|
|
11
|
+
- add `ether-to-astro` as a first-class CLI binary alias alongside `e2a` and `e2a-mcp`
|
|
12
|
+
- add a property-test lane via `npm run test:property` for generated invariants across time utilities, houses, transits, electional context, service-level transits, and rising-sign windows
|
|
13
|
+
- expand validation harness coverage to include service-layer electional context, rising-sign windows, and service transit serialization
|
|
14
|
+
|
|
15
|
+
### Bug fix
|
|
16
|
+
|
|
17
|
+
- ⚠️ Breaking change: `getRisingSignWindows` structured window payloads now emit `durationMs` instead of `durationMinutes`
|
|
18
|
+
- use raw Sun altitude for electional sect classification so slightly negative values that round to `0.00` are still classified correctly as night charts
|
|
19
|
+
- keep rising-sign exact-vs-approximate precision checks active even when approximate windows only match a coarse subsequence of exact windows
|
|
20
|
+
- align the rising-sign property helper with the validation comparator so the generated invariant measures the first real departure from the left sign instead of assuming a direct sign-to-sign jump
|
|
21
|
+
|
|
22
|
+
### Documentation Changes
|
|
23
|
+
|
|
24
|
+
- update `get_transits` contract wording so `include_mundane` describes the shipped mundane positions, aspects, weather metadata, and forecast `mundane.days[]` behavior
|
|
25
|
+
- refresh README transit docs so public docs no longer imply range-aware mundane output is still tracked separately
|
|
26
|
+
|
|
27
|
+
### ⚙️ Chore
|
|
28
|
+
|
|
29
|
+
- extract transit, electional, rising-sign, natal, sky, and chart-output workflows into focused `src/astro-service/*` modules while preserving the shared `AstroService` surface
|
|
30
|
+
- finish migration to shared service types and reorganized test seams so service behavior is easier to validate without surface drift
|
|
31
|
+
- add service-layer validation coverage and a new `npm run test:property` lane for generated invariant checks across core astro and service behavior
|
|
32
|
+
- add regression coverage for the rising-sign precision no-op scenario and contract coverage for mundane schema wording drift
|
|
33
|
+
|
|
34
|
+
## Upgrade notes
|
|
35
|
+
|
|
36
|
+
- 🚨 Breaking change: `getRisingSignWindows` now emits `durationMs` for each window object
|
|
37
|
+
- ❌ `durationMinutes` is no longer present in the structured rising-sign payload
|
|
38
|
+
- 🔧 if you consume rising-sign output programmatically, update any client code that reads or displays the old minute-based field
|
|
39
|
+
- 🧭 if you consume `include_mundane` programmatically, the docs/schema now reflect the richer behavior already shipping on `main`
|
|
40
|
+
|
|
41
|
+
## Included PRs
|
|
42
|
+
|
|
43
|
+
- #44 fix electional sect classification to use raw Sun altitude
|
|
44
|
+
- #46 extract transit workflow into focused service layer modules
|
|
45
|
+
- #49 add `ether-to-astro` binary alias
|
|
46
|
+
- #50 extract electional and rising-sign workflows
|
|
47
|
+
- #51 extract natal, sky, and chart-output workflows
|
|
48
|
+
- #52 finish service docs/types/test migration
|
|
49
|
+
- #53 add service-layer validation coverage and enforce the `durationMs` contract
|
|
50
|
+
- #54 add property-based invariant coverage
|
|
51
|
+
- #55 keep rising-sign precision validation active and align mundane contract docs/schema with shipped behavior
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Releases
|
|
2
|
+
|
|
3
|
+
Release notes for `ether-to-astro` live in this directory.
|
|
4
|
+
|
|
5
|
+
Conventions:
|
|
6
|
+
|
|
7
|
+
- Use one draft file per pending release, named like `1.2.0-draft.md`.
|
|
8
|
+
- Call out breaking API or payload changes explicitly in an `Upgrade notes` section.
|
|
9
|
+
- If a breaking change is kept despite early release-stage flexibility, say so plainly instead of hiding it in feature bullets.
|
|
10
|
+
|
|
11
|
+
Current draft:
|
|
12
|
+
|
|
13
|
+
- [v1.2.0 draft](/Users/salted/Code/astro-mcp/docs/releases/1.2.0-draft.md)
|
|
14
|
+
|
|
15
|
+
Released notes:
|
|
16
|
+
|
|
17
|
+
- [v1.3.0 notes](/Users/salted/Code/astro-mcp/docs/releases/1.3.0.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ether-to-astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Local-first astrology toolkit with a unified e2a binary for CLI and MCP workflows, plus an e2a-mcp compatibility alias.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"node": ">=22"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
+
"ether-to-astro": "dist/loader.js",
|
|
11
12
|
"e2a": "dist/loader.js",
|
|
12
13
|
"e2a-mcp": "dist/mcp-alias.js"
|
|
13
14
|
},
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"start:cli": "node dist/loader.js",
|
|
20
21
|
"postinstall": "node scripts/download-ephemeris.js",
|
|
21
22
|
"test": "vitest tests/unit",
|
|
23
|
+
"test:property": "vitest run tests/property",
|
|
22
24
|
"validate:astro": "vitest run tests/validation",
|
|
23
25
|
"test:ui": "vitest --ui",
|
|
24
26
|
"test:coverage": "vitest run tests/unit --coverage",
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
"@types/node": "^20.0.0",
|
|
57
59
|
"@vitest/coverage-v8": "^4.1.2",
|
|
58
60
|
"@vitest/ui": "^4.1.2",
|
|
61
|
+
"fast-check": "^4.6.0",
|
|
59
62
|
"happy-dom": "^20.8.9",
|
|
60
63
|
"tsx": "^4.21.0",
|
|
61
64
|
"typescript": "^6.0.2",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ChartRenderer } from '../charts.js';
|
|
2
|
+
import { getDefaultTheme } from '../constants.js';
|
|
3
|
+
import { formatDateOnly } from '../formatter.js';
|
|
4
|
+
import { localToUTC, utcToLocal } from '../time-utils.js';
|
|
5
|
+
import type { NatalChart } from '../types.js';
|
|
6
|
+
import { parseDateOnlyInput } from './date-input.js';
|
|
7
|
+
import type { GenerateChartInput, GenerateTransitChartInput } from './service-types.js';
|
|
8
|
+
|
|
9
|
+
interface ChartServiceResult {
|
|
10
|
+
format: 'svg' | 'png' | 'webp';
|
|
11
|
+
outputPath?: string;
|
|
12
|
+
text: string;
|
|
13
|
+
svg?: string;
|
|
14
|
+
image?: {
|
|
15
|
+
data: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ChartOutputServiceDependencies {
|
|
21
|
+
chartRenderer: ChartRenderer;
|
|
22
|
+
now: () => Date;
|
|
23
|
+
writeFile: (path: string, data: string | Buffer, encoding?: BufferEncoding) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Internal chart rendering/output workflow used by `AstroService`.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* This module owns theme defaults, target-date resolution, and inline-vs-file
|
|
31
|
+
* output serialization for natal and transit chart rendering.
|
|
32
|
+
*/
|
|
33
|
+
export class ChartOutputService {
|
|
34
|
+
private readonly chartRenderer: ChartRenderer;
|
|
35
|
+
private readonly now: () => Date;
|
|
36
|
+
private readonly writeFile: (
|
|
37
|
+
path: string,
|
|
38
|
+
data: string | Buffer,
|
|
39
|
+
encoding?: BufferEncoding
|
|
40
|
+
) => Promise<void>;
|
|
41
|
+
|
|
42
|
+
constructor(deps: ChartOutputServiceDependencies) {
|
|
43
|
+
this.chartRenderer = deps.chartRenderer;
|
|
44
|
+
this.now = deps.now;
|
|
45
|
+
this.writeFile = deps.writeFile;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate a natal chart image or SVG for the current chart.
|
|
50
|
+
*/
|
|
51
|
+
async generateNatalChart(
|
|
52
|
+
natalChart: NatalChart,
|
|
53
|
+
input: GenerateChartInput = {}
|
|
54
|
+
): Promise<ChartServiceResult> {
|
|
55
|
+
const theme = input.theme || getDefaultTheme(natalChart.location.timezone);
|
|
56
|
+
const format = input.format || 'svg';
|
|
57
|
+
const outputPath = input.output_path;
|
|
58
|
+
const chart = await this.chartRenderer.generateNatalChart(natalChart, theme, format);
|
|
59
|
+
|
|
60
|
+
if (outputPath) {
|
|
61
|
+
if (format === 'svg') {
|
|
62
|
+
await this.writeFile(outputPath, chart as string, 'utf-8');
|
|
63
|
+
} else {
|
|
64
|
+
await this.writeFile(outputPath, chart as Buffer);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
format,
|
|
68
|
+
outputPath,
|
|
69
|
+
text: `Natal Chart for ${natalChart.name} saved to: ${outputPath}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (format === 'svg') {
|
|
74
|
+
return {
|
|
75
|
+
format,
|
|
76
|
+
text: `Natal Chart for ${natalChart.name}:`,
|
|
77
|
+
svg: chart as string,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
format,
|
|
83
|
+
text: `Natal Chart for ${natalChart.name} (${theme} theme, ${format.toUpperCase()} format):`,
|
|
84
|
+
image: {
|
|
85
|
+
data: (chart as Buffer).toString('base64'),
|
|
86
|
+
mimeType: format === 'png' ? 'image/png' : 'image/webp',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate a transit chart image or SVG for a target date.
|
|
93
|
+
*/
|
|
94
|
+
async generateTransitChart(
|
|
95
|
+
natalChart: NatalChart,
|
|
96
|
+
input: GenerateTransitChartInput = {}
|
|
97
|
+
): Promise<ChartServiceResult> {
|
|
98
|
+
const theme = input.theme ?? getDefaultTheme(natalChart.location.timezone);
|
|
99
|
+
const format = input.format ?? 'svg';
|
|
100
|
+
const targetDate = this.resolveTransitTargetDate(natalChart, input.date);
|
|
101
|
+
const outputPath = input.output_path;
|
|
102
|
+
const chart = await this.chartRenderer.generateTransitChart(
|
|
103
|
+
natalChart,
|
|
104
|
+
targetDate,
|
|
105
|
+
theme,
|
|
106
|
+
format
|
|
107
|
+
);
|
|
108
|
+
const dateLabel = formatDateOnly(targetDate, natalChart.location.timezone);
|
|
109
|
+
|
|
110
|
+
if (outputPath) {
|
|
111
|
+
if (format === 'svg') {
|
|
112
|
+
await this.writeFile(outputPath, chart as string, 'utf-8');
|
|
113
|
+
} else {
|
|
114
|
+
await this.writeFile(outputPath, chart as Buffer);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
format,
|
|
118
|
+
outputPath,
|
|
119
|
+
text: `Transit Chart for ${natalChart.name} (${dateLabel}) saved to ${outputPath}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (format === 'svg') {
|
|
124
|
+
return {
|
|
125
|
+
format,
|
|
126
|
+
text: `Transit Chart for ${natalChart.name} (${dateLabel})`,
|
|
127
|
+
svg: chart as string,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
format,
|
|
133
|
+
text: `Transit Chart for ${natalChart.name} (${dateLabel}, ${theme} theme, ${format.toUpperCase()} format):`,
|
|
134
|
+
image: {
|
|
135
|
+
data: (chart as Buffer).toString('base64'),
|
|
136
|
+
mimeType: format === 'png' ? 'image/png' : 'image/webp',
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the local-noon transit anchor when the caller omits a date.
|
|
143
|
+
*/
|
|
144
|
+
private resolveTransitTargetDate(natalChart: NatalChart, dateStr?: string): Date {
|
|
145
|
+
if (dateStr) {
|
|
146
|
+
const parsed = parseDateOnlyInput(dateStr);
|
|
147
|
+
return localToUTC(parsed, natalChart.location.timezone);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const now = this.now();
|
|
151
|
+
const localNow = utcToLocal(now, natalChart.location.timezone);
|
|
152
|
+
const localNoon = { ...localNow, hour: 12, minute: 0, second: 0 };
|
|
153
|
+
return localToUTC(localNoon, natalChart.location.timezone);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a date-only input into local noon components.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The service treats date-only transit requests as local-noon lookups so the
|
|
8
|
+
* requested calendar day remains stable across timezone conversions.
|
|
9
|
+
*/
|
|
10
|
+
export function parseDateOnlyInput(dateStr: string): {
|
|
11
|
+
year: number;
|
|
12
|
+
month: number;
|
|
13
|
+
day: number;
|
|
14
|
+
hour: number;
|
|
15
|
+
minute: number;
|
|
16
|
+
} {
|
|
17
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr);
|
|
18
|
+
if (!match) {
|
|
19
|
+
throw new Error(`Invalid date format: expected YYYY-MM-DD, got "${dateStr}"`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const year = Number(match[1]);
|
|
23
|
+
const month = Number(match[2]);
|
|
24
|
+
const day = Number(match[3]);
|
|
25
|
+
|
|
26
|
+
if (month < 1 || month > 12) {
|
|
27
|
+
throw new Error(`Invalid month: ${month} (must be 1-12)`);
|
|
28
|
+
}
|
|
29
|
+
if (day < 1 || day > 31) {
|
|
30
|
+
throw new Error(`Invalid day: ${day} (must be 1-31)`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
Temporal.PlainDate.from({ year, month, day });
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(`Invalid calendar date: ${dateStr}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { year, month, day, hour: 12, minute: 0 };
|
|
40
|
+
}
|