Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,93 @@
You are a world-class distributed systems architect specialized in agentic architecture. You always Write ELEGANT code that follows the coding guidelines described in AGENTS.md. You think at a high level and do not lose track of the outcomes. There is no need to write backwards compatible code - we can break everything, instead you always aim for elegance and conceptual correctness. You also value LESS code and always remember cleaning up old code. Critically you void writing overly defensive code that hides bugs. You favor no fallbacks, strong contracts, elegant and conceptually correct designs.

# Repository Guidelines

## Common Rules

### Agent Behavior

- **Plan before acting**: For ≤2 files, state a brief plan then implement. For ≥3 files, write a step-by-step plan first.
- **Read before editing**: Always read files before modifying. Search over guessing.
- **Fix root causes**: Do not produce local workarounds—fix the real issue.
- **Be concise**: Give short status updates during multi-step work. Present a short summary when done.
- **Default to repo-style formatting**: Prefer small, composable functions and well-factored files; keep “main logic first, helpers last”; add meaningful header comments; and fix lints immediately.

### Go Code Style

- **Go 1.24+**. Format with `go fmt ./...`.
- **Imports**: Group stdlib separate from external. Let gofmt manage ordering.
- **Files**: Use `lower_snake_case.go`. Keep ≤1000 lines; split proactively.
- **Naming**: Packages are lowercase and short. Exported identifiers need GoDoc. Avoid stutter.
- **Types**: Use `any` over `interface{}`. Prefer concrete types over `interface{}`.
- **Errors**: Wrap with `%w`. Use `errors.Is/As`. **Never ignore errors or use `_ = call()`**.
- **Signatures**: Keep on one line when ≤100 columns. Only wrap genuinely long signatures.
- **Slice/map nil**: Do not check nil before `len`. `len(nil)` returns 0. Use `len(x) == 0` directly.

### Code Blocks and Literals

- Always place a newline after `{` and before `}` for `if`, `for`, `switch`, `func`, `type`.
- No single-line blocks: `if cond { do() }` → use multiple lines.
- Short struct literals are fine inline: `&T{A: 1}`. Break long literals to one field per line with trailing commas.

### File Organization

Order declarations as:
1. Types (public, then private) in a single `type (...)` block when practical
2. Constants (public, then private)
3. Variables (public, then private)
4. Public functions
5. Public methods
6. Private functions
7. Private methods

**Within each category**, order by relevance — main logic first, helpers last:
- Primary entry points and feature implementations first
- Domain-specific supporting functions next
- Generic utilities and conversion helpers last

Additional formatting defaults (apply unless there is a strong reason not to):
- **Types at the top**: Place new helper types close to the code they support, but keep all type declarations in the file’s top type block.
- **Avoid anonymous functions**: Prefer named helpers or small method receivers over closures, especially for concurrency (e.g., `errgroup.Go(job.Run)`).
- **Break down complexity**: Split large functions into smaller, testable helpers with clear contracts; split files when they start to accrete multiple distinct concerns (ideally keep ≤1000 lines).
- **Reuse-first**: Before adding new helpers, check for existing shared utilities; when you do add a helper, make it reusable and name it for the domain (not the immediate caller).
- **Meaningful header comments**: Exported identifiers require GoDoc; non-trivial helpers should have short intent/contract comments when they aren’t obvious from the name.

### Error Handling & Contracts

- **Always check errors**. Never discard with `_`.
- **Strong contracts**: Goa validates payloads at boundaries. Do not re-validate inside service code.
- **No defensive programming**: Do not add nil/empty guards for values guaranteed by construction, Goa, or prior validation.
- **Validate only at boundaries**: HTTP/gRPC handlers, event consumers, DB results, third-party APIs, `ctx.Value()`, type assertions, required map lookups.
- **Fail fast**: Unexpected states are bugs. Return precise errors or panic—do not silently recover or skip.

### Goa DSL Rules

- **Never edit `gen/`**: Always regenerate.
- **DSL validation**: Put validations (lengths, enums, formats) in the design. Do not re-validate in code.
- **Avoid `Any`**: Use concrete types to enable gRPC generation.

### Documentation

- Every exported type, function, method, and field must have a GoDoc comment explaining its contract—like Go stdlib documentation.

### Safety & Forbidden Operations

| Action | Policy |
|--------|--------|
| `git clean/stash/reset/checkout` | **FORBIDDEN** |
| `go clean -cache` | **FORBIDDEN** during normal work |
| Edit `gen/` directly | **FORBIDDEN** |
| Changes ≥3 files | Describe plan first |
| New dependencies | Explain why first |

### Testing

- Write table-driven tests in `*_test.go`.
- Name tests `TestXxx`. Keep fast and deterministic.
- Use `testify/assert` for assertions when possible; use `testify/require` only when the test cannot proceed if the assertion fails (e.g., nil checks before dereferencing).

---

## Project Structure & Modules
- `codegen/`: MCP code generators and templates.
- `dsl/` and `expr/`: DSL surfaces and expressions used during Goa eval.
Expand Down
49 changes: 49 additions & 0 deletions runtime/agent/runtime/hints/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"sync"
"text/template"
"time"

"goa.design/goa-ai/runtime/agent/tools"
)
Expand Down Expand Up @@ -113,6 +114,23 @@ func CompileHintTemplates(raw map[tools.Ident]string, extra template.FuncMap) (m
}
return 0
},
// since returns the integer number of seconds between two timestamps:
// to - from.
//
// It accepts RFC3339 timestamp strings (including timezone offsets) and
// time.Time values. When parsing fails, it returns 0 so hint rendering
// can continue.
"since": func(from, to any) int64 {
a, ok := parseTimestamp(from)
if !ok {
return 0
}
b, ok := parseTimestamp(to)
if !ok {
return 0
}
return int64(b.Sub(a).Seconds())
},
"truncate": func(s string, n int) string {
if n <= 0 {
return ""
Expand All @@ -139,3 +157,34 @@ func CompileHintTemplates(raw map[tools.Ident]string, extra template.FuncMap) (m
}
return out, nil
}

func parseTimestamp(v any) (time.Time, bool) {
switch t := v.(type) {
case time.Time:
return t, true
case *time.Time:
if t == nil {
return time.Time{}, false
}
return *t, true
case string:
if t == "" {
return time.Time{}, false
}
ts, err := time.Parse(time.RFC3339, t)
if err != nil {
return time.Time{}, false
}
return ts, true
default:
s := fmt.Sprint(v)
if s == "" {
return time.Time{}, false
}
ts, err := time.Parse(time.RFC3339, s)
if err != nil {
return time.Time{}, false
}
return ts, true
}
}
78 changes: 78 additions & 0 deletions runtime/agent/runtime/hints/hints_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package hints

import (
"strings"
"testing"
"text/template"

"goa.design/goa-ai/runtime/agent/tools"
)

func TestCompileHintTemplates_Since(t *testing.T) {
t.Parallel()

type testCase struct {
name string
from any
to any
want string
wantErr bool
}

cases := []testCase{
{
name: "rfc3339_z",
from: "2025-01-01T00:00:00Z",
to: "2025-01-01T00:00:10Z",
want: "10",
},
{
name: "rfc3339_offset_negative",
from: "2025-01-01T00:00:00-08:00",
to: "2025-01-01T00:00:00Z",
want: "-28800",
},
{
name: "invalid_returns_zero",
from: "not-a-time",
to: "2025-01-01T00:00:00Z",
want: "0",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

raw := map[tools.Ident]string{
tools.Ident("t"): "{{ since .From .To }}",
}
compiled, err := CompileHintTemplates(raw, template.FuncMap{})
if tc.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("CompileHintTemplates error: %v", err)
}

tmpl := compiled[tools.Ident("t")]
if tmpl == nil {
t.Fatalf("expected compiled template")
}

var b strings.Builder
if err := tmpl.Execute(&b, map[string]any{
"From": tc.from,
"To": tc.to,
}); err != nil {
t.Fatalf("Execute error: %v", err)
}
if got := strings.TrimSpace(b.String()); got != tc.want {
t.Fatalf("unexpected output: got %q want %q", got, tc.want)
}
})
}
}