ether-to-astro 1.0.2 → 1.2.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 (60) hide show
  1. package/.github/ISSUE_TEMPLATE/bug-report.yml +87 -0
  2. package/.github/ISSUE_TEMPLATE/capability-request.yml +117 -0
  3. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. package/.github/ISSUE_TEMPLATE/paper-cut.yml +59 -0
  5. package/.github/pull_request_template.md +1 -0
  6. package/.github/workflows/release.yml +2 -2
  7. package/.github/workflows/test.yml +2 -2
  8. package/AGENTS.md +46 -1
  9. package/DEVELOPER.md +78 -0
  10. package/README.md +128 -75
  11. package/SETUP.md +100 -41
  12. package/dist/astro-service.d.ts +51 -2
  13. package/dist/astro-service.js +660 -56
  14. package/dist/cli.js +31 -0
  15. package/dist/entrypoint.d.ts +13 -0
  16. package/dist/entrypoint.js +78 -0
  17. package/dist/ephemeris.d.ts +15 -0
  18. package/dist/ephemeris.js +33 -0
  19. package/dist/formatter.d.ts +5 -1
  20. package/dist/formatter.js +4 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +63 -114
  23. package/dist/loader.d.ts +1 -1
  24. package/dist/loader.js +61 -23
  25. package/dist/mcp-alias.d.ts +2 -0
  26. package/dist/mcp-alias.js +8 -0
  27. package/dist/time-utils.d.ts +8 -0
  28. package/dist/time-utils.js +16 -0
  29. package/dist/tool-registry.js +111 -5
  30. package/dist/tool-result.d.ts +8 -0
  31. package/dist/tool-result.js +39 -0
  32. package/dist/types.d.ts +79 -1
  33. package/docs/product/adrs/0001-mcp-vs-skill-boundary.md +96 -0
  34. package/docs/product/architecture-boundaries.md +223 -0
  35. package/docs/product/product-tenets.md +174 -0
  36. package/docs/releases/1.2.0-draft.md +48 -0
  37. package/package.json +7 -7
  38. package/skills/.curated/daily-brief/SKILL.md +75 -0
  39. package/skills/.curated/electional-overlay/SKILL.md +67 -0
  40. package/skills/.curated/weekly-overview/SKILL.md +73 -0
  41. package/skills/.system/write-skill/SKILL.md +90 -0
  42. package/src/astro-service.ts +861 -60
  43. package/src/cli.ts +84 -0
  44. package/src/entrypoint.ts +118 -0
  45. package/src/ephemeris.ts +44 -0
  46. package/src/formatter.ts +13 -1
  47. package/src/index.ts +77 -121
  48. package/src/loader.ts +69 -25
  49. package/src/mcp-alias.ts +10 -0
  50. package/src/time-utils.ts +18 -0
  51. package/src/tool-registry.ts +129 -9
  52. package/src/tool-result.ts +44 -0
  53. package/src/types.ts +91 -1
  54. package/tests/unit/astro-service.test.ts +751 -5
  55. package/tests/unit/cli-commands.test.ts +13 -0
  56. package/tests/unit/entrypoint.test.ts +67 -0
  57. package/tests/unit/error-mapping.test.ts +20 -0
  58. package/tests/unit/formatter.test.ts +6 -0
  59. package/tests/unit/tool-registry.test.ts +114 -2
  60. package/setup.sh +0 -21
@@ -58,9 +58,109 @@ export const MCP_TOOL_SPECS = [
58
58
  return { kind: 'state', data: result.data, text: result.text, natalChart: result.chart };
59
59
  },
60
60
  },
61
+ {
62
+ name: 'get_rising_sign_windows',
63
+ description: 'Get local-time windows for which zodiac signs are rising on a given date and location. Returns deterministic intervals only (no ranking or interpretation).',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ date: {
68
+ type: 'string',
69
+ description: 'Target local date (YYYY-MM-DD)',
70
+ },
71
+ latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
72
+ longitude: {
73
+ type: 'number',
74
+ description: 'Longitude in decimal degrees (-180 to 180)',
75
+ },
76
+ timezone: {
77
+ type: 'string',
78
+ description: 'IANA timezone (e.g., America/New_York)',
79
+ },
80
+ mode: {
81
+ type: 'string',
82
+ enum: ['approximate', 'exact'],
83
+ description: 'Boundary mode. approximate uses coarser stepping; exact refines sign-change boundaries.',
84
+ default: 'approximate',
85
+ },
86
+ },
87
+ required: ['date', 'latitude', 'longitude', 'timezone'],
88
+ },
89
+ requiresNatalChart: false,
90
+ execute: (ctx, args) => {
91
+ const result = ctx.service.getRisingSignWindows({
92
+ date: args.date,
93
+ latitude: args.latitude,
94
+ longitude: args.longitude,
95
+ timezone: args.timezone,
96
+ mode: args.mode,
97
+ });
98
+ return { kind: 'state', data: result.data, text: result.text };
99
+ },
100
+ },
101
+ {
102
+ name: 'get_electional_context',
103
+ description: 'Get stateless electional context for a specific local date, time, and location. Returns deterministic timing facts such as ascendant, sect/day-night classification, Moon phase, applying aspects, and optional ascendant-ruler basics. This tool does not require a natal chart and is separate from get_transits.',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ date: {
108
+ type: 'string',
109
+ description: 'Target local date (YYYY-MM-DD)',
110
+ },
111
+ time: {
112
+ type: 'string',
113
+ description: 'Target local time (HH:mm or HH:mm:ss). DST-ambiguous or nonexistent local times are rejected.',
114
+ },
115
+ timezone: {
116
+ type: 'string',
117
+ description: 'IANA timezone (e.g., America/New_York)',
118
+ },
119
+ latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
120
+ longitude: {
121
+ type: 'number',
122
+ description: 'Longitude in decimal degrees (-180 to 180)',
123
+ },
124
+ house_system: {
125
+ type: 'string',
126
+ enum: ['P', 'K', 'W', 'R'],
127
+ description: 'House system used for ascendant extraction: P=Placidus (default), K=Koch, W=Whole Sign, R=Regiomontanus.',
128
+ },
129
+ include_ruler_basics: {
130
+ type: 'boolean',
131
+ description: 'Include ascendant-ruler position, speed, and retrograde flag. Defaults to false.',
132
+ },
133
+ include_planetary_applications: {
134
+ type: 'boolean',
135
+ description: 'Include applying major aspects between current planets. Defaults to true.',
136
+ },
137
+ orb_degrees: {
138
+ type: 'number',
139
+ description: 'Orb for electional aspect detection in degrees. Defaults to 3 and must be between 0.1 and 10.',
140
+ default: 3,
141
+ },
142
+ },
143
+ required: ['date', 'time', 'timezone', 'latitude', 'longitude'],
144
+ },
145
+ requiresNatalChart: false,
146
+ execute: (ctx, args) => {
147
+ const result = ctx.service.getElectionalContext({
148
+ date: args.date,
149
+ time: args.time,
150
+ timezone: args.timezone,
151
+ latitude: args.latitude,
152
+ longitude: args.longitude,
153
+ house_system: args.house_system,
154
+ include_ruler_basics: args.include_ruler_basics,
155
+ include_planetary_applications: args.include_planetary_applications,
156
+ orb_degrees: args.orb_degrees,
157
+ });
158
+ return { kind: 'state', data: result.data, text: result.text };
159
+ },
160
+ },
61
161
  {
62
162
  name: 'get_transits',
63
- description: 'Get transits (aspects between current/future planets and natal chart). Returns aspects within orb, with exact timing when close. Date defaults to today at local noon in the natal chart timezone.',
163
+ description: 'Get transits (aspects between current/future planets and natal chart). Each transit includes additive placement metadata for both sides (sign, degree, house) so clients can render activation context without reconstructing house logic. Supports mode=snapshot (single-day), mode=best_hit (multi-day compressed preview), and mode=forecast (day-grouped output). If mode is omitted, legacy behavior is preserved: days_ahead=0 resolves to snapshot and days_ahead>0 resolves to best_hit.',
64
164
  inputSchema: {
65
165
  type: 'object',
66
166
  properties: {
@@ -79,9 +179,14 @@ export const MCP_TOOL_SPECS = [
79
179
  },
80
180
  days_ahead: {
81
181
  type: 'number',
82
- description: 'Number of days to look ahead for upcoming transits. 0 = today only. Defaults to 0.',
182
+ description: 'Number of days to look ahead. In snapshot mode only the start day is used. If mode is omitted, legacy behavior is preserved: 0 resolves to snapshot and values > 0 resolve to best_hit.',
83
183
  default: 0,
84
184
  },
185
+ mode: {
186
+ type: 'string',
187
+ enum: ['snapshot', 'best_hit', 'forecast'],
188
+ description: 'Transit output mode: snapshot=single-day, best_hit=compressed preview across range, forecast=day-grouped output. If omitted, legacy behavior is preserved.',
189
+ },
85
190
  max_orb: {
86
191
  type: 'number',
87
192
  description: 'Maximum orb in degrees to include. Defaults to 8.',
@@ -104,6 +209,7 @@ export const MCP_TOOL_SPECS = [
104
209
  categories: args.categories,
105
210
  include_mundane: args.include_mundane,
106
211
  days_ahead: args.days_ahead,
212
+ mode: args.mode,
107
213
  max_orb: args.max_orb,
108
214
  exact_only: args.exact_only,
109
215
  applying_only: args.applying_only,
@@ -139,7 +245,7 @@ export const MCP_TOOL_SPECS = [
139
245
  inputSchema: { type: 'object', properties: {} },
140
246
  requiresNatalChart: false,
141
247
  execute: (ctx, args) => {
142
- const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
248
+ const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
143
249
  const result = ctx.service.getRetrogradePlanets(timezone);
144
250
  return { kind: 'state', data: result.data, text: result.text };
145
251
  },
@@ -160,7 +266,7 @@ export const MCP_TOOL_SPECS = [
160
266
  inputSchema: { type: 'object', properties: {} },
161
267
  requiresNatalChart: false,
162
268
  execute: (ctx, args) => {
163
- const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
269
+ const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
164
270
  const result = ctx.service.getAsteroidPositions(timezone);
165
271
  return { kind: 'state', data: result.data, text: result.text };
166
272
  },
@@ -171,7 +277,7 @@ export const MCP_TOOL_SPECS = [
171
277
  inputSchema: { type: 'object', properties: {} },
172
278
  requiresNatalChart: false,
173
279
  execute: (ctx, args) => {
174
- const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
280
+ const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
175
281
  const result = ctx.service.getNextEclipses(timezone);
176
282
  return { kind: 'state', data: result.data, text: result.text };
177
283
  },
@@ -125,6 +125,14 @@ export declare function mcpError(error: ToolIssue): {
125
125
  * agent-recoverable error codes with suggested fixes.
126
126
  */
127
127
  export declare function mapSweError(context: string, err: unknown, details?: Record<string, unknown>): ToolIssue;
128
+ /**
129
+ * Map generic error messages to structured tool issue codes.
130
+ *
131
+ * @remarks
132
+ * Primarily used at the MCP boundary when unknown errors are caught
133
+ * and need a best-effort classification.
134
+ */
135
+ export declare function mapToolErrorMessageToCode(errorMessage: string): ToolIssueCode;
128
136
  /**
129
137
  * Create a structured error for missing rise/set events
130
138
  *
@@ -107,6 +107,45 @@ export function mapSweError(context, err, details) {
107
107
  details: { ...details, rawMessage: message },
108
108
  };
109
109
  }
110
+ /**
111
+ * Map generic error messages to structured tool issue codes.
112
+ *
113
+ * @remarks
114
+ * Primarily used at the MCP boundary when unknown errors are caught
115
+ * and need a best-effort classification.
116
+ */
117
+ export function mapToolErrorMessageToCode(errorMessage) {
118
+ if (errorMessage.includes('Invalid date format') ||
119
+ errorMessage.includes('Invalid calendar date') ||
120
+ errorMessage.includes('Invalid month') ||
121
+ errorMessage.includes('Invalid day') ||
122
+ errorMessage.includes('days_ahead') ||
123
+ errorMessage.includes('max_orb') ||
124
+ errorMessage.includes('missing julianDay') ||
125
+ errorMessage.includes('Invalid mode') ||
126
+ errorMessage.includes('Invalid latitude') ||
127
+ errorMessage.includes('Invalid longitude')) {
128
+ return 'INVALID_INPUT';
129
+ }
130
+ if (errorMessage.includes('Invalid timezone') || errorMessage.includes('timezone')) {
131
+ return 'INVALID_TIMEZONE';
132
+ }
133
+ if (errorMessage.includes('Invalid house system')) {
134
+ return 'INVALID_HOUSE_SYSTEM';
135
+ }
136
+ if (errorMessage.includes('Ephemeris') || errorMessage.includes('ephemeris')) {
137
+ return 'EPHEMERIS_COMPUTE_FAILED';
138
+ }
139
+ if (errorMessage.includes('write') ||
140
+ errorMessage.includes('ENOENT') ||
141
+ errorMessage.includes('EACCES')) {
142
+ return 'FILE_WRITE_FAILED';
143
+ }
144
+ if (errorMessage.includes('render') || errorMessage.includes('chart')) {
145
+ return 'CHART_RENDER_FAILED';
146
+ }
147
+ return 'INTERNAL_ERROR';
148
+ }
110
149
  /**
111
150
  * Create a structured error for missing rise/set events
112
151
  *
package/dist/types.d.ts CHANGED
@@ -54,6 +54,12 @@ export interface NatalChart {
54
54
  * For polar latitudes (>66°), Whole Sign ('W') may be used as fallback.
55
55
  */
56
56
  houseSystem?: HouseSystem;
57
+ /**
58
+ * Explicit house system requested when the natal chart was created
59
+ * @remarks
60
+ * This stays undefined when the caller relied on runtime defaults.
61
+ */
62
+ requestedHouseSystem?: HouseSystem;
57
63
  /**
58
64
  * UTC equivalent of birth time
59
65
  * @remarks
@@ -181,6 +187,18 @@ export interface TransitData extends BaseTransit {
181
187
  * May be undefined if aspect is not within orb or exact time not calculated
182
188
  */
183
189
  exactTime?: string;
190
+ /** Zodiac sign of the transiting planet */
191
+ transitSign?: string;
192
+ /** Degree of the transiting planet within its sign (0-30) */
193
+ transitDegree?: number;
194
+ /** House occupied by the transiting planet at the transit calculation time */
195
+ transitHouse?: number;
196
+ /** Zodiac sign of the natal planet */
197
+ natalSign?: string;
198
+ /** Degree of the natal planet within its sign (0-30) */
199
+ natalDegree?: number;
200
+ /** House occupied by the natal planet in the natal chart */
201
+ natalHouse?: number;
184
202
  }
185
203
  /**
186
204
  * Response wrapper for transit data
@@ -192,8 +210,12 @@ export interface TransitData extends BaseTransit {
192
210
  export interface TransitResponse {
193
211
  /** ISO date of the transit calculation (YYYY-MM-DD) */
194
212
  date: string;
195
- /** Timezone used for the calculation */
213
+ /** Timezone used for reporting and user-facing day labels */
196
214
  timezone: string;
215
+ /** Natal/local timezone used for date interpretation and astro calculations */
216
+ calculation_timezone?: string;
217
+ /** Timezone used for reporting and user-facing day labels */
218
+ reporting_timezone?: string;
197
219
  /** Array of all active transits for the date */
198
220
  transits: TransitData[];
199
221
  }
@@ -211,6 +233,7 @@ export interface PlanetPositionResponse {
211
233
  /** Array of planet positions */
212
234
  positions: PlanetPosition[];
213
235
  }
236
+ export type ElectionalHouseSystem = Extract<HouseSystem, 'P' | 'K' | 'W' | 'R'>;
214
237
  /**
215
238
  * Types of astrological aspects
216
239
  *
@@ -219,6 +242,61 @@ export interface PlanetPositionResponse {
219
242
  * specific types of interactions and energies.
220
243
  */
221
244
  export type AspectType = 'conjunction' | 'opposition' | 'square' | 'trine' | 'sextile';
245
+ export type ElectionalPhaseName = 'new' | 'crescent' | 'first_quarter' | 'gibbous' | 'full' | 'disseminating' | 'last_quarter' | 'balsamic';
246
+ export interface ElectionalAspect {
247
+ from_body: PlanetName;
248
+ to_body: PlanetName;
249
+ aspect: AspectType;
250
+ orb: number;
251
+ applying: boolean;
252
+ exact_at_utc?: string;
253
+ }
254
+ export interface ElectionalContextResponse {
255
+ input: {
256
+ date: string;
257
+ time: string;
258
+ timezone: string;
259
+ latitude: number;
260
+ longitude: number;
261
+ house_system: ElectionalHouseSystem;
262
+ instant_utc: string;
263
+ jd_ut: number;
264
+ };
265
+ ascendant: {
266
+ longitude: number;
267
+ sign: string;
268
+ degree_in_sign: number;
269
+ };
270
+ sect: {
271
+ is_day_chart: boolean;
272
+ sun_altitude_degrees: number;
273
+ classification: 'day' | 'night';
274
+ };
275
+ moon: {
276
+ longitude: number;
277
+ sign: string;
278
+ phase_angle: number;
279
+ phase_name: ElectionalPhaseName;
280
+ is_void_of_course: boolean | null;
281
+ applying_aspects?: ElectionalAspect[];
282
+ };
283
+ applying_aspects?: ElectionalAspect[];
284
+ ruler_basics?: {
285
+ asc_sign_ruler: {
286
+ body: PlanetName;
287
+ longitude: number;
288
+ sign: string;
289
+ speed: number;
290
+ is_retrograde: boolean;
291
+ };
292
+ };
293
+ meta: {
294
+ deterministic: true;
295
+ requires_natal: false;
296
+ warnings: string[];
297
+ deferred_features: string[];
298
+ };
299
+ }
222
300
  /**
223
301
  * Aspect definitions with angles and default orbs
224
302
  *
@@ -0,0 +1,96 @@
1
+ # ADR 0001: MCP Vs Skill Boundary
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-03-28
5
+
6
+ ## Context
7
+
8
+ `ether-to-astro` is evolving from a set of astrology utilities into infrastructure for AI-native workflows.
9
+
10
+ Recent product exploration introduced requests for:
11
+
12
+ - daily and weekly transit reporting,
13
+ - mundane baseline plus natal modifier reasoning,
14
+ - electional support,
15
+ - Whole Sign-first house activation reporting,
16
+ - and preference-aware planning workflows.
17
+
18
+ Without an explicit boundary, it would be easy to grow the MCP surface into a large collection of personalized reporting tools. That would make the product harder to maintain, harder to reuse, and harder to reason about.
19
+
20
+ ## Decision
21
+
22
+ We will keep the boundary as follows:
23
+
24
+ - `MCP tools` own reusable astrological computation and structured primitives.
25
+ - `Skills or workflows` own synthesis, ranking, personalization, and report generation.
26
+ - `MCP prompts` may expose common workflows for discovery, but they do not become the main home for product logic.
27
+ - `Profiles or preferences` own durable user-specific output choices and heuristics.
28
+
29
+ ## Consequences
30
+
31
+ ### Positive
32
+
33
+ - MCP stays focused and reusable.
34
+ - Skills can evolve quickly without destabilizing the computational layer.
35
+ - Personalized reporting can improve without forcing product-wide API churn.
36
+ - New agents and clients get a clearer mental model of the system.
37
+
38
+ ### Negative
39
+
40
+ - Some workflows will require multiple calls instead of one large convenience tool.
41
+ - Skill authors must still do synthesis work rather than relying on all-in-one MCP endpoints.
42
+ - We need stronger docs and examples so the boundary remains easy to apply.
43
+
44
+ ## Decision Rules
45
+
46
+ Add a capability to MCP when:
47
+
48
+ - it is deterministic or mostly deterministic,
49
+ - it is computation-heavy,
50
+ - reusable across multiple workflows,
51
+ - and risky or repetitive to reconstruct client-side.
52
+
53
+ Keep a capability in a skill when:
54
+
55
+ - it is interpretation-heavy,
56
+ - it is deterministic but mainly encodes workflow policy or ranking,
57
+ - user-specific,
58
+ - likely to evolve rapidly,
59
+ - or achievable by a competent LLM in two calls to stable primitives.
60
+
61
+ ## Examples
62
+
63
+ ### Belongs In MCP
64
+
65
+ - transit forecast data grouped by day
66
+ - mundane aspects
67
+ - house activation metadata
68
+ - electional primitives
69
+ - rising-sign windows
70
+
71
+ ### Belongs In Skills
72
+
73
+ - daily briefing
74
+ - weekly overview synthesis
75
+ - “top influences” ranking
76
+ - best-use guidance
77
+ - personalized electional scoring
78
+
79
+ ### Does Not Belong In MCP
80
+
81
+ - one-user workflow tags
82
+ - customized work or life advice
83
+ - generic prose-generation tools that a skill could perform from structured data
84
+
85
+ ## Follow-Up
86
+
87
+ This ADR implies:
88
+
89
+ - future MCP proposals should justify why the capability cannot stay in a skill,
90
+ - new reporting workflows should start life outside MCP,
91
+ - and docs should make the two-call skill model the default design assumption.
92
+
93
+ ## Related Docs
94
+
95
+ - [Product Tenets](/Users/salted/Code/astro-mcp/docs/product/product-tenets.md)
96
+ - [Architecture Boundaries](/Users/salted/Code/astro-mcp/docs/product/architecture-boundaries.md)
@@ -0,0 +1,223 @@
1
+ # Architecture Boundaries
2
+
3
+ This document translates the product tenets into concrete repository and interface boundaries.
4
+
5
+ ## System Layers
6
+
7
+ The product has four distinct layers:
8
+
9
+ 1. `MCP tools`
10
+ Source of truth for reusable astrological computation.
11
+ 2. `MCP prompts`
12
+ Thin public entry points that help clients discover or initiate common workflows.
13
+ 3. `Assistant skills or workflows`
14
+ AI-native orchestration, synthesis, ranking, and formatting built on top of MCP tools.
15
+ 4. `Profiles or preferences`
16
+ User- or workflow-specific settings that shape output without polluting generic tool contracts.
17
+
18
+ ## The Boundary In One Sentence
19
+
20
+ MCP computes and exposes facts. Skills decide what matters, how to rank it, and how to present it.
21
+
22
+ ## Determinism Rule
23
+
24
+ Deterministic, generic astrology outputs are strong candidates for MCP.
25
+
26
+ This includes:
27
+
28
+ - added flags on existing tools,
29
+ - expanded date windows,
30
+ - grouped forecast output,
31
+ - generic derived astro signals,
32
+ - and reusable metadata that multiple clients would otherwise reconstruct themselves.
33
+
34
+ Determinism is not sufficient by itself.
35
+
36
+ Deterministic outputs should still stay out of MCP when they primarily encode:
37
+
38
+ - personalized policy,
39
+ - workflow-specific scoring,
40
+ - subjective ranking,
41
+ - or narrative conclusions.
42
+
43
+ ## What Counts As A Primitive
44
+
45
+ A primitive is a structured output that:
46
+
47
+ - represents reusable astrological truth,
48
+ - is deterministic or mostly deterministic for the same inputs,
49
+ - is stable across multiple workflows,
50
+ - and does not depend on one user’s preferred interpretation style.
51
+
52
+ Examples of good primitives:
53
+
54
+ - transit events
55
+ - date-grouped forecast windows
56
+ - mundane aspects
57
+ - house activations
58
+ - Moon condition
59
+ - ASC sign and ruler condition
60
+ - rising-sign windows
61
+
62
+ Examples of non-primitives:
63
+
64
+ - “top 3 influences today”
65
+ - “best time to influence leadership”
66
+ - “high opportunity” versus “mixed-intense”
67
+ - custom tags like “report readout” or “spiritual ritual”
68
+
69
+ ## MCP Tool Design Rules
70
+
71
+ ### Prefer Generic Modes Over Tool Proliferation
72
+
73
+ Good:
74
+
75
+ - one transit tool with explicit modes such as `snapshot`, `best_hit`, and `forecast`
76
+ - one electional-context tool with optional include flags for rising windows, Moon condition, and house context
77
+
78
+ Bad:
79
+
80
+ - `get_daily_transits`
81
+ - `get_weekly_transits`
82
+ - `get_best_transits_for_work`
83
+ - `get_spiritual_transits`
84
+
85
+ ### Favor Structured Data Over Prose
86
+
87
+ Good MCP output:
88
+
89
+ - date-grouped JSON
90
+ - exact-time metadata
91
+ - sign, degree, house, and applying/separating state
92
+
93
+ Bad MCP output:
94
+
95
+ - opinionated text summaries
96
+ - personalized advice
97
+ - user-specific ranking labels baked into the contract
98
+
99
+ ### Keep Personalization Out Of MCP
100
+
101
+ Do not hardcode into MCP:
102
+
103
+ - one user’s preferred house system for reporting
104
+ - one user’s preferred electional rulers
105
+ - one user’s work or life categories
106
+ - one user’s “good” and “bad” framing
107
+
108
+ Instead:
109
+
110
+ - expose the raw or lightly structured inputs,
111
+ - then let skills and preferences shape the output.
112
+
113
+ ## Skill Design Rules
114
+
115
+ Skills should:
116
+
117
+ - call the smallest useful set of MCP primitives,
118
+ - avoid reimplementing astro logic,
119
+ - own synthesis and narrative structure,
120
+ - and encode workflow-level decisions such as section order, ranking, tone, and user-preference application.
121
+
122
+ The default target is a one-call or two-call skill flow.
123
+
124
+ Skill-layer examples in this repo are primarily boundary examples and workflow incubators.
125
+
126
+ They should not be read as automatic commitments to build or maintain every listed workflow as a first-class product artifact.
127
+
128
+ ### Good Skill Flow
129
+
130
+ 1. Call forecast primitives.
131
+ 2. Optionally call electional primitives for narrowed windows.
132
+ 3. Synthesize the report.
133
+
134
+ ### Bad Skill Flow
135
+
136
+ - dozens of tiny MCP calls that should have been a reusable primitive
137
+ - custom astro math in the skill
138
+ - pretending uncertain interpretation is hard computation
139
+
140
+ ## Prompt Design Rules
141
+
142
+ Prompts are optional adapters, not the core product logic.
143
+
144
+ Use prompts when you want:
145
+
146
+ - discoverability in MCP-aware clients,
147
+ - a stable public workflow entry point,
148
+ - and a generic wrapper around one or more tools.
149
+
150
+ Do not use prompts as a substitute for:
151
+
152
+ - a missing MCP primitive,
153
+ - or a real skill/workflow spec.
154
+
155
+ ## Preference Design Rules
156
+
157
+ Profiles or preferences should contain durable output preferences and user heuristics.
158
+
159
+ Examples:
160
+
161
+ - reporting timezone
162
+ - preferred house system for interpretation
163
+ - favored electional filters
164
+ - preferred report sections
165
+ - recurring tags or domain labels
166
+
167
+ Preferences should not:
168
+
169
+ - alter astro computation silently,
170
+ - fork the meaning of a generic MCP tool,
171
+ - or create multiple incompatible interpretations of the same primitive.
172
+
173
+ ## When To Add A New MCP Primitive
174
+
175
+ Add a new MCP primitive when all or most of the following are true:
176
+
177
+ - it is deterministic or mostly deterministic,
178
+ - it is computational,
179
+ - it is reusable,
180
+ - multiple skills or clients need it,
181
+ - it would be brittle to reconstruct client-side,
182
+ - and it reduces total system complexity.
183
+
184
+ ## When Not To Add A New MCP Primitive
185
+
186
+ Do not add a new MCP primitive when:
187
+
188
+ - it mainly produces prose,
189
+ - it mainly encodes policy rather than astro fact,
190
+ - it mainly reflects one user’s preferences,
191
+ - the same result can be achieved by a competent LLM in two calls,
192
+ - or it would create a specialized tool for a narrow workflow instead of strengthening a generic primitive.
193
+
194
+ ## Current Repo Implications
195
+
196
+ Based on the current product direction:
197
+
198
+ ### Strong candidates for MCP
199
+
200
+ - range-aware transit forecast data
201
+ - mundane aspect data
202
+ - house-aware transit activations
203
+ - electional primitives
204
+ - rising-sign window helpers
205
+
206
+ ### Candidate workflow experiments after primitive stabilization
207
+
208
+ - daily brief generation
209
+ - weekly overview synthesis
210
+ - electional overlay and ranking
211
+ - action-oriented summaries
212
+ - user-specific framing and categorization
213
+
214
+ ### Strong candidates for docs or examples
215
+
216
+ - reference workflows that show how to combine forecast plus electional context
217
+ - examples of what lives in skills versus MCP
218
+ - preference contract guidance for user-facing reporting
219
+
220
+ ## Related Docs
221
+
222
+ - [Product Tenets](/Users/salted/Code/astro-mcp/docs/product/product-tenets.md)
223
+ - [ADR 0001: MCP Vs Skill Boundary](/Users/salted/Code/astro-mcp/docs/product/adrs/0001-mcp-vs-skill-boundary.md)