From 050daa4ecf38c7d6f9ebfe2366f82a3aa785b6a6 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 21 May 2026 13:09:27 +0900 Subject: [PATCH 1/2] Generate LLMs text during site build --- .github/workflows/jekyll.yml | 55 ++++ .gitignore | 1 + bin/generate_llms_full.php | 68 +++++ bin/serve_local.sh | 2 + llms-full.txt | 569 ----------------------------------- 5 files changed, 126 insertions(+), 569 deletions(-) create mode 100644 .github/workflows/jekyll.yml create mode 100644 bin/generate_llms_full.php delete mode 100644 llms-full.txt diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml new file mode 100644 index 0000000..296656a --- /dev/null +++ b/.github/workflows/jekyll.yml @@ -0,0 +1,55 @@ +name: Deploy Jekyll site to Pages + +on: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2.2' + bundler-cache: true + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Run custom scripts + run: | + ruby bin/merge_md_files.rb + php bin/generate_llms_full.php + - name: Build with Jekyll + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-22.04 + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 7df4ada..8a7b602 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ _site/ watch +llms-full.txt diff --git a/bin/generate_llms_full.php b/bin/generate_llms_full.php new file mode 100644 index 0000000..e4746d9 --- /dev/null +++ b/bin/generate_llms_full.php @@ -0,0 +1,68 @@ + true, + 'onepage.md' => true, + 'ai-assistant.md' => true, +]; + +$files = []; +$iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($manualDir, FilesystemIterator::SKIP_DOTS), +); + +foreach ($iterator as $file) { + if (! $file instanceof SplFileInfo || $file->getExtension() !== 'md') { + continue; + } + + if (isset($skipFiles[$file->getBasename()])) { + continue; + } + + $files[] = $file->getPathname(); +} + +sort($files, SORT_STRING); + +$content = rtrim((string) file_get_contents($llmsFile)) . "\n\n---\n\n# Full Documentation\n\n"; + +foreach ($files as $file) { + $markdown = (string) file_get_contents($file); + $markdown = preg_replace('/\A---\s*\R.*?\R---\s*\R/ms', '', $markdown, 1) ?? $markdown; + $markdown = trim($markdown); + + if ($markdown === '') { + continue; + } + + $relativePath = ltrim(str_replace($baseDir, '', $file), '/'); + $content .= "\n\n"; + $content .= $markdown . "\n\n"; +} + +file_put_contents($outputFile, $content); + +echo "Generated llms-full.txt successfully.\n"; +echo 'Included files: ' . count($files) . "\n"; diff --git a/bin/serve_local.sh b/bin/serve_local.sh index 78c666b..5a05d32 100755 --- a/bin/serve_local.sh +++ b/bin/serve_local.sh @@ -3,4 +3,6 @@ # 'bundle exec' ensures we're using the correct versions of each gem according to our Gemfile.lock. # 'jekyll serve' starts a Jekyll development server. # '--watch' option automatically rebuilds the site when files are modified. +ruby bin/merge_md_files.rb +php bin/generate_llms_full.php bundle exec jekyll serve --watch --trace diff --git a/llms-full.txt b/llms-full.txt deleted file mode 100644 index 9b15add..0000000 --- a/llms-full.txt +++ /dev/null @@ -1,569 +0,0 @@ -# Be Framework — Complete Reference for LLMs - -> Objects don't DO things — they BECOME things. - -Be Framework is a PHP framework for ontological programming. Instead of telling objects what to do, you declare what can exist. Invalid states are not checked for — they are structurally inexpressible. Metamorphosis replaces procedural action. - -## Why Being Over Doing - -A pattern is a better answer to an existing question. A paradigm changes the question itself. Procedural programming asked **HOW** (how to do it). Object-oriented programming asked **WHAT** (what is it). Being-oriented programming asks **WHETHER** (can it even exist). - -Traditional code scatters defensive if statements throughout — "what if called in this state?", "what if null?" — because invalid states are representable. Service layers orchestrate from outside, and objects become obedient data containers. - -In Be, types are existence conditions. There are no types for invalid states, so guard statements disappear. Objects declare their own destiny through `#[Be]` and self-organize without external orchestrators. Side effects complete inside the constructor. Once born, objects are immutable. - -## The Transformation Formula - -Every transformation follows one formula: - -**Immanence** (`#[Input]`) + **Transcendence** (`#[Inject]`) → **New Immanence** (public properties) - -- **Immanence**: What the object inherently carries from the previous stage -- **Transcendence**: External power injected from outside (DI) -- **New Immanence**: The transformed state, expressed as public readonly properties - -Transcendence transforms the immanent and then vanishes. - -```php -final readonly class ValidatedUser -{ - public string $displayName; - public bool $isValid; - - public function __construct( - #[Input] string $name, // Immanence (from UserInput) - #[Input] string $email, // Immanence (from UserInput) - #[Inject] NameFormatter $formatter, // Transcendence (from DI) - #[Inject] EmailValidator $validator // Transcendence (from DI) - ) { - $this->displayName = $formatter->format($name); - $this->isValid = $validator->validate($email); - } -} -``` - -## Key Attributes - -| Attribute | FQCN | Role | -|-----------|------|------| -| `#[Be([Target::class])]` | `Be\Framework\Attribute\Be` | Declares metamorphosis destination(s) | -| `#[Input]` | `Ray\InputQuery\Attribute\Input` | Receives immanence from previous stage | -| `#[Inject]` | `Ray\Di\Di\Inject` | Receives transcendence from DI container | -| `#[Validate]` | `Be\Framework\Attribute\Validate` | Marks semantic validation method | -| `#[Message]` | `Be\Framework\Attribute\Message` | Multilingual exception message | - -Key classes: - -| Class | FQCN | -|-------|------| -| `Becoming` | `Be\Framework\Becoming` | -| `BecomingInterface` | `Be\Framework\BecomingInterface` | -| `SemanticVariableException` | `Be\Framework\Exception\SemanticVariableException` | -| `MomentInterface` | `Be\Framework\MomentInterface` | - -## Metamorphosis Flow - -### Linear (Input → Final) - -The simplest case — no branching needed: - -```php -#[Be([Hello::class])] -final readonly class HelloInput -{ - public function __construct( - public string $name - ) {} -} - -final readonly class Hello -{ - public string $greeting; - - public function __construct( - #[Input] string $name, - #[Inject] Greeting $greeting, - ) { - $this->greeting = "{$greeting->greeting} {$name}"; - } -} -``` - -### Branching (Input → Being → Final) - -When the outcome depends on runtime conditions: - -```php -#[Be([TriageAssessment::class])] -final readonly class PatientArrival -{ - public function __construct( - public float $bodyTemperature, - public int $heartRate - ) {} -} - -#[Be([EmergencyCase::class, ObservationCase::class])] -final readonly class TriageAssessment -{ - public Emergency|Observation $being; - - public function __construct( - #[Input] public float $bodyTemperature, - #[Input] public int $heartRate, - #[Inject] JTASProtocol $protocol - ) { - $urgency = $protocol->assess($bodyTemperature, $heartRate); - $this->being = ($urgency === 'emergency') - ? new Emergency() - : new Observation(); - } -} - -final readonly class EmergencyCase -{ - public string $priority; - public string $color; - - public function __construct( - #[Input] public float $bodyTemperature, - #[Input] public int $heartRate, - #[Input] public Emergency $being - ) { - $this->priority = 'IMMEDIATE'; - $this->color = 'RED'; - } - - public function assignER(): string - { - return "Secure ER Room 1 immediately."; - } -} -``` - -The `$being` property (union type) determines which Final class receives the transformation. The framework matches the type actually assigned to the candidate classes in `#[Be]`. - -Note: `$being` is a conventional name, not a framework requirement. Any public property participates in type matching. - -### Nested (Becoming within Becoming) - -`Becoming` can be injected as transcendence to trigger sub-chains: - -```php -final readonly class OrderProcessing -{ - public PaymentResult $payment; - public ShippingResult $shipping; - - public function __construct( - #[Input] Order $order, - #[Inject] Becoming $becoming - ) { - $this->payment = $becoming(new PaymentInput($order->getPayment())); - $this->shipping = $becoming(new ShippingInput($order->getAddress())); - } -} -``` - -## Executing Metamorphosis - -```php -$injector = new Injector(new AppModule()); -$becoming = new Becoming($injector, 'App\\Semantic'); - -$input = new PatientArrival(bodyTemperature: 39.5, heartRate: 90); -$result = $becoming($input); -// $result IS an EmergencyCase — type determines capability -``` - -`Becoming` is typically injected via DI: - -```php -final readonly class TriagePage -{ - public function __construct( - private BecomingInterface $becoming - ) {} - - public function __invoke(float $temp, int $hr): EmergencyCase|ObservationCase - { - return ($this->becoming)(new PatientArrival($temp, $hr)); - } -} -``` - -## Semantic Variables - -Variable names carry meaning and constraints. Define once, automatically applied everywhere: - -```php -final class Email -{ - #[Validate] - public function validate(string $email): void - { - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - throw new InvalidEmailException(); - } - } -} - -// Automatically applied to ANY constructor parameter named $email -public function __construct(string $email) {} -``` - -### Name Matching Rules - -- Semantic class `Email` validates parameter `$email` (PascalCase → camelCase) -- Semantic class `BodyTemperature` validates parameter `$bodyTemperature` -- Matching is automatic — no explicit wiring needed - -### Decorating Names with Attributes - -Attributes refine existence conditions for the same name: - -```php -final readonly class Age -{ - #[Validate] - public function validate(int $age): void - { - if ($age < 0 || $age > 150) { throw new InvalidAgeException(); } - } - - #[Validate] - public function validateTeen(#[Teen] int $age): void - { - if ($age < 13 || $age > 19) { throw new InvalidTeenAgeException(); } - } -} - -// Basic Age validation only -public function __construct(int $age) {} - -// Age + Teen validation -public function __construct(#[Teen] int $age) {} -``` - -### Cross-Field Validation (Names as Relations) - -When parameter names partially match, values are automatically passed: - -```php -final readonly class DateRange -{ - #[Validate] - public function validate(string $startDate, string $endDate): void - { - if ($startDate > $endDate) { throw new InvalidDateRangeException(); } - } -} - -// DateRange auto-applied because $startDate and $endDate are present -public function __construct(string $startDate, string $endDate) {} -``` - -Confirmation matching also works as cross-field validation: - -```php -final readonly class EmailConfirmation -{ - #[Validate] - public function validate(string $email, string $confirmEmail): void - { - if ($email !== $confirmEmail) { - throw new EmailMismatchException(); - } - } -} - -// EmailConfirmation auto-applied because $email and $confirmEmail are present -public function __construct(string $email, string $confirmEmail) {} -``` - -External service validation also works as cross-field validation: - -```php -final readonly class ZipPrefecture -{ - public function __construct( - private ZipResolver $resolver // Injected via DI - ) {} - - #[Validate] - public function validate(string $zipCode, string $prefecture): void - { - if (!$this->resolver->matches($zipCode, $prefecture)) { - throw new ZipPrefectureMismatchException(); - } - } -} - -// ZipPrefecture auto-applied because $zipCode and $prefecture are present -public function __construct(string $zipCode, string $prefecture) {} -``` - -## Reason Layer - -The Reason Layer gathers the complete tool set for an existence to come into being, into a single object: - -```php -final readonly class ExpressShipping -{ - public function __construct( - private PriorityCarrier $carrier, - private RealTimeTracker $tracker, - ) {} - - public function calculateFee(Weight $weight): Fee { /* ... */ } - public function guaranteeDeliveryBy(Address $addr): DateTimeImmutable { /* ... */ } -} -``` - -### Reason as $being (Dual Role) - -When a reason object is passed as `$being`, its type determines the transformation destination AND provides mode-specific methods: - -```php -final readonly class ExpressDelivery -{ - public Fee $fee; - - public function __construct( - #[Input] OrderData $order, - #[Input] ExpressShipping $being // Type determines destination + provides methods - ) { - $this->fee = $being->calculateFee($order->weight); - } -} -``` - -### Dual Role - -Any Reason object can serve as either `#[Inject]` (providing transcendent capabilities) or `$being` (determining destiny). The difference is not in the object itself, but in how it is used. - -### Why Not Individual Injection? - -Individual `#[Inject]` scatters dependencies. Reason bundles them by meaning — "what does this existence need to come into being?" In tests, swap the entire precondition set at once. - -## Final Objects and $been - -Final Objects are the destination of metamorphosis. `$been` records completion evidence (who, when, what): - -```php -final readonly class SuccessfulOrder -{ - public string $orderId; - public string $confirmationCode; - public BeenProcessed $been; - - public function __construct( - #[Input] Money $total, - #[Input] CreditCard $card, - #[Inject] OrderIdGenerator $generator, - #[Inject] Receipt $receipt - ) { - $this->orderId = $generator->generate(); - $this->confirmationCode = $receipt->generate($total); - $this->been = new BeenProcessed( - actor: $card->getHolderName(), - timestamp: new DateTimeImmutable(), - evidence: ['total' => $total->getAmount()] - ); - } -} -``` - -Two temporal axes: -- `#[Be]` — what to become (direction towards future) -- `$been` — evidence of completion (past perfect) - -## Semantic Exceptions - -### Domain Exceptions with Structured Data - -```php -#[Message([ - 'en' => 'Vital signs indicate non-survivable conditions.', - 'ja' => 'バイタルサインが生存不可能な状態を示しています。' -])] -final readonly class LethalVitalException extends \DomainException {} -``` - -### Error Collection - -The framework collects ALL validation errors, not just the first: - -```php -try { - $becoming(new UserInput('', 'invalid-email', 10)); -} catch (SemanticVariableException $e) { - $messages = $e->getErrors()->getMessages('en'); - // ["Name cannot be empty", "Invalid email format", "Age must be at least 13"] -} -``` - -### Errors as Existence - -Failure is a legitimate result of transformation: - -```php -#[Be([ValidUser::class, InvalidUser::class])] -final readonly class UserValidation -{ - public ValidUser|InvalidUser $being; - - public function __construct(#[Input] string $data) - { - try { - $this->being = new ValidUser($data); - } catch (ValidationException $e) { - $this->being = new InvalidUser($e->getErrors()); - } - } -} -``` - -## Naming Conventions - -### Classes - -| Type | Pattern | Examples | -|------|---------|----------| -| Input | `{Domain}Input` | `UserInput`, `OrderInput`, `PatientArrival` | -| Being | `{State}{Domain}` | `ValidatedUser`, `ProcessedOrder` | -| Final | `{CompletedState}` | `RegisteredUser`, `EmergencyCase` | -| Reason | `{Capability}` | `ExpressShipping`, `JTASProtocol` | -| Semantic | `{MeaningName}` | `Email`, `BodyTemperature`, `Age` | - -### Anti-patterns - -```php -// ❌ Action-oriented (Doing) -class UserValidator, OrderProcessor, CreateUserRequest - -// ✅ Existence-oriented (Being) -class ValidatedUser, ProcessedOrder, UserInput -``` - -### All classes are `final readonly` - -Every Input, Being, and Final class should be `final readonly class`. - -## Project Structure - -``` -src/ -├── Input/ # Starting points (pure immanence) -├── Being/ # Intermediate transformations -├── Moment/ # Essential parts constituting the whole -├── Final/ # Destinations (complete existence) -├── Reason/ # Tool sets enabling existence -├── Semantic/ # Domain vocabulary (what CAN exist) -├── Exception/ # Domain exceptions (why existence fails) -└── Module/ # DI configuration -``` - -## Side Effects - -Side effects are completed inside the constructor, delegated to Reason. The object itself decides — no external orchestrator tells it what to do. - -Persistence and external communication are implementation details confined to Reason. Existence types are freed from database schemas and API constraints. - -## Potential and Moment - -Use this pattern when multiple external operations must succeed atomically — all commit together, or none at all. For simple cases where Reason returns an immediate value, this is unnecessary. - -### Potential: Prepared but Uncommitted - -A Reason method prepares an external operation and returns a **Potential** — an object that holds a deferred operation, realized later via `be()`: - -```php -final class PaymentGateway -{ - public function authorize(string $cardNumber, int $amount): PaymentCapture - { - $authCode = $this->api->authorize($cardNumber, $amount); - - return new PaymentCapture( - $authCode, - $amount, - fn () => $this->api->capture($authCode, $amount), - ); - } -} -``` - -`PaymentCapture` is a Potential — payment is authorized but not yet captured. Calling `be()` commits it. - -### Moment: Holding Potential - -A class that holds a Potential is called a **Moment** — an essential part constituting a Final. A Moment implements `MomentInterface`, provided by the framework: - -```php -interface MomentInterface -{ - public function be(): void; -} -``` - -```php -final readonly class PaymentCompleted implements MomentInterface -{ - public PaymentCapture $capture; - - public function __construct( - #[Input] public string $cardNumber, - #[Input] public int $amount, - #[Inject] PaymentGateway $gateway, - ) { - $this->capture = $gateway->authorize($cardNumber, $amount); - } - - public function be(): void - { - $this->capture->be(); // Realize the potential - } -} -``` - -### Convergence: Final Realizes Moments - -The Final calls `be()` on its Moments in its own constructor — this is not external orchestration but **Doing for Being**: the Final realizing its parts in order to bring itself into existence. - -```php -final readonly class OrderConfirmed -{ - public function __construct( - #[Inject] public InventoryReserved $inventory, - #[Inject] public PaymentCompleted $payment, - #[Inject] public ShippingArranged $shipping, - ) { - // Final's self-determination: realizing parts to bring itself into existence - $this->inventory->be(); - $this->payment->be(); - $this->shipping->be(); - } -} -``` - -## Type IS Capability - -Different types have different methods. Type determines what actions are possible: - -```php -// EmergencyCase has assignER() -// ObservationCase has assignWaitingArea() -$result->assignER(); // Only possible for EmergencyCase — compile-time safe -``` - -You cannot assign an ER room to an observation patient. The type system enforces this. - -## Documentation - -- English: https://be-framework.github.io/manuals/1.0/en/ -- Japanese: https://be-framework.github.io/manuals/1.0/ja/ -- Repository: https://github.com/be-framework/be-framework -- App Skeleton: https://github.com/be-framework/app -- Demos: https://github.com/be-framework/demos -- Claude Code Skills: https://github.com/be-framework/skills From 3fd5bb05ffc29ba5f544f8c28dac3646b3e2631a Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 21 May 2026 13:16:34 +0900 Subject: [PATCH 2/2] Address CodeRabbit review feedback --- .github/workflows/jekyll.yml | 9 +++++++-- bin/generate_llms_full.php | 21 ++++++++++++++++++--- bin/serve_local.sh | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml index 296656a..5302ca3 100644 --- a/.github/workflows/jekyll.yml +++ b/.github/workflows/jekyll.yml @@ -7,8 +7,6 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: "pages" @@ -17,9 +15,13 @@ concurrency: jobs: build: runs-on: ubuntu-22.04 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Ruby uses: ruby/setup-ruby@v1 with: @@ -44,6 +46,9 @@ jobs: uses: actions/upload-pages-artifact@v3 deploy: + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/bin/generate_llms_full.php b/bin/generate_llms_full.php index e4746d9..55aa54c 100644 --- a/bin/generate_llms_full.php +++ b/bin/generate_llms_full.php @@ -46,10 +46,21 @@ sort($files, SORT_STRING); -$content = rtrim((string) file_get_contents($llmsFile)) . "\n\n---\n\n# Full Documentation\n\n"; +$llmsContent = file_get_contents($llmsFile); +if ($llmsContent === false) { + fwrite(STDERR, "Failed to read llms.txt at {$llmsFile}\n"); + exit(1); +} + +$content = rtrim($llmsContent) . "\n\n---\n\n# Full Documentation\n\n"; foreach ($files as $file) { - $markdown = (string) file_get_contents($file); + $markdown = file_get_contents($file); + if ($markdown === false) { + fwrite(STDERR, "Failed to read markdown file at {$file}\n"); + exit(1); + } + $markdown = preg_replace('/\A---\s*\R.*?\R---\s*\R/ms', '', $markdown, 1) ?? $markdown; $markdown = trim($markdown); @@ -62,7 +73,11 @@ $content .= $markdown . "\n\n"; } -file_put_contents($outputFile, $content); +$bytesWritten = file_put_contents($outputFile, $content); +if ($bytesWritten === false || $bytesWritten !== strlen($content)) { + fwrite(STDERR, "Failed to write complete llms-full.txt to {$outputFile}\n"); + exit(1); +} echo "Generated llms-full.txt successfully.\n"; echo 'Included files: ' . count($files) . "\n"; diff --git a/bin/serve_local.sh b/bin/serve_local.sh index 5a05d32..17f4a06 100755 --- a/bin/serve_local.sh +++ b/bin/serve_local.sh @@ -1,4 +1,7 @@ #!/bin/bash +set -euo pipefail +trap 'echo "serve_local.sh failed; check merge_md_files.rb, generate_llms_full.php, or Jekyll output above." >&2' ERR + # This script is used to serve the Jekyll site locally with automatic rebuilding. # 'bundle exec' ensures we're using the correct versions of each gem according to our Gemfile.lock. # 'jekyll serve' starts a Jekyll development server.