Removing intermediate files
CI / test (pull_request) Successful in 18s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 41s
CI / test (pull_request) Successful in 18s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 41s
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
# Design: doc-map input for path-scoped design doc injection (Issue #137)
|
||||
|
||||
## Problem
|
||||
|
||||
review-bot can inject context via `patterns-repo` (external VCS repos) and `conventions-file`
|
||||
(a single file from the reviewed repo). There is no mechanism to inject local repo documentation
|
||||
files scoped to the paths changed in a PR.
|
||||
|
||||
First consumer: `grgl/gargoyle#778` needs a "doc adherence" reviewer that checks code against the
|
||||
module's governing design doc, without injecting every doc in the tree.
|
||||
|
||||
## Approach
|
||||
|
||||
### New: `doc-map` input
|
||||
|
||||
A `.review-bot/doc-map.yml` config file in the reviewed repo maps source path globs to governing
|
||||
design docs. review-bot reads the map, intersects it with changed PR paths, and injects only the
|
||||
relevant docs into the system prompt.
|
||||
|
||||
### Config format
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- paths:
|
||||
- "lib/gargoyle/engine/signal_risk/**"
|
||||
docs:
|
||||
- docs/domain/contexts/risk/risk-controls.md
|
||||
- paths:
|
||||
- "lib/gargoyle/trading/**"
|
||||
docs:
|
||||
- docs/domain/contexts/trading/
|
||||
```
|
||||
|
||||
- `paths` — glob patterns (including `**`) matched against changed file paths in the PR
|
||||
- `docs` — file paths or directory paths (all `.md` files under a directory) to inject
|
||||
- Docs are deduplicated across mappings
|
||||
|
||||
### Architecture
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `review/docmap.go` | YAML parsing, glob matching with `**` support, doc loading via VCS |
|
||||
| `cmd/review-bot/main.go` | Step 6c: parses config, intersects with changed files, calls LoadMatchingDocs |
|
||||
| `budget/budget.go` | New `DesignDocs` section — injected after Conventions in system prompt |
|
||||
| `action.yml` | `doc-map` and `doc-map-max-bytes` inputs, wired to `DOC_MAP_FILE`/`DOC_MAP_MAX_BYTES` |
|
||||
|
||||
### Doc file loading
|
||||
|
||||
- The `doc-map` YAML file is read from the local workspace (like `system-prompt-file`).
|
||||
- Doc files listed in the config are fetched via VCS API (same as `conventions-file`),
|
||||
enabling them to be loaded from any branch without a local checkout.
|
||||
- `GetAllFilesInPath` is tried first; if it returns files, they are treated as a directory listing.
|
||||
If it returns empty, `GetFileContent` is tried as a fallback (single file).
|
||||
|
||||
### Glob matching
|
||||
|
||||
`**` is implemented by splitting patterns and paths on `/`, then matching segment-by-segment.
|
||||
A `**` segment consumes zero or more path segments (not just one level like `*`).
|
||||
|
||||
### Budget integration
|
||||
|
||||
`DesignDocs` is added to `budget.Sections` between `Conventions` and `FileContext`.
|
||||
Trim order: Patterns → Conventions → DesignDocs → FileContext → Diff.
|
||||
Design docs appear in the system prompt under `## Design Documents`.
|
||||
|
||||
### Context size guard
|
||||
|
||||
Default: 100 KB. Configurable via `--doc-map-max-bytes` / `DOC_MAP_MAX_BYTES`.
|
||||
Truncation is noted inline with a `⚠️` message.
|
||||
|
||||
## Error handling
|
||||
|
||||
| Situation | Behavior |
|
||||
|-----------|----------|
|
||||
| `--doc-map` file not found | Fatal error (like `--system-prompt-file`) |
|
||||
| `--doc-map` file invalid YAML | Fatal error with descriptive message |
|
||||
| Unknown YAML keys | Log warning, continue |
|
||||
| Doc file not found in VCS | Log warning, skip |
|
||||
| Doc directory empty or no `.md` files | Log debug, skip |
|
||||
| Total size exceeds limit | Truncate with notice, log warning |
|
||||
| No changed paths match any mapping | No docs injected, review runs normally |
|
||||
| `paths` or `docs` list empty in a mapping | Skip that mapping |
|
||||
@@ -1,334 +0,0 @@
|
||||
# Design: Role-based Review Personas (Issue #51)
|
||||
|
||||
> **Note:** This design was revised during implementation to use JSON instead of YAML
|
||||
> to maintain the repository's zero-external-dependencies convention. All persona
|
||||
> files use JSON format. See "Design Revision" section at the end for details.
|
||||
|
||||
## Problem
|
||||
|
||||
Current review-bot performs generic code review. Every reviewer (regardless of `reviewer-name`) uses the same base prompt and evaluates the same concerns. This leads to:
|
||||
|
||||
1. **Redundancy** — Two reviewers (e.g., GPT + Claude twins) often flag identical issues
|
||||
2. **Gaps** — Generic reviewers miss specialized concerns (security, domain logic, architecture)
|
||||
3. **Noise** — NITs about style mixed with critical security findings
|
||||
4. **No ownership** — Findings lack clear domain attribution
|
||||
|
||||
## Constraints
|
||||
|
||||
- Must work with existing CLI flags and CI workflow patterns
|
||||
- Must not break backwards compatibility (existing configs still work)
|
||||
- Must integrate cleanly with the budget system (personas add to context)
|
||||
- Multiple personas running in parallel must not interfere with each other
|
||||
- Each persona must have clear scope boundaries (no duplication)
|
||||
|
||||
## Proposed Approach
|
||||
|
||||
### 1. Persona Definition
|
||||
|
||||
A persona is a named review role with:
|
||||
- **Identity** — Who am I? What's my expertise?
|
||||
- **Focus** — What do I look for?
|
||||
- **Scope boundaries** — What do I explicitly NOT comment on?
|
||||
- **Severity calibration** — What counts as MAJOR/MINOR/NIT for MY domain?
|
||||
|
||||
Personas are defined in JSON files that can live:
|
||||
1. In the pattern repos (shared across projects)
|
||||
2. In the target repo (project-specific personas)
|
||||
3. Inline via a new `--persona-file` flag (JSON format)
|
||||
|
||||
### 2. Persona File Format
|
||||
|
||||
```json
|
||||
# .review/personas/security.yaml
|
||||
name: security
|
||||
display_name: Security Specialist
|
||||
model_preference: opus # optional hint for expensive analysis
|
||||
|
||||
identity: |
|
||||
You are a security specialist reviewing code for vulnerabilities.
|
||||
Your expertise: OWASP Top 10, injection attacks, auth/authz, secrets management,
|
||||
event sourcing security (replay attacks, event injection).
|
||||
|
||||
focus:
|
||||
- Injection attacks (SQL, command, path traversal, template)
|
||||
- Authentication and authorization gaps
|
||||
- Secrets exposure (hardcoded credentials, tokens in logs)
|
||||
- Input validation (unsanitized input, unsafe deserialization)
|
||||
- Race conditions with security implications
|
||||
- Event sourcing attack vectors
|
||||
|
||||
ignore:
|
||||
- Code style and naming conventions
|
||||
- Performance (unless security-related)
|
||||
- Documentation
|
||||
- General code quality
|
||||
- Test coverage
|
||||
|
||||
severity:
|
||||
critical: "Remote code execution, auth bypass, data exfiltration"
|
||||
major: "Privilege escalation, information disclosure, DoS"
|
||||
minor: "Missing rate limiting, verbose errors"
|
||||
nit: "Theoretical risk with low exploitability"
|
||||
|
||||
output_format: |
|
||||
For each finding:
|
||||
- Severity: [CRITICAL|MAJOR|MINOR|NIT]
|
||||
- Attack vector: How could this be exploited?
|
||||
- Evidence: Code snippet showing the vulnerability
|
||||
- Recommendation: Specific fix
|
||||
```
|
||||
|
||||
### 3. New CLI Flags
|
||||
|
||||
```
|
||||
--persona-file PATH Path to persona JSON file (local or in repo)
|
||||
--persona NAME Built-in persona name (security, architect, domain)
|
||||
```
|
||||
|
||||
Either flag sets the persona. If neither is provided, behavior is unchanged (generic review).
|
||||
|
||||
### 4. Prompt Assembly
|
||||
|
||||
Current flow:
|
||||
```
|
||||
SystemBase → Patterns → Conventions → [LLM]
|
||||
```
|
||||
|
||||
New flow with persona:
|
||||
```
|
||||
PersonaPrompt (from YAML) → Patterns (filtered?) → Conventions → [LLM]
|
||||
```
|
||||
|
||||
The persona's identity/focus/ignore/severity sections become the system prompt, replacing the generic "You are an expert code reviewer" base.
|
||||
|
||||
### 5. Built-in Personas
|
||||
|
||||
Ship with these built-in personas (loadable via `--persona NAME`):
|
||||
|
||||
| Name | Focus |
|
||||
|------|-------|
|
||||
| `security` | Vulnerabilities, auth, secrets |
|
||||
| `architect` | Patterns, consistency, design |
|
||||
| `domain` | Business logic (requires repo-specific config) |
|
||||
| `docs` | Documentation, API clarity |
|
||||
|
||||
Built-in personas live in `review/personas/` as embedded Go assets or YAML shipped with the binary.
|
||||
|
||||
### 6. CI Workflow Integration
|
||||
|
||||
Single persona:
|
||||
```yaml
|
||||
- uses: rodin/review-bot/.gitea/actions/review@v1
|
||||
with:
|
||||
reviewer-name: security
|
||||
persona: security
|
||||
...
|
||||
```
|
||||
|
||||
Multiple personas (parallel jobs):
|
||||
```yaml
|
||||
jobs:
|
||||
review:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: security
|
||||
persona: security
|
||||
- name: architect
|
||||
persona: architect
|
||||
steps:
|
||||
- uses: rodin/review-bot/.gitea/actions/review@v1
|
||||
with:
|
||||
reviewer-name: ${{ matrix.name }}
|
||||
persona: ${{ matrix.persona }}
|
||||
```
|
||||
|
||||
Custom persona from repo:
|
||||
```yaml
|
||||
- uses: rodin/review-bot/.gitea/actions/review@v1
|
||||
with:
|
||||
reviewer-name: trading
|
||||
persona-file: .review/personas/trading.yaml
|
||||
```
|
||||
|
||||
### 7. Persona + Patterns Interaction
|
||||
|
||||
Some personas benefit from filtered patterns:
|
||||
- Security → only security-related patterns
|
||||
- Architect → all patterns (structural focus)
|
||||
- Domain → domain docs, not language patterns
|
||||
|
||||
For v1, keep it simple: all patterns are included regardless of persona. Future enhancement could add `patterns_filter` to persona YAML.
|
||||
|
||||
### 8. Output Format Changes
|
||||
|
||||
Persona name appears in the review header:
|
||||
```markdown
|
||||
# Security Review
|
||||
|
||||
## Summary
|
||||
No critical vulnerabilities found in this change.
|
||||
|
||||
## Findings
|
||||
| # | Severity | File | Line | Finding |
|
||||
...
|
||||
|
||||
## Recommendation
|
||||
**APPROVE** — No security-relevant issues detected.
|
||||
|
||||
---
|
||||
*Review by security*
|
||||
<!-- review-bot:security -->
|
||||
```
|
||||
|
||||
## State/Data Model
|
||||
|
||||
### Persona struct
|
||||
|
||||
```go
|
||||
// review/persona.go
|
||||
type Persona struct {
|
||||
Name string `yaml:"name"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
ModelPref string `yaml:"model_preference,omitempty"`
|
||||
Identity string `yaml:"identity"`
|
||||
Focus []string `yaml:"focus"`
|
||||
Ignore []string `yaml:"ignore"`
|
||||
Severity Severity `yaml:"severity"`
|
||||
OutputFormat string `yaml:"output_format,omitempty"`
|
||||
}
|
||||
|
||||
type Severity struct {
|
||||
Critical string `yaml:"critical"`
|
||||
Major string `yaml:"major"`
|
||||
Minor string `yaml:"minor"`
|
||||
Nit string `yaml:"nit"`
|
||||
}
|
||||
```
|
||||
|
||||
### Loading precedence
|
||||
|
||||
1. `--persona-file PATH` → load from local file system
|
||||
2. `--persona NAME` → load from embedded built-ins
|
||||
3. Neither → use generic system prompt (current behavior)
|
||||
|
||||
## Error Cases
|
||||
|
||||
| Error | Handling |
|
||||
|-------|----------|
|
||||
| Persona file not found | Fatal exit with clear message |
|
||||
| Invalid YAML in persona file | Fatal exit with parse error |
|
||||
| Both `--persona` and `--persona-file` specified | Fatal exit: mutually exclusive |
|
||||
| Unknown built-in persona name | Fatal exit with list of valid names |
|
||||
| Empty identity in persona | Warning, fall back to generic prompt |
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Empty focus list**: Valid — persona relies on identity alone
|
||||
- **Empty ignore list**: Valid — no explicit scope exclusions
|
||||
- **No severity section**: Use default MAJOR/MINOR/NIT definitions
|
||||
- **Model preference set but budget insufficient**: Ignore preference, log warning
|
||||
- **Persona file in pattern repo**: Fetch like other pattern files
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit tests
|
||||
- `persona_test.go`: Parse valid/invalid YAML, validate required fields
|
||||
- `prompt_test.go`: Verify persona prompt assembly
|
||||
- Integration with budget: persona prompts count toward token limit
|
||||
|
||||
### Integration tests
|
||||
- End-to-end with `--persona security` (built-in)
|
||||
- End-to-end with `--persona-file custom.yaml`
|
||||
- Backwards compatibility: no flags = generic behavior
|
||||
|
||||
### Manual verification
|
||||
- Run security persona on a PR with obvious vulnerability
|
||||
- Verify security persona ignores style issues
|
||||
- Verify non-security persona doesn't flag security issues
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Persona types and loading
|
||||
- [ ] `review/persona.go`: Persona struct + YAML parsing
|
||||
- [ ] `review/persona_test.go`: Unit tests
|
||||
- [ ] Embed built-in personas in binary
|
||||
- [ ] Compiles clean, tests pass
|
||||
|
||||
### Phase 2: Prompt generation
|
||||
- [ ] `review/prompt.go`: `BuildPersonaPrompt(p Persona) string`
|
||||
- [ ] Modify `BuildSystemBase()` to accept optional persona
|
||||
- [ ] Integrate persona prompt with budget system
|
||||
- [ ] Tests for prompt assembly
|
||||
|
||||
### Phase 3: CLI integration
|
||||
- [ ] Add `--persona` and `--persona-file` flags
|
||||
- [ ] Flag validation (mutually exclusive, valid names)
|
||||
- [ ] Load persona based on flags
|
||||
- [ ] Pass persona to prompt builder
|
||||
|
||||
### Phase 4: Action integration
|
||||
- [ ] Add `persona` and `persona-file` inputs to action.yml
|
||||
- [ ] Update README with persona examples
|
||||
- [ ] End-to-end CI test
|
||||
|
||||
### Phase 5: Built-in personas
|
||||
- [ ] `security.yaml` built-in
|
||||
- [ ] `architect.yaml` built-in
|
||||
- [ ] `docs.yaml` built-in
|
||||
- [ ] Document each persona's focus
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Persona file location in repo**: Should we support `--persona-file .review/security.yaml` where the file is fetched from the PR's repo (like conventions)? This adds complexity but enables project-specific personas without action changes.
|
||||
|
||||
2. **Model preference enforcement**: If persona specifies `model_preference: opus` but the action uses a different model, should we warn? Override? Ignore? Current thinking: log warning, use the specified model (user controls model via action input).
|
||||
|
||||
3. **Severity override output**: If persona defines custom severity levels (CRITICAL), should the JSON output include them, or map back to standard MAJOR/MINOR/NIT? Current thinking: keep standard output format, use severity calibration only for prompt guidance.
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
1. Persona struct matches YAML schema exactly?
|
||||
2. Built-in personas embedded in binary (not external files)?
|
||||
3. `--persona` and `--persona-file` are mutually exclusive?
|
||||
4. Unknown persona name produces clear error with valid options?
|
||||
5. Empty persona file fields have sensible defaults?
|
||||
6. Persona prompt integrates with budget system (token counting)?
|
||||
7. Backwards compatibility: no flags = current behavior?
|
||||
8. Review header shows persona display name?
|
||||
9. Sentinel still uses reviewer-name (not persona name)?
|
||||
10. Unit tests cover parse errors, missing fields, valid YAML?
|
||||
|
||||
## Design Review Findings (Self-Review)
|
||||
|
||||
### Finding 1: Severity Mapping
|
||||
The persona YAML allows `critical` severity, but the LLM output parser (`review/parser.go`) only accepts MAJOR/MINOR/NIT.
|
||||
|
||||
**Resolution:** Keep standard output format. Persona severity section is ONLY for calibrating the LLM's judgment (prompt guidance). Output must still use MAJOR/MINOR/NIT. Document this clearly in persona format docs.
|
||||
|
||||
### Finding 2: Embedding Built-in Personas
|
||||
Go doesn't natively embed YAML. Must use `//go:embed` directive (Go 1.16+).
|
||||
|
||||
**Resolution:** Create `review/personas/` directory with YAML files and use:
|
||||
```go
|
||||
//go:embed personas/*.yaml
|
||||
var embeddedPersonas embed.FS
|
||||
```
|
||||
|
||||
### Finding 3: display_name vs reviewer-name
|
||||
Design says header shows "persona display name" but sentinel uses "reviewer-name". This is correct - they serve different purposes:
|
||||
- `display_name` → human-readable header ("Security Specialist Review")
|
||||
- `reviewer-name` → machine sentinel for cleanup (`<!-- review-bot:security -->`)
|
||||
|
||||
When persona is used, `display_name` takes precedence for the header title, but `reviewer-name` (CLI flag) is still used for the sentinel.
|
||||
|
||||
## Design Revision: YAML with gopkg.in/yaml.v3
|
||||
|
||||
**Decision:** Add `gopkg.in/yaml.v3` as a dependency.
|
||||
|
||||
YAML is preferred over JSON for persona files because:
|
||||
- Multi-line strings are cleaner (no escaping quotes in identity/focus text)
|
||||
- Comments are supported for documentation
|
||||
- More human-readable for complex persona definitions
|
||||
|
||||
The implementation supports both YAML (`.yaml`, `.yml`) and JSON (`.json`) for backwards compatibility, with YAML as the default for built-in personas.
|
||||
@@ -1,87 +0,0 @@
|
||||
# Design: YAML Support for Persona Files (#57)
|
||||
|
||||
## Problem
|
||||
|
||||
JSON is awkward for persona files that contain multi-line text (identity, severity descriptions). YAML supports cleaner multi-line strings and comments, improving readability and maintainability.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Backwards compatibility: existing JSON personas must continue to work
|
||||
- Security: protect against DoS via deeply nested YAML (AIKIDO-2024-10486)
|
||||
- Consistency: use `.yaml` extension (not `.yml`)
|
||||
- Library: use `github.com/goccy/go-yaml` v1.16.0+ (approved in CONVENTIONS.md); we implement custom AST-based depth/node-count checks for precise alias-aware validation
|
||||
|
||||
## Proposed Approach
|
||||
|
||||
1. **Update `parsePersona`** to detect format from file extension
|
||||
2. **Add YAML parsing** with explicit depth limit (defense in depth)
|
||||
3. **Keep JSON as fallback** for files without `.yaml`/`.yml` extension
|
||||
4. **Convert built-in personas** to YAML format
|
||||
5. **Update embed directive** to include both formats
|
||||
|
||||
### File Extension Detection
|
||||
|
||||
```go
|
||||
func parsePersona(data []byte, source string) (*Persona, error) {
|
||||
isYAML := strings.HasSuffix(source, ".yaml") || strings.HasSuffix(source, ".yml")
|
||||
if isYAML {
|
||||
return parseYAML(data, source)
|
||||
}
|
||||
return parseJSON(data, source)
|
||||
}
|
||||
```
|
||||
|
||||
### YAML Parsing with Depth Protection
|
||||
|
||||
We implement a custom AST-based depth/node-count walk (`checkYAMLDepth` in
|
||||
`review/persona.go`) rather than relying on library decoder options. Key design
|
||||
decisions:
|
||||
|
||||
- **Library:** `github.com/goccy/go-yaml` with `ast.Node`-based traversal
|
||||
- **Dual-map tracking:** `validated` (depth-aware short-circuit) + `visiting` (cycle detection)
|
||||
- **Node-count limit:** Conservative overcounting bounds total validation work
|
||||
- **Alias-aware depth:** Aliases increment depth and are re-checked when encountered at greater depths
|
||||
|
||||
See `review/persona.go:checkYAMLDepth` for the authoritative implementation.
|
||||
|
||||
## State/Data Model
|
||||
|
||||
No new state. Same `Persona` struct, just different parsing.
|
||||
|
||||
## Error Cases
|
||||
|
||||
| Error | Handling |
|
||||
|-------|----------|
|
||||
| Invalid YAML syntax | Return parse error with source file |
|
||||
| Deeply nested YAML | Custom AST walk (`checkYAMLDepth`) rejects before decode |
|
||||
| Unknown extension | Fall back to JSON parsing |
|
||||
| Missing required fields | Validation rejects after parse |
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- File with `.json` extension but YAML content → JSON parse fails, user sees error
|
||||
- File with no extension → defaults to JSON
|
||||
- Embedded persona reference like `builtin:security` → detect by embed path (`personas/X.yaml`)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. Unit tests for YAML parsing (valid, invalid, deeply nested)
|
||||
2. Unit tests for extension detection
|
||||
3. Integration test for built-in personas (now YAML)
|
||||
4. Backwards compat test: verify JSON still works for external files
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
1. [ ] `go-yaml` dependency added at v1.16.0+
|
||||
2. [ ] Extension detection uses case-insensitive comparison
|
||||
3. [ ] YAML parse errors include source file name
|
||||
4. [ ] JSON parsing still works for `.json` files
|
||||
5. [ ] Built-in personas converted to YAML with readable multi-line strings
|
||||
6. [ ] Embed directive updated to include `*.yaml`
|
||||
7. [ ] Test for deeply nested YAML rejection
|
||||
8. [ ] All existing tests pass
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we support both `.yaml` AND `.yml`? Issue says `.yaml` only for consistency, but some users expect `.yml`. **Decision:** Support both for reading, recommend `.yaml` in docs.
|
||||
- Should we add a "format" field to detect mismatched extension/content? **Decision:** No, keep it simple. Extension determines format.
|
||||
Reference in New Issue
Block a user