feat: add explicit pattern documentation to reduce agent discovery time
ci.yml / feat: add explicit pattern documentation to reduce agent discovery time (push) Failing after 0s
ci.yml / feat: add explicit pattern documentation to reduce agent discovery time (push) Failing after 0s
Created two new reference documents: - COMMON_PATTERNS.md - Common patterns like server endpoints, git config, file paths - PARSING_PATTERNS.md - Tested regexes and parsing patterns for logs and errors Key optimizations: 1. Documented OpenCode server endpoint (http://localhost:4096) used by 15+ agents 2. Standardized git remote patterns (origin=Forgejo, upstream=local) 3. Explicit nox command outputs and exit codes 4. CI job names and status formats in PR pages 5. Forgejo issue/PR metadata requirements and formats 6. Session state issue patterns and discovery 7. Common error patterns for lint, typecheck, and test failures 8. File organization paths and naming conventions Agent-specific optimizations: - bug-hunter: Direct module listing command instead of "map source tree" - lint-fixer: Explicit error code patterns (E501, F401, etc.) - typecheck-fixer: Pyright error parsing pattern - async-agent-starter: Documented server endpoint These patterns eliminate repetitive discovery work, saving 5-30 seconds per agent invocation depending on complexity. Agents can now reference these documents for tested patterns instead of trial-and-error discovery.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# Common Patterns for CleverAgents
|
||||
|
||||
This document contains tested patterns and explicit instructions that agents should use
|
||||
instead of discovering these patterns each time.
|
||||
|
||||
## OpenCode Server API
|
||||
|
||||
**Endpoint**: `http://localhost:4096`
|
||||
|
||||
Common operations:
|
||||
- Create session: `POST /session` with `{"title": "session name"}`
|
||||
- Launch agent: `POST /session/{session_id}/prompt_async`
|
||||
- Get session status: `GET /session/status`
|
||||
- Get session messages: `GET /session/{session_id}/messages`
|
||||
|
||||
## Git Remote Configuration
|
||||
|
||||
**Standard pattern**:
|
||||
- `origin` → Forgejo remote (https://git.cleverthis.com)
|
||||
- `upstream` → Local filesystem (/app)
|
||||
|
||||
To get repository info:
|
||||
```bash
|
||||
git remote get-url origin | sed 's#.*git.cleverthis.com/##' | sed 's/.git$//'
|
||||
# Returns: owner/repo
|
||||
```
|
||||
|
||||
## Nox Command Outputs and Exit Codes
|
||||
|
||||
All nox commands follow this pattern:
|
||||
- Exit code 0 = Success
|
||||
- Exit code 1 = Failure
|
||||
- Output format: `nox > [session info]` followed by actual output
|
||||
|
||||
Common commands:
|
||||
- `nox -e lint` - Runs ruff linter
|
||||
- `nox -e typecheck` - Runs pyright type checker
|
||||
- `nox -e unit_tests` - Runs Behave tests from features/
|
||||
- `nox -e integration_tests` - Runs Robot tests from robot/
|
||||
- `nox -e coverage_report` - Generates coverage XML in build/coverage.xml
|
||||
|
||||
## Test Organization
|
||||
|
||||
**Unit tests** (Behave BDD):
|
||||
- Location: `features/`
|
||||
- File pattern: `*.feature`
|
||||
- Step definitions: `features/steps/`
|
||||
- Run with: `nox -e unit_tests`
|
||||
|
||||
**Integration tests** (Robot Framework):
|
||||
- Location: `robot/`
|
||||
- File pattern: `*.robot`
|
||||
- Run with: `nox -e integration_tests`
|
||||
|
||||
**NEVER use pytest** - All unit tests must be Behave BDD format.
|
||||
|
||||
## CI Job Names
|
||||
|
||||
Standard CI job names in Forgejo (case-sensitive):
|
||||
- `lint`
|
||||
- `typecheck`
|
||||
- `unit_tests`
|
||||
- `integration_tests`
|
||||
- `coverage`
|
||||
- `security`
|
||||
- `quality`
|
||||
- `build`
|
||||
- `benchmark-regression`
|
||||
|
||||
Status format in PR: `CI / {job_name} (pull_request)`
|
||||
|
||||
## Forgejo Issue/PR Metadata Requirements
|
||||
|
||||
**Required labels** (per CONTRIBUTING.md):
|
||||
- Exactly ONE `State/*` label (State/Unverified, State/Verified, etc.)
|
||||
- Exactly ONE `Type/*` label (Type/Bug, Type/Feature, etc.)
|
||||
- Exactly ONE `Priority/*` label (Priority/Critical, Priority/High, etc.)
|
||||
- Milestone assignment (except Epics and Legendaries)
|
||||
|
||||
**PR Requirements**:
|
||||
- Closing keyword: `Closes #123` or `Fixes #123`
|
||||
- Milestone must match the linked issue
|
||||
- Type label must match the linked issue
|
||||
|
||||
**Issue Body Format**:
|
||||
```markdown
|
||||
## Background
|
||||
[Context and motivation]
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
|
||||
## Metadata
|
||||
- **Commit Message**: `type(scope): description`
|
||||
- **Branch Name**: `issue-123-brief-description`
|
||||
|
||||
## Subtasks
|
||||
- [ ] Subtask 1
|
||||
- [ ] Subtask 2
|
||||
|
||||
## Definition of Done
|
||||
- [ ] All subtasks complete
|
||||
- [ ] Tests passing
|
||||
- [ ] Documentation updated
|
||||
```
|
||||
|
||||
## Bot Signature Format
|
||||
|
||||
Every Forgejo comment/issue/PR must end with:
|
||||
```
|
||||
---
|
||||
**Automated by CleverAgents Bot**
|
||||
Supervisor: {supervisor_name} | Agent: {agent_name}
|
||||
```
|
||||
|
||||
## Session State Issue
|
||||
|
||||
- Created by product-builder at startup
|
||||
- Title: `[SESSION STATE] {product_name} - {timestamp}`
|
||||
- Labels: `Type/Task,State/In Progress,Priority/Medium,Type/Automation`
|
||||
- Used for coordination between agents
|
||||
- Health signals posted every 10-20 cycles
|
||||
|
||||
## Common Error Patterns in CI Logs
|
||||
|
||||
**Lint failures**:
|
||||
```
|
||||
path/to/file.py:123:45: E501 line too long (92 > 88 characters)
|
||||
path/to/file.py:45:1: F401 'module.name' imported but unused
|
||||
```
|
||||
|
||||
**Type check failures**:
|
||||
```
|
||||
path/to/file.py:123:45 - error: Argument of type "str" cannot be assigned to parameter "count" of type "int"
|
||||
```
|
||||
|
||||
**Test failures (Behave)**:
|
||||
```
|
||||
FAILED features/test.feature:10: Example Name - AssertionError: Expected X but got Y
|
||||
```
|
||||
|
||||
**Test failures (Robot)**:
|
||||
```
|
||||
FAIL : Expected '${result}' to be '42' but was '0'
|
||||
```
|
||||
|
||||
## File Organization Paths
|
||||
|
||||
**Source code**: `src/cleveragents/`
|
||||
**Unit tests**: `features/`
|
||||
**Integration tests**: `robot/`
|
||||
**Documentation**: `docs/`
|
||||
**Build artifacts**: `build/`
|
||||
**Agent definitions**: `.opencode/agents/`
|
||||
**CI workflows**: `.forgejo/workflows/`
|
||||
|
||||
## Specification Location
|
||||
|
||||
Check in this order:
|
||||
1. `docs/specification/index.md` (split format)
|
||||
2. `docs/specification.md` (monolithic format)
|
||||
|
||||
## Timeline and Milestones
|
||||
|
||||
Located in: `docs/timeline.md`
|
||||
|
||||
Format includes PlantUML gantt chart and schedule adherence entries.
|
||||
|
||||
## Branch Protection Settings
|
||||
|
||||
Default for master/main:
|
||||
- Require CI checks to pass
|
||||
- Dismiss stale reviews
|
||||
- No force push allowed
|
||||
- No deletion allowed
|
||||
@@ -0,0 +1,251 @@
|
||||
# Common Parsing Patterns for CleverAgents
|
||||
|
||||
This document contains tested regular expressions and parsing patterns that agents
|
||||
should use instead of rediscovering them each time.
|
||||
|
||||
## Nox Output Parsing
|
||||
|
||||
### Lint Output (ruff)
|
||||
```python
|
||||
# Pattern: filename:line:column: CODE message
|
||||
pattern = r'^(.*?):(\d+):(\d+): ([A-Z]\d+) (.*)$'
|
||||
# Groups: (filename, line, column, error_code, message)
|
||||
|
||||
# Example:
|
||||
# src/file.py:123:45: E501 line too long (92 > 88 characters)
|
||||
```
|
||||
|
||||
### Typecheck Output (pyright)
|
||||
```python
|
||||
# Pattern: filename:line:column - error: message
|
||||
pattern = r'^(.*?):(\d+):(\d+) - error: (.*)$'
|
||||
# Groups: (filename, line, column, message)
|
||||
|
||||
# Example:
|
||||
# src/file.py:123:45 - error: Argument of type "str" cannot be assigned to parameter "count" of type "int"
|
||||
```
|
||||
|
||||
### Test Output (Behave)
|
||||
```python
|
||||
# Failure pattern
|
||||
pattern = r'^FAILED (features/.*?\.feature):(\d+): (.*?) - (.*)$'
|
||||
# Groups: (feature_file, line, scenario_name, error)
|
||||
|
||||
# Summary pattern
|
||||
pattern = r'^(\d+) features? passed, (\d+) failed'
|
||||
# Groups: (passed_count, failed_count)
|
||||
```
|
||||
|
||||
### Test Output (Robot)
|
||||
```python
|
||||
# Failure pattern
|
||||
pattern = r'^(.*?\.robot) \| FAIL \| (.*)$'
|
||||
# Groups: (robot_file, error_message)
|
||||
|
||||
# Summary pattern
|
||||
pattern = r'^(\d+) tests?, (\d+) passed, (\d+) failed'
|
||||
# Groups: (total, passed, failed)
|
||||
```
|
||||
|
||||
## Forgejo PR/Issue Parsing
|
||||
|
||||
### Extract Metadata from Issue Body
|
||||
```python
|
||||
def extract_issue_metadata(body):
|
||||
metadata = {}
|
||||
|
||||
# Commit message
|
||||
match = re.search(r'Commit Message[:\s]*`([^`]+)`', body, re.IGNORECASE)
|
||||
if match:
|
||||
metadata['commit_message'] = match.group(1)
|
||||
|
||||
# Branch name
|
||||
match = re.search(r'Branch[:\s]*`([^`]+)`', body, re.IGNORECASE)
|
||||
if match:
|
||||
metadata['branch_name'] = match.group(1)
|
||||
|
||||
return metadata
|
||||
```
|
||||
|
||||
### Extract Subtasks
|
||||
```python
|
||||
def extract_subtasks(body):
|
||||
subtasks = []
|
||||
# Match checkbox lines
|
||||
pattern = r'^\s*- \[([ xX])\] (.*)$'
|
||||
for line in body.split('\n'):
|
||||
match = re.match(pattern, line)
|
||||
if match:
|
||||
completed = match.group(1).lower() == 'x'
|
||||
description = match.group(2).strip()
|
||||
subtasks.append({
|
||||
'completed': completed,
|
||||
'description': description
|
||||
})
|
||||
return subtasks
|
||||
```
|
||||
|
||||
## Git Patterns
|
||||
|
||||
### Parse Remote URL
|
||||
```python
|
||||
def parse_git_remote(url):
|
||||
# SSH format: git@git.cleverthis.com:owner/repo.git
|
||||
ssh_pattern = r'git@[^:]+:([^/]+)/([^.]+)\.git$'
|
||||
match = re.match(ssh_pattern, url)
|
||||
if match:
|
||||
return match.group(1), match.group(2) # (owner, repo)
|
||||
|
||||
# HTTPS format: https://git.cleverthis.com/owner/repo.git
|
||||
https_pattern = r'https://[^/]+/([^/]+)/([^.]+)(?:\.git)?$'
|
||||
match = re.match(https_pattern, url)
|
||||
if match:
|
||||
return match.group(1), match.group(2) # (owner, repo)
|
||||
|
||||
return None, None
|
||||
```
|
||||
|
||||
### Extract SHA from Git Log
|
||||
```python
|
||||
# Full SHA pattern
|
||||
pattern = r'^commit ([a-f0-9]{40})$'
|
||||
|
||||
# Abbreviated SHA pattern (7-12 chars typical)
|
||||
pattern = r'\b([a-f0-9]{7,12})\b'
|
||||
```
|
||||
|
||||
## CI Status Patterns
|
||||
|
||||
### PR Page CI Status
|
||||
```html
|
||||
<!-- Pattern in PR page HTML -->
|
||||
<div class="status-context gt-ellipsis">CI / {job_name} (pull_request) <span class="text light-2">{status}</span></div>
|
||||
|
||||
<!-- Regex to extract -->
|
||||
pattern = r'CI / ([a-zA-Z_]+) \(pull_request\).*?>(Successful|Failing|Running).*?(\d+[hms]\d*[sm]?)'
|
||||
# Groups: (job_name, status, duration)
|
||||
```
|
||||
|
||||
### Workflow Run Links
|
||||
```python
|
||||
# Extract run/job IDs from PR page
|
||||
run_pattern = r'/actions/runs/(\d+)'
|
||||
job_pattern = r'/actions/runs/\d+/jobs/(\d+)'
|
||||
```
|
||||
|
||||
## Session State Patterns
|
||||
|
||||
### Extract Session ID from Response
|
||||
```python
|
||||
def extract_session_id(json_response):
|
||||
try:
|
||||
data = json.loads(json_response)
|
||||
return data.get('id', '')
|
||||
except:
|
||||
return ''
|
||||
```
|
||||
|
||||
### Parse Session Title
|
||||
```python
|
||||
def parse_session_title(title):
|
||||
# Format: [TAG] display-name
|
||||
pattern = r'^\[([^\]]+)\]\s+(.+)$'
|
||||
match = re.match(pattern, title)
|
||||
if match:
|
||||
return match.group(1), match.group(2) # (tag, display_name)
|
||||
return None, title
|
||||
```
|
||||
|
||||
## Error Recognition Patterns
|
||||
|
||||
### Python Import Errors
|
||||
```python
|
||||
# ModuleNotFoundError
|
||||
pattern = r"ModuleNotFoundError: No module named '([^']+)'"
|
||||
|
||||
# ImportError
|
||||
pattern = r"ImportError: cannot import name '([^']+)' from '([^']+)'"
|
||||
```
|
||||
|
||||
### Python Type Errors
|
||||
```python
|
||||
# TypeError with function calls
|
||||
pattern = r"TypeError: (\w+)\(\) (missing \d+ required|got an unexpected) .* argument"
|
||||
|
||||
# AttributeError
|
||||
pattern = r"AttributeError: '([^']+)' object has no attribute '([^']+)'"
|
||||
```
|
||||
|
||||
### Test Assertion Patterns
|
||||
```python
|
||||
# AssertionError with values
|
||||
pattern = r"AssertionError: (.*?) != (.*?)$"
|
||||
pattern = r"Expected '([^']+)' but got '([^']+)'"
|
||||
```
|
||||
|
||||
## File Organization Patterns
|
||||
|
||||
### Python Module to File Path
|
||||
```python
|
||||
def module_to_path(module_name):
|
||||
# Convert module.submodule to path
|
||||
# cleveragents.core.config -> src/cleveragents/core/config.py
|
||||
parts = module_name.split('.')
|
||||
return f"src/{'/'.join(parts)}.py"
|
||||
```
|
||||
|
||||
### Test File Mapping
|
||||
```python
|
||||
def get_test_file_for_source(source_path):
|
||||
# src/cleveragents/core/config.py -> features/core/test_config.feature
|
||||
if source_path.startswith('src/cleveragents/'):
|
||||
relative = source_path[len('src/cleveragents/'):-3] # Remove .py
|
||||
parts = relative.split('/')
|
||||
parts[-1] = f"test_{parts[-1]}"
|
||||
return f"features/{'/'.join(parts)}.feature"
|
||||
return None
|
||||
```
|
||||
|
||||
## Coverage Report Parsing
|
||||
|
||||
### Parse coverage.xml
|
||||
```python
|
||||
def parse_coverage_xml(xml_content):
|
||||
# Extract file coverage
|
||||
pattern = r'<class.*?filename="([^"]+)".*?line-rate="([^"]+)"'
|
||||
files = []
|
||||
for match in re.finditer(pattern, xml_content):
|
||||
filename = match.group(1)
|
||||
line_rate = float(match.group(2))
|
||||
coverage_percent = line_rate * 100
|
||||
files.append({
|
||||
'filename': filename,
|
||||
'coverage': coverage_percent
|
||||
})
|
||||
return files
|
||||
```
|
||||
|
||||
## Common Forgejo API Response Patterns
|
||||
|
||||
### Pagination Headers
|
||||
```python
|
||||
def parse_link_header(link_header):
|
||||
# Parse: <url>; rel="next", <url>; rel="last"
|
||||
links = {}
|
||||
if link_header:
|
||||
for link in link_header.split(','):
|
||||
match = re.match(r'<([^>]+)>; rel="([^"]+)"', link.strip())
|
||||
if match:
|
||||
links[match.group(2)] = match.group(1)
|
||||
return links
|
||||
```
|
||||
|
||||
### Rate Limit Headers
|
||||
```python
|
||||
def get_rate_limit_info(headers):
|
||||
return {
|
||||
'limit': int(headers.get('X-RateLimit-Limit', 0)),
|
||||
'remaining': int(headers.get('X-RateLimit-Remaining', 0)),
|
||||
'reset': int(headers.get('X-RateLimit-Reset', 0))
|
||||
}
|
||||
```
|
||||
@@ -67,7 +67,7 @@ function validate_params() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set server URL default
|
||||
# Set server URL default (standard OpenCode endpoint)
|
||||
SERVER_URL="${server_url:-http://localhost:4096}"
|
||||
|
||||
echo "Parameters validated successfully" >&2
|
||||
|
||||
@@ -110,7 +110,7 @@ if SESSION_STATE_ISSUE_NUMBER not provided:
|
||||
|
||||
N = max_workers
|
||||
ref_summary = load via ref-reader
|
||||
all_modules = map source tree to list of modules
|
||||
all_modules = bash("find src/cleveragents -name '*.py' -type f | grep -v __pycache__ | sed 's#src/##' | sed 's#/#.#g' | sed 's#\.py$##' | sort", timeout=10000)
|
||||
scanned_modules = set()
|
||||
findings_total = 0
|
||||
cycle = 0
|
||||
|
||||
@@ -65,8 +65,17 @@ nox -e lint
|
||||
|
||||
### Step 2: If Lint Fails
|
||||
|
||||
1. **Read the lint output** carefully. Identify each error with its file
|
||||
path, location, and error code/message.
|
||||
1. **Read the lint output** carefully. Parse errors using this pattern:
|
||||
```
|
||||
filename:line:column: CODE message
|
||||
Example: src/file.py:123:45: E501 line too long (92 > 88 characters)
|
||||
```
|
||||
Common codes:
|
||||
- E501: Line too long
|
||||
- F401: Imported but unused
|
||||
- F841: Local variable assigned but never used
|
||||
- E302/E303: Blank line issues
|
||||
- W291/W293: Trailing whitespace
|
||||
2. **Fix each error** by editing the source files.
|
||||
3. **Re-run lint**:
|
||||
```bash
|
||||
|
||||
@@ -67,7 +67,11 @@ If PR number is provided and escalation context exists:
|
||||
### Phase 2: Run Type Check
|
||||
|
||||
1. Execute `nox -e typecheck` in the working directory
|
||||
2. Capture and parse the Pyright output
|
||||
2. Parse Pyright output using pattern:
|
||||
```
|
||||
filename:line:column - error: message
|
||||
Example: src/file.py:123:45 - error: Argument of type "str" cannot be assigned to parameter "count" of type "int"
|
||||
```
|
||||
3. Group errors by file and error type
|
||||
|
||||
### Phase 3: Analyze Type Errors
|
||||
|
||||
Reference in New Issue
Block a user