diff --git a/.claude/hooks/stop-quality-gate.mjs b/.claude/hooks/stop-quality-gate.mjs new file mode 100644 index 0000000..7475dcd --- /dev/null +++ b/.claude/hooks/stop-quality-gate.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node +// Stop hook: warns about uncommitted changes, wrong branch, and version mismatches + +import { execSync } from 'child_process'; + +const git = (cmd) => execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }); + +let warnings = ''; + +try { + // Check if on main branch + const branch = git('git rev-parse --abbrev-ref HEAD').trim(); + if (branch === 'main' || branch === 'master') { + warnings += `On ${branch} branch! Create a feature branch before committing. `; + } + + // Check for uncommitted changes + let hasChanges = false; + try { + git('git diff --quiet'); + } catch { + hasChanges = true; + } + try { + git('git diff --cached --quiet'); + } catch { + hasChanges = true; + } + if (hasChanges) { + warnings += 'Uncommitted changes detected. '; + } + + // Check for version mismatch between core and web + const coreVersion = git('node -p "require(\'./packages/core/package.json\').version"').trim(); + const webVersion = git('node -p "require(\'./packages/web/package.json\').version"').trim(); + if (coreVersion !== webVersion) { + warnings += `Version mismatch: core=${coreVersion} web=${webVersion}. `; + } +} catch { + // Best effort +} + +if (warnings) { + console.log( + JSON.stringify({ + systemMessage: `Quality gate: ${warnings}` + }) + ); +} + +process.exit(0); diff --git a/.claude/settings.json b/.claude/settings.json index 0186b4a..3f0f706 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,64 @@ { + "permissions": { + "allow": [ + "Bash(pnpm *)", + "Bash(pnpm)", + "Bash(git status*)", + "Bash(git diff*)", + "Bash(git log*)", + "Bash(git branch*)", + "Bash(git checkout*)", + "Bash(git switch*)", + "Bash(git add *)", + "Bash(git commit *)", + "Bash(git push *)", + "Bash(git rebase*)", + "Bash(git stash*)", + "Bash(git rev-parse*)", + "Bash(git show *)", + "Bash(git fetch*)", + "Bash(git ls-files*)", + "Bash(git merge *)", + "Bash(git tag *)", + "Bash(git remote *)", + "Bash(gh pr *)", + "Bash(gh issue *)", + "Bash(gh api *)", + "Bash(gh run *)", + "Bash(ls *)", + "Bash(mkdir *)", + "Bash(head *)", + "Bash(tail *)", + "Bash(wc *)", + "Bash(bash scripts/deploy.sh*)", + "Bash(docker buildx *)" + ], + "deny": [ + "Bash(git push --force *main*)", + "Bash(git push --force *master*)", + "Bash(git push origin main*)", + "Bash(git push origin master*)", + "Bash(git checkout main && git merge*)", + "Bash(git switch main && git merge*)", + "Bash(rm -rf /)", + "Bash(rm -rf ~*)", + "Bash(rm -rf ..*)", + "Bash(curl * | sh*)", + "Bash(curl * | bash*)", + "Bash(wget * | sh*)", + "Bash(npm install*)", + "Bash(npm i *)", + "Bash(npm ci*)", + "Bash(npm add *)", + "Bash(yarn *)", + "Write(.env)", + "Write(.env.local)", + "Edit(.env)", + "Edit(.env.local)", + "Write(pnpm-lock.yaml)", + "Edit(pnpm-lock.yaml)" + ] + }, "hooks": { "PreToolUse": [ { @@ -23,6 +83,17 @@ } ] } + ], + "Stop": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop-quality-gate.mjs\"" + } + ] + } ] } } diff --git a/.claude/settings.local.json.example b/.claude/settings.local.json.example new file mode 100644 index 0000000..e101d98 --- /dev/null +++ b/.claude/settings.local.json.example @@ -0,0 +1,8 @@ +{ + "// README": "Copy to .claude/settings.local.json and customize. This file is gitignored.", + "permissions": { + "allow": [ + "Bash(cp /path/to/your/web-app-template/* /path/to/netrock-cli/templates/*)" + ] + } +} diff --git a/.claude/skills/add-feature/SKILL.md b/.claude/skills/add-feature/SKILL.md new file mode 100644 index 0000000..581dcbe --- /dev/null +++ b/.claude/skills/add-feature/SKILL.md @@ -0,0 +1,46 @@ +--- +description: Scaffold a new generator feature (definition, manifest, templates) +user-invocable: true +argument-hint: ' ' +--- + +# /add-feature + +Scaffolds a new feature for the netrock generator following the checklist in CLAUDE.md. + +## Steps + +1. **Define the feature** in `packages/core/src/features/definitions.ts`: + - Add to the `featureDefinitions` array + - Set id, name, description, details, dependencies, group + - Set `required: false`, `defaultEnabled: false` unless told otherwise + +2. **Create the manifest** at `packages/core/src/manifests/{feature-id}.ts`: + - Follow the pattern from existing manifests (e.g., `email.ts`, `jobs.ts`) + - Export a `register{Feature}Manifest()` function + - List all template files with correct `templated` flags + +3. **Register the manifest** in `packages/core/src/manifests/index.ts`: + - Import and call the register function in `registerAllManifests()` + +4. **Add template files** to `templates/`: + - Use `MyProject` as the namespace placeholder + - Add `@feature` markers in existing cross-cutting template files + +5. **Update snapshots**: `pnpm --filter @netrock/core exec vitest run --update` + +6. **Verify**: `pnpm test && pnpm build` + +7. **Add CI matrix entry** in `.github/workflows/ci.yml` if testable independently + +## Checklist + +After implementation, verify: + +- Feature in `featureDefinitions` with correct deps +- Manifest lists all files with correct `templated` flags +- Manifest registered in index.ts +- Template files exist in `templates/` +- No residual `@feature` markers in non-templated files +- `pnpm test` passes (snapshots updated) +- `pnpm build` passes diff --git a/.claude/skills/bump-version/SKILL.md b/.claude/skills/bump-version/SKILL.md new file mode 100644 index 0000000..015433b --- /dev/null +++ b/.claude/skills/bump-version/SKILL.md @@ -0,0 +1,29 @@ +--- +description: Bump version in both packages and update CHANGELOG.md +user-invocable: true +argument-hint: '[major|minor|patch] [description]' +--- + +# /bump-version + +Bumps the version in both `packages/core/package.json` and `packages/web/package.json` (they must always match), and adds an entry to CHANGELOG.md. + +## Steps + +1. Read the current version from `packages/core/package.json` +2. Determine the bump type from the argument (default: patch). Follow the versioning table in CLAUDE.md. +3. Calculate the new version +4. Update `version` in both: + - `packages/core/package.json` + - `packages/web/package.json` +5. Add a new section at the top of CHANGELOG.md (after the header) with today's date + - Use the format: `## [X.Y.Z] - YYYY-MM-DD` with Added/Changed/Fixed subsections + - If the user provided a description, use it. Otherwise, review `git log` since the last tag/version and summarize. + - Focus on what users experience (see CLAUDE.md "What goes in the changelog") +6. Commit: `chore: bump to {version}` + +## Rules + +- Both package.json versions MUST match after the bump +- Never bump for internal refactoring, snapshot updates, CI changes, or CLAUDE.md edits +- Changelog entries focus on generated project changes and web UI changes diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md new file mode 100644 index 0000000..c605384 --- /dev/null +++ b/.claude/skills/create-pr/SKILL.md @@ -0,0 +1,37 @@ +--- +description: Create a pull request for the current branch +user-invocable: true +argument-hint: '[base branch]' +--- + +# /create-pr + +Creates a pull request for the current branch. + +## Steps + +1. Check `git status` - commit any uncommitted changes first +2. Verify NOT on main +3. Review all commits on this branch: `git log main..HEAD --oneline` +4. Push: `git push -u origin $(git branch --show-current)` +5. Create PR with `gh pr create`: + - Title: Conventional Commit format, under 70 chars + - Base: argument if provided, otherwise `main` + - Body format: + +``` +## Summary +- Bullet points summarizing the changes + +## Test plan +- [ ] `pnpm test` passes +- [ ] `pnpm build` passes +- [ ] [Additional checks relevant to the changes] +``` + +6. Report the PR URL + +## Rules + +- Squash-and-merge only +- No Co-Authored-By lines diff --git a/.claude/skills/deploy/SKILL.md b/.claude/skills/deploy/SKILL.md new file mode 100644 index 0000000..2a8a4f8 --- /dev/null +++ b/.claude/skills/deploy/SKILL.md @@ -0,0 +1,31 @@ +--- +description: Build and deploy the web UI to Docker Hub +user-invocable: true +--- + +# /deploy + +Builds and pushes the Docker image, then provides the VPS update command. + +## Steps + +1. Verify on `main` branch (warn if not) +2. Verify no uncommitted changes +3. Verify tests pass: `pnpm test && pnpm build` +4. Run the deploy script: + + ```bash + bash scripts/deploy.sh + ``` + + This builds `fpindej/netrock-web:{version}` + `:latest` and pushes to Docker Hub. + +5. Report the deployed version and VPS command: + ``` + cd /var/apps/netrock-cli && docker compose pull && docker compose up -d + ``` + +## Rules + +- Never deploy with failing tests +- If there are generator-facing changes, version must be bumped first (use `/bump-version`) diff --git a/.claude/skills/sync-templates/SKILL.md b/.claude/skills/sync-templates/SKILL.md new file mode 100644 index 0000000..3039d01 --- /dev/null +++ b/.claude/skills/sync-templates/SKILL.md @@ -0,0 +1,48 @@ +--- +description: Sync template files from the source netrock repo +user-invocable: true +argument-hint: '[PR number or description]' +--- + +# /sync-templates + +Syncs template files from the source project (fpindej/netrock) into this generator's `templates/` directory. + +The source repo is at `~/source/web-app-template`. + +## Steps + +1. **Identify what to sync**: If a PR number is given, fetch the PR diff with `gh pr view` / `gh pr diff` from `fpindej/netrock`. Otherwise, ask the user. + +2. **Copy files** from source to `templates/`: + - Root files (`.gitignore`, `CLAUDE.md`, `.claude/`) map to `templates/` directly + - `src/backend/...` maps to `templates/src/backend/...` + - `src/frontend/...` maps to `templates/src/frontend/...` + +3. **Preserve @feature markers**: This is critical. + - Before copying a `templated: true` file, read the existing template version + - After copying, compare: if markers were lost, restore them manually + - For non-templated files, direct copy is safe + - When in doubt, check the manifest: `grep` for the file path in `packages/core/src/manifests/` + +4. **Register new files** in the relevant manifest (`packages/core/src/manifests/*.ts`): + - Set `templated: true` for files containing `@feature` markers + - Determine which manifest based on the feature the file belongs to + +5. **Add @feature markers** to new files if they contain feature-specific content: + - Auth-specific code: `# @feature auth` / `# @end` or `` / `` + - Frontend-specific: `@feature frontend` + - Check existing template files for patterns + +6. **Update snapshots**: `pnpm --filter @netrock/core exec vitest run --update` + +7. **Verify**: `pnpm test && pnpm build` + +8. Commit: `feat(templates): sync {description} from netrock PR #{number}` + +## Key Rules + +- NEVER lose existing `@feature` markers - they are the core value of this generator +- Non-templated files can be copied directly +- Always run tests after sync - snapshot failures reveal missed markers +- `Directory.Packages.props` is `templated: true` (has @feature markers) - never direct copy diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md new file mode 100644 index 0000000..3b2d3d0 --- /dev/null +++ b/.claude/skills/verify/SKILL.md @@ -0,0 +1,21 @@ +--- +description: Run full verification (tests, build, type-check) +user-invocable: true +--- + +# /verify + +Run the verification pipeline. Report each step's result. Stop on first failure. + +```bash +# Core tests + build +pnpm --filter @netrock/core exec vitest run && pnpm --filter @netrock/core build + +# Web type-check +pnpm --filter @netrock/web check + +# Full build (both packages) +pnpm build +``` + +Fix all failures before reporting success. Loop until green. diff --git a/.gitignore b/.gitignore index 8fc1f38..397789a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ Thumbs.db *.swp *.swo +# Claude Code local settings (user-specific permission overrides) +.claude/settings.local.json + # Generated project output (pnpm generate defaults to output/) /output/ /test-app/