diff --git a/AGENTS.md b/AGENTS.md index c62a81a..a614b84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,53 +1,9 @@ -# Repository Agent Guidance +# spigot-invisibilty agent entrypoint -## User-story-driven development +The canonical stories, engineering guidance, and **all process documents** are in the private [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md). -The `design/` directory is the OKF v0.1 product record for this repository. Use user stories to plan, implement, verify, and track all behavior. +Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/spigot-invisibilty/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/spigot-invisibilty/` stories. Also follow the parent workspace `AGENTS.md` when present. -Before changing behavior: +For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-invisibilty/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access. -1. Read `design/index.md` and every story related to the requested behavior. -2. Draft updates to an existing story or create a new `design/user-stories/us-NNN-short-name.md` story before implementation. -3. Define observable acceptance criteria using user or operator language. -4. Present the relevant new or updated stories and acceptance criteria to the user for review, and wait for explicit confirmation before changing implementation code. -5. Incorporate requested story changes before proceeding. -6. Set story status to `in-progress` while approved implementation is incomplete. - -While implementing: - -1. Work in vertical slices against the documented acceptance criteria. -2. Add tests for important behavior before implementation when practical. -3. Keep implementation references and related-story links current. -4. Do not mark an acceptance criterion complete until the behavior exists and has been validated. - -Before completing or committing: - -1. Set a completed story status to `done` only when all acceptance criteria are complete and verified. -2. Check completed acceptance criteria and record validation evidence where appropriate. -3. Update `design/index.md` and `design/user-stories/index.md` whenever stories are added, renamed, moved, or materially reclassified. -4. Add a high-level entry to `design/log.md` under the verified current date. -5. Run OKF validation along with relevant Gradle tests, lint checks, and builds. - -## OKF conventions - -- Every non-reserved Markdown file in `design/` must have YAML frontmatter with a non-empty `type`. -- User stories use `type: User Story` and include `status`, `title`, and `description`. -- Allowed story statuses are `backlog`, `in-progress`, and `done`. -- Every user story includes an acceptance-criteria section using Markdown task-list items. -- `design/index.md` and `design/log.md` are reserved OKF files and follow the OKF index/log structures. -- Store user stories in `design/user-stories/` and keep their catalog current. -- Use standard Markdown links and keep repository-local links valid when files move. -- Preserve unknown frontmatter extensions. - -## Java and Spigot development - -- Use the Java version and Spigot API version declared by the Gradle build. -- Treat compiler warnings as errors. -- Prefer test-first development for domain rules and state transitions when practical. -- Run `./gradlew clean check jar` before completing implementation work. -- Keep Bukkit event handlers thin and move testable game rules into focused domain services. -- Do not perform blocking file or network operations on the server tick thread. - -## Timestamps - -Always run `date +%Y-%m-%d` before adding or updating dates in stories or the design log. Use RFC 3339 UTC timestamps (`YYYY-MM-DDTHH:MM:SSZ`) when a date-time is required. Never guess dates. +Development follows [Development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md): approved stories, failing tests, passing implementation, verification, then source/wiki commit and push. GitOps updates are committed locally **without pushing**; only [Do release](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/do-release.md) authorizes a reviewed GitOps push. diff --git a/build.gradle.kts b/build.gradle.kts index 355ac6d..fd82206 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,11 +46,3 @@ tasks.processResources { expand("version" to pluginVersion) } } - -tasks.register("validateOkf") { - commandLine("./scripts/validate-okf.sh") -} - -tasks.check { - dependsOn("validateOkf") -} diff --git a/design/index.md b/design/index.md deleted file mode 100644 index 004529a..0000000 --- a/design/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -okf_version: "0.1" ---- - -# Spigot Stealth Design - -Spigot Stealth rewards players who spend time under invisibility effects from potions they drink, then lets unlocked players conceal their identity for a later session. [Session concealment](user-stories/us-002-rejoin-without-visible-identity.md) includes command-suggestion filtering and scrambled message and overhead names. - -[Administration](user-stories/us-004-administer-player-stealth.md) provides private concealed-player counts at login and command-based inspection. - -## Explore - -- [User stories](user-stories/) - Approved requirements for progression, stealth sessions, commands, persistence, and delivery. -- [Design log](log.md) - Material product decisions and bundle changes. diff --git a/design/log.md b/design/log.md deleted file mode 100644 index c6d80be..0000000 --- a/design/log.md +++ /dev/null @@ -1,39 +0,0 @@ -# Spigot Stealth Design Log - -## 2026-09-06 - -- **Completion**: Extended US-004 with permission-gated private concealed-player counts for joining admins and arrival notices for online admins, including command hints, zero-count silence, and no duplicate notice for concealed admins. Verified all 70 tests, the JAR build, and OKF validation; not deployed or live-server tested. - -- **Completion**: Extended US-002 with concealed-username suggestion filtering, scrambled default chat and vanilla message/advancement names, scrambled overhead tags, and display-name restoration. Verified 65 automated tests, the Gradle JAR build, and OKF validation; live-server compatibility remains untested and custom chat/scoreboard integrations are outside this slice. - -## 2026-09-05 - -- **Fix**: Reworked US-002 server-list filtering for Purpur's native `NameAndId` samples by filtering names through Bukkit, limiting ProtocolLib to guarded count adjustment, and rate-limiting compatibility warnings; verified the complete Gradle build and OKF bundle. -- **Completion**: Extended US-002, US-004, and US-005 with a persistent, permission-gated sleep-count policy that excludes concealed players by default, applies immediately, restores prior player state, and supports contextual administration; verified the complete Gradle build and OKF bundle. -- **Completion**: Extended US-002 with ProtocolLib filtering of concealed sessions from multiplayer server-list counts and player samples while preserving actual online state and advertised capacity; verified the complete Gradle build and OKF bundle. - -## 2026-09-04 - -- **Completion**: Extended US-002 so concealed players disconnect without a public quit announcement while ordinary quit messages remain unchanged; verified listener tests, the complete Gradle build, and the OKF bundle. -- **Completion**: Extended US-003 and US-004 with contextual, prefix-filtered command completion that suppresses generic player suggestions, plus an administrative list of all known online and offline unlocked players; verified the complete Gradle build and OKF bundle. - -## 2026-08-14 - -- **Completion**: Completed US-004 with exact known online and offline targeting, complete status, idempotent grants, confirmed resets, active concealment discovery, operator-default permissions, asynchronous persisted replies, and console audits; verified the full Gradle build. -- **Implementation**: Began US-004 with test-first exact offline targeting, grant/reset transitions, concealed-player listing, permissions, confirmation, and audit logging. -- **Completion**: Completed US-002 with single-use durable prepared logins, join-announcement suppression, ProtocolLib tab-only removal, scoreboard overhead-name suppression, visible physical entities, periodic observer refresh, respawn restoration, and disconnect or disable cleanup; verified the full Gradle build. -- **Implementation**: Began US-002 with test-first prepared-login consumption, session-scoped concealment, join suppression, tab removal, and overhead-name presentation. -- **Completion**: Completed US-003 with `/stealth progress`, live unsaved interval inclusion, configured target and remaining output, concise duration formatting, and unlocked usage guidance; verified the full Gradle build. -- **Implementation**: Began US-003 with player-facing progress command and human-readable duration tests. -- **Completion**: Completed US-001 with direct potion-drink effect attribution, monotonic elapsed-time accumulation, safe refresh and stop transitions, durable UUID progress, exact-once unlocking, and title plus chat presentation; verified the full Gradle build. -- **Implementation**: Began US-001 with monotonic-clock progression tests and potion-cause event attribution. -- **Completion**: Completed US-005 with validated eight-hour defaults and configurable messages, UUID-keyed immutable state, RFC 3339 timing metadata, safe invalid-record defaults, unknown-field preservation, atomic YAML replacement, periodic saves, and serialized off-thread persistence; verified the full Gradle build. -- **Implementation**: Began US-005 with test-first validated settings, UUID-keyed state, and defensive asynchronous persistence. -- **Completion**: Completed US-006 with a Java 17 Gradle build, strict compiler linting, Spigot API, JUnit 5, Mockito, plugin metadata, OKF validation, Gitea CI, conventional-commit checks, semantic releases, and versioned release assets; verified the full build and a `1.2.3` release JAR. -- **Implementation**: Began US-006 with a test-driven Java 17, Gradle, Spigot, OKF validation, and Gitea delivery foundation modeled on Spigot Tyrant. -- **Creation**: Established the OKF v0.1 product record for Spigot Stealth. -- **Decision**: Only invisibility from a potion the player directly drinks contributes to the default eight-hour unlock requirement. -- **Decision**: An unlocked player who disconnects while their qualifying potion effect remains active conceals their identity for the entirety of their next online session. -- **Decision**: Each concealed session requires a new qualifying potion and disconnect; concealment never carries automatically into another session. -- **Decision**: Concealed players remain physically visible but have no join announcement, tab-list entry, or overhead name tag for any player, including administrators. -- **Decision**: Administrators can inspect and manage online or offline progression and list currently concealed players. diff --git a/design/user-stories/index.md b/design/user-stories/index.md deleted file mode 100644 index 5762ea3..0000000 --- a/design/user-stories/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Spigot Stealth User Stories - -1. [US-001: Accumulate invisibility time and unlock stealth](us-001-accumulate-invisibility-and-unlock.md) - Earn stealth by spending eight hours under invisibility effects from directly consumed potions. -2. [US-002: Rejoin without a visible identity](us-002-rejoin-without-visible-identity.md) - Turn a qualifying invisible disconnect into one concealed online session with filtered name suggestions and scrambled message and overhead names. -3. [US-003: Check personal stealth progress](us-003-check-personal-progress.md) - View accumulated time, remaining time, and unlock status. -4. [US-004: Inspect and manage player stealth](us-004-administer-player-stealth.md) - Inspect and modify online or offline progression, find concealed players, and receive private login count notices. -5. [US-005: Configure and persist stealth progression](us-005-configure-and-persist-progression.md) - Keep progression durable and operator-configurable. -6. [US-006: Build, test, and release the plugin](us-006-build-test-and-release.md) - Provide repeatable Gradle builds and Gitea delivery. diff --git a/design/user-stories/us-001-accumulate-invisibility-and-unlock.md b/design/user-stories/us-001-accumulate-invisibility-and-unlock.md deleted file mode 100644 index 8a1eeb1..0000000 --- a/design/user-stories/us-001-accumulate-invisibility-and-unlock.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -type: User Story -title: "US-001: Accumulate invisibility time and unlock stealth" -description: Reward sustained use of directly consumed invisibility potions with the stealth ability. -status: done ---- - -# US-001: Accumulate invisibility time and unlock stealth - -As a **player**, I want my qualifying invisibility time to accumulate so that sustained potion use eventually unlocks stealth. - -## Acceptance criteria - -- [x] Only time spent online under an invisibility effect produced by a potion the player directly drank contributes to progression. -- [x] Invisibility received from splash potions, lingering potions, tipped arrows, commands, plugins, or other sources does not contribute. -- [x] Qualifying time stops when the effect ends, is removed, is replaced by a non-qualifying source, the player disconnects, or the plugin disables. -- [x] Refreshed or overlapping qualifying effects never count elapsed time more than once. -- [x] Qualifying time accumulates across effects, sessions, and server restarts. -- [x] The ability unlocks when accumulated qualifying time reaches eight hours by default. -- [x] Reaching the threshold grants the unlock exactly once without discarding excess elapsed time. -- [x] When an online player unlocks stealth, they receive both a full-screen title and a chat message explaining the ability. -- [x] Progress and unlock ownership are associated with the player's UUID rather than their current name. - -## Validation - -Automated tests verify monotonic accumulation across intervals, refresh without duplicate time, exact-once threshold crossing with excess-time preservation, direct potion-drink cause filtering, non-qualifying replacement, UUID state, and online title and chat presentation. The complete `./gradlew clean check jar` lifecycle passes. - -## Related - -- [Rejoin without a visible identity](us-002-rejoin-without-visible-identity.md) -- [Check personal stealth progress](us-003-check-personal-progress.md) -- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/design/user-stories/us-002-rejoin-without-visible-identity.md b/design/user-stories/us-002-rejoin-without-visible-identity.md deleted file mode 100644 index 9dbb335..0000000 --- a/design/user-stories/us-002-rejoin-without-visible-identity.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -type: User Story -title: "US-002: Rejoin without a visible identity" -description: Let an unlocked player turn a qualifying invisible disconnect into one identity-concealed session. -status: done ---- - -# US-002: Rejoin without a visible identity - -As an **unlocked player**, I want to disconnect while invisibility from a potion I drank is active so that my identity is concealed throughout my next session. - -## Acceptance criteria - -- [x] An unlocked player becomes eligible for a concealed login only by disconnecting while an invisibility effect from a potion they directly drank remains active. -- [x] A player who has not unlocked stealth cannot prepare a concealed login. -- [x] An ordinary disconnect without an active qualifying effect clears any preparation for the next login. -- [x] On a prepared login, no public join announcement is shown. -- [x] When a concealed player disconnects, no public quit or disconnect announcement is shown. -- [x] Ordinary players' quit messages remain unchanged. -- [x] Concealment is checked before disconnect cleanup so announcement suppression is reliable. -- [x] Throughout the concealed session, the player is absent from every other player's tab list, including administrators' tab lists. -- [x] The multiplayer server list's online-player count excludes currently concealed players. -- [x] Concealed players are excluded from any player-name sample shown for the server-list count, while ordinary players remain represented. -- [x] The public count never becomes negative, and the configured maximum-player count remains unchanged. -- [x] Server-list concealment changes only the public ping response and does not alter actual online-player state or gameplay. -- [x] Server-list filtering remains error-free when Purpur represents player samples with native `NameAndId` values rather than Mojang `GameProfile` values. -- [x] A server-ping compatibility failure leaves the original response usable and does not produce repeated unhandled listener exceptions. -- [x] By default, concealed players are excluded from sleep-percentage calculations. -- [x] When the sleep-count policy is `include`, concealed players count normally; ordinary players are never modified by either policy. -- [x] A player's previous sleeping-ignore state is restored when concealment ends, the player disconnects or is reset, the policy changes to `include`, or the plugin disables. -- [x] Policy changes apply immediately to currently concealed players. -- [x] Throughout the concealed session, the overhead name is rendered as scrambled magic text, including for administrators, while the physical character stays visible. -- [x] Concealed usernames are excluded from outgoing command suggestions, including `/msg`, `/tell`, and `/w`; manually addressing a known username remains possible. -- [x] Default chat display names and vanilla private-message and advancement name components are scrambled without changing message content or signed chat bodies. -- [x] Masked message name components do not expose the original identity through hover or click actions. -- [x] Previous display names and ordinary suggestions return when concealment ends; ordinary players remain unaffected. -- [x] The concealed player's physical character remains visible in the world and retains ordinary movement, interaction, combat, and permission behavior. -- [x] The concealed player receives a private message explaining that stealth is active for the session. -- [x] Concealment lasts until the player disconnects and is handled predictably across death and plugin reload or disable events. -- [x] Disconnecting consumes the current concealed session; another concealed login requires another qualifying potion and qualifying disconnect. -- [x] Merely owning the unlock never conceals an ordinary login or carries concealment automatically into a later session. -- [x] Prepared-login state survives a server restart between the qualifying disconnect and the next login. - -## Validation - -Automated tests verify unlocked and locked disconnect transitions, ordinary-disconnect clearing, one-login consumption, concealed join and quit announcement suppression, preservation of ordinary announcements, private activation messaging, ordinary-login presentation, tab removal for existing and new observers, overhead-name suppression, active concealed-session tracking, ProtocolLib-independent native player-sample filtering, guarded public server-list count adjustment, nonnegative counts, unchanged maximum capacity, default sleep-count exclusion, immediate policy changes, restoration of prior sleeping-ignore state, and the absence of entity-hiding calls. ProtocolLib is declared as a required dependency, prepared state round trips through YAML, and `./gradlew clean check jar` passes. - -Identity masking verification (2026-09-06): `./gradlew --offline clean check jar` passed all 65 tests and OKF validation. Tests cover semantic name slots, preserved message content, stripped identifying actions, reflected suggestion entries and chat-bound records, safe packet failure handling, immutable name snapshots, overhead formatting, and display-name restoration after repeated concealment. Presentation tests first failed to compile against the missing snapshot API, then passed after implementation. These are automated unit checks, not a live-client smoke test. - -## Identity masking scope - -Masking applies to active concealed sessions, not every potion effect. Outgoing username suggestions are filtered across commands; entering a known username manually still works. Default Bukkit chat uses a temporary scrambled `Anonymous` display name. Vanilla translated announcements and chat-type bound names are masked at the packet boundary without editing signed message bodies. The overhead tag uses scoreboard magic formatting on the profile name, not a profile rename; this is visual concealment, not anonymity against modified clients. - -Custom chat formats that ignore Bukkit display names, nicknames, custom scoreboards, and third-party messaging plugins may need dedicated integration. Unsupported packet layouts retain the original packet and emit one warning per packet type rather than interrupting chat delivery. Live-server/client compatibility still requires a smoke test, particularly after Minecraft or ProtocolLib upgrades. - -Implementation: [identity presentation](../../src/main/java/games/dmg/spigotstealth/BukkitIdentityPresentation.java), [packet listener](../../src/main/java/games/dmg/spigotstealth/ProtocolLibIdentityMaskingListener.java), and [semantic name masking](../../src/main/java/games/dmg/spigotstealth/StealthChatMasker.java). - -## Related - -- [Accumulate invisibility time and unlock stealth](us-001-accumulate-invisibility-and-unlock.md) -- [Inspect and manage player stealth](us-004-administer-player-stealth.md) -- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/design/user-stories/us-003-check-personal-progress.md b/design/user-stories/us-003-check-personal-progress.md deleted file mode 100644 index 55d46b7..0000000 --- a/design/user-stories/us-003-check-personal-progress.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -type: User Story -title: "US-003: Check personal stealth progress" -description: Show a player their accumulated qualifying invisibility time and stealth unlock status. -status: done ---- - -# US-003: Check personal stealth progress - -As a **player**, I want to check my stealth progress so that I know how close I am to unlocking the ability and how to use it afterward. - -## Acceptance criteria - -- [x] `/stealth progress` reports the player's accumulated qualifying invisibility time. -- [x] Before unlock, the command reports the configured target and remaining duration. -- [x] While a qualifying effect is active, the report includes elapsed time not yet written during the current tracking interval. -- [x] After unlock, the command clearly reports that stealth is unlocked and explains how to prepare a concealed login. -- [x] Durations are presented in a concise, human-readable form. -- [x] Repeated command use does not change progression or concealment state. -- [x] The command has clear usage metadata and an appropriate player permission. -- [x] `/stealth` tab-completes `progress`, filtered by the entered prefix. -- [x] Unsupported argument positions return no suggestions instead of Bukkit's generic player list. - -## Validation - -Automated tests verify concise duration formatting, live in-flight progress, configured target and remaining output, command non-mutation, unlocked usage guidance, prefix-filtered completion, and suppression of irrelevant suggestions. Command metadata and the complete `./gradlew clean check jar` lifecycle pass. - -## Related - -- [Accumulate invisibility time and unlock stealth](us-001-accumulate-invisibility-and-unlock.md) -- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/design/user-stories/us-004-administer-player-stealth.md b/design/user-stories/us-004-administer-player-stealth.md deleted file mode 100644 index 6fed946..0000000 --- a/design/user-stories/us-004-administer-player-stealth.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -type: User Story -title: "US-004: Inspect and manage player stealth" -description: Give administrators durable controls for online and offline progression, unlocks, and active concealment. -status: done ---- - -# US-004: Inspect and manage player stealth - -As a **server administrator**, I want to inspect and correct player stealth state so that I can support players whether they are online or offline. - -## Acceptance criteria - -- [x] `/stealthadmin status ` reports accumulated time, unlock status, prepared-login state, and current concealment state. -- [x] Status inspection works for online players and known offline players selected by exact current or previously recorded name or UUID. -- [x] `/stealthadmin grant ` grants the unlock to an online or known offline player without altering accumulated time unnecessarily. -- [x] Granting an already-owned unlock is safe and clearly reports that no change was needed. -- [x] Granting the unlock sends the normal full-screen title and chat notification when the target is online. -- [x] `/stealthadmin reset confirm` clears accumulated time, unlock ownership, prepared-login state, and current concealment for an online or known offline player. -- [x] Reset requires explicit confirmation and safely restores an online concealed player to ordinary identity presentation. -- [x] `/stealthadmin list` lists every currently online concealed player and clearly reports when there are none. -- [x] Concealed players remain absent from administrators' ordinary tab lists and retain scrambled overhead names; the admin command is the supported discovery mechanism. -- [x] An administrator joining while concealed players are online receives a private count and a hint to use `/stealthadmin list`. -- [x] When a concealed player joins, online administrators receive a private arrival notice, the updated concealed-player count, and the command hint. -- [x] Notifications require `spigotstealth.admin`; ordinary players receive no administrative notice. -- [x] Counts include the newly joined concealed player, and a joining concealed administrator receives only one administrative notification. -- [x] An administrator joining when no concealed players are online receives no notification. -- [x] Commands clearly reject unknown, ambiguous, malformed, or otherwise invalid targets without creating unintended player records. -- [x] Administrative inspection and modification require an operator-default administrative permission. -- [x] State-changing operations persist before success is reported and are safe under retries. -- [x] Grant and reset actions record the administrator, target UUID, and action in the server log without blocking the server tick thread. -- [x] `/stealthadmin` tab-completes `status`, `grant`, `reset`, and `list`, filtered by the entered prefix. -- [x] Target positions for `status`, `grant`, and `reset` suggest known player names, and reset's final argument suggests `confirm`. -- [x] Administrative completions require the administrative permission, and irrelevant positions return no suggestions instead of Bukkit's generic player list. -- [x] `/stealthadmin list unlocked` lists every known online or offline player with stealth unlocked. -- [x] The unlocked list is sorted case-insensitively, identifies nameless records by UUID, and clearly reports when it is empty. -- [x] Existing `/stealthadmin list` behavior continues to list currently concealed online players. -- [x] `/stealthadmin sleepcount status` reports whether concealed players are included in or excluded from sleep-percentage calculations. -- [x] `/stealthadmin sleepcount ` persists the policy before reporting success, applies it immediately, and records the administrator and policy in the server log. -- [x] The `sleepcount`, `status`, `include`, and `exclude` arguments are permission-gated and contextually tab-completed. - -## Validation - -Automated tests verify exact offline name and UUID resolution, ambiguous and unknown rejection without record creation, complete status output, idempotent grants and notification, complete resets with presentation cleanup, online concealed-player filtering, online and offline unlocked-player listing, contextual permission-gated completion, durable sleep-count status and policy changes, immediate policy refresh, confirmation and permission gates, persisted-before-success replies, and audit records. The complete `./gradlew clean check jar` lifecycle passes. - -Login notice verification (2026-09-06): `./gradlew --offline clean check jar` passed all 70 tests and OKF validation. New tests first failed against the absent listener, then passed after implementation. Tests cover permission gates, zero-count silence, singular/plural counts, concealed-admin deduplication, ordinary-join silence for existing admins, and a prepared-login transition followed by an accurate administrative notice. Live-server verification remains pending. - -Implementation: [admin join listener](../../src/main/java/games/dmg/spigotstealth/StealthAdminJoinListener.java), registered at `MONITOR` after the session listener's `HIGHEST` login transition. Notices expose only counts; names remain available through `/stealthadmin list`. - -## Related - -- [Rejoin without a visible identity](us-002-rejoin-without-visible-identity.md) -- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/design/user-stories/us-005-configure-and-persist-progression.md b/design/user-stories/us-005-configure-and-persist-progression.md deleted file mode 100644 index b315d3e..0000000 --- a/design/user-stories/us-005-configure-and-persist-progression.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -type: User Story -title: "US-005: Configure and persist stealth progression" -description: Give operators validated settings and durable, defensive storage for stealth behavior. -status: done ---- - -# US-005: Configure and persist stealth progression - -As a **server operator**, I want stealth progression to be configurable and durable so that the plugin remains predictable across restarts and balance changes. - -## Acceptance criteria - -- [x] The qualifying-time threshold is configurable and defaults to eight hours. -- [x] Player-facing progress, unlock, prepared-login, and concealed-session messages are configurable. -- [x] Startup validates required settings before registering partially functional listeners, commands, or tasks. -- [x] Invalid required configuration prevents initialization and produces a clear server log message. -- [x] UUID-keyed state stores accumulated qualifying duration, unlock ownership, active qualifying timing data, prepared-login state, and any current concealment metadata needed for safe recovery. -- [x] Qualifying runtime intervals use a monotonic elapsed-time source so wall-clock adjustments cannot grant or remove progress. -- [x] Durable timestamps, when required, use RFC 3339 UTC notation. -- [x] State is saved periodically, after material state changes, and during orderly plugin disable. -- [x] State uses atomic replacement where supported so an interrupted write does not replace valid data with a partial file. -- [x] Corrupt, unknown, or invalid records cannot silently grant time, an unlock, a prepared login, or concealment. -- [x] Unknown forward-compatible fields are preserved where practical. -- [x] Persistence work does not perform blocking file operations on the server tick thread. -- [x] The sleep-count policy persists in `state.yml` and defaults safely to `exclude` when missing or invalid. - -## Validation - -Verified settings defaults and rejection, packaged configuration, safe UUID-state defaults, sleep-count policy defaults and round trips, RFC 3339 round trips, unknown-field preservation, invalid-record rejection, atomic repository writes, and dedicated-thread loading and saving with automated tests and `./gradlew clean check jar`. - -## Related - -- [Accumulate invisibility time and unlock stealth](us-001-accumulate-invisibility-and-unlock.md) -- [Rejoin without a visible identity](us-002-rejoin-without-visible-identity.md) -- [Inspect and manage player stealth](us-004-administer-player-stealth.md) diff --git a/design/user-stories/us-006-build-test-and-release.md b/design/user-stories/us-006-build-test-and-release.md deleted file mode 100644 index 50ad48a..0000000 --- a/design/user-stories/us-006-build-test-and-release.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -type: User Story -title: "US-006: Build, test, and release the plugin" -description: Give maintainers repeatable builds, automated verification, and versioned Gitea releases. -status: done ---- - -# US-006: Build, test, and release the plugin - -As a **plugin maintainer**, I want automated builds and releases modeled on Spigot Tyrant so that tested, correctly versioned artifacts can be distributed consistently. - -## Acceptance criteria - -- [x] The Gradle Kotlin DSL project compiles against Spigot API `26.2-R0.1-SNAPSHOT` using a Java 17 toolchain. -- [x] Compiler lint warnings fail the build. -- [x] Automated JUnit 5 and Mockito tests run as part of the Gradle check lifecycle. -- [x] Pushes and pull requests build and test the plugin in Gitea Actions. -- [x] Pull requests validate conventional commit messages. -- [x] CI stores a `spigot-stealth` development JAR as a workflow artifact. -- [x] Main-branch conventional commits drive semantic versioning. -- [x] A successful release builds a versioned JAR and attaches it to the corresponding Gitea release. -- [x] Build files, Gradle wrapper, workflows, and release behavior follow `../spigot-tyrant/` where applicable while using Spigot Stealth names and identifiers. -- [x] OKF validation using `okf-base.yaml` runs locally through the repository validation script and in CI. - -## Validation - -Verified with `./gradlew clean check jar`, a release-version `1.2.3` JAR with matching embedded plugin metadata, and `./scripts/validate-okf.sh`. - -## Related - -- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/okf-base.yaml b/okf-base.yaml deleted file mode 100644 index 5fde94a..0000000 --- a/okf-base.yaml +++ /dev/null @@ -1,23 +0,0 @@ -okf_version: "0.1" - -base: - name: Spigot Stealth Design - roots: - - path: design - reserved_files: - index: index.md - log: log.md - -profile: - types: - User Story: - required: [type, title, description, status] - optional: [] - status_values: [backlog, in-progress, done] - date_fields: [] - -hygiene: - broken_links: error - reserved_files: error - unknown_fields: error - split_candidates: off diff --git a/scripts/validate-okf.sh b/scripts/validate-okf.sh deleted file mode 100755 index 03eee8a..0000000 --- a/scripts/validate-okf.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env bash -# OKF Bundle Validator v0.1 -# Usage: validate.sh -# Checks conformance with OKF v0.1 spec: -# E1: All non-reserved .md files have YAML frontmatter -# E2: All frontmatter has non-empty 'type' field -# E3: Reserved files follow structure rules - -set -euo pipefail - -REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -BUNDLE="${1:-${REPOSITORY_ROOT}/design}" -MANIFEST="${REPOSITORY_ROOT}/okf-base.yaml" - -if command -v okflint >/dev/null 2>&1; then - exec okflint validate --manifest "$MANIFEST" "$BUNDLE" -fi - -echo "Warning: okflint is unavailable; running core OKF v0.1 validation only." >&2 - -ERRORS=0 -WARNINGS=0 -TOTAL=0 - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' - -if [ ! -d "$BUNDLE" ]; then - echo -e "${RED}Error: '$BUNDLE' is not a directory${NC}" - exit 1 -fi - -echo "Validating OKF bundle: $BUNDLE" -echo "---" - -# Find all .md files -while IFS= read -r -d '' file; do - TOTAL=$((TOTAL + 1)) - relative="${file#$BUNDLE/}" - basename=$(basename "$file") - - # Skip reserved files (validate separately) - if [[ "$basename" == "index.md" || "$basename" == "log.md" ]]; then - # E3: Check reserved file structure - if [[ "$basename" == "index.md" ]]; then - # index.md should NOT have frontmatter (except bundle root may have okf_version) - if head -1 "$file" | grep -q "^---$"; then - # Allow only if it's bundle root and contains okf_version - if [[ "$relative" != "index.md" ]]; then - echo -e "${RED}E3: $relative — index.md should not have frontmatter${NC}" - ERRORS=$((ERRORS + 1)) - fi - fi - fi - if [[ "$basename" == "log.md" ]]; then - # log.md should have date headings in YYYY-MM-DD format - if ! grep -qE "^## [0-9]{4}-[0-9]{2}-[0-9]{2}" "$file" 2>/dev/null; then - if [ -s "$file" ]; then - echo -e "${YELLOW}W: $relative — log.md has no ISO 8601 date headings${NC}" - WARNINGS=$((WARNINGS + 1)) - fi - fi - fi - continue - fi - - # E1: Check for YAML frontmatter - if ! head -1 "$file" | grep -q "^---$"; then - echo -e "${RED}E1: $relative — no YAML frontmatter${NC}" - ERRORS=$((ERRORS + 1)) - continue - fi - - # Extract frontmatter (between first --- and second ---) - frontmatter=$(sed -n '2,/^---$/p' "$file" | sed '$d') - - # E2: Check for non-empty type field - type_value=$(echo "$frontmatter" | grep -E "^type:" | sed 's/^type:\s*//' | tr -d '"' | tr -d "'" | xargs) - if [ -z "$type_value" ]; then - echo -e "${RED}E2: $relative — missing or empty 'type' field${NC}" - ERRORS=$((ERRORS + 1)) - continue - fi - - # Warnings for recommended fields - if ! echo "$frontmatter" | grep -qE "^title:"; then - echo -e "${YELLOW}W1: $relative — missing recommended 'title' field${NC}" - WARNINGS=$((WARNINGS + 1)) - fi - if ! echo "$frontmatter" | grep -qE "^description:"; then - echo -e "${YELLOW}W1: $relative — missing recommended 'description' field${NC}" - WARNINGS=$((WARNINGS + 1)) - fi - -done < <(find "$BUNDLE" -name "*.md" -type f -print0 | sort -z) - -# Summary -echo "---" -echo "Files scanned: $TOTAL" -if [ $ERRORS -eq 0 ]; then - echo -e "${GREEN}✅ Bundle is OKF v0.1 conformant${NC}" -else - echo -e "${RED}❌ $ERRORS error(s) — bundle is NOT conformant${NC}" -fi -if [ $WARNINGS -gt 0 ]; then - echo -e "${YELLOW}⚠ $WARNINGS warning(s)${NC}" -fi - -exit $ERRORS