This file provides guidance for agentic coding agents operating in this repository.
Clock is a lightweight visual scheduling framework based on Go cron, supporting DAG task dependencies and bash command execution. Frontend and backend are packaged into a single binary via go:embed.
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run tests for a specific package
go test -v ./internal/service/
# Run tests matching a pattern
go test -v -run "TestExecutor" ./internal/service/
# Format code
go fmt ./...
# Build for Linux (AMD64)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o clock cmd/clock/main.go
# Build using Makefile
make build # Linux binary
make mac # macOS binary
make win # Windows binary
make all # Build all platforms + frontendcd server/webapp
# Install dependencies
npm install
# Development mode (hot reload)
npm run dev
# Production build (outputs to web/dist)
npm run build
# Type checking only
vue-tsc --noEmitImports
- Standard library imports first, third-party imports second, local imports third
- Separate groups with blank lines
- Use
clock/prefix for local imports (e.g.,"clock/internal/domain")
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"github.com/labstack/echo/v4"
"clock/internal/domain"
"clock/internal/logger"
)Naming Conventions
- Structs:
PascalCase(e.g.,TaskHandler,Executor) - Interfaces:
PascalCasewithersuffix where appropriate (e.g.,Repository,Service) - Variables:
camelCase(e.g.,taskRepo,runID) - Constants:
PascalCaseorcamelCasedepending on scope (package-level constants use PascalCase) - Acronyms: preserve original casing (e.g.,
ID,URL,UUIDnotId,Url,Uuid) - Private fields:
camelCasestarting with letter (e.g.,taskRepo,runningMu) - Package name: short, lowercase, no underscores
Error Handling
- Use
errors.New()for static error messages - Use
fmt.Errorf("context: %w", err)for wrapping errors with context - Return errors early; avoid nested error handling
- Log errors at the call site with
logger.Errorf("context: %v", err) - Use custom error types (
apperrors.AppError) for domain-specific errors
// Good - early return with error
if err != nil {
logger.Errorf("[GetTask] failed: %v", err)
return HandleError(c, err)
}
// Good - wrapped error with context
return fmt.Errorf("failed to get task: %w", err)Struct Tags
- GORM tags:
gorm:"primaryKey",gorm:"index:idx_cid",gorm:"default:1" - JSON tags:
json:"fieldName"
Concurrency
- Use
sync.RWMutexfor read-write locks - Always use
deferto unlock mutexes - Use
sync.WaitGroupfor waiting on goroutines
e.runningMu.RLock()
defer e.runningMu.RUnlock()Context
- Pass
context.Contextas first parameter when needed - Use
context.WithCancel,context.WithTimeoutfor cancellation/timeout
TypeScript
- Use strict mode (enabled in tsconfig.json)
- Prefer
interfaceovertypefor object shapes - Use explicit types; avoid
any - Use
Record<K, V>for map-like objects
// Good
interface Task {
tid: number
cid: number
name: string
status: number
}
// Good - Record for maps
const statusMap: Record<number, string> = { 1: 'pending', 2: 'running' }Vue Components
- Use
<script setup lang="ts">syntax - Define props with
defineProps<{...}>()orwithDefaults - Use
ref()for primitives,reactive()for objects - Prefer Composition API over Options API
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { Task } from '@/types/model'
const loading = ref(false)
const list = ref<Task[]>([])
const form = reactive({
name: '',
command: ''
})
</script>Imports
- Use path alias
@/forsrc/(configured in tsconfig.json) - Import types explicitly with
import type
import { getTasks, putTask } from '@/api/task'
import type { Task, Container } from '@/types/model'Styling
- Use SCSS with CSS custom properties (variables)
- Use
scopedattribute for component styles - Use Element Plus components with PascalCase tags
clock/
├── cmd/clock/
│ └── main.go # Entry point
├── internal/
│ ├── config/ # TOML config loading
│ ├── domain/ # Core models (Task, Container, Relation, TaskLog, Message)
│ ├── errors/ # Custom error types
│ ├── handler/ # Echo HTTP handlers
│ ├── logger/ # Zap logger wrapper
│ ├── middleware/ # JWT auth, request logging
│ ├── repository/ # GORM data access layer
│ ├── router/ # Route registration
│ └── service/ # Business logic (SchedulerService, Executor, StreamHub)
├── pkg/util/ # Utility functions
├── server/webapp/ # Vue 3 frontend
│ └── src/
│ ├── api/ # Axios API clients
│ ├── stores/ # Pinia stores
│ ├── types/ # TypeScript type definitions
│ └── views/ # Vue page components
├── configs/ # Configuration files
└── Makefile # Build automation
- Default: SQLite
- Also supports: MySQL, PostgreSQL
- Configured via
[storage]section inconfigs/config.toml - ORM: GORM with
glebarez/sqlitedriver
StreamHubmanages subscriber channelsExecutorpublishesStreamEvent(task_start, task_end, stdout, stderr) to all subscribers- Clients connect via
/v1/messageSSE endpoint
Executor.runStageTasks()uses Kahn's algorithm for topological sorting- Each stage executes all tasks with in-degree 0 in parallel
- After completion, remove executed nodes and their outgoing edges
- Token passed via Header (
Authorization: Bearer <token>), Cookie, or Query parameter - Configured in
[auth]section ofconfigs/config.toml
- Go tests use standard
testingpackage - Test files:
*_test.gosuffix - No test files found in current codebase; add tests when modifying functionality
- VSCode with Go extension, Volar for Vue
- JetBrains GoLand / WebStorm
- Enable format on save for Go (
gofmt) and TypeScript/Prettier