diff --git a/AGENTS.md b/AGENTS.md index 57d4981..28ce35b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,6 +134,7 @@ Every template was derived from the 28 locally cloned `kirchDev` / `TitusKirch` | `turborepo` | `turbo.json` | 3 | | `tofu` | `*.tf` / `*.tofu` / lock file | 1 | | `laravel` | `artisan` | 1 | +| `octane` | `config/octane.php` | 3 | | `nuxt` | `nuxt.config.*` | 1 | | `tauri` | `src-tauri/` | 1 | | `storybook` | `.storybook/` | 1 | @@ -168,6 +169,8 @@ What the brief said stays true, just narrower than the old prompt copy implied: Deliberate omissions, each for a reason worth keeping: +- **`octane` carries the installer's spellings verbatim** (`frankenphp`, not `/frankenphp`). `octane:install` appends its lines unless the exact line is already there, so a template in its own spelling absorbs them and a re-run adds nothing. It is named after Octane rather than a server: the server is configuration, the files are Octane's. +- **`php@v2` and `laravel@v2` follow the Laravel 13 skeleton's own `.gitignore`, in its spelling**, so a fresh `laravel new` is absorbed instead of reported as equivalent spellings. `/auth.json` (Composer's registry credentials), `.phpactor.json` and `/.phpstan.cache` sit in `php`, not `laravel`: any Composer project has them. `/.phpstan.cache` is a kirchDev convention for PHPStan's `tmpDir` rather than a PHPStan default — `app` still writes to `build/phpstan`, where the line is simply inert. `laravel@v2` adds `/public/fonts-manifest.dev.json`, `Homestead.json`/`.yaml` and `/storage/pail` — the one `storage/` path a template may name, because nothing is committed there: `pail` creates the directory and its keeper at runtime, and without the line that keeper appears as an untracked file. The skeleton's editor and agent lines (`/.codex`, `/.cursor/`, `/.nova`, `/.zed`) are deliberately **not** in either: the estate commits `.codex/` and agent config, and the editors are machine stacks. - **`composer.lock` is in no template.** A library ignores it, an application commits it — a project decision, so it belongs in the free zone. - **The JVM stack is `gradle`, not `java`.** What a JVM repo ignores follows the build tool — Gradle writes `build/`, Maven `target/` — so a `java` stack would render lines pointing at nothing in half the repos. `*.jar` is left out because the Gradle wrapper jar is committed. A `maven` stack waits until a repo needs one. - **`dist` belongs to `node` alone**, not to `go`. goreleaser writes there too, but a line may live in only one stack (below), and `node` claims it. @@ -188,10 +191,10 @@ git reads a `.gitignore` in every directory, so a repository has more than one. | :---------- | :--------------------------------------------- | :--------------------------------- | | `managed` | it carries a `# region gitignore-sync` | checked by `check --recursive` | | `keeper` | **content**: a `*` plus `!.gitignore` | skipped — it holds an empty dir | -| `framework` | **path**: under `storage/`, `bootstrap/cache/`, `.husky/` | skipped — the framework owns it | +| `framework` | **path**: under `storage/`, `bootstrap/cache/`, `.husky/` — or Laravel's `database/.gitignore` holding exactly `*.sqlite*` | skipped — the framework owns it | | `plain` | everything else | measured by `audit --recursive` | -A keeper is recognised by what it says, not where it sits, so the idiom holds for any framework that uses it. A framework stub needs the path, and that path is matched at **any** position: `services/core/storage/logs` in a monorepo counts as readily as a root `storage/logs`. `--include-stubs` measures them anyway. +A keeper is recognised by what it says, not where it sits, so the idiom holds for any framework that uses it. A framework stub needs the path, and that path is matched at **any** position: `services/core/storage/logs` in a monorepo counts as readily as a root `storage/logs`. Laravel's `database/.gitignore` is the one stub that needs its content too: a bare `database/` path would claim a repository's own file, so only the skeleton's exact `*.sqlite*` counts. `--include-stubs` measures them anyway. **A recursive scan must not walk into generated output**, and the skip list comes from two places rather than a hand-kept list: @@ -206,7 +209,7 @@ A directory holding its own `.git` is skipped too: a submodule or an agent workt `src/templates/index.ts` keeps **every version a stack has ever shipped**, ascending. Reconciling reads the version the section marker names, so upgrading `node@v1` to `node@v2` knows which lines it put there itself and which the user added. Without that history an upgrade would "rescue" its own dropped lines into the free zone. When you change a template, **add a version — never edit one in place.** -**That rule is checked, not trusted.** `tests/templates.lock.json` holds every locked version with its lines, and `tests/templates.lock.test.ts` fails when one of them changes. Editing `node@v1` in place therefore cannot reach `main` unless someone also runs `pnpm templates:lock` — which shows up as a diff on the lock, and that diff is the thing a reviewer has to be asked about. Adding a *new* version needs no re-run (the test only guards what the lock already knows), but run it anyway so the next change is guarded too. +**That rule is checked, not trusted.** `tests/templates.lock.json` holds every locked version with its lines, and `tests/templates.lock.test.ts` fails when one of them changes. Editing `node@v1` in place therefore cannot reach `main` unless someone also runs `pnpm templates:lock` — which shows up as a diff on the lock, and that diff is the thing a reviewer has to be asked about. Adding a *new* version needs no re-run (the test only guards what the lock already knows), but run it anyway so the next change is guarded too. The script locks **every** version still in the registry, not only the current one — `php@v1` stays guarded after `php@v2` ships, because `reconcile` still reads it. It writes plain JSON; run `oxfmt` over the lock afterwards. The history grows, slowly. Dropping a very old version is allowed and degrades safely: `reconcile` falls back to the current template, which means it rescues more than it needs to rather than deleting something it shouldn't. @@ -216,7 +219,7 @@ Four template rules that are not style: - **Never a bare `.vscode/` or `.idea/`.** git does not descend into an ignored directory, which makes `!` exceptions under it technically impossible. Always `.vscode/*` plus targeted exceptions. - **The `!` exceptions are the files the estate actually tracks**, measured, not guessed: `extensions.json` (every repo that shares anything), `settings.json` (seven), `mcp.json` (two). `tasks.json` and `*.code-snippets`, which the toptal block unignores, are tracked by no repo at all. No repo tracks any `.idea` content, so `intellij` ships no exceptions. -- **`agents` carries working files, not configuration.** Across the estate **126 files** under `.claude/`, `.codex/` and `.opencode/` are committed — `settings.json`, skills, `default.rules`, agent definitions. Only `settings.local.json` and `worktrees/` describe one machine, and only those two are in the block. `.claude/worktrees/` is included because it accumulates for real and the one repo that ignores it today does so in `.git/info/exclude` — private knowledge that belongs where the team can see it. +- **`agents` carries working files, not configuration.** Across the estate **126 files** under `.claude/`, `.codex/` and `.opencode/` are committed — `settings.json`, skills, `default.rules`, agent definitions. Only `settings.local.json` and `worktrees/` describe one machine, and only those two are in `agents@v1`. `agents@v2` adds `.codex/config.toml`: Laravel Boost writes it with absolute paths of the machine it ran on, and both repos that commit one (`app`, `gildstone`) committed exactly that — the file, not the directory, so `.codex/rules/` stays committable. `.claude/worktrees/` is included because it accumulates for real and the one repo that ignores it today does so in `.git/info/exclude` — private knowledge that belongs where the team can see it. - **`node_modules` belongs to the `node` stack, not to `core`** — a pure Go repo then correctly does not get it. That it lands almost everywhere in practice is a consequence of the estate, not a reason to blur the model. ### Two surfaces, one tool diff --git a/CLAUDE.md b/CLAUDE.md index 57d4981..28ce35b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,7 @@ Every template was derived from the 28 locally cloned `kirchDev` / `TitusKirch` | `turborepo` | `turbo.json` | 3 | | `tofu` | `*.tf` / `*.tofu` / lock file | 1 | | `laravel` | `artisan` | 1 | +| `octane` | `config/octane.php` | 3 | | `nuxt` | `nuxt.config.*` | 1 | | `tauri` | `src-tauri/` | 1 | | `storybook` | `.storybook/` | 1 | @@ -168,6 +169,8 @@ What the brief said stays true, just narrower than the old prompt copy implied: Deliberate omissions, each for a reason worth keeping: +- **`octane` carries the installer's spellings verbatim** (`frankenphp`, not `/frankenphp`). `octane:install` appends its lines unless the exact line is already there, so a template in its own spelling absorbs them and a re-run adds nothing. It is named after Octane rather than a server: the server is configuration, the files are Octane's. +- **`php@v2` and `laravel@v2` follow the Laravel 13 skeleton's own `.gitignore`, in its spelling**, so a fresh `laravel new` is absorbed instead of reported as equivalent spellings. `/auth.json` (Composer's registry credentials), `.phpactor.json` and `/.phpstan.cache` sit in `php`, not `laravel`: any Composer project has them. `/.phpstan.cache` is a kirchDev convention for PHPStan's `tmpDir` rather than a PHPStan default — `app` still writes to `build/phpstan`, where the line is simply inert. `laravel@v2` adds `/public/fonts-manifest.dev.json`, `Homestead.json`/`.yaml` and `/storage/pail` — the one `storage/` path a template may name, because nothing is committed there: `pail` creates the directory and its keeper at runtime, and without the line that keeper appears as an untracked file. The skeleton's editor and agent lines (`/.codex`, `/.cursor/`, `/.nova`, `/.zed`) are deliberately **not** in either: the estate commits `.codex/` and agent config, and the editors are machine stacks. - **`composer.lock` is in no template.** A library ignores it, an application commits it — a project decision, so it belongs in the free zone. - **The JVM stack is `gradle`, not `java`.** What a JVM repo ignores follows the build tool — Gradle writes `build/`, Maven `target/` — so a `java` stack would render lines pointing at nothing in half the repos. `*.jar` is left out because the Gradle wrapper jar is committed. A `maven` stack waits until a repo needs one. - **`dist` belongs to `node` alone**, not to `go`. goreleaser writes there too, but a line may live in only one stack (below), and `node` claims it. @@ -188,10 +191,10 @@ git reads a `.gitignore` in every directory, so a repository has more than one. | :---------- | :--------------------------------------------- | :--------------------------------- | | `managed` | it carries a `# region gitignore-sync` | checked by `check --recursive` | | `keeper` | **content**: a `*` plus `!.gitignore` | skipped — it holds an empty dir | -| `framework` | **path**: under `storage/`, `bootstrap/cache/`, `.husky/` | skipped — the framework owns it | +| `framework` | **path**: under `storage/`, `bootstrap/cache/`, `.husky/` — or Laravel's `database/.gitignore` holding exactly `*.sqlite*` | skipped — the framework owns it | | `plain` | everything else | measured by `audit --recursive` | -A keeper is recognised by what it says, not where it sits, so the idiom holds for any framework that uses it. A framework stub needs the path, and that path is matched at **any** position: `services/core/storage/logs` in a monorepo counts as readily as a root `storage/logs`. `--include-stubs` measures them anyway. +A keeper is recognised by what it says, not where it sits, so the idiom holds for any framework that uses it. A framework stub needs the path, and that path is matched at **any** position: `services/core/storage/logs` in a monorepo counts as readily as a root `storage/logs`. Laravel's `database/.gitignore` is the one stub that needs its content too: a bare `database/` path would claim a repository's own file, so only the skeleton's exact `*.sqlite*` counts. `--include-stubs` measures them anyway. **A recursive scan must not walk into generated output**, and the skip list comes from two places rather than a hand-kept list: @@ -206,7 +209,7 @@ A directory holding its own `.git` is skipped too: a submodule or an agent workt `src/templates/index.ts` keeps **every version a stack has ever shipped**, ascending. Reconciling reads the version the section marker names, so upgrading `node@v1` to `node@v2` knows which lines it put there itself and which the user added. Without that history an upgrade would "rescue" its own dropped lines into the free zone. When you change a template, **add a version — never edit one in place.** -**That rule is checked, not trusted.** `tests/templates.lock.json` holds every locked version with its lines, and `tests/templates.lock.test.ts` fails when one of them changes. Editing `node@v1` in place therefore cannot reach `main` unless someone also runs `pnpm templates:lock` — which shows up as a diff on the lock, and that diff is the thing a reviewer has to be asked about. Adding a *new* version needs no re-run (the test only guards what the lock already knows), but run it anyway so the next change is guarded too. +**That rule is checked, not trusted.** `tests/templates.lock.json` holds every locked version with its lines, and `tests/templates.lock.test.ts` fails when one of them changes. Editing `node@v1` in place therefore cannot reach `main` unless someone also runs `pnpm templates:lock` — which shows up as a diff on the lock, and that diff is the thing a reviewer has to be asked about. Adding a *new* version needs no re-run (the test only guards what the lock already knows), but run it anyway so the next change is guarded too. The script locks **every** version still in the registry, not only the current one — `php@v1` stays guarded after `php@v2` ships, because `reconcile` still reads it. It writes plain JSON; run `oxfmt` over the lock afterwards. The history grows, slowly. Dropping a very old version is allowed and degrades safely: `reconcile` falls back to the current template, which means it rescues more than it needs to rather than deleting something it shouldn't. @@ -216,7 +219,7 @@ Four template rules that are not style: - **Never a bare `.vscode/` or `.idea/`.** git does not descend into an ignored directory, which makes `!` exceptions under it technically impossible. Always `.vscode/*` plus targeted exceptions. - **The `!` exceptions are the files the estate actually tracks**, measured, not guessed: `extensions.json` (every repo that shares anything), `settings.json` (seven), `mcp.json` (two). `tasks.json` and `*.code-snippets`, which the toptal block unignores, are tracked by no repo at all. No repo tracks any `.idea` content, so `intellij` ships no exceptions. -- **`agents` carries working files, not configuration.** Across the estate **126 files** under `.claude/`, `.codex/` and `.opencode/` are committed — `settings.json`, skills, `default.rules`, agent definitions. Only `settings.local.json` and `worktrees/` describe one machine, and only those two are in the block. `.claude/worktrees/` is included because it accumulates for real and the one repo that ignores it today does so in `.git/info/exclude` — private knowledge that belongs where the team can see it. +- **`agents` carries working files, not configuration.** Across the estate **126 files** under `.claude/`, `.codex/` and `.opencode/` are committed — `settings.json`, skills, `default.rules`, agent definitions. Only `settings.local.json` and `worktrees/` describe one machine, and only those two are in `agents@v1`. `agents@v2` adds `.codex/config.toml`: Laravel Boost writes it with absolute paths of the machine it ran on, and both repos that commit one (`app`, `gildstone`) committed exactly that — the file, not the directory, so `.codex/rules/` stays committable. `.claude/worktrees/` is included because it accumulates for real and the one repo that ignores it today does so in `.git/info/exclude` — private knowledge that belongs where the team can see it. - **`node_modules` belongs to the `node` stack, not to `core`** — a pure Go repo then correctly does not get it. That it lands almost everywhere in practice is a consequence of the estate, not a reason to blur the model. ### Two surfaces, one tool diff --git a/scripts/templates-lock.ts b/scripts/templates-lock.ts index b462702..20786a9 100644 --- a/scripts/templates-lock.ts +++ b/scripts/templates-lock.ts @@ -15,12 +15,22 @@ */ import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; -import { currentTemplate, stackOrder } from '../src/templates/index.ts'; +import { + currentTemplate, + stackOrder, + templateAt +} from '../src/templates/index.ts'; const lock: Record = {}; +// Every version, not only the current one: a stack's older versions stay in the +// registry so `reconcile` can read what a section put there itself, and a +// version that is still read must stay guarded after its successor ships. for (const stack of stackOrder()) { - const template = currentTemplate(stack); - if (template) lock[`${stack}@v${template.version}`] = template.lines; + const current = currentTemplate(stack)?.version ?? 0; + for (let version = 1; version <= current; version++) { + const template = templateAt(stack, version); + if (template) lock[`${stack}@v${template.version}`] = template.lines; + } } const path = join(import.meta.dirname, '..', 'tests', 'templates.lock.json'); diff --git a/src/detect.ts b/src/detect.ts index 613d880..510e1c3 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -80,6 +80,8 @@ const fingerprints: Record = { storybook: { source: 'repo', files: ['.storybook'] }, php: { source: 'repo', files: ['composer.json'] }, laravel: { source: 'repo', files: ['artisan'] }, + // Published by `octane:install`, whichever server the repo picks. + octane: { source: 'repo', files: ['config/octane.php'] }, go: { source: 'repo', files: ['go.mod'] }, tofu: { source: 'repo', diff --git a/src/discover.ts b/src/discover.ts index 1f1ee7f..a06e61d 100644 --- a/src/discover.ts +++ b/src/discover.ts @@ -79,6 +79,27 @@ const FRAMEWORK_PATHS = [ '.husky' // husky ]; +/** + * Laravel's `database/.gitignore`, recognised by path **and** content: the + * skeleton ships it holding exactly `*.sqlite*`. A path alone would claim any + * repository's own `database/.gitignore`, which is somebody's decision rather + * than a framework's stub. + */ +const LARAVEL_DATABASE_STUB = ['*.sqlite*']; + +const isLaravelDatabaseStub = (rel: string, text: string): boolean => { + const dir = rel.slice(0, rel.lastIndexOf('/')); + if (dir !== 'database' && !dir.endsWith('/database')) return false; + const lines = text + .split('\n') + .map((l) => l.trim()) + .filter((l) => l !== '' && !l.startsWith('#')); + return ( + lines.length === LARAVEL_DATABASE_STUB.length && + lines.every((l, i) => l === LARAVEL_DATABASE_STUB[i]) + ); +}; + const underFrameworkPath = (rel: string): boolean => { const dir = rel.slice(0, rel.lastIndexOf('/') + 1); return FRAMEWORK_PATHS.some( @@ -111,7 +132,9 @@ function classify(file: string, rel: string): FoundKind { } if (/^#\s*region\s+gitignore-sync\s*$/m.test(text)) return 'managed'; if (isDirectoryKeeper(text)) return 'keeper'; - if (underFrameworkPath(rel)) return 'framework'; + if (underFrameworkPath(rel) || isLaravelDatabaseStub(rel, text)) { + return 'framework'; + } return 'plain'; } diff --git a/src/templates/index.ts b/src/templates/index.ts index a64eba7..7d0207a 100644 --- a/src/templates/index.ts +++ b/src/templates/index.ts @@ -40,6 +40,20 @@ const registry: Record = { stack: 'agents', version: 1, lines: ['.claude/settings.local.json', '.claude/worktrees/'] + }, + // v2: `.codex/config.toml` as written by Laravel Boost's `boost:install` + // carries one machine's absolute paths (`wsl.exe`, `/root/projects/…`). + // Both repos in the estate that commit the file (`app`, `gildstone`) + // committed exactly that, and none commits a shared one. Only the file: + // `.codex/rules/` stays committed wherever it is. + { + stack: 'agents', + version: 2, + lines: [ + '.claude/settings.local.json', + '.claude/worktrees/', + '.codex/config.toml' + ] } ], // Conflict and backup droppings every git repo can produce. Fingerprinted on @@ -128,13 +142,37 @@ const registry: Record = { stack: 'php', version: 1, lines: ['/vendor', '/.phpunit.cache', '/.phpunit.result.cache'] + }, + // v2 follows what the Laravel skeleton writes for Composer and PHP tooling, + // in its spelling, so a fresh `laravel new` is absorbed rather than reported + // as equivalent spellings: + // - `auth.json` holds Composer's registry credentials (private Packagist, + // GitHub tokens). A leaked token is worth a line in every PHP repo, which + // is why it sits here and not in `laravel`. + // - `.phpunit.result.cache` unanchored, as PHPUnit writes it wherever the + // suite runs from. + // - `.phpactor.json` is one developer's language-server settings. + // - `/.phpstan.cache` is the kirchDev convention for PHPStan's `tmpDir` + // (`gildstone`, `mc-network`); PHPStan has no fixed default, so a repo + // that points `tmpDir` elsewhere simply never produces the path. + { + stack: 'php', + version: 2, + lines: [ + '/vendor', + '/.phpunit.cache', + '.phpunit.result.cache', + '/auth.json', + '.phpactor.json', + '/.phpstan.cache' + ] } ], - // `/storage/pail` is deliberately absent, and so is every other `storage/` - // subdirectory: Laravel commits a directory keeper in each of them (`*` plus - // `!.gitignore`), which already ignores their contents. Repeating the path - // here would ignore the keeper itself — the same defect a bare `.vscode` - // causes, only self-inflicted. Found by running the rollout against `app`. + // Every committed `storage/` subdirectory is deliberately absent: Laravel + // commits a directory keeper in each of them (`*` plus `!.gitignore`), which + // already ignores their contents. Repeating the path here would ignore the + // keeper itself — the same defect a bare `.vscode` causes, only + // self-inflicted. Found by running the rollout against `app`. laravel: [ { stack: 'laravel', @@ -149,6 +187,54 @@ const registry: Record = { '_ide_helper_models.php', '.phpstorm.meta.php' ] + }, + // v2 catches up with the Laravel 13 skeleton's own `.gitignore`: + // - `/storage/pail` is the one `storage/` path that belongs here. Nothing is + // committed there: `pail` creates the directory and its keeper at runtime, + // so without the line that keeper shows up as an untracked file after the + // first `pail`, instead of never being seen. + // - `/public/fonts-manifest.dev.json` is written by the skeleton's Vite font + // plugin in dev mode. + // - `Homestead.json` / `Homestead.yaml` are written by `homestead make` with + // one machine's paths. + { + stack: 'laravel', + version: 2, + lines: [ + '/public/build', + '/public/hot', + '/public/storage', + '/public/fonts-manifest.dev.json', + '/storage/*.key', + '/storage/pail', + '/bootstrap/ssr', + '_ide_helper.php', + '_ide_helper_models.php', + '.phpstorm.meta.php', + 'Homestead.json', + 'Homestead.yaml' + ] + } + ], + // Exactly what `octane:install` appends to `.gitignore`, spelled the way it + // appends it: FrankenPHP's downloaded binary, its worker script and Caddy's + // state, and RoadRunner's binary and generated config. Kept verbatim so the + // installer's lines are absorbed rather than reported as equivalent + // spellings, and so a re-run of the installer (which checks for the exact + // line) adds nothing. Named after Octane, not a server: which server a repo + // picks is configuration, the files are Octane's. Found in `app`, + // `gildstone` and `mc-network`. + octane: [ + { + stack: 'octane', + version: 1, + lines: [ + '**/caddy', + 'frankenphp', + 'frankenphp-worker.php', + 'rr', + '.rr.yaml' + ] } ], // Three repos in the estate run turbo (`oggsbreinig`, `gildstone`, and the diff --git a/tests/detect.test.ts b/tests/detect.test.ts index 6dc1381..b4c0907 100644 --- a/tests/detect.test.ts +++ b/tests/detect.test.ts @@ -36,6 +36,13 @@ describe('detect', () => { ); }); + it('fingerprints octane from its published config', () => { + const dir = fresh(); + mkdirSync(join(dir, 'config')); + writeFileSync(join(dir, 'config', 'octane.php'), ''); + expect(names(dir)).toContain('octane'); + }); + it('fingerprints gradle from a build script one level down', () => { const dir = fresh(); mkdirSync(join(dir, 'apps')); diff --git a/tests/discover.test.ts b/tests/discover.test.ts index fb471b8..cc0cd87 100644 --- a/tests/discover.test.ts +++ b/tests/discover.test.ts @@ -124,6 +124,26 @@ describe('findGitignores', () => { ); }); + it("labels Laravel's database stub by path and content, nested or not", () => { + const root = fresh(); + write(root, '.', 'node_modules\n'); + write(root, 'database', '*.sqlite*\n'); + write(root, 'services/core/database', '*.sqlite*\n'); + const found = kinds(root); + expect(found['database/.gitignore']).toBe('framework'); + expect(found['services/core/database/.gitignore']).toBe('framework'); + }); + + it('keeps a hand-written database/.gitignore plain', () => { + const root = fresh(); + write(root, '.', 'node_modules\n'); + write(root, 'database', '*.sqlite*\ndumps/\n'); + write(root, 'other', '*.sqlite*\n'); + const found = kinds(root); + expect(found['database/.gitignore']).toBe('plain'); + expect(found['other/.gitignore']).toBe('plain'); + }); + it('never descends into dependency or build output', () => { const root = fresh(); write(root, '.', 'node_modules\n'); diff --git a/tests/fixtures/agents.in b/tests/fixtures/agents.in new file mode 100644 index 0000000..1851986 --- /dev/null +++ b/tests/fixtures/agents.in @@ -0,0 +1,17 @@ +# region gitignore-sync +# stacks: core, agents +# ───────────────────────────────────────── + +# region core@v1 +.DS_Store +# endregion + +# region agents@v1 +.claude/settings.local.json +.claude/worktrees/ +# endregion + +# endregion + +# Boost MCP config with machine-specific paths +.codex/config.toml diff --git a/tests/fixtures/agents.out b/tests/fixtures/agents.out new file mode 100644 index 0000000..96daba5 --- /dev/null +++ b/tests/fixtures/agents.out @@ -0,0 +1,15 @@ +# region gitignore-sync +# stacks: core, agents +# ───────────────────────────────────────── + +# region core@v1 +.DS_Store +# endregion + +# region agents@v2 +.claude/settings.local.json +.claude/worktrees/ +.codex/config.toml +# endregion + +# endregion diff --git a/tests/fixtures/laravel.in b/tests/fixtures/laravel.in new file mode 100644 index 0000000..525c8b3 --- /dev/null +++ b/tests/fixtures/laravel.in @@ -0,0 +1,33 @@ +# region gitignore-sync +# stacks: core, php, laravel +# ───────────────────────────────────────── + +# endregion + +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.codex +/.cursor/ +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/fonts-manifest.dev.json +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +_ide_helper.php +Homestead.json +Homestead.yaml +Thumbs.db diff --git a/tests/fixtures/laravel.out b/tests/fixtures/laravel.out new file mode 100644 index 0000000..19670c2 --- /dev/null +++ b/tests/fixtures/laravel.out @@ -0,0 +1,46 @@ +# region gitignore-sync +# stacks: core, php, laravel +# ───────────────────────────────────────── + +# region core@v1 +.DS_Store +# endregion + +# region php@v2 +/vendor +/.phpunit.cache +.phpunit.result.cache +/auth.json +.phpactor.json +/.phpstan.cache +# endregion + +# region laravel@v2 +/public/build +/public/hot +/public/storage +/public/fonts-manifest.dev.json +/storage/*.key +/storage/pail +/bootstrap/ssr +_ide_helper.php +_ide_helper_models.php +.phpstorm.meta.php +Homestead.json +Homestead.yaml +# endregion + +# endregion + +*.log +.env +.env.backup +.env.production +/.codex +/.cursor/ +/.idea +/.nova +/.vscode +/.zed +/node_modules +Thumbs.db diff --git a/tests/fixtures/octane.in b/tests/fixtures/octane.in new file mode 100644 index 0000000..7502f27 --- /dev/null +++ b/tests/fixtures/octane.in @@ -0,0 +1,17 @@ +# region gitignore-sync +# stacks: core, php, octane +# ───────────────────────────────────────── + +# endregion + +/vendor +/auth.json +.phpactor.json +.phpunit.result.cache + +# Octane +**/caddy +frankenphp +frankenphp-worker.php +rr +.rr.yaml diff --git a/tests/fixtures/octane.out b/tests/fixtures/octane.out new file mode 100644 index 0000000..579971c --- /dev/null +++ b/tests/fixtures/octane.out @@ -0,0 +1,26 @@ +# region gitignore-sync +# stacks: core, php, octane +# ───────────────────────────────────────── + +# region core@v1 +.DS_Store +# endregion + +# region php@v2 +/vendor +/.phpunit.cache +.phpunit.result.cache +/auth.json +.phpactor.json +/.phpstan.cache +# endregion + +# region octane@v1 +**/caddy +frankenphp +frankenphp-worker.php +rr +.rr.yaml +# endregion + +# endregion diff --git a/tests/templates.lock.json b/tests/templates.lock.json index d8e23b9..94f397f 100644 --- a/tests/templates.lock.json +++ b/tests/templates.lock.json @@ -1,6 +1,11 @@ { "core@v1": [".DS_Store"], "agents@v1": [".claude/settings.local.json", ".claude/worktrees/"], + "agents@v2": [ + ".claude/settings.local.json", + ".claude/worktrees/", + ".codex/config.toml" + ], "git@v1": [ "*.orig", "*.rej", @@ -39,6 +44,14 @@ "crash.*.log" ], "php@v1": ["/vendor", "/.phpunit.cache", "/.phpunit.result.cache"], + "php@v2": [ + "/vendor", + "/.phpunit.cache", + ".phpunit.result.cache", + "/auth.json", + ".phpactor.json", + "/.phpstan.cache" + ], "laravel@v1": [ "/public/build", "/public/hot", @@ -49,6 +62,27 @@ "_ide_helper_models.php", ".phpstorm.meta.php" ], + "laravel@v2": [ + "/public/build", + "/public/hot", + "/public/storage", + "/public/fonts-manifest.dev.json", + "/storage/*.key", + "/storage/pail", + "/bootstrap/ssr", + "_ide_helper.php", + "_ide_helper_models.php", + ".phpstorm.meta.php", + "Homestead.json", + "Homestead.yaml" + ], + "octane@v1": [ + "**/caddy", + "frankenphp", + "frankenphp-worker.php", + "rr", + ".rr.yaml" + ], "turborepo@v1": [".turbo"], "rust@v1": ["/target", "**/*.rs.bk"], "gradle@v1": [".gradle/", ".kotlin/", "build/"],