From 4aa0b438fbba3fcb0d7e301be8e0e5fc7c409921 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Fri, 14 Aug 2026 23:48:22 -0400 Subject: [PATCH] docs(design): add Spigot Stealth user stories --- AGENTS.md | 53 +++++++++ design/index.md | 12 ++ design/log.md | 10 ++ design/user-stories/index.md | 8 ++ ...-001-accumulate-invisibility-and-unlock.md | 28 +++++ .../us-002-rejoin-without-visible-identity.md | 31 +++++ .../us-003-check-personal-progress.md | 25 ++++ .../us-004-administer-player-stealth.md | 31 +++++ ...s-005-configure-and-persist-progression.md | 31 +++++ .../us-006-build-test-and-release.md | 27 +++++ okf-base.yaml | 23 ++++ scripts/validate-okf.sh | 111 ++++++++++++++++++ 12 files changed, 390 insertions(+) create mode 100644 AGENTS.md create mode 100644 design/index.md create mode 100644 design/log.md create mode 100644 design/user-stories/index.md create mode 100644 design/user-stories/us-001-accumulate-invisibility-and-unlock.md create mode 100644 design/user-stories/us-002-rejoin-without-visible-identity.md create mode 100644 design/user-stories/us-003-check-personal-progress.md create mode 100644 design/user-stories/us-004-administer-player-stealth.md create mode 100644 design/user-stories/us-005-configure-and-persist-progression.md create mode 100644 design/user-stories/us-006-build-test-and-release.md create mode 100644 okf-base.yaml create mode 100755 scripts/validate-okf.sh diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c62a81a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,53 @@ +# Repository Agent Guidance + +## User-story-driven development + +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 changing behavior: + +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. diff --git a/design/index.md b/design/index.md new file mode 100644 index 0000000..93643e3 --- /dev/null +++ b/design/index.md @@ -0,0 +1,12 @@ +--- +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. + +## 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 new file mode 100644 index 0000000..73a0a98 --- /dev/null +++ b/design/log.md @@ -0,0 +1,10 @@ +# Spigot Stealth Design Log + +## 2026-08-14 + +- **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 new file mode 100644 index 0000000..748f41e --- /dev/null +++ b/design/user-stories/index.md @@ -0,0 +1,8 @@ +# 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. +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 and find concealed players. +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 new file mode 100644 index 0000000..72e879e --- /dev/null +++ b/design/user-stories/us-001-accumulate-invisibility-and-unlock.md @@ -0,0 +1,28 @@ +--- +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: backlog +--- + +# 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 + +- [ ] Only time spent online under an invisibility effect produced by a potion the player directly drank contributes to progression. +- [ ] Invisibility received from splash potions, lingering potions, tipped arrows, commands, plugins, or other sources does not contribute. +- [ ] Qualifying time stops when the effect ends, is removed, is replaced by a non-qualifying source, the player disconnects, or the plugin disables. +- [ ] Refreshed or overlapping qualifying effects never count elapsed time more than once. +- [ ] Qualifying time accumulates across effects, sessions, and server restarts. +- [ ] The ability unlocks when accumulated qualifying time reaches eight hours by default. +- [ ] Reaching the threshold grants the unlock exactly once without discarding excess elapsed time. +- [ ] When an online player unlocks stealth, they receive both a full-screen title and a chat message explaining the ability. +- [ ] Progress and unlock ownership are associated with the player's UUID rather than their current name. + +## 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 new file mode 100644 index 0000000..46de37e --- /dev/null +++ b/design/user-stories/us-002-rejoin-without-visible-identity.md @@ -0,0 +1,31 @@ +--- +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: backlog +--- + +# 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 + +- [ ] An unlocked player becomes eligible for a concealed login only by disconnecting while an invisibility effect from a potion they directly drank remains active. +- [ ] A player who has not unlocked stealth cannot prepare a concealed login. +- [ ] An ordinary disconnect without an active qualifying effect clears any preparation for the next login. +- [ ] On a prepared login, no public join announcement is shown. +- [ ] Throughout the concealed session, the player is absent from every other player's tab list, including administrators' tab lists. +- [ ] Throughout the concealed session, no overhead name tag identifies the player to any other player, including administrators. +- [ ] The concealed player's physical character remains visible in the world and retains ordinary movement, interaction, combat, and permission behavior. +- [ ] The concealed player receives a private message explaining that stealth is active for the session. +- [ ] Concealment lasts until the player disconnects and is handled predictably across death and plugin reload or disable events. +- [ ] Disconnecting consumes the current concealed session; another concealed login requires another qualifying potion and qualifying disconnect. +- [ ] Merely owning the unlock never conceals an ordinary login or carries concealment automatically into a later session. +- [ ] Prepared-login state survives a server restart between the qualifying disconnect and the next login. + +## 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 new file mode 100644 index 0000000..93bd135 --- /dev/null +++ b/design/user-stories/us-003-check-personal-progress.md @@ -0,0 +1,25 @@ +--- +type: User Story +title: "US-003: Check personal stealth progress" +description: Show a player their accumulated qualifying invisibility time and stealth unlock status. +status: backlog +--- + +# 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 + +- [ ] `/stealth progress` reports the player's accumulated qualifying invisibility time. +- [ ] Before unlock, the command reports the configured target and remaining duration. +- [ ] While a qualifying effect is active, the report includes elapsed time not yet written during the current tracking interval. +- [ ] After unlock, the command clearly reports that stealth is unlocked and explains how to prepare a concealed login. +- [ ] Durations are presented in a concise, human-readable form. +- [ ] Repeated command use does not change progression or concealment state. +- [ ] The command has clear usage metadata and an appropriate player permission. + +## 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 new file mode 100644 index 0000000..0033216 --- /dev/null +++ b/design/user-stories/us-004-administer-player-stealth.md @@ -0,0 +1,31 @@ +--- +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: backlog +--- + +# 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 + +- [ ] `/stealthadmin status ` reports accumulated time, unlock status, prepared-login state, and current concealment state. +- [ ] Status inspection works for online players and known offline players selected by exact current or previously recorded name or UUID. +- [ ] `/stealthadmin grant ` grants the unlock to an online or known offline player without altering accumulated time unnecessarily. +- [ ] Granting an already-owned unlock is safe and clearly reports that no change was needed. +- [ ] Granting the unlock sends the normal full-screen title and chat notification when the target is online. +- [ ] `/stealthadmin reset confirm` clears accumulated time, unlock ownership, prepared-login state, and current concealment for an online or known offline player. +- [ ] Reset requires explicit confirmation and safely restores an online concealed player to ordinary identity presentation. +- [ ] `/stealthadmin list` lists every currently online concealed player and clearly reports when there are none. +- [ ] Concealed players remain absent from administrators' ordinary tab lists and retain hidden overhead name tags; the admin command is the supported discovery mechanism. +- [ ] Commands clearly reject unknown, ambiguous, malformed, or otherwise invalid targets without creating unintended player records. +- [ ] Administrative inspection and modification require an operator-default administrative permission. +- [ ] State-changing operations persist before success is reported and are safe under retries. +- [ ] Grant and reset actions record the administrator, target UUID, and action in the server log without blocking the server tick thread. + +## 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 new file mode 100644 index 0000000..d911a1a --- /dev/null +++ b/design/user-stories/us-005-configure-and-persist-progression.md @@ -0,0 +1,31 @@ +--- +type: User Story +title: "US-005: Configure and persist stealth progression" +description: Give operators validated settings and durable, defensive storage for stealth behavior. +status: backlog +--- + +# 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 + +- [ ] The qualifying-time threshold is configurable and defaults to eight hours. +- [ ] Player-facing progress, unlock, prepared-login, and concealed-session messages are configurable. +- [ ] Startup validates required settings before registering partially functional listeners, commands, or tasks. +- [ ] Invalid required configuration prevents initialization and produces a clear server log message. +- [ ] 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. +- [ ] Qualifying runtime intervals use a monotonic elapsed-time source so wall-clock adjustments cannot grant or remove progress. +- [ ] Durable timestamps, when required, use RFC 3339 UTC notation. +- [ ] State is saved periodically, after material state changes, and during orderly plugin disable. +- [ ] State uses atomic replacement where supported so an interrupted write does not replace valid data with a partial file. +- [ ] Corrupt, unknown, or invalid records cannot silently grant time, an unlock, a prepared login, or concealment. +- [ ] Unknown forward-compatible fields are preserved where practical. +- [ ] Persistence work does not perform blocking file operations on the server tick thread. + +## 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 new file mode 100644 index 0000000..1e22ae0 --- /dev/null +++ b/design/user-stories/us-006-build-test-and-release.md @@ -0,0 +1,27 @@ +--- +type: User Story +title: "US-006: Build, test, and release the plugin" +description: Give maintainers repeatable builds, automated verification, and versioned Gitea releases. +status: backlog +--- + +# 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 + +- [ ] The Gradle Kotlin DSL project compiles against Spigot API `26.2-R0.1-SNAPSHOT` using a Java 17 toolchain. +- [ ] Compiler lint warnings fail the build. +- [ ] Automated JUnit 5 and Mockito tests run as part of the Gradle check lifecycle. +- [ ] Pushes and pull requests build and test the plugin in Gitea Actions. +- [ ] Pull requests validate conventional commit messages. +- [ ] CI stores a `spigot-stealth` development JAR as a workflow artifact. +- [ ] Main-branch conventional commits drive semantic versioning. +- [ ] A successful release builds a versioned JAR and attaches it to the corresponding Gitea release. +- [ ] Build files, Gradle wrapper, workflows, and release behavior follow `../spigot-tyrant/` where applicable while using Spigot Stealth names and identifiers. +- [ ] OKF validation using `okf-base.yaml` runs locally through the repository validation script and in CI. + +## Related + +- [Configure and persist stealth progression](us-005-configure-and-persist-progression.md) diff --git a/okf-base.yaml b/okf-base.yaml new file mode 100644 index 0000000..5fde94a --- /dev/null +++ b/okf-base.yaml @@ -0,0 +1,23 @@ +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 new file mode 100755 index 0000000..03eee8a --- /dev/null +++ b/scripts/validate-okf.sh @@ -0,0 +1,111 @@ +#!/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