Repository navigation
Bound new Apple Maps connections with a connect timeout; guard SNAPSHOT releases #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2bf48cd
22f8e6a
c0f4028
3134895
c251a8b
65dfa30
86153c0
9b4efe8
1d02d2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,23 +13,64 @@ alwaysApply: true | |
|
|
||
| ## Rule Summary [SUM] | ||
|
|
||
| - [GT1a-d] Git & Permissions (elevated-only git; no destructive commands) | ||
| - [FS1a-g] File Creation & Type Safety (typed records, no maps, no raw types) | ||
| - [ZA1a-d] Zero Tolerance Policy (zero assumptions, validation, forbidden practices, dependency verification) | ||
| - [GT1a-h] Git & Permissions (elevated-only git; no destructive commands) | ||
| - [CC1a-d] Clean Code & DDD (Mandatory) | ||
| - [ID1a-d] Idiomatic Patterns & Defaults | ||
| - [DS1a-e] Dependency Source Verification | ||
| - [FS1a-h] File Creation & Type Safety (typed records, no maps, no raw types) | ||
| - [LOC1a-e] Line Count Ceiling (500 lines max; SRP enforcer; zero tolerance) | ||
| - [MO1a-g] No Monoliths (Strict SRP; Decision Logic; Extension/OCP) | ||
| - [ND1a-c] Naming Discipline (intent-revealing identifiers only) | ||
| - [AB1a-c] Abstraction Discipline (YAGNI; no anemic wrappers; earn reuse) | ||
| - [CS1a-f] Code Smells / Clean Code (DRY; primitive obsession; magic literals) | ||
| - [RC1a-e] Root Cause Resolution (no fallbacks; no error swallowing; let errors surface) | ||
| - [NO1a-e] Null/Optional Discipline (Optional for singletons; empty collections; no null returns) | ||
| - [AR1a-d] Architecture & DDD (domain records; ports/adapters; layer boundaries) | ||
| - [TS1a-d] Testing Standards (coverage mandatory; observable behavior; refactor-resilient) | ||
| - [VR1a-c] Verification Loops (build/test/lint steps) | ||
| - [VR1a-d] Verification Loops (build/test/lint steps; completion tail) | ||
|
|
||
| ## [ZA1] Zero Tolerance Policy | ||
|
|
||
| - [ZA1a] **Zero Assumptions**: Do not assume behavior, APIs, or versions. Verify in the codebase/docs first. | ||
| - [ZA1b] **Source Verification**: For dependency code questions, inspect `~/.m2` JARs or `~/.gradle/caches/` first; fallback to upstream GitHub; never answer without referencing code. | ||
| - [ZA1c] **Forbidden Practices**: | ||
| - No `Map<String, Object>`, raw types, unchecked casts, `@SuppressWarnings`, or `eslint-disable` in production. | ||
| - No trusting memory—verify every import/API/config against current docs. | ||
| - [ZA1d] **Mandatory Research**: You MUST research dependency questions and correct usage. Never use legacy or `@deprecated` usage from dependencies. Ensure correct usage by reviewing related code directly in `node_modules` or Gradle caches and using online tool calls. | ||
|
|
||
| ## [GT1] Git & Permissions | ||
|
|
||
| - [GT1a] All git commands require elevated permissions; never run without escalation. | ||
| - [GT1b] Never remove `.git/index.lock` automatically—stop and ask the user. | ||
| - [GT1c] No destructive git commands (`git restore`, `git reset`, force checkout) unless explicitly ordered. | ||
| - [GT1d] Do not skip commit signing or hooks; no `--no-verify`. | ||
| - [GT1b] Never remove `.git/index.lock` automatically—stop and ask the user or seek explicit approval. | ||
| - [GT1c] Read-only git commands (e.g., `git status`, `git diff`, `git log`, `git show`) never require permission. Any git command that writes to the working tree, index, or history requires explicit permission. | ||
| - [GT1d] Do not skip commit signing or hooks; no `--no-verify`. No `Co-authored-by` or AI attribution. | ||
| - [GT1e] Destructive git commands are prohibited unless explicitly ordered by the user (e.g., `git restore`, `git reset`, force checkout). | ||
| - [GT1f] Treat existing staged/unstaged changes as intentional unless the user says otherwise; never “clean up” someone else’s work unprompted. | ||
| - [GT1g] A task's authorization covers its worktree lifecycle: creating the task branch/worktree, committing in it, merging to `dev` (the integration branch), and pushing `dev`. Destructive commands (`git reset`, `git restore`, `git clean`, `git rebase`, force checkout, history rewrites) still require explicit user orders. | ||
| - [GT1h] When in doubt whether a git command writes, treat it as write and request explicit approval. | ||
|
|
||
| ## [CC1] Clean Code & DDD (Mandatory) | ||
|
|
||
| - [CC1a] **Mandatory Principles**: Clean Code principles (Robert C. Martin) and Domain-Driven Design (DDD) are **mandatory** and required in this repository. | ||
| - [CC1b] **DRY (Don't Repeat Yourself)**: Avoid redundant code. Reuse code where appropriate and consistent with clean code principles. | ||
| - [CC1c] **YAGNI (You Aren't Gonna Need It)**: Do not build features or abstractions "just in case". Implement only what is required for the current task. | ||
| - [CC1d] **Clean Architecture**: Dependencies point inward. Domain logic has zero framework imports. | ||
|
|
||
| ## [ID1] Idiomatic Patterns & Defaults | ||
|
|
||
| - [ID1a] **Defaults First**: Always prefer the idiomatic, expected, and default patterns provided by the framework, library, or SDK (Java 21+, etc.). | ||
| - [ID1b] **Custom Justification**: Custom implementations require a compelling reason. If you can't justify it, use the standard way. | ||
| - [ID1c] **No Reinventing**: Do not build custom utilities for things the platform already does. | ||
| - [ID1d] **Dependencies**: Make careful use of dependencies. Do not make assumptions—use the correct idiomatic behavior to avoid boilerplate. | ||
|
|
||
| ## [DS1] Dependency Source Verification | ||
|
|
||
| - [DS1a] **Locate**: Find source JARs in Gradle cache: `find ~/.gradle/caches/modules-2/files-2.1 -name "*-sources.jar" | grep <artifact>`. | ||
| - [DS1b] **List**: View JAR contents without extraction: `unzip -l <jar_path> | grep <ClassName>`. | ||
| - [DS1c] **Read**: Pipe specific file content to stdout: `unzip -p <jar_path> <internal/path/to/Class.java>`. | ||
| - [DS1d] **Search**: To use `ast-grep` on dependencies, pipe content directly: `unzip -p <jar> <file> | ast-grep run --pattern '...' --lang java --stdin`. No temp files required. | ||
| - [DS1e] **Efficiency**: Do not extract full JARs. Use CLI piping for instant access. | ||
|
|
||
| ## [FS1] File Creation & Type Safety | ||
|
|
||
|
|
@@ -40,6 +81,26 @@ alwaysApply: true | |
| - [FS1e] Domain has zero framework imports; dependencies point inward. | ||
| - [FS1f] No generic utilities: reject `*Utils/*Helper/*Common`; use domain-specific names. | ||
| - [FS1g] Domain value types: wrap identifiers, amounts, and values with invariants in records. | ||
| - [FS1h] File size discipline: see [LOC1a] and [MO1a]. | ||
|
|
||
| ## [LOC1] Line Count Ceiling (Repo-Wide) | ||
|
|
||
| - [LOC1a] All written, non-generated source files in this repository MUST be <= 500 lines (`wc -l`), including `AGENTS.md` | ||
| - [LOC1b] SRP Enforcer: This 500-line "stick" forces modularity (DDD/SRP); > 500 lines = too many responsibilities (see [MO1d]) | ||
| - [LOC1c] Zero Tolerance: No edits allowed to files > 500 LOC (even legacy); you MUST split/retrofit before applying your change | ||
| - [LOC1d] Enforcement: run line count checks and treat failures as merge blockers | ||
| - [LOC1e] Exempt files: generated content, lockfiles, and large example/data dumps | ||
|
|
||
| ## [MO1] No Monoliths | ||
|
|
||
| - [MO1a] No monoliths: avoid multi-concern files and catch-all modules | ||
| - [MO1b] New work starts in new files; when touching a monolith, extract at least one seam | ||
| - [MO1c] If safe extraction impossible, halt and ask | ||
| - [MO1d] Strict SRP: each unit serves one actor; separate logic that changes for different reasons | ||
| - [MO1e] Boundary rule: cross-module interaction happens only through explicit, typed contracts with dependencies pointing inward; don’t reach into other modules’ internals or mix web/use-case/domain/persistence concerns in one unit | ||
| - [MO1f] Decision Logic: New feature → New file; Bug fix → Edit existing; Logic change → Extract/Replace | ||
| - [MO1g] Extension (OCP): Add functionality via new classes/composition; do not modify stable code to add features | ||
| - Contract: `docs/contracts/code-change.md` | ||
|
|
||
| ## [ND1] Naming Discipline | ||
|
|
||
|
|
@@ -97,3 +158,5 @@ alwaysApply: true | |
| - [VR1a] Build: `./gradlew build`; expect success. | ||
| - [VR1b] Tests: `./gradlew test`; targeted runs use `--tests`. | ||
| - [VR1c] Lint: `./gradlew spotlessCheck` (if configured). | ||
| - [VR1d] Completion tail: with local gates green, commit in the task worktree, merge to `dev`, push, and watch the push-triggered remote CI run to a terminal verdict (one watcher per SHA, polls >= 60s apart); fix failures, commit, and push until green. | ||
| - [VR1e] File GitHub issues only for material defects or features (behavior, correctness, security, performance, data quality, or a governed contract); fix pedantic/nitpick/style-only findings in place or drop them, never file them. Fixes and issue scope follow the minimalism bar: reuse before new code and simplify before completing, per the `ponytail` and `ce-simplify-code` skills. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||
| [](LICENSE.md) | ||||||
| [](https://context7.com/williamagh/apple-maps-java) | ||||||
| [](https://deepwiki.com/WilliamAGH/apple-maps-java) | ||||||
| [](https://www.mintlify.com/WilliamAGH/apple-maps-java) | ||||||
|
|
||||||
| # Apple Maps Server SDK for Java | ||||||
|
|
||||||
|
|
@@ -38,6 +39,8 @@ Apple provides three primary ways to integrate Maps. This library supports #1 (S | |||||
|
|
||||||
| ## Installation | ||||||
|
|
||||||
| > Latest stable release: **0.1.5** — [view on Maven Central](https://central.sonatype.com/artifact/com.williamcallahan/apple-maps-java) | [view on mvnrepository](https://mvnrepository.com/artifact/com.williamcallahan/apple-maps-java) | ||||||
|
||||||
| > Latest stable release: **0.1.5** — [view on Maven Central](https://central.sonatype.com/artifact/com.williamcallahan/apple-maps-java) | [view on mvnrepository](https://mvnrepository.com/artifact/com.williamcallahan/apple-maps-java) | |
| > View the latest release on [Maven Central](https://central.sonatype.com/artifact/com.williamcallahan/apple-maps-java) | [mvnrepository](https://mvnrepository.com/artifact/com.williamcallahan/apple-maps-java) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent README updater from rewriting snapshot coordinate
Adding this -SNAPSHOT dependency example introduces a regression with the existing UpdateReadmeVersion workflow: its global regex replacement updates every implementation("com.williamcallahan:apple-maps-java:...") occurrence, so the next scheduled/release run will overwrite this snapshot line to the latest release version and break the snapshot instructions. Scope the replacement to the release-install snippet (or explicitly exclude the snapshot block) so docs stay correct after automation runs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UpdateReadmeVersion will overwrite snapshot coordinates with the stable version
The UpdateReadmeVersion.yaml perl substitutions use the /g (global) flag, so they match all occurrences of the dependency coordinates in the file — including the new snapshot section. The next time that workflow runs (daily schedule, or on the next release), it will replace 0.1.6-SNAPSHOT in both the Gradle and Maven snapshot snippets with the latest Maven Central stable version (e.g., 0.1.5), silently corrupting the documentation for consumers who rely on snapshots.
The simplest fix is to guard the regex so it only touches non-SNAPSHOT versions:
# Gradle snippet — stable only
s/(implementation\("com\.williamcallahan:apple-maps-java:)(?!.*SNAPSHOT)[^"]+("\))/$1$v$2/g;
# Maven snippet — stable only
s#(<dependency>\s*<groupId>com\.williamcallahan</groupId>\s*<artifactId>apple-maps-java</artifactId>\s*<version>)(?!.*SNAPSHOT)[^<]+(</version>\s*</dependency>)#$1$v$2#gms;Alternatively, wrap each stable snippet in HTML comment markers and scope the replacement to only that region.
Prompt To Fix With AI
This is a comment left during a code review.
Path: README.md
Line: 77-102
Comment:
**`UpdateReadmeVersion` will overwrite snapshot coordinates with the stable version**
The `UpdateReadmeVersion.yaml` perl substitutions use the `/g` (global) flag, so they match **all** occurrences of the dependency coordinates in the file — including the new snapshot section. The next time that workflow runs (daily schedule, or on the next release), it will replace `0.1.6-SNAPSHOT` in both the Gradle and Maven snapshot snippets with the latest Maven Central stable version (e.g., `0.1.5`), silently corrupting the documentation for consumers who rely on snapshots.
The simplest fix is to guard the regex so it only touches non-SNAPSHOT versions:
```perl
# Gradle snippet — stable only
s/(implementation\("com\.williamcallahan:apple-maps-java:)(?!.*SNAPSHOT)[^"]+("\))/$1$v$2/g;
# Maven snippet — stable only
s#(<dependency>\s*<groupId>com\.williamcallahan</groupId>\s*<artifactId>apple-maps-java</artifactId>\s*<version>)(?!.*SNAPSHOT)[^<]+(</version>\s*</dependency>)#$1$v$2#gms;
```
Alternatively, wrap each stable snippet in HTML comment markers and scope the replacement to only that region.
How can I resolve this? If you propose a fix, please make it concise.| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| --- | ||
| title: "Code change policy contract" | ||
| usage: "Use whenever creating/modifying files: where to put code, when to create new types, and how to stay SRP/DDD compliant" | ||
| description: "Evergreen contract for change decisions (new file vs edit), repository structure/naming, and domain model hierarchy; references rule IDs in `AGENTS.md`" | ||
| --- | ||
|
|
||
| # Code Change Policy Contract | ||
|
|
||
| See `AGENTS.md` ([LOC1a-e], [MO1a-g], [FS1a-h], [ND1a-c], [CC1a-d], [TS1a-d], [VR1a-c]). | ||
|
|
||
| ## Non-negotiables (applies to every change) | ||
|
|
||
| - **SRP/DDD only**: each new type/method has one reason to change ([MO1d], [CC1a]). | ||
| - **New feature → new file**; do not grow monoliths ([MO1b], [FS1b]). | ||
| - **No edits to >500 LOC files**; first split/retrofit ([LOC1c]). | ||
| - **Domain is framework-free**; dependencies point inward ([CC1d], [FS1e]). | ||
| - **No DTOs**; domain records/interfaces are the API response types. | ||
| - **No map payloads** (`Map<String,Object>`, stringly helpers, map-based mappers) ([ZA1c], [FS1b]). | ||
|
|
||
| ## Decision matrix: create new file vs edit existing | ||
|
|
||
| Use this as a hard rule, not a suggestion. | ||
|
|
||
| | Situation | MUST do | MUST NOT do | | ||
| |----------|---------|-------------| | ||
| | New user-facing behavior (new endpoint, new domain capability) | Add a new, narrowly scoped type in the correct layer/package ([MO1b]) | “Just add a method” to an unrelated class ([MO1a], [MO1d]) | | ||
| | Bug fix (existing behavior wrong) | Edit the smallest correct owner; add/adjust tests to lock behavior ([MO1f], [TS1a]) | Create a parallel/shadow implementation ([RC1d]) | | ||
| | Logic change in stable code | Extract/replace via composition; keep stable code stable ([MO1g]) | Add flags, shims, or “compat” paths to hide uncertainty ([RC1e]) | | ||
| | Touching a large/overloaded file | Extract at least one seam (new type + typed contract) ([MO1b], [MO1e]) | Grow the file further ([MO1a]) | | ||
| | Reuse needed across features | Add a domain value object / explicit port / explicit service with intent-revealing name ([CS1a], [AR1c], [ND1a]) | Add `*Utils/*Helper/*Common/*Base*` grab bags ([FS1f]) | | ||
|
|
||
| ### When adding a method is allowed | ||
|
|
||
| Adding to an existing type is allowed only when all are true: | ||
|
|
||
| - It is the **same responsibility** as the type’s existing purpose ([MO1d]). | ||
| - The method’s inputs belong together (avoid data clumps/long parameter lists; extract a parameter record when needed) ([CS1b], [CS1c]). | ||
| - The method does not pull in a new dependency direction (dependencies still point inward) ([CC1d]). | ||
|
|
||
| If any bullet fails, create a new type and inject it explicitly. | ||
|
|
||
| ## Create-new-type checklist (before you write code) | ||
|
|
||
| 1. **Search/reuse first**: confirm a type/pattern doesn’t already exist ([FS1a], [ZA1a]). | ||
| 2. **Pick the correct layer** (web → use case → domain → adapters/out) ([AR1a]). | ||
| 3. **Pick the correct feature package** (feature-first, lowercase, singular nouns). | ||
| 4. **Name by role** (ban generic names; suffix declares meaning) ([ND1a-b]). | ||
| 5. **Keep the file small** (stay comfortably under 500 LOC; split by concept early) ([LOC1a], [MO1d]). | ||
| 6. **Add/adjust tests** using existing patterns/utilities ([TS1a], [TS1b]). | ||
| 7. **Verify** with repo-standard commands (`./gradlew build`, `./gradlew spotlessCheck`) ([VR1a], [VR1c]). | ||
|
|
||
| ## Repository structure and naming (placement is part of the contract) | ||
|
|
||
| ### Canonical roots (Java) | ||
|
|
||
| Only these root packages are allowed ([AR1a]): | ||
|
|
||
| - `com.williamcallahan.applemaps.boot` | ||
| - `com.williamcallahan.applemaps.adapters` | ||
| - `com.williamcallahan.applemaps.cli` | ||
| - `com.williamcallahan.applemaps.domain` | ||
|
|
||
| ### Feature-first package rule | ||
|
|
||
| All layers organize by **feature first**, then by role. | ||
|
|
||
| Examples: | ||
| - `domain/model/place/...` | ||
| - `domain/port/place/...` | ||
| - `adapters/mapsserver/...` | ||
|
|
||
| ### No mixed packages | ||
|
|
||
| A package contains either: | ||
| - Direct classes only, or | ||
| - Subpackages only (plus optional `package-info.java`). | ||
|
|
||
| If you need both, insert one more nesting level. | ||
|
|
||
| ## Domain model hierarchy | ||
|
|
||
| - Domain records are immutable value objects. | ||
| - Validation happens in constructors. | ||
| - Domain → API mapping happens once at the boundary. | ||
|
|
||
| ## Layer responsibility contract | ||
|
|
||
| ### Controllers / CLI | ||
|
|
||
| Allowed: | ||
| - Bind/validate inputs. | ||
| - Delegate to use cases/domain services. | ||
| - Return domain records. | ||
|
|
||
| Prohibited: | ||
| - Business logic. | ||
|
|
||
| ### Domain | ||
|
|
||
| Allowed: | ||
| - Pure business logic. | ||
| - Invariants. | ||
|
|
||
| Prohibited: | ||
| - Framework imports. | ||
| - Persistence details. | ||
|
|
||
| ## Verification gates (do not skip) | ||
|
|
||
| - LOC enforcement: manual check / script ([LOC1c]). | ||
| - Build/test/lint: `./gradlew build` ([VR1a]). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "$schema": "https://mintlify.com/docs.json", | ||
| "theme": "mint", | ||
| "name": "Apple Maps Server SDK for Java", | ||
| "colors": { | ||
| "primary": "#007ec6" | ||
| }, | ||
| "navigation": { | ||
| "groups": [ | ||
| { | ||
| "group": "Getting Started", | ||
| "pages": [ | ||
| "usage", | ||
| "authorization" | ||
| ] | ||
| }, | ||
| { | ||
| "group": "CLI", | ||
| "pages": [ | ||
| "cli" | ||
| ] | ||
| }, | ||
| { | ||
| "group": "Testing", | ||
| "pages": [ | ||
| "tests" | ||
| ] | ||
| }, | ||
| { | ||
| "group": "Contracts", | ||
| "pages": [ | ||
| "contracts/code-change" | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing VERSION_NAME to avoid silent skips.
If
VERSION_NAMEis absent or empty, this step will quietly setis_snapshot=falseand skip snapshot publishing. Adding strict mode + a non-empty check makes the failure loud and easy to debug. Tiny CI hygiene win. 🌱✅ Suggested hardening
- name: Check if SNAPSHOT version id: version_check + shell: bash run: | - VERSION=$(grep '^VERSION_NAME=' gradle.properties | cut -d'=' -f2) + set -euo pipefail + VERSION=$(grep -E '^VERSION_NAME=' gradle.properties | head -n1 | cut -d'=' -f2 | tr -d '\r') + if [[ -z "$VERSION" ]]; then + echo "::error::VERSION_NAME not found in gradle.properties" + exit 1 + fi echo "Version: $VERSION" if [[ "$VERSION" == *-SNAPSHOT ]]; then - echo "is_snapshot=true" >> $GITHUB_OUTPUT + echo "is_snapshot=true" >> "$GITHUB_OUTPUT" else - echo "is_snapshot=false" >> $GITHUB_OUTPUT + echo "is_snapshot=false" >> "$GITHUB_OUTPUT" fi🤖 Prompt for AI Agents