Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .claude/skills/dotkernel-package-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: dotkernel-package-status
description: Use when asked whether a Dotkernel package is still maintained, what a package does, which PHP versions it supports, whether it is safe for a new project, or what the state of the Dotkernel open-source portfolio is.
Answer from the published listing rather than from memory.
---

# Dotkernel package status

Dotkernel publishes the support status of its open-source packages as a machine-readable listing, rebuilt daily from the GitHub organization.
Prefer it over recollection: lifecycle states change, and a package that was active at training time may be security-only now.

- Data: <https://www.dotkernel.com/dotkernel-packages.json>
- Human-readable page: <https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/>
- Site that publishes both, and the code that generates them: <https://github.com/dotkernel/dotkernel.com>

The listing is a product of the dotkernel.com site rather than of any single package, so nothing needs to be installed to use it and it works from any repository.
Report a wrong or stale entry against `dotkernel/dotkernel.com`, and a wrong lifecycle value against the package's own repository, which is where the `OSSMETADATA` file that declares it lives.

## Shape

```json
{
"generated_at": "2026-08-05T10:14:45+00:00",
"org": "dotkernel",
"packages": [
{
"name": "dot-cache",
"url": "https://github.com/dotkernel/dot-cache",
"description": "Dotkernel cache component",
"lifecycle": "active",
"php": "~8.3.0 || ~8.4.0",
"archived": false
}
]
}
```

- `name` - the bare repository name. The Composer package is `dotkernel/<name>`.
- `description` - the GitHub repository description; `null` when the repository has none.
- `lifecycle` - see below.
Values outside the four listed are possible and mean the repository declared something unrecognized; report it as undeclared rather than guessing.
- `php` - the `require.php` constraint from the package's `composer.json`, or `null` when it declares none or the file could not be read.
A `null` is missing data, not "any PHP version."
- `archived` - GitHub's own archived flag, which is independent of `lifecycle`.
A repository can be archived on GitHub while still declaring an older lifecycle, and the page surfaces that as a separate "Archived on GitHub" note.

## What each lifecycle means for a consumer

| Value | What to tell someone |
|-----------------|----------------------------------------------------------------------------------------------------|
| `active` | Actively developed, new features and fixes. Safe for a new project. |
| `maintenance` | Still supported, receiving fixes but no new features. Safe to stay on; expect no new capabilities. |
| `security-only` | Only security fixes are released. Plan an upgrade or replacement now. |
| `archived` | No longer maintained. Do not use in new projects; migrate existing ones. |

## Answering well

- Cite `generated_at`.
The listing is a daily snapshot, so say what it was current as of.
- Absence is not non-existence.
A repository is only listed when it carries an `OSSMETADATA` file declaring its lifecycle, and the site excludes non-package repositories (documentation, skeleton apps, the site itself).
If a name is missing, say it is not in the published package listing and check <https://github.com/orgs/dotkernel/repositories> - do not say it does not exist.
- For "is X maintained?", lead with the lifecycle sentence above, then the PHP constraint if the question is about upgrading.
- For "what should I use instead?", the listing gives no successor field.
Say the package is archived or security-only and point at the GitHub `url` and the lifecycle page rather than inventing a replacement.
- If the fetch fails, say so.
Do not fall back to remembered status, which is exactly what this listing exists to replace.

The JSON is served from the document root of dotkernel.com, so its URL tracks the deployed file name.
If the request 404s, load the human-readable page above and read the "View as JSON" link from it, or check the `dataFile` setting in `config/autoload/packages.global.php` in <https://github.com/dotkernel/dotkernel.com>.
98 changes: 98 additions & 0 deletions .claude/skills/dotkernel-packages/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
name: dotkernel-packages
description: Use when working on the Dotkernel packages OSS lifecycle page published at dotkernel.com and built from the dotkernel/dotkernel.com repository - regenerating the listing, adding or removing a package, changing what a card shows, adjusting ignoreRepos, or explaining a bin/generate-packages warning.
---

# Dotkernel packages lifecycle page

The page lists Dotkernel's open-source packages with their support status.
It renders a pre-generated JSON file - the request path never calls GitHub.

## Where this lives

- Published page: <https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/>
- Data file: <https://www.dotkernel.com/dotkernel-packages.json>
- Source repository: <https://github.com/dotkernel/dotkernel.com>

Every path below is relative to the root of `dotkernel/dotkernel.com`.
If the current working directory is a different project, the change belongs in that repository, not here - clone it or switch to that checkout before editing.

## The pieces

| Concern | Path in `dotkernel/dotkernel.com` |
|---------------------|---------------------------------------------------------------------------|
| Route | `src/App/src/RoutesDelegator.php` |
| Handler (read-only) | `src/App/src/Handler/GetPackagesViewHandler.php` |
| Template | `src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig` |
| Card styles | `src/App/assets/scss/components/_custom.scss` (`Package lifecycle cards`) |
| Generator | `src/App/src/Service/PackageGenerator.php` |
| GitHub transport | `src/App/src/Service/GitHubClient.php` |
| Config | `config/autoload/packages.global.php` |
| CLI entry point | `bin/generate-packages` |
| Data file | `public/dotkernel-packages.json` (gitignored) |

Two rules follow from this split, and breaking either is the usual cause of a confusing result:

- The handler only reads.
A change to the generator shows up on the page only after the data file is rebuilt.
- The data file is gitignored, so it does not travel with a deploy.
Production depends on the cron run.

## How a repository gets onto the page

A repository is a published package when it contains an `OSSMETADATA` file with an `osslifecycle` value.
That is the whole opt-in - no allow-list to edit:

```text
osslifecycle=active
```

Recognized values, which are also the display order (`LIFECYCLE_ORDER`): `active`, `maintenance`, `security-only`, `archived`.
Anything else is kept, sorted last, and rendered with the muted `package-card--unknown` treatment.
Equal lifecycles sort by name.

To keep a repository off the page, add its bare name (case-insensitive) to `ignoreRepos` in `config/autoload/packages.global.php`.

Each entry in the data file is built from three sources:

- `name`, `url`, `description`, `archived` - the `/orgs/{org}/repos` listing, so they cost no extra request.
- `lifecycle` - the repository's `OSSMETADATA`.
- `php` - `require.php` in the repository's `composer.json`; `null` when absent or unreadable.

`description` and `php` are `null` for anything unusable rather than an empty string, and the template omits the element instead of rendering a blank one.

## Regenerating

From the root of a `dotkernel/dotkernel.com` checkout:

```bash
php bin/generate-packages
```

Credentials live in `config/autoload/local.php` under the `github` key (`authBearer`, `userAgent`, `org`) - see `local.php.dist`.
Requests fall back to unauthenticated when no token is set, which works but hits a much lower rate limit.

The run is deliberately conservative: it writes to a `.tmp` sibling and renames, and it aborts with exit code 1 if more than 20% of per-repository requests fail (`FAILURE_THRESHOLD`), leaving the previous listing in place.
A non-zero exit means the page keeps serving yesterday's data, which is the intended outcome.

## Reading the output

- `Skipped by ignoreRepos: …` - informational, those repositories were excluded on purpose.
- `WARNING: ignoreRepos entries matched nothing (renamed or deleted?)` - stale `ignoreRepos` entries.
Prune them, or a renamed repository silently reappears on the site.
- `WARNING: <repo>: could not read OSSMETADATA (…)` - the request failed and counts toward the 20% threshold.
The repository is left off this run.
- `WARNING: <repo>: OSSMETADATA present but no osslifecycle value found, skipped` - the file exists but has no parsable `osslifecycle=`; fix it in that repository.
- `WARNING: <repo>: could not read composer.json (…)` - costs the PHP badge constraint, not the package.

## After changing anything in the page

- Changed the payload shape? Rerun `php bin/generate-packages`, since the local data file predates the change.
- Touched the SCSS?
Run `npm run build` - `public/css/app.css` is tracked, so the compiled file must be committed with the source.
- Run `vendor/bin/phpunit`, `vendor/bin/phpcs`, `vendor/bin/phpstan analyse`.
Coverage lives in `test/Unit/App/Service/PackageGeneratorTest.php`, `test/Unit/App/Factory/PackageGeneratorFactoryTest.php`, and `test/Unit/App/Handler/GetPackagesViewHandlerTest.php`.
- Renaming the data file means updating `packages.global.php`, `.gitignore`, the "View as JSON" link in the template, and the fallback default in `src/App/src/Factory/PackageGeneratorFactory.php`.

Do not add `dotkernel-packages-oss-lifecycle` to `routes.page` in `local.php`.
It has a dedicated handler routed in `RoutesDelegator`, and listing it again registers the path twice.