diff --git a/.github/workflows/downloads-badge.yml b/.github/workflows/downloads-badge.yml
new file mode 100644
index 0000000..815bda9
--- /dev/null
+++ b/.github/workflows/downloads-badge.yml
@@ -0,0 +1,114 @@
+name: downloads badge
+
+# Deck ships under two Packagist names: the current `promptphp/deck` and the
+# deprecated `veeqtoh/prompt-deck`, which still receives a large share of
+# installs. Shields cannot sum packages, so this job publishes a combined
+# total to a shields endpoint JSON on the orphan `badges` branch.
+#
+# Scheduled runs only fire from the default branch.
+
+on:
+ schedule:
+ - cron: "17 4 * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: downloads-badge
+ cancel-in-progress: false
+
+jobs:
+ badge:
+ name: Publish combined download count
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Sum Packagist downloads
+ id: totals
+ run: |
+ set -euo pipefail
+
+ fetch_total() {
+ curl -fsSL --retry 3 --retry-delay 5 \
+ "https://packagist.org/packages/$1.json" \
+ | jq -e '.package.downloads.total'
+ }
+
+ current=$(fetch_total "promptphp/deck")
+ legacy=$(fetch_total "veeqtoh/prompt-deck")
+ total=$((current + legacy))
+
+ echo "promptphp/deck: ${current}"
+ echo "veeqtoh/prompt-deck: ${legacy}"
+ echo "combined: ${total}"
+
+ # Format the way shields does: 1234 -> 1.2k, 1234567 -> 1.2M,
+ # dropping a trailing .0 so we render "15k" rather than "15.0k".
+ # The unit is promoted after rounding, so 999999 reads "1M" not "1000k".
+ message=$(awk -v n="${total}" 'BEGIN {
+ if (n < 1000) { printf "%d", n; exit }
+
+ v = n / 1000; u = "k"
+
+ if (v >= 999.95) { v = v / 1000; u = "M" }
+
+ s = sprintf("%.1f", v)
+ sub(/\.0$/, "", s)
+ printf "%s%s", s, u
+ }')
+
+ echo "message=${message}" >> "$GITHUB_OUTPUT"
+
+ - name: Skip when the badge is unchanged
+ id: check
+ run: |
+ set -euo pipefail
+
+ existing=$(curl -fsSL \
+ "https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/badges/downloads.json" \
+ | jq -r '.message' 2>/dev/null || echo "")
+
+ if [ "${existing}" = "${{ steps.totals.outputs.message }}" ]; then
+ echo "Badge already reads [${existing}] — nothing to publish."
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Publish to the badges branch
+ if: steps.check.outputs.changed == 'true'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ MESSAGE: ${{ steps.totals.outputs.message }}
+ run: |
+ set -euo pipefail
+
+ mkdir badges && cd badges
+
+ jq -n \
+ --arg message "${MESSAGE}" \
+ '{
+ schemaVersion: 1,
+ label: "total downloads",
+ message: $message,
+ color: "blue"
+ }' > downloads.json
+
+ cat downloads.json
+
+ # A single-commit orphan branch: force-pushing keeps the badge
+ # history from growing without bound and never touches 0.x.
+ git init -q
+ git checkout -q -b badges
+ git remote add origin \
+ "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
+
+ git add downloads.json
+ git \
+ -c user.name="github-actions[bot]" \
+ -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
+ commit -q -m "Update combined download count to ${MESSAGE}"
+
+ git push -q --force origin badges
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80b6e3c..e802988 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
+## [0.4.4] - 2026-08-03
+
+### Fixed
+
+- Fixed migration publishing, which silently failed on case-sensitive filesystems. `DeckServiceProvider` pointed at `src/database/migrations` while the directory is `src/Database/migrations`, so `vendor:publish --tag=deck-migrations` reported success whilst failing on Linux and macOS case-sensitive volumes.
+- Fixed `make:prompt` overwriting the `active_version` key by rewriting the prompt's root `metadata.json`. Creating a new version silently promoted it to active. The file is now merged, preserving `active_version`, the existing description, the original `created_at`, a populated `variables` list, and any keys added by hand.
+- Fixed prompt metadata being unreachable. `make:prompt` wrote the name, description, and roles to the prompt's root `metadata.json`, but `PromptManager` only ever read the version-level `v{n}/metadata.json`, so `PromptTemplate::metadata()` was always empty and the `prompt:list` description column was always blank. `make:prompt` now writes version-level metadata, and metadata reads merge the prompt-level file with the version-level file, version keys winning. The `active_version` key is excluded from `metadata()`.
+- Fixed `prompt:test --ver=v2` silently rendering the active version instead of the one requested. `(int) 'v2'` evaluated to a falsy `0`, so the command fell through to `active()` while reporting the wrong version number in its header. The command now uses the `ResolvesVersion` trait, accepts both `2` and `v2`, and fails with a clear message on unparseable input.
+- Fixed the `PromptPHP\Deck\Database\Factories\` PSR-4 mapping pointing at the non-existent lowercase `src/database/factories/`.
+
+- Fixed the README downloads badge reporting the deprecated `veeqtoh/prompt-deck` package instead of a combined figure.
+- Fixed the docs landing page linking to the pre-rename `promptphp/prompt-deck` repository, and the README licence link pointing at a `master` branch that does not exist.
+
+### Added
+
+- `make:prompt` now prints how to activate the version it just created when a different version is live.
+- Added a scheduled `downloads badge` workflow that publishes the combined Packagist download count for `promptphp/deck` and the deprecated `veeqtoh/prompt-deck` to a shields endpoint on the orphan `badges` branch.
+- Added a changelog page to the documentation site, under a new Releases group, with an RSS feed at `/changelog/rss.xml`.
+
+### Removed
+
+- Removed the `PromptPHP\Deck\Database\Seeders\` autoload mapping, which pointed at a directory that does not exist.
+
## [0.4.3] - 2026-07-29
### Fixed
diff --git a/README.md b/README.md
index 6b040ee..ce64a32 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
-
-
+
+
diff --git a/composer.json b/composer.json
index e55e63b..cd4dc80 100644
--- a/composer.json
+++ b/composer.json
@@ -15,9 +15,7 @@
}
],
"keywords": [
- "promptphp",
- "deck",
- "prompt-deck",
+ "laravel",
"ai",
"prompts",
"prompt-management",
@@ -26,9 +24,7 @@
"variable-interpolation",
"performance-tracking",
"ab-testing",
- "laravel",
- "laravel-ai",
- "laravel-package"
+ "promptphp"
],
"require": {
"php": "^8.2",
@@ -51,8 +47,7 @@
"autoload": {
"psr-4": {
"PromptPHP\\Deck\\": "src/",
- "PromptPHP\\Deck\\Database\\Factories\\": "src/database/factories/",
- "PromptPHP\\Deck\\Database\\Seeders\\": "src/database/seeders/"
+ "PromptPHP\\Deck\\Database\\Factories\\": "src/Database/Factories/"
}
},
"autoload-dev": {
@@ -71,9 +66,11 @@
}
},
"scripts": {
- "test": [
- "vendor/bin/pint --test",
- "vendor/bin/pest"
+ "test": "vendor/bin/pest",
+ "test:architecture": "vendor/bin/pest tests/Architecture",
+ "format": "vendor/bin/pint",
+ "test:lint": [
+ "vendor/bin/pint --parallel --test"
]
},
"minimum-stability": "dev",
diff --git a/docs/advanced/api-reference.mdx b/docs/advanced/api-reference.mdx
index da57f2b..84dc0cb 100644
--- a/docs/advanced/api-reference.mdx
+++ b/docs/advanced/api-reference.mdx
@@ -185,7 +185,7 @@ $prompt->name(); // 'order-summary'
#### `metadata(): array`
-Get the prompt metadata. Returns an empty array if no metadata is defined.
+Get the prompt metadata: the prompt's root `metadata.json` merged with the version's own `metadata.json`, version-level keys winning. The `active_version` key is excluded. Returns an empty array if no metadata is defined.
```php
$prompt->metadata(); // ['description' => '...', 'variables' => [...]]
diff --git a/docs/changelog.mdx b/docs/changelog.mdx
new file mode 100644
index 0000000..3212da8
--- /dev/null
+++ b/docs/changelog.mdx
@@ -0,0 +1,134 @@
+---
+title: "Changelog"
+description: "New features, fixes, and breaking changes in Deck by PromptPHP."
+rss: true
+---
+
+Deck follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Subscribe to the [RSS feed](https://deck.promptphp.com/changelog/rss.xml) to be notified of new releases, or browse the [full history on GitHub](https://github.com/promptphp/deck/blob/0.x/CHANGELOG.md).
+
+
+
+**Fixed**
+
+- **Migrations now publish on case-sensitive filesystems.** `vendor:publish --tag=deck-migrations` reported success without copying anything on Linux and case-sensitive macOS volumes. If your `database/migrations` directory came up empty after installing Deck, publish again after upgrading.
+- **Creating a version no longer changes which version is live.** `make:prompt` rewrote the prompt's root `metadata.json` from scratch, discarding the `active_version` key, so scaffolding a draft silently promoted it to active. The file is now merged: `active_version`, your description, the original `created_at`, a populated `variables` list, and any keys you added by hand all survive.
+- **Prompt metadata is now readable.** `make:prompt` recorded the name, description, and roles in the prompt's root `metadata.json`, but Deck only ever read the version-level `v{n}/metadata.json`. `PromptTemplate::metadata()` was always empty and the `prompt:list` description column was always blank. `make:prompt` now writes version-level metadata too, and reads merge the two files with version keys winning. See [Metadata](/core/prompts#metadata).
+- **`prompt:test --ver=v2` renders the version you asked for.** The `v` prefix was discarded during parsing, so the command quietly rendered the active version while reporting the wrong number in its header. Both `2` and `v2` now work, and an unparseable value fails with a clear message.
+- Corrected the `PromptPHP\Deck\Database\Factories\` PSR-4 mapping, which pointed at a directory that does not exist.
+- Corrected the README downloads badge, which reported the deprecated `veeqtoh/prompt-deck` package, and the stale repository and licence links.
+
+**Added**
+
+- `make:prompt` now tells you how to promote a new version when a different one is active:
+
+ ```
+ Version 2 of the [order-summary] prompt has been created successfully with the following roles: system.
+
+ v1 is still the active version.
+ Run `php artisan prompt:activate order-summary v2` to make v2 live.
+ ```
+
+**Removed**
+
+- Removed the `PromptPHP\Deck\Database\Seeders\` autoload mapping, which pointed at a directory that does not exist.
+
+
+
+
+
+**Fixed**
+
+- Widened the `sebastian/diff` constraint to allow `v8` and `v9`, so Deck can be installed alongside Pest 5. [#12](https://github.com/promptphp/deck/pull/12)
+
+
+
+
+
+**Fixed**
+
+- `Deck::get()` and `Deck::activate()` now accept mixed version types, resolving versions through the shared `ResolvesVersion` trait.
+
+
+
+
+
+**Fixed**
+
+- `prompt:activate` now accepts both `1` and `v1` version formats.
+
+
+
+
+
+
+ This release renamed the package and its namespace. See the [upgrade guide](https://github.com/promptphp/deck/blob/0.x/UPGRADE.md) before upgrading from `v0.3.x`.
+
+
+**Changed**
+
+- Renamed the package from `veeqtoh/prompt-deck` to `promptphp/deck`.
+- Renamed the PHP namespace from `Veeqtoh\PromptDeck` to `PromptPHP\Deck`.
+- Renamed the public package identity from Prompt Deck to Deck by PromptPHP.
+- Updated installation, usage, README, badges, documentation links, and package metadata for the new PromptPHP organisation. [#7](https://github.com/promptphp/deck/pull/7)
+
+**Removed**
+
+- Removed the old `Veeqtoh\PromptDeck` public namespace.
+- Removed old Prompt Deck naming from the main public API.
+
+
+
+
+
+**Added**
+
+- Published this documentation site, built on Mintlify. [#5](https://github.com/promptphp/deck/pull/5)
+- Added dedicated pages for installation, configuration, commands, prompt management, Laravel AI SDK integration, tracking, testing, and the API reference.
+
+**Changed**
+
+- Reworked the documentation from flat markdown files into organised MDX pages. [#6](https://github.com/promptphp/deck/pull/6)
+
+**Fixed**
+
+- Fixed stale documentation links and navigation paths.
+
+
+
+
+
+**Added**
+
+- Added a `CHANGELOG` to the repository.
+- Added a GitHub Actions workflow for automated testing.
+- Added a `.gitattributes` file to manage text handling and export-ignore rules.
+
+**Changed**
+
+- Refined the Composer package keywords for discoverability.
+
+
+
+
+
+**Added**
+
+- Added support for Laravel 13. [#3](https://github.com/promptphp/deck/pull/3)
+- Added the `scaffold_on_make_agent` option to toggle auto-scaffolding of prompts when you run `make:agent`. See [Configuration](/getting-started/configuration).
+
+
+
+
+
+The first release. [#1](https://github.com/promptphp/deck/pull/1)
+
+**Added**
+
+- Versioned prompt management with file-based storage in structured prompt directories.
+- Variable interpolation for prompt templates.
+- Artisan commands for creating, listing, testing, diffing, and activating prompts. See [Commands](/core/commands).
+- Prompt execution tracking. See [Tracking](/advanced/tracking).
+- A/B testing through versioned prompt activation and tracking.
+- Optional Laravel AI SDK integration. See [Laravel AI SDK](/integrations/ai-sdk).
+
+
diff --git a/docs/core/commands.mdx b/docs/core/commands.mdx
index c1e09a6..69a2550 100644
--- a/docs/core/commands.mdx
+++ b/docs/core/commands.mdx
@@ -142,7 +142,7 @@ Output:
- If the prompts directory does not exist, a warning is displayed.
- If no prompts are found, an informational message is shown.
-- Descriptions come from each version's `metadata.json`.
+- Descriptions come from the prompt's root `metadata.json`, and a version may override the shared description in its own `metadata.json`.
## prompt:activate
diff --git a/docs/core/make-prompt.mdx b/docs/core/make-prompt.mdx
index d5d77db..bc8cdd8 100644
--- a/docs/core/make-prompt.mdx
+++ b/docs/core/make-prompt.mdx
@@ -86,17 +86,22 @@ resources/prompts/
│ ├── system.md # Always created
│ ├── user.md # Created with --user or -u
│ ├── assistant.md # Created with --role=assistant
- │ └── developer.md # Created with --role=developer
+ │ ├── developer.md # Created with --role=developer
+ │ └── metadata.json # This version's metadata
├── v2/
│ └── ...
- └── metadata.json
+ └── metadata.json # Prompt-level metadata
```
The file extension is controlled by the `deck.extension` configuration value (default: `md`). For example, setting it to `txt` produces `system.txt`, `user.txt`, etc.
### Metadata
-A `metadata.json` file is written to the prompt root directory each time the command runs. It captures:
+The command writes two metadata files: one at the prompt root describing the prompt as a whole, and one inside the version directory describing that version.
+
+#### Prompt metadata
+
+`/metadata.json` is **merged**, never replaced, each time the command runs:
```json
{
@@ -111,10 +116,33 @@ A `metadata.json` file is written to the prompt root directory each time the com
| Field | Description |
| ------------- | ------------------------------------------------------------------------------- |
| `name` | The kebab-case prompt name. |
-| `description` | A human-readable summary. Populated via `--desc=` or the interactive flow. |
-| `roles` | An ordered list of every role that was scaffolded. Always starts with `system`. |
-| `variables` | Reserved for future use (template variable extraction). |
-| `created_at` | ISO 8601 timestamp of creation. |
+| `description` | A human-readable summary. Populated via `--desc=` or the interactive flow. Kept as-is when you create a new version without supplying a new description. |
+| `roles` | An ordered list of every role scaffolded for the version just created. Always starts with `system`. |
+| `variables` | Reserved for future use (template variable extraction). Never reset once you populate it. |
+| `created_at` | ISO 8601 timestamp of when the **prompt** was first created. |
+
+Any other keys you add by hand are preserved — including `active_version`, so **scaffolding a new version never changes which version your application serves**. When another version is active, the command tells you how to promote the one you just created:
+
+```
+Version 2 of the [order-summary] prompt has been created successfully with the following roles: system.
+
+v1 is still the active version.
+Run `php artisan prompt:activate order-summary v2` to make v2 live.
+```
+
+#### Version metadata
+
+`/v{n}/metadata.json` records that version alone:
+
+```json
+{
+ "version": 2,
+ "roles": ["system", "user"],
+ "created_at": "2025-01-20T09:12:00+00:00"
+}
+```
+
+Add your own keys here to override prompt-level metadata for a single version — see [Metadata](/core/prompts#metadata) for how the two files merge.
## Roles
diff --git a/docs/core/prompts.mdx b/docs/core/prompts.mdx
index a4d9951..50daf51 100644
--- a/docs/core/prompts.mdx
+++ b/docs/core/prompts.mdx
@@ -161,14 +161,23 @@ if ($prompt->has('assistant')) {
### Metadata
-Each prompt version can carry metadata (stored in `metadata.json` at the version level). Access it via the `metadata` method:
+Metadata comes from two files, and `metadata` returns them merged:
+
+1. The prompt's root `metadata.json` — shared by every version (name, description, and anything else you record there).
+2. The version's own `v{n}/metadata.json` — specific to that one version.
+
+Version-level keys win when both files define the same key, so a version can override the shared description without affecting its siblings.
```php
$prompt->metadata();
// ['description' => 'Summarises customer orders', 'variables' => ['tone', 'input'], ...]
```
-Metadata is an associative array. If no `metadata.json` exists in the version directory, an empty array is returned.
+Metadata is an associative array. If neither file exists, an empty array is returned.
+
+
+ The `active_version` key is never included. It records which version your application serves, which is routing state rather than metadata about the template you loaded. Read it with `Deck::active()` instead.
+
### Name and version
diff --git a/docs/docs.json b/docs/docs.json
index 88b475a..b5c8c9e 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -1,7 +1,7 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
- "name": "Prompt Deck by PromptPHP",
+ "name": "Deck by PromptPHP",
"colors": {
"primary": "#6366F1",
"light": "#818CF8",
@@ -39,6 +39,12 @@
"advanced/testing",
"advanced/api-reference"
]
+ },
+ {
+ "group": "Releases",
+ "pages": [
+ "changelog"
+ ]
}
],
"global": {
diff --git a/docs/index.mdx b/docs/index.mdx
index b3cadaa..670a4a0 100644
--- a/docs/index.mdx
+++ b/docs/index.mdx
@@ -14,7 +14,7 @@ mode: "custom"
✨ Formerly Prompt Deck - Now Deck by PromptPHP
-