chore: release v0.20.42
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: pr-reviewer
|
||||
description: Use for deep PR reviews. Reads the diff against Oikos Hard Constraints and returns a structured verdict bucketed into Blocking / Should fix / Nice to have with file:line references. Isolated context keeps the full diff out of the main thread.
|
||||
tools: Read, Grep, Glob, Bash(gh pr *), Bash(gh api *), Bash(git diff *), Bash(git log *)
|
||||
model: opus
|
||||
memory: project
|
||||
color: orange
|
||||
---
|
||||
|
||||
You are reviewing a single PR for Oikos, a self-hosted family planner PWA. The parent thread has delegated the deep read to you so its context stays free.
|
||||
|
||||
## Inputs
|
||||
|
||||
Expect the parent to pass the PR number. Start with `gh pr view <n> --repo ulsklyc/oikos --json title,body,headRefName,baseRefName,files,author,state` and `gh pr diff <n> --repo ulsklyc/oikos`.
|
||||
|
||||
## How to read
|
||||
|
||||
Check every changed file against the Hard Constraints in `CLAUDE.md`:
|
||||
|
||||
- Frontend: no frameworks, no bundlers, no CSS libraries. Lucide is the only exception and must stay self-hosted.
|
||||
- ES modules only (`import`/`export`). No `require`.
|
||||
- No `eval`. No `innerHTML` writes of any kind — including static SVG strings. The PostToolUse hook enforces this but reviewers catch what escapes.
|
||||
- All UI text goes through `t('key')` from `public/i18n.js`. `de` is the reference locale and must contain every new key.
|
||||
- Dates via `formatDate()` / `formatTime()`. No manual formatting.
|
||||
- `server/db.js` migrations array is append-only. Flag any edit to an existing entry as Blocking.
|
||||
- Design values come from `public/styles/tokens.css`. Flag raw hex, px, rem values in CSS.
|
||||
- Every route handler wrapped in try/catch. Response shape `{ data }` on success, `{ error, code }` on failure.
|
||||
- Tests: `test-<module>.js` at project root, registered as `test:<module>` in `package.json`, `--experimental-sqlite` flag in the script.
|
||||
- `CHANGELOG.md` has a new bullet under `## [Unreleased]`.
|
||||
|
||||
## Output format
|
||||
|
||||
Return a single markdown block grouped as:
|
||||
|
||||
```
|
||||
## Blocking
|
||||
- `path/to/file.js:42` — <concrete reason tied to a constraint>
|
||||
|
||||
## Should fix
|
||||
- `path/to/file.js:120` — <reason>
|
||||
|
||||
## Nice to have
|
||||
- ...
|
||||
|
||||
## Verdict
|
||||
<one sentence: request-changes | approve | close>
|
||||
```
|
||||
|
||||
Be specific. Quote the offending line. Cite which constraint is violated. Never list things that are fine.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- English only.
|
||||
- Never post the review yourself. Return the markdown and let the parent invoke `gh pr review`.
|
||||
- If the diff is huge (>1000 lines) and clearly outside scope, return `close` with a short explanation — don't try to nitpick.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: repo-auditor
|
||||
description: Monthly health sweep for the Oikos repo. Surfaces stale issues, dormant branches, untracked TODOs, outdated deps, dead test files, and un-released commits. Runs in a worktree so the main checkout stays untouched.
|
||||
tools: Read, Grep, Glob, Bash(gh *), Bash(git *), Bash(npm outdated *)
|
||||
model: sonnet
|
||||
isolation: worktree
|
||||
memory: project
|
||||
color: purple
|
||||
---
|
||||
|
||||
You are auditing the Oikos repo. Report only — never push, never close, never file PRs. Returning a concise markdown report is the entire job.
|
||||
|
||||
## Checks
|
||||
|
||||
Run all six and report each even if clean.
|
||||
|
||||
1. **Stale issues** — `gh issue list --repo ulsklyc/oikos --state open --json number,title,updatedAt,labels`. Flag any open issue with `updatedAt` older than 90 days or labelled `needs-info` for >14 days.
|
||||
2. **Dormant branches** — `git branch -r --format '%(refname:short) %(committerdate:iso)'`. Flag remote branches with no commits in 30+ days that aren't `main` or a protected branch.
|
||||
3. **Untracked TODOs** — `grep -rn "TODO\|FIXME\|XXX\|HACK" --include='*.js' --include='*.css' --include='*.md' --exclude-dir=node_modules`. Cross-reference with open issues. Flag TODOs that don't link to an issue number.
|
||||
4. **Outdated deps** — `npm outdated --json` inside `oikos/`. Flag anything with a major upgrade available and anything with a known CVE (check `gh api /repos/ulsklyc/oikos/dependabot/alerts` if dependabot is on).
|
||||
5. **Dead test files** — list all `test-*.js` at `oikos/` root, cross-check against `package.json` scripts. Flag any `test-*.js` not wired into a `test:*` script. Flag any `test:*` script pointing at a missing file.
|
||||
6. **Un-released commits** — `git log v<latest-tag>..main --oneline`. If there are commits on `main` beyond the latest tag AND `## [Unreleased]` in `CHANGELOG.md` has bullets, recommend running `/release-prep`.
|
||||
|
||||
## Output format
|
||||
|
||||
```
|
||||
# Repo audit — <ISO date>
|
||||
|
||||
## Stale issues
|
||||
- #<n> <title> — <days> days idle
|
||||
|
||||
## Dormant branches
|
||||
- <branch> — last commit <ISO date>
|
||||
|
||||
## Untracked TODOs
|
||||
- `<file>:<line>` — <excerpt>
|
||||
|
||||
## Outdated dependencies
|
||||
- <pkg> <current> → <latest> (<type>)
|
||||
|
||||
## Dead test files
|
||||
- <filename> — <reason>
|
||||
|
||||
## Release status
|
||||
<one sentence>
|
||||
|
||||
## Suggested actions
|
||||
<3-5 bullets, ordered by impact>
|
||||
```
|
||||
|
||||
## Hard rules
|
||||
|
||||
- Read-only. No `git push`, no `gh issue close`, no file edits.
|
||||
- Stick to facts visible in the repo. Don't speculate about user intent.
|
||||
- If a check returns nothing, write `none` — don't omit the section.
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
|
||||
case "$FILE_PATH" in
|
||||
*.js|*.mjs|*.html) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
[ -f "$FILE_PATH" ] || exit 0
|
||||
|
||||
MATCHES=$(grep -nE '\.innerHTML[[:space:]]*[+]?=[^=]' "$FILE_PATH" || true)
|
||||
|
||||
if [ -n "$MATCHES" ]; then
|
||||
jq -n --arg matches "$MATCHES" --arg path "$FILE_PATH" '{
|
||||
decision: "block",
|
||||
reason: ("innerHTML write detected in " + $path + ":\n" + $matches + "\n\nUse textContent, createElement, createElementNS, replaceChildren, or appendChild. See CLAUDE.md Hard Constraints."),
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PostToolUse",
|
||||
additionalContext: "Revert this change and use DOM APIs. The innerHTML ban has no exceptions — not even for static SVG sprite strings."
|
||||
}
|
||||
}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: db-migrations
|
||||
description: Append-only migration rules for server/db.js
|
||||
paths:
|
||||
- oikos/server/db.js
|
||||
---
|
||||
|
||||
- The `migrations` array in this file is **append-only**. Never modify, reorder, split, merge, or delete an existing entry. Even fixing a typo in an existing migration SQL string is forbidden.
|
||||
- New work goes into a NEW entry appended to the end of the array. Give it the next monotonic `version` number.
|
||||
- Each migration's SQL must be idempotent where possible (`CREATE TABLE IF NOT EXISTS`, `ALTER TABLE ... ADD COLUMN` guarded by a prior check). Runtime catches errors and logs them but the migration should not need the catch to succeed.
|
||||
- The `schema_migrations` table tracks which versions have run on a given DB. Changing or renumbering an existing entry silently skips the change on every upgraded install — which is why the append-only rule exists.
|
||||
- If you need to fix a bad migration, append a new migration that performs the fix. Never rewrite history.
|
||||
- Production DBs may have data created by every prior migration. Test new migrations against a copy of a populated DB when the change touches existing tables.
|
||||
- No data loss migrations without explicit user sign-off in the PR description. Adding a column with `NOT NULL` and no default on a populated table counts as data loss.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: public-pages
|
||||
description: Rules for frontend pages and web components
|
||||
paths:
|
||||
- oikos/public/pages/**/*.js
|
||||
- oikos/public/components/**/*.js
|
||||
---
|
||||
|
||||
- **Pages** (`public/pages/*.js`) export an async `render(container, params)` function. No side effects on import — only the default `render` export may mutate state. Pages are invoked by `public/router.js`.
|
||||
- **Components** (`public/components/*.js`) — one custom element per file, class name ends in `Element`, tag name uses the `oikos-` prefix (`<oikos-task-list>`). Register once per file with `customElements.define`.
|
||||
- **UI text** — every string the user reads goes through `t('key')` from `public/i18n.js`. Never hardcode German, English, or any other language. Add the key to every locale file under `public/locales/`. `de` is the reference locale and must stay complete.
|
||||
- **Dates and times** — `formatDate(value, opts)` and `formatTime(value, opts)` from `public/i18n.js`. Never call `toLocaleString`, `toISOString`, `Intl.DateTimeFormat` directly in pages or components.
|
||||
- **No `innerHTML`** — not for user data, not for static SVG strings, not for templates. Use `document.createElement`, `document.createElementNS` (for SVG), `replaceChildren`, `appendChild`, `textContent`. The PostToolUse hook blocks violations on save.
|
||||
- **API calls** — go through `apiFetch()` from `public/api.js`. It handles CSRF, session expiry, and error envelope. Never call `fetch()` directly from a page.
|
||||
- **Navigation** — use `router.navigate(path)` from `public/router.js`. Never set `location.href` or `location.pathname` directly.
|
||||
- **Styling** — reference tokens from `public/styles/tokens.css`. No raw hex, rgb(), rem, or px in component CSS — use `var(--token-name)`.
|
||||
- **Icons** — use the self-hosted Lucide helper if one exists in the repo, otherwise `createElementNS('http://www.w3.org/2000/svg', ...)`. Never inline an SVG via `innerHTML`.
|
||||
- **Lifecycle** — components must clean up listeners in `disconnectedCallback`. Debounce or throttle heavy handlers via `public/utils/ux.js`.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: server-routes
|
||||
description: Rules for Express route handlers under server/routes/
|
||||
paths:
|
||||
- oikos/server/routes/**/*.js
|
||||
---
|
||||
|
||||
- Every route handler wraps its body in `try/catch`. The catch path logs via the existing logger and returns `{ error: string, code: number }` with an appropriate HTTP status. No unhandled promise rejections.
|
||||
- Success responses return `{ data: ... }`. Never return a raw array or primitive.
|
||||
- Validate input at the boundary: request body, params, query. Reject with 400 on malformed input before touching the DB.
|
||||
- Session + CSRF are enforced by middleware in `server/middleware/`. Don't re-implement auth inside a handler. Don't skip CSRF on mutating routes.
|
||||
- Dates: accept and emit ISO 8601 strings. Store as TEXT in SQLite. Convert to `Date` only at the edges.
|
||||
- `better-sqlite3` is synchronous. Never `await` a `db.prepare()` / `.run()` / `.get()` / `.all()` call. If you find an `await` in front of a db call, it's a bug.
|
||||
- No `innerHTML` anywhere (server-side string building into HTML is fine as long as it doesn't end up in a frontend `innerHTML`; prefer JSON).
|
||||
- Route files export a factory `(db) => router` pattern consistent with existing files in this directory. Read a neighbour file before adding a new one.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: tests
|
||||
description: Rules for integration test files at project root
|
||||
paths:
|
||||
- oikos/test-*.js
|
||||
---
|
||||
|
||||
- File names match the pattern `test-<module>.js` and live at the project root alongside `package.json`.
|
||||
- Every test file has a matching npm script `test:<module>` in `package.json`. The `test` aggregate script runs them all. When you add a new `test-*.js`, add it to both places.
|
||||
- Runner is `node --test` (built-in, Node ≥22). Scripts that hit the DB pass `--experimental-sqlite`.
|
||||
- Database in tests is an in-memory `better-sqlite3` instance built via the same `buildSchema` path used by production. Never mock `better-sqlite3`. Never stub out migrations. If a test needs seed data, insert it through normal route handlers or the same queries prod uses.
|
||||
- Assertions via `node:assert/strict`. Imports use `import` syntax. No `require`.
|
||||
- Tests must be deterministic. No network calls, no real filesystem writes outside `os.tmpdir()`, no `Date.now()` without faking.
|
||||
- A failing test is a real failure. Don't wrap in `t.skip` or comment out to make CI green.
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-innerhtml.sh",
|
||||
"timeout": 10,
|
||||
"statusMessage": "Checking for innerHTML writes..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: fix-issue
|
||||
description: Take a GitHub issue from triage through a PR on a fix/<id> branch
|
||||
disable-model-invocation: true
|
||||
user-invocable: true
|
||||
argument-hint: "<issue-number>"
|
||||
allowed-tools:
|
||||
- Bash(gh issue *)
|
||||
- Bash(gh pr *)
|
||||
- Bash(git checkout *)
|
||||
- Bash(git add *)
|
||||
- Bash(git commit *)
|
||||
- Bash(git push *)
|
||||
- Bash(npm test *)
|
||||
- Bash(npm run test:*)
|
||||
- Read
|
||||
- Edit
|
||||
- Write
|
||||
- Grep
|
||||
- Glob
|
||||
---
|
||||
|
||||
Run from inside `oikos/`. `$1` is the issue number.
|
||||
|
||||
1. **Load context** — `gh issue view $1 --repo ulsklyc/oikos --comments`. Read linked PRs, commits, related issues. Stop and report if the issue is already closed or duplicated.
|
||||
2. **Triage before coding** — classify: bug, enhancement, question, invalid. If reproduction steps are missing or scope is unclear, post a question via `gh issue comment $1 --body "..."` and stop. Do not guess intent.
|
||||
3. **Branch + implement** — `git checkout -b fix/$1`. Make the minimal change that solves the reported problem. Respect the Hard Constraints from CLAUDE.md. Add or extend a `test-<module>.js` suite that would have caught the bug. Run `npm test` — all suites must pass before moving on.
|
||||
4. **Ship** — `git add` only the files actually changed by this fix, commit with a Conventional Commit subject (`fix: <short summary> (#$1)`), push `git push -u origin fix/$1`, then `gh pr create --fill --base main` with a body that closes the issue (`Closes #$1`) and summarises root cause + fix.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never work on `main` directly. If you're accidentally on `main`, stop and switch.
|
||||
- Never bypass the PostToolUse innerHTML hook. If it fires, fix the DOM code — don't disable the hook.
|
||||
- Never `git add -A` or `git add .`. Stage files by name.
|
||||
- If the fix needs a DB change, it goes into a NEW entry at the end of the `migrations` array in `server/db.js`. Never edit existing entries.
|
||||
- Do not run the `release-prep` skill here — releases happen after the PR is merged, on `main`.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: issue-triage
|
||||
description: Classify one or all open issues, apply labels, request missing info
|
||||
disable-model-invocation: true
|
||||
user-invocable: true
|
||||
argument-hint: "[<issue-number> | all]"
|
||||
allowed-tools:
|
||||
- Bash(gh issue *)
|
||||
- Bash(gh label *)
|
||||
- Read
|
||||
- Grep
|
||||
---
|
||||
|
||||
Run from inside `oikos/`. `$1` is a specific issue number or the literal `all` (default: `all` → every open issue without labels).
|
||||
|
||||
1. **Load** — for a single issue: `gh issue view $1 --repo ulsklyc/oikos --comments`. For `all`: `gh issue list --repo ulsklyc/oikos --state open --label '' --json number,title,body,author,createdAt`.
|
||||
2. **Classify** — for each issue, pick ONE primary class and apply labels via `gh issue edit <n> --repo ulsklyc/oikos --add-label "<labels>"`:
|
||||
- `bug` — reproduction + expected vs. actual behaviour present and plausible against current code
|
||||
- `enhancement` — new feature or UX improvement, no existing regression
|
||||
- `question` — user needs help using Oikos, not a code change
|
||||
- `invalid` — spam, duplicate, or out of scope (self-hosted family planner)
|
||||
Add area labels where obvious: `calendar`, `tasks`, `shopping`, `meals`, `budget`, `notes`, `contacts`, `reminders`, `i18n`, `pwa`, `security`, `docs`.
|
||||
3. **Request missing info** — if reproduction steps, expected behaviour, Oikos version, or browser are missing on a `bug`, post a single comment asking for exactly what's missing. Apply label `needs-info`.
|
||||
4. **Close spam/duplicates** — `gh issue close <n> --repo ulsklyc/oikos --reason "not planned" --comment "<english explanation>"`. Always leave a reason.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never assign issues to other humans.
|
||||
- Never post more than one triage comment per issue per run.
|
||||
- All comments in English.
|
||||
- If unsure between two classes, default to `question` and ask for clarification — don't guess.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: pr-review
|
||||
description: Review a PR against the Oikos Hard Constraints and decide close/request-changes/merge
|
||||
disable-model-invocation: true
|
||||
user-invocable: true
|
||||
argument-hint: "<pr-number>"
|
||||
allowed-tools:
|
||||
- Bash(gh pr *)
|
||||
- Bash(gh api *)
|
||||
- Bash(git *)
|
||||
- Bash(npm test *)
|
||||
- Bash(npm run test:*)
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
---
|
||||
|
||||
Run from inside `oikos/`. `$1` is the PR number.
|
||||
|
||||
1. **Fetch** — `gh pr view $1 --repo ulsklyc/oikos`, `gh pr diff $1 --repo ulsklyc/oikos`, `gh pr checks $1 --repo ulsklyc/oikos`. Note failing checks. Delegate the deep read to the `pr-reviewer` subagent (`Agent({ subagent_type: "pr-reviewer", ... })`) so main-thread context stays free.
|
||||
2. **Constraint check** — walk the diff against CLAUDE.md Hard Constraints:
|
||||
- No frontend frameworks / bundlers / CSS libraries added
|
||||
- No `require`, only `import`/`export`
|
||||
- No `eval`, no `innerHTML`
|
||||
- All UI text via `t('key')`; `de` locale updated
|
||||
- Migrations append-only (`server/db.js`)
|
||||
- Design values from `public/styles/tokens.css`
|
||||
- Route handlers wrapped in try/catch
|
||||
- API shape `{ data }` / `{ error, code }`
|
||||
Also check: PR has tests touching the relevant `test-<module>.js`, CHANGELOG entry under `## [Unreleased]`, commit subjects are Conventional Commits.
|
||||
3. **Decide**
|
||||
- **Blocking issue found** → `gh pr review $1 --request-changes --body "<english, grouped by file:line>"` and stop.
|
||||
- **Not a fit** → explain politely and `gh pr close $1 --comment "..."`.
|
||||
- **Clean** → `gh pr review $1 --approve --body "LGTM"` then `gh pr merge $1 --squash --delete-branch`. After merge, fetch `main` locally and consider running `/release-prep`.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Comments and review bodies: always English.
|
||||
- Never merge with failing required checks. Never force-merge.
|
||||
- Never close a PR without a reason comment — silent closes burn contributor trust.
|
||||
- Never push directly to the PR branch. If a small fix is warranted, ask the contributor.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: release-prep
|
||||
description: Bump patch version, update CHANGELOG, commit, tag, push and create GitHub release
|
||||
disable-model-invocation: true
|
||||
user-invocable: true
|
||||
argument-hint: "[patch|minor|major]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Edit
|
||||
- Bash(npm version *)
|
||||
- Bash(git add *)
|
||||
- Bash(git commit *)
|
||||
- Bash(git tag *)
|
||||
- Bash(git push *)
|
||||
- Bash(gh release create *)
|
||||
- Bash(git status *)
|
||||
- Bash(git log *)
|
||||
- Bash(git diff *)
|
||||
---
|
||||
|
||||
Run from inside `oikos/`. Argument selects the semver bump (default: patch).
|
||||
|
||||
1. **Pre-flight** — run `git status` and `git diff --staged`. Abort if the tree is dirty with unrelated files. Summarise the pending changes.
|
||||
2. **CHANGELOG** — open `oikos/CHANGELOG.md`. Insert a new `## [X.Y.Z] - YYYY-MM-DD` block immediately below `## [Unreleased]`. Use today's date from the `currentDate` context. Only use Keep-a-Changelog sections: `### Added`, `### Changed`, `### Fixed`, `### Removed`, `### Security`. One bullet per user-facing change in English. Never invent entries that aren't in the diff.
|
||||
3. **Version bump** — run `npm version ${1:-patch} --no-git-tag-version`. Read the new version from `oikos/package.json`.
|
||||
4. **Stage** — `git add oikos/CHANGELOG.md oikos/package.json oikos/package-lock.json` plus any other files from the task. Never use `git add -A` or `git add .`.
|
||||
5. **Commit** — `git commit -m "chore: release vX.Y.Z"`. Do not pass `--no-verify`. If a hook fails, fix the cause and create a new commit.
|
||||
6. **Tag** — `git tag vX.Y.Z`.
|
||||
7. **Push** — `git push && git push --tags`.
|
||||
8. **GitHub Release** — `gh release create vX.Y.Z --repo ulsklyc/oikos --title "vX.Y.Z" --notes "<CHANGELOG block body>"`. Paste the new CHANGELOG section verbatim as notes.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never `--force`, never `--no-verify`, never `--no-gpg-sign`.
|
||||
- The `GH_TOKEN` must come from the shell environment, never from a hard-coded literal in this file or in commit messages.
|
||||
- If `gh release create` fails with a 401/403, stop and report — do not paste a token inline.
|
||||
- If `git status` shows uncommitted work unrelated to the release, stop and ask the user.
|
||||
Reference in New Issue
Block a user