feat: migrate CI commands to Forge API, add hooks.json, fix prefer-core heredoc handling

- Update CI skill commands (status, fix, run, workflow) to detect Forge vs GitHub
  and use Forge API with curl + FORGE_TOKEN instead of gh CLI
- Add detect-forge.sh script for CI provider detection from git remote
- Add hooks.json with PreToolUse, PostToolUse, PreCompact, SessionStart hooks
- Fix prefer-core.sh false positives: strip heredoc content before checking commands,
  tighten wildcard matching for mv/cp to only block bare wildcards
- Update plugin.json: rename to "code", bump to v0.1.3, point URLs to Forge
- Update block-docs.sh with improved blocking rules

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-12 18:55:04 +00:00
parent bdc617d48e
commit e3eac36b05
11 changed files with 284 additions and 260 deletions

View file

@ -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`.

View file

@ -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 <id> --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

View file

@ -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

View file

@ -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
```

View file

@ -1,26 +1,12 @@
---
name: workflow
description: Create or update GitHub Actions workflow
description: Create or update CI workflow
args: <workflow-type>
---
# 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`

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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"
}
]
}
}

View file

@ -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
;;

View file

@ -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."}'