Conversation
|
Introduces a code-first living documentation system that embeds user stories directly in controller code via PHP 8 attributes (#[Feature], #[UserStory], #[NoStory]), extracts them into a JSON manifest, and builds a browsable VitePress static site with dual navigation by feature and by persona. - 3 PHP attribute classes in app/Attributes/ - specs:extract artisan command using nikic/php-parser AST analysis (already a transitive composer dependency, no new requirement) - JSON manifest at docs/specs/manifest.json, 8 narrative markdown files in docs/specs/narratives/ - Standalone VitePress site in specs-site/ with prebuild page generation (independent package.json, does not touch the app's Vite/npm setup) - GitHub Actions workflow for GitHub Pages deployment - Removes the historical features/ directory (Gherkin-style specs), superseded by the annotation system; untouched since this branch first diverged from develop so nothing current is lost - Adds a Living Specifications section to CLAUDE.md describing the workflow for keeping annotations current Rebuilt from the original feature/living-specs branch (~600 commits behind develop) on top of current develop. The @story annotations on controllers and tests are ported in follow-up commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit's message described this addition but the section was left out of what got staged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the user story attributes from the original feature/living-specs branch onto the current controller code. Annotated via #[Feature] on each controller class and #[UserStory]/#[NoStory] on public action methods, matched by method name against the branch's final annotated state and merged with a 3-way text merge (base = branch's original divergence point, mine = current develop, theirs = branch tip) so that unrelated evolution of these files (return-type hints added since) is preserved alongside the new attributes. 168 user stories across 8 features and 6 personas, grouped into themes within each feature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports @story:ClassName::method docblock references from the original feature/living-specs branch onto the current test suite, linking test methods to the user stories added in the previous commit. specs:extract picks these up via regex over docblocks so the VitePress site can show per-story test coverage. Merged the same way as the controller annotations: a 3-way text merge per file (branch divergence point / current develop / branch tip), which correctly threads new docblock lines through method signatures that have since gained return-type hints, without disturbing them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fc617f1 to
a0299fd
Compare
|
Rebuilt this branch on current develop rather than rebasing — the original was ~600 commits behind and the @story docblock diffs no longer applied after the Vite migration and general drift. What changed:
Needs human follow-up:
|
SonarCloud (rightly) wants write permissions scoped to the deploy job rather than granted workflow-wide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzY1phBjyT3HogpeS53zon
- generate-pages.mjs: give every string sort an explicit localeCompare comparator (default sort is lexicographic-by-code-unit) - SpecsExtract: coalesce php-parser dynamic-property iterables to [] - the analyzer cannot see them initialized, and a malformed node would genuinely make them so Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzY1phBjyT3HogpeS53zon
Sonar's PHP analyzer cannot see php-parser's vendor property declarations, so it treats every AST property read as uninitialized even behind a null-coalesce. Assign to explicit locals and mark the three reads NOSONAR with justification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzY1phBjyT3HogpeS53zon
|
|
@edwh I can't recall who needed to action this one? |
|
@ngm — this one is mine to action. Below is the review of the outstanding test-plan item ("review a sample of controller annotations for accuracy"). There are a few fixes to make and then (AI-assisted review by Claude, run at a maintainer's request. Each finding below was checked against the code.) Sample: 35 Wrong or misleading stories
Minor
Checked and correct
Process gaps
Non-annotation checks (fine)
To merge: fix the export personas, drop the extra |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Exports are public (Guest/ThirdParty), drop the online-event story from PartyController::create, make the Repair Directory persona explicit, give allEvents a calendar story, fix stale device comments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Actioned the review. Changes by point (all pushed to this branch):
Not verified locally: PHPUnit (the container mounts the main checkout, so it cannot see this worktree) - CircleCI is the check for that. The PR description is rewritten to describe the end state, with the |
|
|
||
| - name: Install dependencies | ||
| working-directory: specs-site | ||
| run: npm ci |
… independent Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|




Summary
#[Feature],#[UserStory],#[NoStory]).php artisan specs:extractparses the code with nikic/php-parser and writesdocs/specs/manifest.json: 188 stories across 8 features and 7 personas (Admin, Host, Restarter, NetworkCoordinator, Guest, ThirdParty, RepairDirectoryAdmin). Stories are grouped by theme within each feature.@story:docblock references (168 of 188 stories have at least one linked test). A reference can use the short class name (GroupController::stats) only when exactly one controller declares that method; otherwise it must use the fully-qualified class name, so the web andAPI\controllers of the same name cannot be confused.specs-site/is built from the manifest (browse by feature or by persona, with narratives, coverage markers and source links) and deployed to GitHub Pages fromdevelopby a GitHub Actions workflow.features/directory of Gherkin files; the useful content now lives in the annotations.Live preview: https://therestartproject.github.io/restarters.net/
What the check enforces
php artisan specs:extract --checkexits non-zero if any of these is true:app/Http/Controllershas neither#[UserStory]nor#[NoStory];@story:reference points at a method that does not exist, or is ambiguous between two controllers;docs/specs/manifest.jsondiffers from what the code now generates.It runs in CircleCI as its own early step ("Check living specs", before PHPUnit) and as
tests/Unit/SpecsExtractTest.php.Persona notes
ExportControllerare public (therestartproject.org/download-dataset uses them), so their stories are written for Guest and ThirdParty.RepairDirectoryAdminpersona (Repair Directory SuperAdmin or RegionalAdmin), distinct from the platform Administrator.API\EventController::createEventv2;PartyController::createonly has the "open the create form" story.CalendarEventsController::allEventshas a ThirdParty story like the other iCal feeds.StyleController,MapsProxyController) are#[NoStory];PreviewDeployController, the network tag endpoints,API\GroupController::listSummaryv2and the ORDS repair export (API\RepairController) have stories based on their real permission checks.Test plan
php artisan specs:extractgenerates the manifest (188 stories) and--checkpasses on this branch--checkreports unannotated methods and unresolvable@story:references (verified with a deliberately bad reference)node generate-pages.mjsin specs-site/ generates pages from the new manifestdevelop, conflicts resolved keeping develop's behaviournpm run buildof the VitePress site (only page generation checked after the latest changes)