feat: rewrite CLAUDE.md, add tests for agentic + prompts packages
CLAUDE.md: - Complete rewrite reflecting current architecture (30 files, 6.5K lines) - Documents all 33 MCP tools, 7 agent types, dispatch flow - Removes all references to deleted packages Tests: - pkg/agentic: 20 tests covering paths, extractPRNumber, truncate, countFindings, parseRetryAfter, resolveHost, baseAgent, validPlanStatus, generatePlanID, extractJSONField - pkg/prompts: 7 tests covering Template, Persona, ListTemplates, ListPersonas, prefix duplication check Fix: rename support/support-responder → support/responder (caught by test) Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
73fa4ba6c9
commit
9f4afb9a15
4 changed files with 316 additions and 123 deletions
221
CLAUDE.md
221
CLAUDE.md
|
|
@ -1,163 +1,138 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
This file provides guidance to Claude Code when working with code in this repository.
|
||||
|
||||
## Session Context
|
||||
|
||||
Running on **Claude Max20 plan** with **1M context window** (Opus 4.6).
|
||||
|
||||
## Overview
|
||||
|
||||
**core-agent** is a polyglot monorepo (Go + PHP) for AI agent orchestration. The Go side handles agent-side execution, CLI commands, and autonomous agent loops. The PHP side (Laravel package `lthn/agent`) provides the backend API, persistent storage, multi-provider AI services, and admin panel. They communicate via REST API.
|
||||
**core-agent** is the AI agent orchestration platform for the Core ecosystem. Single Go binary (`core-agent`) that runs as an MCP server — either via stdio (Claude Code integration) or HTTP daemon (cross-agent communication).
|
||||
|
||||
The repo also contains Claude Code plugins (5), Codex plugins (13), a Gemini CLI extension, and two MCP servers.
|
||||
**Module:** `forge.lthn.ai/core/agent`
|
||||
|
||||
## Core CLI — Always Use It
|
||||
|
||||
**Never use raw `go`, `php`, or `composer` commands.** The `core` CLI wraps both toolchains and is enforced by PreToolUse hooks that will block violations.
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------|--------|
|
||||
| `go test` | `core go test` |
|
||||
| `go build` | `core build` |
|
||||
| `go fmt` | `core go fmt` |
|
||||
| `go vet` | `core go vet` |
|
||||
| `golangci-lint` | `core go lint` |
|
||||
| `composer test` / `./vendor/bin/pest` | `core php test` |
|
||||
| `./vendor/bin/pint` / `composer lint` | `core php fmt` |
|
||||
| `./vendor/bin/phpstan` | `core php stan` |
|
||||
| `php artisan serve` | `core php dev` |
|
||||
|
||||
## Build & Test Commands
|
||||
## Build & Test
|
||||
|
||||
```bash
|
||||
# Go
|
||||
core go test # Run all Go tests
|
||||
core go test --run TestMemoryRegistry_Register_Good # Run single test
|
||||
core go qa # Full QA: fmt + vet + lint + test
|
||||
core go qa full # QA + race detector + vuln scan
|
||||
core go cov # Test coverage
|
||||
core build # Verify Go packages compile
|
||||
go build ./... # Build all packages
|
||||
go build ./cmd/core-agent/ # Build the binary
|
||||
go test ./... -count=1 -timeout 60s # Run tests
|
||||
go vet ./... # Vet
|
||||
go install ./cmd/core-agent/ # Install to $GOPATH/bin
|
||||
```
|
||||
|
||||
# PHP
|
||||
core php test # Run Pest suite
|
||||
core php test --filter=AgenticManagerTest # Run specific test file
|
||||
core php fmt # Format (Laravel Pint)
|
||||
core php stan # Static analysis (PHPStan)
|
||||
core php qa # Full PHP QA pipeline
|
||||
|
||||
# MCP servers (standalone builds)
|
||||
cd cmd/mcp && go build -o agent-mcp . # Stdio MCP server
|
||||
cd google/mcp && go build -o google-mcp . # HTTP MCP server (port 8080)
|
||||
|
||||
# Workspace
|
||||
make setup # Full bootstrap (deps + core + clone repos)
|
||||
core dev health # Status across repos
|
||||
Cross-compile for Charon (Linux):
|
||||
```bash
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o core-agent-linux ./cmd/core-agent/
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Forgejo
|
||||
|
|
||||
[ForgejoSource polls]
|
||||
|
|
||||
v
|
||||
+-- Go: jobrunner Poller --+ +-- PHP: Laravel Backend --+
|
||||
| ForgejoSource | | AgentApiController |
|
||||
| DispatchHandler ---------|----->| /v1/plans |
|
||||
| CompletionHandler | | /v1/sessions |
|
||||
| ResolveThreadsHandler | | /v1/plans/*/phases |
|
||||
+--------------------------+ +-------------+------------+
|
||||
|
|
||||
[Eloquent models]
|
||||
AgentPlan, AgentPhase,
|
||||
AgentSession, BrainMemory
|
||||
cmd/core-agent/main.go Entry point (mcp + serve commands)
|
||||
pkg/agentic/ MCP tools — dispatch, verify, remote, mirror, review queue
|
||||
pkg/brain/ OpenBrain — recall, remember, messaging
|
||||
pkg/monitor/ Background monitoring + repo sync
|
||||
pkg/prompts/ Embedded templates + personas (go:embed)
|
||||
```
|
||||
|
||||
### Go Packages (`pkg/`)
|
||||
### Binary Modes
|
||||
|
||||
- **`lifecycle/`** — Core domain layer. Task, AgentInfo, Plan, Phase, Session types. Agent registry (Memory/SQLite/Redis backends), task router (capability matching + load scoring), allowance system (quota enforcement), dispatcher (orchestrates dispatch with exponential backoff), event system, brain (vector store), context (git integration).
|
||||
- **`loop/`** — Autonomous agent reasoning engine. Prompt-parse-execute cycle against any `inference.TextModel` with tool calling and streaming.
|
||||
- **`orchestrator/`** — Clotho protocol for dual-run verification and agent orchestration.
|
||||
- **`jobrunner/`** — Poll-dispatch engine for agent-side work execution. Polls Forgejo for work items, executes phases, reports results.
|
||||
- `core-agent mcp` — stdio MCP server for Claude Code
|
||||
- `core-agent serve` — HTTP daemon (Charon, CI, cross-agent). PID file, health check, registry.
|
||||
|
||||
### Go Commands (`cmd/`)
|
||||
### MCP Tools (33)
|
||||
|
||||
- **`tasks/`** — `core ai tasks`, `core ai task [id]` — task management
|
||||
- **`agent/`** — `core ai agent` — agent machine management (add, list, status, fleet)
|
||||
- **`dispatch/`** — `core ai dispatch` — work queue processor (watch, run)
|
||||
- **`workspace/`** — `core workspace task`, `core workspace agent` — git worktree isolation
|
||||
- **`mcp/`** — Standalone stdio MCP server exposing `marketplace_list`, `marketplace_plugin_info`, `core_cli`, `ethics_check`
|
||||
| Category | Tools |
|
||||
|----------|-------|
|
||||
| Dispatch | `agentic_dispatch`, `agentic_dispatch_remote`, `agentic_status`, `agentic_status_remote` |
|
||||
| Workspace | `agentic_prep_workspace`, `agentic_resume`, `agentic_watch` |
|
||||
| PR/Review | `agentic_create_pr`, `agentic_list_prs`, `agentic_create_epic`, `agentic_review_queue` |
|
||||
| Mirror | `agentic_mirror` (Forge → GitHub sync) |
|
||||
| Scan | `agentic_scan` (Forge issues) |
|
||||
| Brain | `brain_recall`, `brain_remember`, `brain_forget` |
|
||||
| Messaging | `agent_send`, `agent_inbox`, `agent_conversation` |
|
||||
| Plans | `agentic_plan_create`, `agentic_plan_read`, `agentic_plan_update`, `agentic_plan_delete`, `agentic_plan_list` |
|
||||
| Files | `file_read`, `file_write`, `file_edit`, `file_delete`, `file_rename`, `file_exists`, `dir_list`, `dir_create` |
|
||||
| Language | `lang_detect`, `lang_list` |
|
||||
|
||||
### PHP (`src/php/`)
|
||||
### Agent Types
|
||||
|
||||
- **Namespace**: `Core\Mod\Agentic\` (service provider: `Boot`)
|
||||
- **Models/** — 19 Eloquent models (AgentPlan, AgentPhase, AgentSession, BrainMemory, Task, Prompt, etc.)
|
||||
- **Services/** — AgenticManager (multi-provider: Claude/Gemini/OpenAI), BrainService (Ollama+Qdrant), ForgejoService, AI services with stream parsing and retry traits
|
||||
- **Controllers/** — AgentApiController (REST endpoints)
|
||||
- **Actions/** — Single-purpose action classes (Brain, Forge, Phase, Plan, Session, Task)
|
||||
- **View/** — Livewire admin panel components (Dashboard, Plans, Sessions, ApiKeys, Templates, Playground, etc.)
|
||||
- **Mcp/** — MCP tool implementations (Brain, Content, Phase, Plan, Session, State, Task, Template)
|
||||
- **Migrations/** — 10 migrations (run automatically on boot)
|
||||
| Agent | Command | Use |
|
||||
|-------|---------|-----|
|
||||
| `claude:opus` | Claude Code | Complex coding, architecture |
|
||||
| `claude:sonnet` | Claude Code | Standard tasks |
|
||||
| `claude:haiku` | Claude Code | Quick/cheap tasks, discovery |
|
||||
| `gemini` | Gemini CLI | Fast batch ops |
|
||||
| `codex` | Codex CLI | Autonomous coding |
|
||||
| `codex:review` | Codex review | Deep security analysis |
|
||||
| `coderabbit` | CodeRabbit CLI | Code quality review |
|
||||
|
||||
## Claude Code Plugins (`claude/`)
|
||||
### Dispatch Flow
|
||||
|
||||
Five plugins installable individually or via marketplace:
|
||||
```
|
||||
dispatch → agent works → closeout sequence (review → fix → simplify → re-review)
|
||||
→ commit → auto PR → inline tests → pass → auto-merge on Forge
|
||||
→ push to GitHub → CodeRabbit reviews → merge or dispatch fix agent
|
||||
```
|
||||
|
||||
| Plugin | Commands |
|
||||
|--------|----------|
|
||||
| **code** | `/code:remember`, `/code:yes`, `/code:qa` |
|
||||
| **review** | `/review:review`, `/review:security`, `/review:pr`, `/review:pipeline` |
|
||||
| **verify** | `/verify:verify`, `/verify:ready`, `/verify:tests` |
|
||||
| **qa** | `/qa:qa`, `/qa:fix`, `/qa:check`, `/qa:lint` |
|
||||
| **ci** | `/ci:ci`, `/ci:workflow`, `/ci:fix`, `/ci:run`, `/ci:status` |
|
||||
### Personas (pkg/prompts/lib/personas/)
|
||||
|
||||
### Hooks (code plugin)
|
||||
116 personas across 16 domains. Path = context, filename = lens.
|
||||
|
||||
**PreToolUse**: `prefer-core.sh` blocks destructive operations (`rm -rf`, `sed -i`, `xargs rm`, `find -exec rm`, `grep -l | ...`, `mv/cp *`) and raw go/php commands. `block-docs.sh` prevents random `.md` file creation.
|
||||
```
|
||||
prompts.Persona("engineering/security-developer") # code-level security review
|
||||
prompts.Persona("smm/security-secops") # social media incident response
|
||||
prompts.Persona("devops/senior") # infrastructure architecture
|
||||
```
|
||||
|
||||
**PostToolUse**: Auto-formats Go (`gofmt`) and PHP (`pint`) after edits. Warns about debug statements (`dd()`, `dump()`, `fmt.Println()`).
|
||||
### Templates (pkg/prompts/lib/templates/)
|
||||
|
||||
**PreCompact**: Saves session state. **SessionStart**: Restores session context.
|
||||
Prompt templates for different task types: `coding`, `conventions`, `security`, `verify`, plus YAML plan templates (`bug-fix`, `code-review`, `new-feature`, `refactor`, etc.)
|
||||
|
||||
## Other Directories
|
||||
## Key Patterns
|
||||
|
||||
- **`codex/`** — 13 Codex plugins mirroring Claude structure plus ethics, guardrails, perf, issue, coolify, awareness
|
||||
- **`agents/`** — 13 specialist agent categories (design, engineering, marketing, product, testing, etc.) with example configs and system prompts
|
||||
- **`google/gemini-cli/`** — Gemini CLI extension (TypeScript, `npm run build`)
|
||||
- **`google/mcp/`** — HTTP MCP server exposing `core_go_test`, `core_dev_health`, `core_dev_commit`
|
||||
- **`docs/`** — `architecture.md` (deep dive), `development.md` (comprehensive dev guide), `docs/plans/` (design documents)
|
||||
- **`scripts/`** — Environment setup scripts (`install-core.sh`, `install-deps.sh`, `agent-runner.sh`, etc.)
|
||||
### Shared Paths (pkg/agentic/paths.go)
|
||||
|
||||
All paths use `CORE_WORKSPACE` env var, fallback `~/Code/.core`:
|
||||
- `WorkspaceRoot()` — agent workspaces
|
||||
- `CoreRoot()` — ecosystem config
|
||||
- `PlansRoot()` — agent plans
|
||||
- `AgentName()` — `AGENT_NAME` env or hostname detection
|
||||
- `GitHubOrg()` — `GITHUB_ORG` env or "dAppCore"
|
||||
|
||||
### Error Handling
|
||||
|
||||
`coreerr.E("pkg.Method", "message", err)` from go-log. Always 3 args. NEVER `fmt.Errorf`.
|
||||
|
||||
### File I/O
|
||||
|
||||
`coreio.Local.Read/Write/EnsureDir` from go-io. `WriteMode(path, content, 0600)` for sensitive files. NEVER `os.ReadFile/WriteFile`.
|
||||
|
||||
### HTTP Responses
|
||||
|
||||
Always check `err != nil` BEFORE accessing `resp.StatusCode`. Split into two checks.
|
||||
|
||||
## Plugin (claude/core/)
|
||||
|
||||
The Claude Code plugin provides:
|
||||
- **MCP server** via `mcp.json` (auto-registers core-agent)
|
||||
- **Hooks** via `hooks.json` (PostToolUse inbox notifications, auto-format, debug warnings)
|
||||
- **Agents**: `agent-task-code-review`, `agent-task-code-simplifier`
|
||||
- **Commands**: dispatch, status, review, recall, remember, scan, etc.
|
||||
- **Skills**: security review, architecture review, test analysis, etc.
|
||||
|
||||
## Testing Conventions
|
||||
|
||||
### Go
|
||||
|
||||
Uses `testify/assert` and `testify/require`. Name tests with suffixes:
|
||||
- `_Good` — happy path
|
||||
- `_Bad` — expected error conditions
|
||||
- `_Ugly` — panics and edge cases
|
||||
|
||||
Use `require` for preconditions (stops on failure), `assert` for verifications (reports all failures).
|
||||
|
||||
### PHP
|
||||
|
||||
Pest with Orchestra Testbench. Feature tests use `RefreshDatabase`. Helpers: `createWorkspace()`, `createApiKey($workspace, ...)`.
|
||||
- Use `testify/assert` + `testify/require`
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- **UK English**: colour, organisation, centre, licence, behaviour
|
||||
- **Go**: standard `gofmt`, errors via `core.E("scope.Method", "what failed", err)`
|
||||
- **PHP**: `declare(strict_types=1)`, full type hints, PSR-12 via Pint, Pest syntax for tests
|
||||
- **Shell**: `#!/bin/bash`, JSON input via `jq`, output `{"decision": "approve"|"block", "message": "..."}`
|
||||
- **Commits**: conventional — `type(scope): description` (e.g. `feat(lifecycle): add exponential backoff`)
|
||||
- **Licence**: EUPL-1.2 CIC
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Tool | Version | Purpose |
|
||||
|------|---------|---------|
|
||||
| Go | 1.26+ | Go packages, CLI, MCP servers |
|
||||
| PHP | 8.2+ | Laravel package, Pest tests |
|
||||
| Composer | 2.x | PHP dependencies |
|
||||
| `core` CLI | latest | Wraps Go/PHP toolchains (enforced by hooks) |
|
||||
| `jq` | any | JSON parsing in shell hooks |
|
||||
|
||||
Go module is `forge.lthn.ai/core/agent`, participates in a Go workspace (`go.work`) resolving all `forge.lthn.ai/core/*` dependencies locally.
|
||||
- **UK English**: colour, organisation, centre, initialise
|
||||
- **Commits**: `type(scope): description` with `Co-Authored-By: Virgil <virgil@lethean.io>`
|
||||
- **Licence**: EUPL-1.2
|
||||
- **SPDX**: `// SPDX-License-Identifier: EUPL-1.2` on every file
|
||||
|
|
|
|||
134
pkg/agentic/paths_test.go
Normal file
134
pkg/agentic/paths_test.go
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCoreRoot_Good_EnvVar(t *testing.T) {
|
||||
t.Setenv("CORE_WORKSPACE", "/tmp/test-core")
|
||||
assert.Equal(t, "/tmp/test-core", CoreRoot())
|
||||
}
|
||||
|
||||
func TestCoreRoot_Good_Fallback(t *testing.T) {
|
||||
t.Setenv("CORE_WORKSPACE", "")
|
||||
home, _ := os.UserHomeDir()
|
||||
assert.Equal(t, home+"/Code/.core", CoreRoot())
|
||||
}
|
||||
|
||||
func TestWorkspaceRoot_Good(t *testing.T) {
|
||||
t.Setenv("CORE_WORKSPACE", "/tmp/test-core")
|
||||
assert.Equal(t, "/tmp/test-core/workspace", WorkspaceRoot())
|
||||
}
|
||||
|
||||
func TestPlansRoot_Good(t *testing.T) {
|
||||
t.Setenv("CORE_WORKSPACE", "/tmp/test-core")
|
||||
assert.Equal(t, "/tmp/test-core/plans", PlansRoot())
|
||||
}
|
||||
|
||||
func TestAgentName_Good_EnvVar(t *testing.T) {
|
||||
t.Setenv("AGENT_NAME", "clotho")
|
||||
assert.Equal(t, "clotho", AgentName())
|
||||
}
|
||||
|
||||
func TestAgentName_Good_Fallback(t *testing.T) {
|
||||
t.Setenv("AGENT_NAME", "")
|
||||
name := AgentName()
|
||||
assert.True(t, name == "cladius" || name == "charon", "expected cladius or charon, got %s", name)
|
||||
}
|
||||
|
||||
func TestGitHubOrg_Good_EnvVar(t *testing.T) {
|
||||
t.Setenv("GITHUB_ORG", "myorg")
|
||||
assert.Equal(t, "myorg", GitHubOrg())
|
||||
}
|
||||
|
||||
func TestGitHubOrg_Good_Fallback(t *testing.T) {
|
||||
t.Setenv("GITHUB_ORG", "")
|
||||
assert.Equal(t, "dAppCore", GitHubOrg())
|
||||
}
|
||||
|
||||
func TestBaseAgent_Good(t *testing.T) {
|
||||
assert.Equal(t, "claude", baseAgent("claude:opus"))
|
||||
assert.Equal(t, "claude", baseAgent("claude:haiku"))
|
||||
assert.Equal(t, "gemini", baseAgent("gemini:flash"))
|
||||
assert.Equal(t, "codex", baseAgent("codex"))
|
||||
}
|
||||
|
||||
func TestExtractPRNumber_Good(t *testing.T) {
|
||||
assert.Equal(t, 123, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/123"))
|
||||
assert.Equal(t, 1, extractPRNumber("https://forge.lthn.ai/core/agent/pulls/1"))
|
||||
}
|
||||
|
||||
func TestExtractPRNumber_Bad_Empty(t *testing.T) {
|
||||
assert.Equal(t, 0, extractPRNumber(""))
|
||||
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/agent/pulls/"))
|
||||
}
|
||||
|
||||
func TestTruncate_Good(t *testing.T) {
|
||||
assert.Equal(t, "hello", truncate("hello", 10))
|
||||
assert.Equal(t, "hel...", truncate("hello world", 3))
|
||||
}
|
||||
|
||||
func TestCountFindings_Good(t *testing.T) {
|
||||
assert.Equal(t, 0, countFindings("No findings"))
|
||||
assert.Equal(t, 2, countFindings("- Issue one\n- Issue two\nSummary"))
|
||||
assert.Equal(t, 1, countFindings("⚠ Warning here"))
|
||||
}
|
||||
|
||||
func TestParseRetryAfter_Good(t *testing.T) {
|
||||
d := parseRetryAfter("please try after 4 minutes and 56 seconds")
|
||||
assert.InDelta(t, 296.0, d.Seconds(), 1.0)
|
||||
}
|
||||
|
||||
func TestParseRetryAfter_Good_MinutesOnly(t *testing.T) {
|
||||
d := parseRetryAfter("try after 5 minutes")
|
||||
assert.InDelta(t, 300.0, d.Seconds(), 1.0)
|
||||
}
|
||||
|
||||
func TestParseRetryAfter_Bad_NoMatch(t *testing.T) {
|
||||
d := parseRetryAfter("some random text")
|
||||
assert.InDelta(t, 300.0, d.Seconds(), 1.0) // defaults to 5 min
|
||||
}
|
||||
|
||||
func TestResolveHost_Good(t *testing.T) {
|
||||
assert.Equal(t, "10.69.69.165:9101", resolveHost("charon"))
|
||||
assert.Equal(t, "127.0.0.1:9101", resolveHost("cladius"))
|
||||
assert.Equal(t, "127.0.0.1:9101", resolveHost("local"))
|
||||
}
|
||||
|
||||
func TestResolveHost_Good_CustomPort(t *testing.T) {
|
||||
assert.Equal(t, "192.168.1.1:9101", resolveHost("192.168.1.1"))
|
||||
assert.Equal(t, "192.168.1.1:8080", resolveHost("192.168.1.1:8080"))
|
||||
}
|
||||
|
||||
func TestExtractJSONField_Good(t *testing.T) {
|
||||
json := `[{"url":"https://github.com/dAppCore/go-io/pull/1"}]`
|
||||
assert.Equal(t, "https://github.com/dAppCore/go-io/pull/1", extractJSONField(json, "url"))
|
||||
}
|
||||
|
||||
func TestExtractJSONField_Bad_Missing(t *testing.T) {
|
||||
assert.Equal(t, "", extractJSONField(`{"name":"test"}`, "url"))
|
||||
assert.Equal(t, "", extractJSONField("", "url"))
|
||||
}
|
||||
|
||||
func TestValidPlanStatus_Good(t *testing.T) {
|
||||
assert.True(t, validPlanStatus("draft"))
|
||||
assert.True(t, validPlanStatus("in_progress"))
|
||||
assert.True(t, validPlanStatus("draft"))
|
||||
}
|
||||
|
||||
func TestValidPlanStatus_Bad(t *testing.T) {
|
||||
assert.False(t, validPlanStatus("invalid"))
|
||||
assert.False(t, validPlanStatus(""))
|
||||
}
|
||||
|
||||
func TestGeneratePlanID_Good(t *testing.T) {
|
||||
id := generatePlanID("Fix the login bug in auth service")
|
||||
assert.True(t, len(id) > 0)
|
||||
assert.True(t, strings.Contains(id, "fix-the-login-bug"))
|
||||
}
|
||||
84
pkg/prompts/prompts_test.go
Normal file
84
pkg/prompts/prompts_test.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package prompts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTemplate_Good_YAML(t *testing.T) {
|
||||
content, err := Template("bug-fix")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "name:")
|
||||
}
|
||||
|
||||
func TestTemplate_Good_MD(t *testing.T) {
|
||||
content, err := Template("prod-push-polish")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, len(content) > 0)
|
||||
}
|
||||
|
||||
func TestTemplate_Bad_NotFound(t *testing.T) {
|
||||
_, err := Template("nonexistent-template")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPersona_Good(t *testing.T) {
|
||||
content, err := Persona("engineering/security-developer")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "name:")
|
||||
assert.Contains(t, content, "Security")
|
||||
}
|
||||
|
||||
func TestPersona_Good_SMM(t *testing.T) {
|
||||
content, err := Persona("smm/security-developer")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "OAuth")
|
||||
}
|
||||
|
||||
func TestPersona_Bad_NotFound(t *testing.T) {
|
||||
_, err := Persona("nonexistent/persona")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestListTemplates_Good(t *testing.T) {
|
||||
templates := ListTemplates()
|
||||
assert.True(t, len(templates) >= 10, "expected at least 10 templates, got %d", len(templates))
|
||||
assert.Contains(t, templates, "bug-fix")
|
||||
assert.Contains(t, templates, "code-review")
|
||||
}
|
||||
|
||||
func TestListPersonas_Good(t *testing.T) {
|
||||
personas := ListPersonas()
|
||||
assert.True(t, len(personas) >= 90, "expected at least 90 personas, got %d", len(personas))
|
||||
|
||||
// Check cross-domain security-developer exists
|
||||
hasEngSec := false
|
||||
hasSMMSec := false
|
||||
for _, p := range personas {
|
||||
if p == "engineering/security-developer" {
|
||||
hasEngSec = true
|
||||
}
|
||||
if p == "smm/security-developer" {
|
||||
hasSMMSec = true
|
||||
}
|
||||
}
|
||||
assert.True(t, hasEngSec, "engineering/security-developer not found")
|
||||
assert.True(t, hasSMMSec, "smm/security-developer not found")
|
||||
}
|
||||
|
||||
func TestListPersonas_Good_NoPrefixDuplication(t *testing.T) {
|
||||
for _, p := range ListPersonas() {
|
||||
parts := strings.Split(p, "/")
|
||||
if len(parts) == 2 {
|
||||
domain := parts[0]
|
||||
file := parts[1]
|
||||
assert.False(t, strings.HasPrefix(file, domain+"-"),
|
||||
"persona %q has redundant domain prefix in filename", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue