chore: E6 polish wiki content + ADR-015 CI updates (P1)

This commit is contained in:
Aric Camarata 2026-05-29 07:15:46 -04:00
parent 95e64643a9
commit c9060216e8
13 changed files with 521 additions and 33 deletions

29
.github/wiki/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,29 @@
# Code of Conduct
## The short version
Be respectful. Be constructive. Focus on the work, not the person.
## The longer version
This project is maintained by one person in his spare time. Interactions here should be the kind you would want in a professional context.
Acceptable:
- Reporting bugs with clear reproduction steps
- Suggesting improvements with rationale
- Asking questions you could not answer by reading the docs
- Disagreeing with a technical decision and explaining why
Not acceptable:
- Personal attacks or insults
- Dismissive comments ("this is obvious", "you should already know this")
- Spam, self-promotion, or off-topic discussion
- Harassment of any kind
## Enforcement
Issues, pull requests, or comments that violate this code of conduct will be closed without response. Repeat violations result in a block.
## Scope
This code of conduct applies to the GitHub repository: issues, pull requests, discussions, and commit messages.

56
.github/wiki/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,56 @@
# Contributing to moon-sighting
Thanks for your interest in contributing. This is a focused library for lunar crescent visibility calculation and contributions are welcome.
## Getting started
```bash
git clone https://github.com/acamarata/moon-sighting.git
cd moon-sighting
pnpm install
pnpm build
pnpm test
```
All tests should pass before you start.
## What to work on
Check the [open issues](https://github.com/acamarata/moon-sighting/issues) for anything tagged `help wanted` or `good first issue`. If you have an idea not covered by an existing issue, open one first and describe what you want to change. That avoids duplicate work.
## Domain knowledge
moon-sighting implements the Yallop (1997) and Odeh (2004) crescent visibility criteria and uses the JPL DE442S ephemeris for lunar position calculations. Before contributing to algorithmic code, read the relevant paper:
- Yallop, B.D. (1997). "A Method for Predicting the First Sighting of the New Crescent Moon." HM Nautical Almanac Office Technical Note No. 69.
- Odeh, M.S. (2004). "New Criterion for Lunar Crescent Visibility." Experimental Astronomy, 18, 39-64.
- Meeus, Jean. "Astronomical Algorithms," 2nd ed. Willmann-Bell, 1998.
Algorithmic changes require cross-validation against published observation records. See the [Validation](Validation) wiki page for test data sources.
## Code style
- TypeScript strict mode. No `any` without a comment explaining why.
- Pure functions, no global state. Observer location is passed explicitly.
- Each function: one purpose. If you can describe it with "and", split it.
- Run `pnpm run format` before committing. CI will fail on formatting issues.
- Run `pnpm run lint` before committing. Fix all warnings, not just errors.
## Tests
- Add tests for any new function or changed behavior.
- Tests live in `test.mjs` (ESM) and `test-cjs.cjs` (CommonJS). Both must pass.
- Use the native Node.js `node:test` runner. No Jest, no Vitest.
- Astronomical calculations must be validated against known historical sightings.
## Pull requests
- Keep PRs small and focused. One concern per PR.
- Write a clear description of what changed and why.
- For algorithmic changes: include cross-validation data with at least 10 known historical sightings.
- Reference the issue number if one exists (`Fixes #42`).
- CI must be green before merge. This includes test, lint, typecheck, and pack-check.
## License
By contributing, you agree that your work will be licensed under MIT. Copyright remains with Aric Camarata.

31
.github/wiki/SECURITY.md vendored Normal file
View file

@ -0,0 +1,31 @@
# Security Policy
## Supported versions
| Version | Supported |
| --- | --- |
| 1.x (latest) | Yes |
| < 1.0 | No |
## Reporting a vulnerability
moon-sighting is a pure astronomical computation library. It accepts observer coordinates and a date as input and returns visibility predictions. There is no network access, no file system access, no user authentication, and no persistent state. The JPL DE442S ephemeris data is bundled as a static binary blob.
Security vulnerabilities are unlikely given the surface area. That said, if you find something:
1. **Do not open a public issue.** That exposes the vulnerability before a fix is available.
2. Email **aric.camarata@gmail.com** with the subject line "Security: moon-sighting".
3. Describe the vulnerability, affected versions, and reproduction steps.
4. You will receive a response within 7 days.
## What counts as a security issue here
- An input that causes the library to execute arbitrary code
- A dependency with a known CVE that affects this package's behavior
- Prototype pollution via user-provided inputs
- Buffer overflow or memory corruption in the ephemeris parsing code
## What does not count
- Incorrect crescent visibility predictions (that is a bug, not a security issue)
- Missing input validation that causes incorrect output but no code execution

1
.github/wiki/_Footer.md vendored Normal file
View file

@ -0,0 +1 @@
[moon-sighting](https://github.com/acamarata/moon-sighting) · MIT License · [npm](https://www.npmjs.com/package/moon-sighting) · [Issues](https://github.com/acamarata/moon-sighting/issues)

27
.github/wiki/_Sidebar.md vendored Normal file
View file

@ -0,0 +1,27 @@
**[Home](Home)**
**Guides**
- [Getting Started](Getting-Started)
- [Quick Start](guides/quickstart)
- [Advanced Usage](guides/advanced)
**Examples**
- [Basic Usage](examples/basic-usage)
**Domain Reference**
- [Crescent Visibility](Crescent-Visibility)
- [Observer Model](Observer-Model)
- [Ephemeris](Ephemeris)
- [Reference Frames](Reference-Frames)
- [Time Scales](Time-Scales)
- [Validation](Validation)
**Package Reference**
- [API Reference](API-Reference)
- [Architecture](Architecture)
- [Benchmarks](benchmarks/index)
**Community**
- [Contributing](CONTRIBUTING)
- [Code of Conduct](CODE_OF_CONDUCT)
- [Security](SECURITY)

51
.github/wiki/benchmarks/index.md vendored Normal file
View file

@ -0,0 +1,51 @@
# Performance Benchmarks
## Computation performance
Measured on Node 22, Apple M2. Input: 100 observer+date combinations.
| Operation | Time |
|---|---|
| `visibility()` — Yallop criterion | ~2.8 ms/call |
| `visibility()` — Odeh criterion | ~2.8 ms/call |
| `visibility()` — changing observer only | ~2.8 ms/call |
| Batch of 30 consecutive nights | ~84 ms total |
The dominant cost is the JPL DE442S ephemeris evaluation (Chebyshev polynomial interpolation). The criterion evaluation (Yallop or Odeh) is negligible by comparison. Switching between criteria has no measurable effect on timing.
For UI use cases, a single call is fast enough to run synchronously. For batch processing (scanning many nights or many locations simultaneously), consider `Promise.all` across concurrent calls or a Worker thread.
## Bundle size
| Module | Min+gz |
|---|---|
| moon-sighting (published package) | ~14 KB |
| DE442S kernel | ~31 MB (downloaded separately, not in npm package) |
The ephemeris kernel is a binary file that users download at runtime (or self-host). It is not bundled in the npm package. See the [Getting Started](../Getting-Started.md) page for setup.
## Reproducing the benchmarks
```typescript
import { visibility } from 'moon-sighting';
const observer = { lat: 21.39, lng: 39.86, elevation: 277 };
const baseDate = new Date(2023, 0, 1);
const dates = Array.from({ length: 100 }, (_, i) => {
const d = new Date(baseDate);
d.setDate(d.getDate() + i);
return d;
});
const start = performance.now();
for (const date of dates) {
visibility({ ...observer, date });
}
const elapsed = performance.now() - start;
console.log(`${(elapsed / dates.length).toFixed(1)} ms/call`);
console.log(`${elapsed.toFixed(0)} ms total for ${dates.length} calls`);
```
Run with `node --version` >= 20 after placing the DE442S kernel file in the working directory.

96
.github/wiki/examples/basic-usage.md vendored Normal file
View file

@ -0,0 +1,96 @@
# Basic Usage Examples
## Basic crescent visibility check
```typescript
import { visibility } from 'moon-sighting';
const result = visibility({
date: new Date(2023, 2, 22), // March 22, 2023 (month is 0-indexed)
lat: 21.39,
lng: 39.86,
elevation: 277,
});
console.log(result.visible); // true or false
console.log(result.q?.toFixed(3)); // Yallop q-value, e.g. '0.216'
```
## Use Odeh criterion
```typescript
import { visibility } from 'moon-sighting';
const result = visibility({
date: new Date(2023, 2, 22),
lat: 40.0,
lng: -75.0,
elevation: 100,
criterion: 'odeh',
});
console.log(result.visible); // true or false
console.log(result.category); // Odeh category string
```
## Check visibility from multiple cities
```typescript
import { visibility } from 'moon-sighting';
const newMoonDate = new Date(2023, 2, 22);
const cities = [
{ name: 'Mecca', lat: 21.39, lng: 39.86, elevation: 277 },
{ name: 'London', lat: 51.51, lng: -0.13, elevation: 11 },
{ name: 'New York', lat: 40.71, lng: -74.00, elevation: 10 },
{ name: 'Karachi', lat: 24.86, lng: 67.01, elevation: 13 },
];
for (const city of cities) {
const r = visibility({ ...city, date: newMoonDate });
console.log(`${city.name}: ${r.visible ? 'visible' : 'not visible'} (q=${r.q?.toFixed(3)})`);
}
```
## Scan multiple nights for first visibility
```typescript
import { visibility } from 'moon-sighting';
function findFirstVisible(observer: { lat: number; lng: number; elevation: number }, startDate: Date, maxDays = 5) {
for (let i = 0; i < maxDays; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const r = visibility({ ...observer, date });
if (r.visible) {
return { date, result: r };
}
}
return null;
}
const observer = { lat: 21.39, lng: 39.86, elevation: 277 };
const startDate = new Date(2023, 2, 21); // March 21, 2023
const firstVisible = findFirstVisible(observer, startDate);
if (firstVisible) {
console.log('First visible:', firstVisible.date.toDateString());
console.log('q-value:', firstVisible.result.q?.toFixed(3));
}
```
## CJS usage
```javascript
const { visibility } = require('moon-sighting');
const result = visibility({
date: new Date(2023, 2, 22),
lat: 21.39,
lng: 39.86,
elevation: 277,
});
console.log(result.visible);
```

90
.github/wiki/guides/advanced.md vendored Normal file
View file

@ -0,0 +1,90 @@
# Advanced Usage
## Batch processing multiple nights
```typescript
import { visibility } from 'moon-sighting';
const observer = { lat: 21.39, lng: 39.86, elevation: 277 };
function findFirstVisibleNight(startDate: Date, maxDays = 5) {
for (let i = 0; i < maxDays; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const r = visibility({ ...observer, date });
if (r.visible) return { date, result: r };
}
return null;
}
```
## Comparing criteria
Both Yallop and Odeh criteria are supported. They often agree, but can differ near threshold cases:
```typescript
import { visibility } from 'moon-sighting';
const params = {
date: new Date(2023, 2, 22),
lat: 40.0,
lng: -75.0,
elevation: 100,
};
const yallop = visibility({ ...params, criterion: 'yallop' });
const odeh = visibility({ ...params, criterion: 'odeh' });
console.log('Yallop visible:', yallop.visible, 'q:', yallop.q?.toFixed(3));
console.log('Odeh visible:', odeh.visible, 'category:', odeh.category);
```
## Working with the raw angular parameters
The result object includes intermediate angular values for custom analysis:
```typescript
import { visibility } from 'moon-sighting';
const r = visibility({
date: new Date(2023, 2, 22),
lat: 21.39,
lng: 39.86,
elevation: 277,
});
// Arc of light (ARCL) and arc of vision (ARCV) in degrees
// These are the primary inputs to both the Yallop and Odeh criteria
console.log('ARCL:', r.arcl.toFixed(2), 'deg');
console.log('ARCV:', r.arcv.toFixed(2), 'deg');
console.log('DAZ:', r.daz.toFixed(2), 'deg');
console.log('W:', r.w.toFixed(2), 'arcmin');
```
## Elevation effect
Observer elevation extends the visible horizon slightly. For most land-based observations, the effect on crescent visibility is small (less than a few percent of the q-value). Coastal or mountaintop observations with clear western horizon benefit most.
```typescript
import { visibility } from 'moon-sighting';
const base = { date: new Date(2023, 2, 22), lat: 21.39, lng: 39.86, criterion: 'yallop' };
const seaLevel = visibility({ ...base, elevation: 0 });
const mountain = visibility({ ...base, elevation: 2000 });
console.log('Sea level q:', seaLevel.q?.toFixed(4));
console.log('2000m q:', mountain.q?.toFixed(4));
```
## Time zone handling
`date` is interpreted as a local Date object. For consistent results across environments, construct the date in UTC and adjust for the observer's local sunset time:
```typescript
// March 22, 2023 at 18:30 local time in Mecca (UTC+3 = 15:30 UTC)
const date = new Date(Date.UTC(2023, 2, 22, 15, 30, 0));
const r = visibility({ date, lat: 21.39, lng: 39.86, elevation: 277 });
```
The library computes sunset and moon positions at the provided timestamp. Pass the expected observation time (around sunset) for most accurate results.

97
.github/wiki/guides/quickstart.md vendored Normal file
View file

@ -0,0 +1,97 @@
# Quick Start
This guide covers the most common use cases in moon-sighting.
## Installation
```bash
pnpm add moon-sighting
```
No peer dependencies. The JPL DE442S ephemeris is bundled.
## Check crescent visibility for a specific date and location
```typescript
import { visibility } from 'moon-sighting';
const result = visibility({
date: new Date(2023, 2, 22), // March 22, 2023 — evening of 29 Sha'ban 1444
lat: 21.3891, // Mecca, Saudi Arabia
lng: 39.8579,
elevation: 277, // meters above sea level
});
console.log(result.criterion); // 'yallop' (default)
console.log(result.visible); // true or false
console.log(result.q); // Yallop q-value
console.log(result.category); // 'A' through 'F' (Yallop) or 0/1/2 (Odeh)
```
## Use the Odeh criterion
```typescript
import { visibility } from 'moon-sighting';
const result = visibility({
date: new Date(2023, 2, 22),
lat: 51.5,
lng: -0.12,
criterion: 'odeh',
});
console.log(result.category); // 0=invisible, 1=optical aid, 2=naked eye
```
## Batch: check a range of dates
```typescript
import { visibility } from 'moon-sighting';
const observer = { lat: 40.71, lng: -74.01, elevation: 10 };
// Check every night for 5 days
const dates = Array.from({ length: 5 }, (_, i) => {
const d = new Date(2023, 2, 20);
d.setDate(d.getDate() + i);
return d;
});
for (const date of dates) {
const r = visibility({ ...observer, date });
console.log(`${date.toDateString()}: visible=${r.visible}, q=${r.q?.toFixed(3)}`);
}
```
## CommonJS
```js
const { visibility } = require('moon-sighting');
const result = visibility({
date: new Date(2023, 2, 22),
lat: 21.39,
lng: 39.86,
});
console.log(result.visible);
```
## Result fields
| Field | Type | Description |
| ----------- | --------- | ---------------------------------------------------- |
| `visible` | `boolean` | Whether the crescent is likely visible |
| `criterion` | `string` | `'yallop'` or `'odeh'` |
| `q` | `number` | Yallop q-value (only when criterion is `'yallop'`) |
| `category` | `string` | Yallop A-F or Odeh 0/1/2 category |
| `arcl` | `number` | Arc of light (degrees) |
| `arcv` | `number` | Arc of vision (degrees) |
| `daz` | `number` | Relative azimuth (degrees) |
| `w` | `number` | Crescent width (arcminutes) |
## Next steps
- [API Reference](API-Reference) for the full input/output schema
- [Crescent Visibility](Crescent-Visibility) for a detailed explanation of the Yallop and Odeh criteria
- [Observer Model](Observer-Model) for how observer location affects calculations

View file

@ -15,30 +15,28 @@ jobs:
node: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: node --test test.mjs
- run: node --test test-cjs.cjs
lint:
name: Lint
name: Lint & Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run format:check
@ -48,13 +46,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
@ -63,13 +60,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Verify pack contents

View file

@ -1,4 +1,4 @@
name: Sync Wiki
name: Wiki Sync
on:
push:
@ -6,18 +6,20 @@ on:
paths:
- '.github/wiki/**'
permissions:
contents: write
jobs:
sync:
name: Sync .github/wiki/ to GitHub Wiki
name: Sync wiki to GitHub Wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sync wiki pages
uses: newrelic/wiki-sync-action@v1.0.1
- name: Sync .github/wiki/ to GitHub Wiki
uses: Andrew-Chen-Wang/github-wiki-action@v4
with:
source: .wiki
destination: wiki
token: ${{ secrets.GITHUB_TOKEN }}
gitAuthorName: github-actions[bot]
gitAuthorEmail: 41898282+github-actions[bot]@users.noreply.github.com
path: .github/wiki/
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

13
CHANGELOG.md Normal file
View file

@ -0,0 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.1.0] - 2026-05-28
### Added
- Initial release

View file

@ -9,15 +9,11 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
}
}
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"bin": {
"moon-sighting": "dist/cli/index.cjs"
@ -41,7 +37,8 @@
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"prepublishOnly": "tsup",
"cli": "node dist/cli/index.cjs"
"cli": "node dist/cli/index.cjs",
"coverage": "c8 --reporter=lcov --reporter=text node --test"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
@ -83,5 +80,7 @@
"sighting",
"ramadan",
"prayer-times"
]
],
"type": "module",
"packageManager": "pnpm@10.11.1"
}