diff --git a/.env.example b/.env.example index 2378a8d..d5430e9 100644 --- a/.env.example +++ b/.env.example @@ -18,7 +18,7 @@ MODRINTH_TOKEN= MODRINTH_PROJECT_ID=metropolis-builder # Optional Markdown override; empty generates notes from commits since the last tag. MODRINTH_CHANGELOG= -MODRINTH_GAME_VERSIONS=26.2 +MODRINTH_GAME_VERSIONS=26.2,26.3 MODRINTH_LOADERS=paper MODRINTH_VERSION_TYPE= MODRINTH_DEPENDENCIES= diff --git a/.gitignore b/.gitignore index c860c0a..b5e83df 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ bin/ /backups/ /server/ .env +.settings/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fbd7265 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# Metropolis Working Rules + +Start with [CONTRIBUTING.md](CONTRIBUTING.md) for the first-contribution path, +architecture, build/test commands, and operational safety. [README.md](README.md) +is the player/server-admin behavior reference. Other files in `docs/` are +optional reference material, not competing onboarding guides. + +- Use **Metropolis** in human-facing prose. Preserve machine-facing identifiers, + including `Metropolis`, `com.majinnaibu.bukkitplugins.metropolis`, artifact + names, plugin IDs, commands, permission nodes, and URLs. +- Inspect the current diff before editing and preserve unrelated work. Do not + edit generated `build/`, `target/`, or `.gradle/` output. Never read, expose, + or commit `.env`, server/player data, or publishing credentials. +- Keep claims aligned with source, build configuration, CI, and scripts. State + Paper targets separately from versions actually built or tested. Keep + implemented behavior, planned work, automated coverage, and manual scenarios + distinct. +- When editing Java, preserve the existing AGPL header, use Gradle Spotless, + and keep Bukkit/Paper state and API access on the main thread. Avoid unrelated + gameplay changes. +- Before editing `docs/project-description.json`, read + `docs/projects.schema.json`. Validate the JSON against that schema without + changing the schema to accommodate invalid metadata. Preserve exactly the + existing top-level project key, `id`, `title`, `docId`, `image.url`, and + `headerImage.url` when present. Preserve `headerImage: null` or its absence. + These are fixed consuming-project integration values: do not derive or + “correct” their spelling, capitalization, spacing, or slug from repository + names. Image paths are relative to the consuming project's root; do not + resolve them here, convert them to repository URLs, or replace them with + generated/remote URLs. Preserve all other integration fields unless the task + requires a change. If schema compliance conflicts with preserving protected + values, stop and explain the conflict before editing. +- Do not start/stop servers, deploy, restore, publish, tag, or release without + explicit authorization. `tools/stop-server.sh` can match unrelated Paper + processes; `tools/restore-server.sh` replaces archive top-level items. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aafa29..cf14589 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,18 +1,57 @@ -# Contributing +# Contributing to Metropolis -## Development Setup +This is the single onboarding entry point for new contributors. Shared rules +for people and coding agents are in [AGENTS.md](AGENTS.md); read them before +editing, especially the scraper-metadata preservation and operational-safety +rules. -Use Java 25 or newer. The Gradle wrapper downloads Gradle 9.8; dependencies are declared in `build.gradle` and resolved from Maven Central, PaperMC, and EngineHub. +## First Contribution -```sh -./gradlew build -``` +1. Read [README.md](README.md) for supported server versions, installation, + configuration, and commands. +2. Trace the relevant implementation using the source map below and check + nearby tests before changing behavior. +3. Make a focused change. Add or update automated tests for behavior changes; + update the manual pre-release checklist for behavior that needs a real + server. +4. Run `./gradlew test` and `./gradlew build`. Report any manual scenarios that + were not run rather than describing them as passing. +5. Update user-facing descriptions if setup, supported versions, commands, + dependencies, or behavior changed. -The standard build runs JUnit 5 tests without starting Minecraft. Tests use Mockito for Bukkit and external-plugin boundaries. Keep domain and configuration behavior testable without a running server. +## Architecture and Source Map -## Live Server Tests +- `MetropolisPlugin` loads configuration, connects Bukkit to WorldGuard and + WorldEdit, allocates plots, generates blocks, and manages regions. +- `Plot`, `PlayerHome`, grid/layout/support classes, and home stores implement + plot geometry, region ownership, allocation, and selected-home persistence. +- `commands/` contains the command handlers; `eventlisteners/` contains join + behavior. +- `src/main/resources/plugin.yml` declares dependencies, commands, and command + permissions. `config.yml` is the bundled configuration template. +- `src/test/` contains isolated JUnit/Mockito tests. `src/integrationTest/` + contains the opt-in tests that require a running server and RCON. +- `build.gradle` defines dependencies, Java/Gradle versions, formatting, and + test tasks. `.github/workflows/gradle.yml` runs tests and builds on pushes + and pull requests to `develop`. -Live tests are opt-in and run separately from `build`: +## Prerequisites and Checks + +- JDK 25. The Gradle wrapper uses Gradle 9.8 and can download the required + Gradle distribution; dependencies resolve from Maven Central, PaperMC, and + EngineHub. +- `./gradlew test` runs server-free JUnit tests. +- `./gradlew build` runs the normal build and verification lifecycle. The + plugin jar is written under `build/libs/`. +- CI compiles against Paper API 26.2, runs `test`, then builds. It does not + start a Minecraft server or run `integrationTest`. + +The opt-in `integrationTest` source set currently has two tests: checking that +Metropolis appears in `/plugins`, and creating a coordinate reservation then +checking WorldGuard's `regions.yml`. It mutates the configured server's +WorldGuard data and needs a running Paper server with RCON, WorldGuard, and +WorldEdit. Use a disposable server, bind RCON to a trusted interface, and use +a temporary password: ```sh METROPOLIS_RCON_HOST=127.0.0.1 \ @@ -22,12 +61,27 @@ METROPOLIS_SERVER_DIR=/path/to/server \ ./gradlew integrationTest ``` -Use a disposable or backed-up Paper server with compatible WorldGuard and WorldEdit plugin jars installed. Keep RCON local, use a temporary password, and restore the server after tests that modify regions or worlds. +Set `METROPOLIS_WORLD` if the test world is not named `world`. These tests do +not cover the broader in-game workflows in [human-tests.md](docs/human-tests.md). -## Pull Requests +## Operational Safety -- Keep changes focused and include tests for behavior changes. -- Run `./gradlew build` before submitting. -- Run `integrationTest` only when changing behavior that requires Paper or the external plugins; do not add it to the default build. -- Update `README.md` and project descriptions when user-facing setup, commands, or dependencies change. -- Keep generated build output, server data, credentials, and local `.env` files out of commits. +Use the build/test commands above for ordinary contribution work. The scripts +under `tools/` also include server start/stop, deployment, backup/restore, and +publishing operations; they are not required for a normal build. Read a +script before use and get explicit authorization before running operational +or release actions. In particular, `stop-server.sh` matches Paper jar +processes by command line, and `restore-server.sh` replaces matching top-level +server items from an archive. Never commit `.env`, credentials, server state, +or generated build output. + +## Optional References + +- [README.md](README.md): player/server-admin usage reference. +- [human-tests.md](docs/human-tests.md): manual pre-release checklist, not an + automated test report. +- [TODO.md](docs/TODO.md): historical backlog; the maintained plan is + [modernization-plan.md](docs/modernization-plan.md). +- [Modrinth description](docs/modrinth-description.md): hosting copy reference. +- [Scraper metadata](docs/project-description.md) and + [schema](docs/projects.schema.json): consuming-site project entry. diff --git a/README.md b/README.md index ad5bc19..46127c1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ Metropolis expands a protected Minecraft city as players join. Each player can r ## Requirements -- Paper 26.2 or 26.3, with Java 25 or newer +- Paper 26.2 or 26.3, with Java 25 or newer. The build and CI compile against + Paper API 26.2; CI does not launch a server to verify runtime behavior on + either version. - WorldGuard 7.0.19 - WorldEdit 7.4.5 @@ -114,4 +116,9 @@ Assign these permission nodes to groups or individual users in your permission p ## License -GNU General Public License v3. +GNU Affero General Public License v3 or later. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the single developer onboarding +path, source map, checks, and operational safety notes. diff --git a/docs/TODO.md b/docs/TODO.md index 151c894..8a1939e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,4 +1,9 @@ -# Modernize Metropolis Plugin: Implementation Tasks +# Historical Modernization Backlog + +This file preserves the earlier task list for context. Its status labels and +acceptance text are historical and are not the current implementation report. +Use the maintained [modernization plan](modernization-plan.md) for current +implementation status, pending work, and completion criteria. | ID | Status | Title | |:-------:|:--------:|:------| diff --git a/docs/human-tests.md b/docs/human-tests.md index ec7cf6f..468beeb 100644 --- a/docs/human-tests.md +++ b/docs/human-tests.md @@ -1,6 +1,10 @@ -# Metropolis Human Testing +# Metropolis Manual Pre-release Checklist -Use this checklist for behavior that depends on a real Paper world, player interactions, or the installed WorldGuard and WorldEdit plugins. Automated tests cover some isolated logic, but they do not establish that the complete in-game workflows behave correctly. +This is a manual checklist of expected behavior that needs a real Paper world, +player interaction, or installed WorldGuard/WorldEdit plugins. Checklist items +are scenarios to run, not test results; no item is considered passed until a +person performs it and records the outcome. Automated coverage is summarized +separately below. ## Test Environment @@ -10,7 +14,7 @@ Use this checklist for behavior that depends on a real Paper world, player inter - Keep a server console open and check it for errors after startup, commands, joins, and restarts. - Record the Metropolis config, world name, test player UUIDs, command output, and any unexpected region bounds or block changes. -## Human Checklist +## Manual Scenarios ### Startup and world setup @@ -78,34 +82,20 @@ Use this checklist for behavior that depends on a real Paper world, player inter - Test commands as console, permitted player, and player without the relevant permission. Check player-only commands, target-player arguments, unknown names, malformed arguments, and permission denials for useful feedback and no unintended changes. - If upgrading an existing server, verify a legacy name-keyed `currentHomes.yml` entry resolves to the right UUID and the player's selected home is retained. -## Existing Automated Coverage +## Automated Coverage (Separate from This Checklist) -Run `./gradlew test` for the server-free JUnit suite. It has 62 tests covering: +Run `./gradlew test` for the server-free JUnit suite. It covers isolated plot +geometry/allocation rules, road and stair layouts/build plans, WorldGuard +region occupancy and reservation lookup, spawn/city bounds, home identity and +selection persistence, permission overrides, player lookup, and selected +command/event behavior. These unit tests do not load a real Paper server. -- `AvenueStairwayLayoutTest`: centered per-segment street and avenue flights, both border directions, independent cadence, top-tread alignment, inverted backing plan, exact-width openings, and invalid-fit rejection. -- `PlotLevelSupportTest`: rejection of upper plots with missing logical support cells and complete coverage requirements across all lower levels. -- `CityRegionBoundsTest`: City bounds expand to include elevated plots and Spawn without shrinking existing protection bounds. -- `PlotGridLayoutTest`: zero-offset compatibility, offsets on X/Y/Z, axis-specific spacing, and negative logical-index recovery. -- `WorldGuardRegionOccupancyTest`: arbitrary saved-region bounds are excluded while City/global regions are treated as non-occupying envelopes. -- `PlotWorldBorderBoundsTest`: search bounds follow WorldBorder size/center and require the full candidate footprint inside it. -- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation. -- `AvenueStairwayBuilderTest`: configured stair material and facing/half data for avenue and street directions, upside-down backing stairs, exact upper-road AIR placements, and no block writes at lower-road Y. -- `RoadLayoutTest`: independently sized street/avenue bands, correct road-type tagging, non-overlapping corners, and disabled roads at zero width. -- `RoadBlockWriterTest`: no-op writes for existing roads, filling missing road blocks, and preserving stair treads. -- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment. -- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key. -- `PlayerJoinListenerTest`: the welcome bounds message when a home is supplied by a mock. -- `PlayerLookupTest`: case-insensitive full-name matching and rejection of prefix matching. -- `PlayerHomeTest`: UUID-based region identity and display-name retention. -- `HomePermissionOverrideTest`: dotted permission-node parsing, priority and tie resolution, invalid entries, and username/permission/default precedence. -- `HomeNumberAllocatorTest`: choosing the first unused positive home number. -- `MetropolisHomeMoveCommandTest`: selecting a sender's home, selecting an offline target's home, and rejecting invalid requests. -- `PlotReservationLookupTest`: owner/member matching, exclusion of homes/City/Spawn, legacy reservation markers, and region-name aliases. -- `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback. -- `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates. -- `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit. -- `MetropolisDebugGenerateTestHomesCommandTest`: asynchronous tick-queued generation, one home per interval, no overlapping batches, and continuing test-player names across repeated runs. - -The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data. - -The integration scope described in `TODO.md` is broader than the tests currently present. Treat the scenarios in this guide as unverified until a human has run them on the target Paper and plugin versions. +The separate `src/integrationTest` source set currently contains two live +checks: Metropolis appears in `/plugins`, and a coordinate reservation is +persisted in WorldGuard's `regions.yml`. It does not verify restart behavior, +join allocation, WorldEdit-selection reservations, permissions, teleport +workflows, generated blocks, or protection behavior. See +[CONTRIBUTING.md](../CONTRIBUTING.md) for prerequisites and safe invocation. +Those integration checks have not been run as part of this documentation +update. The manual scenarios above remain unverified until individually run +and recorded against the target server/plugins. diff --git a/docs/modernization-plan.md b/docs/modernization-plan.md new file mode 100644 index 0000000..b71d92f --- /dev/null +++ b/docs/modernization-plan.md @@ -0,0 +1,59 @@ +# Metropolis Modernization Plan + +This is the maintained status and follow-up plan. Implemented behavior is +described in [README.md](../README.md); automated and manual coverage are +separated in [human-tests.md](human-tests.md). A feature is not considered +runtime-verified merely because its code or an isolated unit test exists. + +## Completed + +- **Build and CI modernization.** Gradle 9.8 builds the Java 25 plugin and + expands the version into `plugin.yml`. CI runs `test` and the plugin build + for pushes and pull requests to `develop`. Completion check: running + `./gradlew test build` succeeds; CI provides the corresponding hosted check. +- **Paper/WorldGuard/WorldEdit integration.** The plugin declares Paper API + 26.2, WorldGuard 7.0.19, and WorldEdit 7.4.5, and uses WorldGuard regions + for city, home, and reservation geometry. Paper 26.2/26.3 are project + targets; the current build and CI do not perform live server compatibility + checks. Completion check for runtime claims: run the applicable manual + scenarios in `human-tests.md` on a disposable server. +- **Plot, home, and command features.** The implementation includes join-time + initial home allocation, explicit additional-home acquisition, UUID-based + active-home persistence with legacy name-key loading, user/permission plot + overrides, reservations, multi-level support checks, and configured + road/stair generation. Isolated tests cover parts of these rules; see + `src/test/` and `human-tests.md` for their boundaries. + +## Pending + +- **Expand live-server integration coverage.** The current `integrationTest` + suite only checks plugin visibility and persistence of one coordinate-based + WorldGuard reservation. Add focused disposable-server checks for join-time + home allocation/ownership and selected-home restart persistence. Completion + criteria: tests assert the expected region/UUID state before and after a + restart, clean up their own test regions, and remain excluded from `build`. +- **Choose optional login allocation policies (MET-023/MET-024).** Current + behavior allocates the configured initial home count (default one) when a + player has no home; later homes require `/metropolis-home-acquire`. Decide + whether to offer grant-all-to-limit or one-more-per-login policies before + implementing them. Completion criteria: documented config semantics, + compatibility defaults, and unit tests for limits, reconnects, and existing + home selections. +- **Choose an allocation traversal option (MET-025).** The proposal to select + a rectangle/cuboid or ring-based traversal remains unimplemented. First + document deterministic ordering and how each policy treats occupied regions, + levels, and WorldBorder bounds. Completion criteria: configuration and + focused allocation tests establish repeatable ordering without overlaps. +- **Research conversion of existing worlds (MET-026).** There is no automatic + world/config conversion workflow. Document the legacy data/config formats, + backups and rollback requirements, and a safe migration strategy before + proposing implementation. Completion criteria: a reviewed conversion + specification plus disposable-world validation; do not claim migration + support before that work is complete. + +## Deferred Ideas + +Economy-based home sales, arbitrary connected plot shapes, and alternate +multi-plot footprints remain product ideas, not committed work. Reconsider them +only after requirements, compatibility constraints, and acceptance criteria +are defined. \ No newline at end of file diff --git a/docs/modrinth-description.md b/docs/modrinth-description.md index b65d0e7..37c84e2 100644 --- a/docs/modrinth-description.md +++ b/docs/modrinth-description.md @@ -11,6 +11,8 @@ Metropolis grows a protected Minecraft city as players join. It assigns home plo WorldGuard and WorldEdit are required dependencies. Install all three plugins in the server's `plugins` directory. Metropolis targets Paper only; Bukkit and Spigot servers are not supported. +The project targets Paper 26.2 and 26.3, while its build/CI compile against the +Paper 26.2 API and do not run a live server compatibility check. ## Features diff --git a/docs/project-description.json b/docs/project-description.json index 2a2bdd8..b4966fa 100644 --- a/docs/project-description.json +++ b/docs/project-description.json @@ -1,23 +1,29 @@ { - "id": "metropolis", - "title": "Metropolis", - "description": "A Paper 26.2/26.3 city-planning plugin that turns player onboarding into procedural urban growth with protected UUID-owned homes, roads, reservations, and configurable infrastructure.", - "docId": "Metropolis", - "image": { - "url": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.", - "alt": "Pixel-art aerial view of a Minecraft city with player home plots and connecting roads", - "prompt": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.", - "width": 0, - "height": 0, - "backgroundClassName": "bg-surface" - }, - "url": "/projects/metropolis", - "tags": ["open-source"], - "source": { - "type": "github", - "url": "https://github.com/headhunter45/Metropolis" - }, - "feedback": { - "url": "https://github.com/headhunter45/Metropolis/issues" + "metropolis": { + "id": "metropolis", + "title": "Metropolis", + "description": "City manager that automatically creates protected home plots and roads as players join a Minecraft server.", + "docId": "Metropolis", + "headerImage": null, + "platforms": [ + { "name": "Minecraft", "info": "Paper server plugin" } + ], + "image": { + "url": "/projects/metropolis/logo.png", + "alt": "Metropolis Minecraft plugin logo", + "width": 1024, + "height": 1024, + "backgroundClassName": "bg-surface" + }, + "url": "/projects/metropolis", + "tags": ["Open-Source", "Game", "Minecraft"], + "source": { + "type": "github", + "url": "https://github.com/headhunter45/Metropolis" + }, + "feedback": { + "type": "github-issues", + "url": "https://github.com/headhunter45/Metropolis/issues" + } } } diff --git a/docs/project-description.md b/docs/project-description.md index 7421592..c2899b7 100644 --- a/docs/project-description.md +++ b/docs/project-description.md @@ -1,5 +1,8 @@ # Metropolis +The project targets Paper 26.2 and 26.3; Gradle and CI compile against Paper +API 26.2, and CI does not run a live server compatibility check. + Metropolis turns player onboarding into city planning. As players arrive, the Paper plugin finds the next available plot, builds the surrounding roads and infrastructure, and extends a WorldGuard-protected city that feels designed rather than randomly generated. The project combines procedural spatial logic, persistent UUID-based ownership, configurable construction rules, WorldEdit-assisted reservations, and a command suite for acquiring and selecting multiple homes. Home limits and plot sizes can be assigned per user or through permission-manager groups using configurable permission tiers. Reservation owners and members can teleport to their reserved plots, with explicit selection when they belong to multiple reservations. Active-home selections persist across reconnects and server restarts. It is also a modernization case study: a legacy plugin brought forward to Paper 26.2 and 26.3 with Gradle, focused unit tests, and opt-in live-server integration tests. Bukkit and Spigot servers are outside the support target. diff --git a/docs/projects.schema.json b/docs/projects.schema.json new file mode 100644 index 0000000..192c707 --- /dev/null +++ b/docs/projects.schema.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Projects", + "description": "Project metadata keyed by the project route slug.", + "type": "object", + "patternProperties": { + "^[a-z0-9]+(?:-[a-z0-9]+)*$": { "$ref": "#/definitions/project" } + }, + "additionalProperties": false, + "definitions": { + "image": { + "type": "object", + "required": ["url", "width", "height"], + "properties": { + "url": { + "type": "string", + "description": "Public asset path or remote image URL. A blank header image URL falls back to image.", + "pattern": "^(\\s*|/[^/].*|https?://.+)$" + }, + "alt": { "type": "string" }, + "prompt": { "type": "string" }, + "name": { "type": "string" }, + "width": { "type": "integer", "minimum": 1 }, + "height": { "type": "integer", "minimum": 1 }, + "backgroundClassName": { "type": "string" } + }, + "additionalProperties": false + }, + "projectLink": { + "type": "object", + "required": ["url"], + "properties": { + "type": { "type": "string" }, + "url": { "type": "string", "pattern": "^(\\s*|https?://.+)$" } + }, + "additionalProperties": false + }, + "platform": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "pattern": "\\S" }, + "info": { "type": "string" }, + "links": { + "type": "array", + "items": { + "type": "object", + "required": ["label", "url"], + "properties": { + "label": { "type": "string", "pattern": "\\S" }, + "url": { + "type": "string", + "format": "uri", + "pattern": "^https?://.+" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "project": { + "type": "object", + "required": ["id", "title", "description", "tags"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, + "title": { "type": "string", "pattern": "\\S" }, + "description": { "type": "string", "pattern": "\\S" }, + "docId": { "type": "string", "minLength": 1 }, + "image": { "$ref": "#/definitions/image" }, + "headerImage": { + "description": "Optional detail-page logo override. Omit, set to null, or use a blank URL to fall back to image. Listing cards continue to use image.", + "anyOf": [{ "$ref": "#/definitions/image" }, { "type": "null" }] + }, + "status": { "type": "string" }, + "platforms": { + "type": "array", + "items": { "$ref": "#/definitions/platform" } + }, + "url": { + "type": "string", + "pattern": "^/projects/[a-z0-9]+(?:-[a-z0-9]+)*$" + }, + "tags": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + }, + "source": { + "allOf": [ + { "$ref": "#/definitions/projectLink" }, + { "required": ["type"] } + ] + }, + "feedback": { "$ref": "#/definitions/projectLink" }, + "repository": { + "type": "string", + "format": "uri", + "pattern": "^https?://.+" + }, + "features": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "technology": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + } + }, + "additionalProperties": false + } + } +}