diff --git a/claude/ci/commands/ci.md b/claude/ci/commands/ci.md index 2471186..ee0e2e2 100644 --- a/claude/ci/commands/ci.md +++ b/claude/ci/commands/ci.md @@ -6,7 +6,7 @@ args: [status|run|logs|fix] # CI Integration -Check GitHub Actions status and manage CI workflows. +Check CI status and manage workflows using the `core` CLI (supports Forgejo and GitHub). ## Commands @@ -15,66 +15,40 @@ Check GitHub Actions status and manage CI workflows. /ci:ci /ci:ci status ``` - -Check current CI status for the repo/branch. - -### Run workflow -``` -/ci:ci run -/ci:ci run tests +```bash +core dev ci +core dev ci --branch $(git branch --show-current) +core dev ci --failed ``` -Trigger a workflow run. - -### View logs +### List workflows ``` -/ci:ci logs -/ci:ci logs 12345 +/ci:ci workflows +``` +```bash +core dev workflow list ``` -View logs from a workflow run. +### Issues +``` +/ci:ci issues +``` +```bash +core dev issues +core dev issues --assignee @me +``` + +### Reviews / PRs +``` +/ci:ci reviews +``` +```bash +core dev reviews +core dev reviews --all +``` ### Fix failing CI ``` /ci:ci fix ``` - -Analyse failing CI and suggest fixes. - -## Implementation - -### Check status -```bash -gh run list --limit 5 -gh run view --log-failed -``` - -### Trigger workflow -```bash -gh workflow run tests.yml -``` - -### View logs -```bash -gh run view 12345 --log -``` - -## CI Status Report - -```markdown -## CI Status: main - -| Workflow | Status | Duration | Commit | -|----------|--------|----------|--------| -| Tests | ✓ passing | 2m 34s | abc123 | -| Lint | ✓ passing | 45s | abc123 | -| Build | ✗ failed | 1m 12s | abc123 | - -### Failing: Build -``` -Error: go build failed - pkg/api/handler.go:42: undefined: ErrNotFound -``` - -**Suggested fix**: Add missing error definition -``` +Analyse failing CI and suggest fixes. See `/ci:fix`. diff --git a/claude/ci/commands/fix.md b/claude/ci/commands/fix.md index 722592d..6690416 100644 --- a/claude/ci/commands/fix.md +++ b/claude/ci/commands/fix.md @@ -9,89 +9,39 @@ Analyse failing CI runs and suggest/apply fixes. ## Process -1. **Get failing run** +1. **Get failing runs** ```bash - gh run list --status failure --limit 1 - gh run view --log-failed + core dev ci --failed ``` 2. **Analyse failure** - - Parse error messages + - Parse error messages from CI output - Identify root cause - Check if local issue or CI-specific -3. **Suggest fix** +3. **Reproduce locally** + ```bash + core go test + core go lint + core go vet + ``` + +4. **Suggest fix** - Code changes if needed - CI config changes if needed -4. **Apply fix** (if approved) +5. **Apply fix** (if approved) ## Common CI Failures ### Test Failures -``` -Error: go test failed ---- FAIL: TestFoo -``` -→ Fix the failing test locally, then push +→ Run `core go test --run TestFoo`, fix the test, push ### Lint Failures -``` -Error: golangci-lint failed -file.go:42: undefined: X -``` -→ Fix lint issue locally +→ Run `core go lint`, fix lint issues ### Build Failures -``` -Error: go build failed -cannot find package -``` -→ Run `go mod tidy`, check imports +→ Run `core build`, check imports, run `core go fmt` ### Dependency Issues -``` -Error: go mod download failed -``` -→ Check go.mod, clear cache, retry - -### Timeout -``` -Error: Job exceeded time limit -``` -→ Optimise tests or increase timeout in workflow - -## Output - -```markdown -## CI Failure Analysis - -**Run**: #12345 -**Workflow**: Tests -**Failed at**: 2024-01-15 14:30 - -### Error -``` ---- FAIL: TestCreateUser (0.02s) - handler_test.go:45: expected 200, got 500 -``` - -### Analysis -The test expects a 200 response but gets 500. This indicates the handler is returning an error. - -### Root Cause -Looking at recent changes, `ErrNotFound` was removed but still referenced. - -### Fix -Add the missing error definition: -```go -var ErrNotFound = errors.New("not found") -``` - -### Commands -```bash -# Apply fix and push -git add . && git commit -m "fix: add missing ErrNotFound" -git push -``` -``` +→ Check go.mod, `core go fmt`, retry diff --git a/claude/ci/commands/run.md b/claude/ci/commands/run.md index 7a501ae..b84c2f0 100644 --- a/claude/ci/commands/run.md +++ b/claude/ci/commands/run.md @@ -6,71 +6,28 @@ args: [workflow-name] # Run Workflow -Manually trigger a GitHub Actions workflow. +Trigger a CI workflow or view available workflows. ## Usage ``` -/ci:run # Run default workflow -/ci:run tests # Run specific workflow -/ci:run release # Trigger release workflow +/ci:run # List available workflows +/ci:run tests # Trigger specific workflow ``` -## Process - -1. **List available workflows** - ```bash - gh workflow list - ``` - -2. **Trigger workflow** - ```bash - gh workflow run tests.yml - gh workflow run tests.yml --ref feature-branch - ``` - -3. **Watch progress** - ```bash - gh run watch - ``` - -## Common Workflows - -| Workflow | Trigger | Purpose | -|----------|---------|---------| -| `tests.yml` | Push, PR | Run test suite | -| `lint.yml` | Push, PR | Run linters | -| `build.yml` | Push | Build artifacts | -| `release.yml` | Tag | Create release | -| `deploy.yml` | Manual | Deploy to environment | - -## Output - -```markdown -## Workflow Triggered - -**Workflow**: tests.yml -**Branch**: feature/add-auth -**Run ID**: 12345 - -Watching progress... - -``` -⠋ Tests running... - ✓ Setup (12s) - ✓ Install dependencies (45s) - ⠋ Run tests (running) -``` - -**Run completed in 2m 34s** ✓ -``` - -## Options +## Commands ```bash -# Run with inputs (for workflows that accept them) -gh workflow run deploy.yml -f environment=staging +# List available workflows +core dev workflow list -# Run on specific ref -gh workflow run tests.yml --ref main +# Sync workflows across repos +core dev workflow sync ``` + +## Notes + +- Forgejo Actions uses `.forgejo/workflows/` or `.github/workflows/` (both supported) +- Workflows are triggered automatically on push/PR/tag +- Manual dispatch: use the Forgejo web UI at `forge.lthn.ai/{owner}/{repo}/actions` +- Runner: `build-noc` on the noc server diff --git a/claude/ci/commands/status.md b/claude/ci/commands/status.md index afd7d99..b866891 100644 --- a/claude/ci/commands/status.md +++ b/claude/ci/commands/status.md @@ -5,59 +5,59 @@ description: Show CI status for current branch # CI Status -Show GitHub Actions status for the current branch. +Show CI status for the current branch. ## Usage ``` /ci:status -/ci:status --all # All recent runs -/ci:status --branch X # Specific branch ``` -## Commands +## Detection + +Detect the CI provider from git remote, then use the appropriate method: ```bash -# Current branch status -gh run list --branch $(git branch --show-current) --limit 5 +REMOTE_URL=$(git remote get-url origin 2>/dev/null) +``` -# Get details of latest run -gh run view --log-failed +### Forgejo (forge.lthn.ai) -# Watch running workflow -gh run watch +```bash +# Extract owner/repo from remote +OWNER_REPO=$(git remote get-url origin 2>/dev/null | sed -E 's#.*forge\.lthn\.ai[:/]+([0-9]+/)?##; s#\.git$##') + +# List recent workflow runs (requires FORGEJO_TOKEN or use web UI) +curl -s "https://forge.lthn.ai/api/v1/repos/${OWNER_REPO}/actions/tasks?limit=10&state=running" + +# Or just open the Actions page +echo "https://forge.lthn.ai/${OWNER_REPO}/actions" +``` + +### GitHub (fallback, requires gh CLI) + +```bash +core dev ci +core dev ci --branch $(git branch --show-current) +core dev ci --failed ``` ## Output +Present results as a status table: + ```markdown -## CI Status: feature/add-auth +## CI Status: main -| Workflow | Status | Duration | Commit | When | -|----------|--------|----------|--------|------| -| Tests | ✓ pass | 2m 34s | abc123 | 5m ago | -| Lint | ✓ pass | 45s | abc123 | 5m ago | -| Build | ✓ pass | 1m 12s | abc123 | 5m ago | +| Workflow | Status | When | +|----------|--------|------| +| Tests | pass | 5m ago | +| Build | pass | 5m ago | -**All checks passing** ✓ - ---- - -Or if failing: - -| Workflow | Status | Duration | Commit | When | -|----------|--------|----------|--------|------| -| Tests | ✗ fail | 1m 45s | abc123 | 5m ago | -| Lint | ✓ pass | 45s | abc123 | 5m ago | -| Build | - skip | - | abc123 | 5m ago | - -**1 workflow failing** - -### Tests Failure -``` ---- FAIL: TestCreateUser - expected 200, got 500 +**All checks passing** ``` -Run `/ci:fix` to analyse and fix. +If no API token available, output the web URL: +``` +View CI status: https://forge.lthn.ai/{owner}/{repo}/actions ``` diff --git a/claude/ci/commands/workflow.md b/claude/ci/commands/workflow.md index a98b974..8f95a4b 100644 --- a/claude/ci/commands/workflow.md +++ b/claude/ci/commands/workflow.md @@ -1,26 +1,12 @@ --- name: workflow -description: Create or update GitHub Actions workflow +description: Create or update CI workflow args: --- # Workflow Generator -Create or update GitHub Actions workflows. - -## Workflow Types - -### test -Standard test workflow for Go/PHP projects. - -### lint -Linting workflow with golangci-lint or PHPStan. - -### release -Release workflow with goreleaser or similar. - -### deploy -Deployment workflow (requires configuration). +Create or update CI workflows. Forgejo Actions uses the same YAML format as GitHub Actions. ## Usage @@ -30,6 +16,24 @@ Deployment workflow (requires configuration). /ci:workflow release ``` +## List existing workflows + +```bash +core dev workflow list +``` + +## Sync workflows across repos + +```bash +core dev workflow sync +``` + +## Workflow directory + +Forgejo supports both: +- `.forgejo/workflows/` (preferred) +- `.github/workflows/` (also works) + ## Templates ### Go Test Workflow @@ -49,28 +53,35 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: '1.22' - - run: go test -v ./... + go-version-file: go.mod + - run: go test -v -race ./... ``` -### PHP Test Workflow +### Go Release Workflow (core build) ```yaml -name: Tests +name: Release on: push: - branches: [main] - pull_request: - branches: [main] + tags: ['v*'] jobs: - test: + release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: shivammathur/setup-php@v2 with: - php-version: '8.3' - - run: composer install - - run: composer test + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Build and release + run: core build release --we-are-go-for-launch ``` + +## Forgejo Notes + +- Runner label: `ubuntu-latest` (maps to Forgejo runner labels) +- Secrets: Set via repo Settings → Actions → Secrets +- Runner: `build-noc` on the noc server +- Web UI: `forge.lthn.ai/{owner}/{repo}/actions` diff --git a/claude/ci/scripts/detect-forge.sh b/claude/ci/scripts/detect-forge.sh new file mode 100755 index 0000000..5f31710 --- /dev/null +++ b/claude/ci/scripts/detect-forge.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Detect CI provider from git remote +# Outputs: "forgejo" or "github" or "unknown" +# Also exports FORGE_API, FORGE_OWNER, FORGE_REPO + +REMOTE_URL=$(git remote get-url origin 2>/dev/null) + +if echo "$REMOTE_URL" | grep -q "forge.lthn.ai"; then + echo "forgejo" + # Extract owner/repo from SSH or HTTPS URL + # SSH: ssh://git@forge.lthn.ai:2223/core/go.git + # HTTPS: https://forge.lthn.ai/core/go.git + OWNER_REPO=$(echo "$REMOTE_URL" | sed -E 's#.*forge\.lthn\.ai[:/]+([0-9]+/)?##; s#\.git$##') + export FORGE_API="https://forge.lthn.ai/api/v1" + export FORGE_OWNER=$(echo "$OWNER_REPO" | cut -d'/' -f1) + export FORGE_REPO=$(echo "$OWNER_REPO" | cut -d'/' -f2) +elif echo "$REMOTE_URL" | grep -qE "github\.com"; then + echo "github" +else + echo "unknown" +fi diff --git a/claude/ci/scripts/post-push-ci.sh b/claude/ci/scripts/post-push-ci.sh index f84797f..86dbc3b 100755 --- a/claude/ci/scripts/post-push-ci.sh +++ b/claude/ci/scripts/post-push-ci.sh @@ -5,13 +5,13 @@ read -r input EXIT_CODE=$(echo "$input" | jq -r '.tool_response.exit_code // 0') if [ "$EXIT_CODE" = "0" ]; then - # Check if repo has workflows - if [ -d ".github/workflows" ]; then + # Check if repo has workflows (Forgejo or GitHub) + if [ -d ".forgejo/workflows" ] || [ -d ".github/workflows" ]; then cat << 'EOF' { "hookSpecificOutput": { "hookEventName": "PostToolUse", - "additionalContext": "Push successful. CI workflows will run shortly.\n\nRun `/ci:status` to check progress or `gh run watch` to follow live." + "additionalContext": "Push successful. CI workflows will run shortly.\n\nRun `/ci:status` to check progress or `core dev ci` to view status." } } EOF diff --git a/claude/code/.claude-plugin/plugin.json b/claude/code/.claude-plugin/plugin.json index a4b3c65..20e7199 100644 --- a/claude/code/.claude-plugin/plugin.json +++ b/claude/code/.claude-plugin/plugin.json @@ -1,16 +1,13 @@ { - "name": "core-agent", + "name": "code", "description": "Advanced Claude Code plugin for Host UK monorepo - core CLI integration, data collection skills, and autonomous workflows", - "version": "0.1.0", + "version": "0.1.3", "author": { "name": "Lethean", "email": "hello@host.uk.com" }, - "homepage": "https://github.com/host-uk/core-agent", - "repository": { - "type": "git", - "url": "https://github.com/host-uk/core-agent.git" - }, + "homepage": "https://forge.lthn.ai/core/agent", + "repository": "ssh://git@forge.lthn.ai:2223/core/agent.git", "license": "EUPL-1.2", "keywords": [ "devops", diff --git a/claude/code/hooks/hooks.json b/claude/code/hooks/hooks.json new file mode 100644 index 0000000..fc38fe6 --- /dev/null +++ b/claude/code/hooks/hooks.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://claude.ai/schemas/hooks.json", + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/prefer-core.sh" + } + ], + "description": "Block destructive commands (rm -rf, sed -i, xargs rm) and enforce core CLI" + }, + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/block-docs.sh" + } + ], + "description": "Block random .md file creation" + } + ], + "PostToolUse": [ + { + "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.go$\"", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/go-format.sh" + } + ], + "description": "Auto-format Go files after edits" + }, + { + "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.php$\"", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/php-format.sh" + } + ], + "description": "Auto-format PHP files after edits" + }, + { + "matcher": "tool == \"Edit\"", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/check-debug.sh" + } + ], + "description": "Warn about debug statements (dd, dump, fmt.Println)" + }, + { + "matcher": "tool == \"Bash\" && tool_input.command matches \"^git commit\"", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/post-commit-check.sh" + } + ], + "description": "Warn about uncommitted work after git commit" + } + ], + "PreCompact": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/pre-compact.sh" + } + ], + "description": "Save state before auto-compact to prevent amnesia" + } + ], + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-start.sh" + } + ], + "description": "Restore recent session context on startup" + } + ] + } +} diff --git a/claude/code/hooks/prefer-core.sh b/claude/code/hooks/prefer-core.sh index 52ce773..cefcbb4 100755 --- a/claude/code/hooks/prefer-core.sh +++ b/claude/code/hooks/prefer-core.sh @@ -3,14 +3,24 @@ # # BLOCKS: # - Raw go commands (use core go *) -# - Destructive grep patterns (sed -i, xargs rm, etc.) +# - Destructive patterns (sed -i, xargs rm, etc.) # - Mass file operations (rm -rf, mv/cp with wildcards) -# - Any sed outside of safe patterns # # This prevents "efficient shortcuts" that nuke codebases read -r input -command=$(echo "$input" | jq -r '.tool_input.command // empty') +full_command=$(echo "$input" | jq -r '.tool_input.command // empty') + +# Strip heredoc content — only check the actual command, not embedded text +# This prevents false positives from code/docs inside heredocs +command=$(echo "$full_command" | sed -n '1p') +if echo "$command" | grep -qE "<<\s*['\"]?[A-Z_]+"; then + # First line has heredoc marker — only check the command portion before << + command=$(echo "$command" | sed -E 's/\s*<<.*$//') +fi + +# For multi-line commands joined with && or ;, check each segment +# But still only the first line (not heredoc body) # === HARD BLOCKS - Never allow these === @@ -23,9 +33,10 @@ if echo "$command" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r| fi fi -# Block mv/cp with wildcards (mass file moves) -if echo "$command" | grep -qE '(mv|cp)\s+.*\*'; then - echo '{"decision": "block", "message": "BLOCKED: Mass file move/copy with wildcards is not allowed. Move files individually."}' +# Block mv/cp with dangerous wildcards (e.g. `cp * /tmp`, `mv ./* /dest`) +# Allow specific file copies that happen to use glob in a for loop or path +if echo "$command" | grep -qE '(mv|cp)\s+(\.\/)?\*\s'; then + echo '{"decision": "block", "message": "BLOCKED: Mass file move/copy with bare wildcards is not allowed. Copy files individually."}' exit 0 fi @@ -47,7 +58,7 @@ if echo "$command" | grep -qE 'sed\s+(-[a-zA-Z]*i|--in-place)'; then exit 0 fi -# Block sed piped to file operations +# Block sed piped to file operations (but not inside heredocs) if echo "$command" | grep -qE 'sed.*\|.*tee|sed.*>'; then echo '{"decision": "block", "message": "BLOCKED: sed with file output is not allowed. Use the Edit tool for file changes."}' exit 0 @@ -67,14 +78,14 @@ fi # === REQUIRE CORE CLI === -# Block raw go commands +# Block raw go commands (only check first line, not heredoc content) case "$command" in "go test"*|"go build"*|"go fmt"*|"go mod tidy"*|"go vet"*|"go run"*) echo '{"decision": "block", "message": "Use `core go test`, `core build`, `core go fmt --fix`, etc. Raw go commands are not allowed."}' exit 0 ;; "go "*) - # Other go commands - warn but allow + # Other go commands - block echo '{"decision": "block", "message": "Prefer `core go *` commands. If core does not have this command, ask the user."}' exit 0 ;; diff --git a/claude/code/scripts/block-docs.sh b/claude/code/scripts/block-docs.sh index dfac1da..676f1c2 100755 --- a/claude/code/scripts/block-docs.sh +++ b/claude/code/scripts/block-docs.sh @@ -16,6 +16,16 @@ if [[ -n "$FILE_PATH" ]]; then echo "$input" exit 0 ;; + # Allow Claude memory and plan files + */.claude/*.md|*/.claude/**/*.md) + echo "$input" + exit 0 + ;; + # Allow plugin development (commands, skills) + */commands/*.md|*/skills/*.md|*/skills/**/*.md) + echo "$input" + exit 0 + ;; # Block other .md files *.md) echo '{"decision": "block", "message": "Use README.md or docs/ for documentation. Random .md files clutter the repo."}'