docs(01): create phase 1 dev environment plans

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Mühl 2026-04-08 11:05:45 +02:00
parent 29b54a340f
commit 5bdb8bf3f5
No known key found for this signature in database
GPG key ID: 925AC7D69955293F
3 changed files with 430 additions and 3 deletions

View file

@ -29,11 +29,11 @@ Decimal phases appear between their surrounding integers in numeric order.
2. A local Kimai instance starts and is accessible in the browser
3. The Kimai instance contains seeded time entry data spanning multiple months
4. A plugin directory is symlinked/mounted so code changes are reflected without reinstallation
**Plans**: TBD
**Plans:** 2 plans
Plans:
- [ ] 01-01: TBD
- [ ] 01-02: TBD
- [ ] 01-01-PLAN.md — Nix flake with devshell, process-compose config, and bootstrap script
- [ ] 01-02-PLAN.md — Run setup, create plugin scaffold, verify end-to-end
### Phase 2: Plugin Scaffold + Data Layer
**Goal**: Plugin is recognized by Kimai, shows a widget on the dashboard, and serves aggregated daily time data via API

View file

@ -0,0 +1,241 @@
---
phase: 01-dev-environment
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- flake.nix
- dev/process-compose.yaml
- dev/setup.sh
- .gitignore
- .envrc
autonomous: true
requirements:
- DEV-01
must_haves:
truths:
- "nix develop drops into a shell with PHP 8.2+, Composer 2.x, Node 22.x, MariaDB, symfony-cli, and process-compose"
- "process-compose up starts MariaDB on port 3307 with a local data directory and Symfony dev server"
- "setup.sh clones Kimai, installs deps, creates DB, loads fixtures, and symlinks plugin"
artifacts:
- path: "flake.nix"
provides: "Nix devshell with all required packages and PHP extensions"
contains: "php82"
- path: "dev/process-compose.yaml"
provides: "MariaDB + Symfony dev server process definitions"
contains: "mariadb"
- path: "dev/setup.sh"
provides: "One-time Kimai bootstrap script"
contains: "kimai:reset:dev"
- path: ".gitignore"
provides: "Ignore dev artifacts (kimai clone, mariadb data, node_modules)"
contains: "dev/kimai"
key_links:
- from: "dev/process-compose.yaml"
to: "dev/.mariadb-data"
via: "mysqld --datadir"
pattern: "mariadb-data"
- from: "dev/setup.sh"
to: "dev/kimai"
via: "git clone kimai"
pattern: "git clone.*kimai"
---
<objective>
Create the Nix flake, process-compose config, and bootstrap script for the Kimai dev environment.
Purpose: Provide a reproducible, one-command dev setup that gives the developer PHP 8.2+, MariaDB, Composer, Node, and all tooling needed for Kimai plugin development.
Output: flake.nix, dev/process-compose.yaml, dev/setup.sh, .gitignore
</objective>
<execution_context>
@/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/workflows/execute-plan.md
@/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phase-1/RESEARCH.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Create Nix flake with devshell</name>
<files>flake.nix, .envrc, .gitignore</files>
<action>
Create `flake.nix` with a devshell providing:
**Packages:**
- PHP 8.2 with extensions: gd, intl, mbstring, pdo, pdo_mysql, xml, xsl, zip, tokenizer (use `pkgs.php82.buildEnv { extensions = { enabled, all }: enabled ++ (with all; [ xsl pdo_mysql ]); }`)
- `pkgs.php82Packages.composer` for Composer
- `pkgs.nodejs` (22.x) for JS tooling
- `pkgs.mariadb` (11.4.x) for database
- `pkgs.symfony-cli` for Symfony dev server
- `pkgs.process-compose` for multi-service management
**Flake structure:**
- Use `flake-utils.lib.eachDefaultSystem` for portability
- Input: `nixpkgs` (use `github:NixOS/nixpkgs/nixpkgs-unstable`)
- Single devShell output
- Shell hook: print a short message with available commands (setup, start)
**Also create:**
- `.envrc` with `use flake` for direnv integration
- `.gitignore` with entries for:
- `dev/kimai/` (cloned Kimai instance)
- `dev/.mariadb-data/` (MariaDB data directory)
- `dev/.mariadb.sock` (MariaDB socket)
- `dev/.mariadb.pid` (MariaDB PID file)
- `node_modules/`
- `vendor/`
- `.direnv/`
- `result` (nix build output)
- `*.js.map` (source maps)
- `dist/` (built JS output)
</action>
<verify>
<automated>cd /home/toph/code/toph/kimai-heatmap && nix develop --command bash -c "php --version && composer --version && node --version && mysqld --version && symfony version && process-compose version" 2>&1 | head -20</automated>
</verify>
<done>nix develop provides PHP 8.2+, Composer, Node 22.x, MariaDB, symfony-cli, and process-compose. All commands resolve.</done>
</task>
<task type="auto">
<name>Task 2: Create process-compose config and setup script</name>
<files>dev/process-compose.yaml, dev/setup.sh</files>
<action>
**Create `dev/process-compose.yaml`:**
```yaml
version: "0.5"
processes:
mariadb:
command: |
mysqld --datadir=$PWD/dev/.mariadb-data \
--socket=$PWD/dev/.mariadb.sock \
--port=3307 \
--skip-grant-tables \
--skip-networking=false \
--bind-address=127.0.0.1
readiness_probe:
exec:
command: mysqladmin --socket=$PWD/dev/.mariadb.sock ping
initial_delay_seconds: 2
period_seconds: 1
shutdown:
command: mysqladmin --socket=$PWD/dev/.mariadb.sock shutdown
kimai:
command: symfony server:start --no-tls --dir=$PWD/dev/kimai --port=8010
depends_on:
mariadb:
condition: process_healthy
```
Use port 8010 for Kimai to avoid conflicts. Use `$PWD` for paths so process-compose works from project root.
**Create `dev/setup.sh`** (make executable with `chmod +x`):
The script should:
1. Check if already set up (if `dev/kimai` exists, skip clone)
2. Initialize MariaDB data directory if not present:
```bash
mysql_install_db --datadir=./dev/.mariadb-data --auth-root-authentication-method=normal
```
3. Start MariaDB temporarily for setup (background, wait for ready):
```bash
mysqld --datadir=./dev/.mariadb-data --socket=./dev/.mariadb.sock --port=3307 --skip-grant-tables --bind-address=127.0.0.1 &
MARIADB_PID=$!
# Wait for MariaDB to be ready
for i in $(seq 1 30); do mysqladmin --socket=./dev/.mariadb.sock ping 2>/dev/null && break; sleep 1; done
```
4. Create the `kimai` database:
```bash
mysql --socket=./dev/.mariadb.sock -e "CREATE DATABASE IF NOT EXISTS kimai;"
```
5. Clone Kimai 2.52.0:
```bash
git clone -b 2.52.0 --depth 1 https://github.com/kimai/kimai.git dev/kimai
```
6. Configure Kimai `.env.local` (NOT .env -- .env.local overrides):
```bash
cat > dev/kimai/.env.local << 'ENVEOF'
DATABASE_URL=mysql://root@127.0.0.1:3307/kimai?charset=utf8mb4&serverVersion=11.4.8-MariaDB
APP_SECRET=change-me-dev-only-not-production
APP_ENV=dev
ENVEOF
```
7. Run Composer install in Kimai:
```bash
cd dev/kimai && composer install --no-interaction
```
8. Run Kimai install (creates schema):
```bash
bin/console kimai:install -n
```
9. Load dev fixtures (seeds test data):
```bash
bin/console kimai:reset:dev -n
```
10. Symlink plugin directory:
```bash
mkdir -p var/plugins
ln -sf "$(cd ../.. && pwd)" var/plugins/KimaiHeatmapBundle
```
11. Clear Kimai cache:
```bash
bin/console cache:clear
```
12. Stop temporary MariaDB:
```bash
kill $MARIADB_PID 2>/dev/null
wait $MARIADB_PID 2>/dev/null
```
13. Print success message with instructions to run `process-compose -f dev/process-compose.yaml up`
**Important details:**
- Use `set -euo pipefail` at the top
- Use `127.0.0.1` NOT `localhost` in DATABASE_URL (localhost triggers Unix socket mode on Linux)
- The symlink target name MUST be `KimaiHeatmapBundle` (matches bundle class name)
- Use `--skip-grant-tables` for MariaDB since this is local dev only
- The script must be idempotent: skip steps that are already done (check for existing directories)
</action>
<verify>
<automated>cd /home/toph/code/toph/kimai-heatmap && test -x dev/setup.sh && test -f dev/process-compose.yaml && head -5 dev/process-compose.yaml | grep -q "version" && echo "OK"</automated>
</verify>
<done>dev/process-compose.yaml defines MariaDB + Kimai processes with health checks. dev/setup.sh is executable and contains the full bootstrap sequence (clone, install, seed, symlink).</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Local dev only | No external network exposure; MariaDB binds to 127.0.0.1, Kimai on localhost:8010 |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-01 | Information Disclosure | MariaDB --skip-grant-tables | accept | Local dev only, bound to 127.0.0.1, no production data. Acceptable for dev environment. |
| T-01-02 | Information Disclosure | APP_SECRET in .env.local | accept | Dev-only value, .env.local is gitignored inside dev/kimai/ (Kimai's own .gitignore), not committed to plugin repo |
</threat_model>
<verification>
1. `nix develop` provides all required tools (PHP 8.2+, Composer, Node, MariaDB, symfony-cli, process-compose)
2. `dev/setup.sh` exists, is executable, and contains the complete bootstrap sequence
3. `dev/process-compose.yaml` defines MariaDB and Kimai processes with dependency ordering
4. `.gitignore` excludes dev artifacts
</verification>
<success_criteria>
Running `nix develop` enters a shell with all dev tools. The setup script and process-compose config are ready to be executed in Plan 02.
</success_criteria>
<output>
After completion, create `.planning/phases/01-dev-environment/01-01-SUMMARY.md`
</output>

View file

@ -0,0 +1,186 @@
---
phase: 01-dev-environment
plan: 02
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- KimaiHeatmapBundle.php
- composer.json
autonomous: false
requirements:
- DEV-02
- DEV-03
must_haves:
truths:
- "Running dev/setup.sh bootstraps a working Kimai instance with seeded timesheet data"
- "The Kimai instance has timesheet entries spanning multiple months"
- "Kimai recognizes KimaiHeatmapBundle as a loaded plugin"
- "The Kimai dashboard is accessible in the browser at localhost:8010"
artifacts:
- path: "KimaiHeatmapBundle.php"
provides: "Symfony bundle class implementing Kimai PluginInterface"
contains: "implements PluginInterface"
- path: "composer.json"
provides: "Plugin metadata with kimai-plugin type and autoload config"
contains: "kimai-plugin"
key_links:
- from: "dev/kimai/var/plugins/KimaiHeatmapBundle"
to: "project root"
via: "symlink"
pattern: "KimaiHeatmapBundle"
- from: "KimaiHeatmapBundle.php"
to: "Kimai plugin loader"
via: "PluginInterface implementation"
pattern: "PluginInterface"
---
<objective>
Run the setup script to bootstrap Kimai, create the minimal plugin scaffold, and verify the full dev environment works end-to-end.
Purpose: Validate that the dev environment from Plan 01 actually produces a working Kimai instance with test data and a recognized plugin.
Output: Working Kimai instance, minimal plugin bundle class and composer.json, human-verified browser access.
</objective>
<execution_context>
@/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/workflows/execute-plan.md
@/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phase-1/RESEARCH.md
@.planning/phases/01-dev-environment/01-01-SUMMARY.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Create plugin scaffold and run setup</name>
<files>KimaiHeatmapBundle.php, composer.json</files>
<action>
**Step 1: Create the minimal plugin bundle class** at project root `KimaiHeatmapBundle.php`:
```php
<?php
namespace KimaiPlugin\KimaiHeatmapBundle;
use App\Plugin\PluginInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class KimaiHeatmapBundle extends Bundle implements PluginInterface
{
}
```
**Step 2: Create `composer.json`** at project root:
```json
{
"name": "kimai-plugin/heatmap-bundle",
"type": "kimai-plugin",
"description": "GitHub-style activity heatmap dashboard widget for Kimai",
"license": "MIT",
"autoload": {
"psr-4": {
"KimaiPlugin\\KimaiHeatmapBundle\\": ""
}
},
"extra": {
"kimai": {
"require": 25200,
"name": "Activity Heatmap"
}
}
}
```
**Step 3: Run the setup script** (from within `nix develop`):
```bash
cd /home/toph/code/toph/kimai-heatmap
bash dev/setup.sh
```
This will clone Kimai, install deps, init the database, load fixtures, symlink the plugin, and clear cache. If any step fails, debug and fix the setup script or flake.nix as needed.
**Troubleshooting guidance:**
- If MariaDB fails to start: check that `dev/.mariadb-data` was initialized. Run `mysql_install_db --datadir=./dev/.mariadb-data --auth-root-authentication-method=normal` manually.
- If Composer fails with extension errors: check `php -m` output and adjust the `php.buildEnv` extensions in flake.nix.
- If `kimai:install` fails with DB connection error: verify DATABASE_URL uses `127.0.0.1` not `localhost`, and MariaDB is running on port 3307.
- If plugin symlink issues: verify the link is `dev/kimai/var/plugins/KimaiHeatmapBundle -> /home/toph/code/toph/kimai-heatmap` (absolute path, directory name matches bundle class).
**Step 4: Verify seeded data exists:**
```bash
cd dev/kimai && bin/console doctrine:query:sql "SELECT COUNT(*) as cnt FROM kimai2_timesheet"
```
Should return a count of several hundred entries minimum.
**Step 5: Verify plugin is loaded:**
```bash
cd dev/kimai && bin/console kimai:plugins
```
Should list "Activity Heatmap" (the plugin name from composer.json extra.kimai.name).
</action>
<verify>
<automated>cd /home/toph/code/toph/kimai-heatmap/dev/kimai && bin/console kimai:plugins 2>&1 | grep -i "heatmap" && bin/console doctrine:query:sql "SELECT COUNT(*) as cnt FROM kimai2_timesheet" 2>&1</automated>
</verify>
<done>Kimai instance running with seeded data. Plugin listed in kimai:plugins output. Database contains timesheet entries.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Verify dev environment in browser</name>
<action>Human verifies the complete dev environment works end-to-end by accessing Kimai in the browser and confirming the plugin is loaded.</action>
<what-built>Complete dev environment: Nix devshell with local Kimai instance, seeded test data, and plugin recognized by Kimai.</what-built>
<how-to-verify>
1. From the project root, run: `process-compose -f dev/process-compose.yaml up`
2. Wait for both MariaDB and Kimai to show as healthy/running
3. Open browser to http://127.0.0.1:8010
4. Log in with username `susan_super` password `password` (super admin from fixtures)
5. Verify the dashboard loads (no errors)
6. Navigate to System > Plugins -- verify "Activity Heatmap" appears in the list
7. Stop process-compose with Ctrl+C
</how-to-verify>
<verify>Human confirms Kimai dashboard loads and plugin is listed</verify>
<done>Dashboard accessible, plugin visible in admin, seeded data present</done>
<resume-signal>Type "approved" or describe issues</resume-signal>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Plugin -> Kimai | Plugin runs inside Kimai's PHP process with full access to Kimai internals |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-03 | Elevation of Privilege | PluginInterface | accept | Plugin runs with same privileges as Kimai itself. This is by design in Kimai's plugin architecture. Personal use, single user. |
| T-01-04 | Information Disclosure | Fixture credentials (password: "password") | accept | Dev environment only. Fixtures create test users with known passwords. Not exposed beyond localhost. |
</threat_model>
<verification>
1. `bin/console kimai:plugins` lists "Activity Heatmap"
2. `SELECT COUNT(*) FROM kimai2_timesheet` returns > 100 entries
3. Kimai dashboard loads in browser at http://127.0.0.1:8010
4. Plugin appears in System > Plugins admin page
</verification>
<success_criteria>
Developer can run `nix develop`, execute `dev/setup.sh` once, then `process-compose -f dev/process-compose.yaml up` to get a working Kimai instance with seeded data and the heatmap plugin recognized. Dashboard accessible in browser, plugin listed in admin.
</success_criteria>
<output>
After completion, create `.planning/phases/01-dev-environment/01-02-SUMMARY.md`
</output>