diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index a6c6e4f..0000000 --- a/.cursorrules +++ /dev/null @@ -1,16 +0,0 @@ -# EagleEye — Cursor Rules - -> Read `AGENTS.md` at the start of every session for full project context. - -EagleEye is a Dart CLI tool for detecting architecture violations in Dart projects. - -## Critical Rules - -- Dart CLI only — no platform dependencies in `lib/` -- No cyclic dependencies between `lib/` subdirectories -- `lib/model/` depends on nothing -- Tests use `package:test` framework -- Commit messages: English, semantic -- Never expose internal implementation from `lib/` as public API - -For complete rules and skills, read `ai/`. diff --git a/.gemini/context.md b/.gemini/context.md deleted file mode 100644 index 8282910..0000000 --- a/.gemini/context.md +++ /dev/null @@ -1,18 +0,0 @@ -# EagleEye — Gemini - -> Read `AGENTS.md` at the start of every session for full project context. - -EagleEye is a Dart CLI tool for detecting architecture violations in Dart projects. - -## Critical Rules - -- Dart CLI only — no platform dependencies in `lib/` -- No cyclic dependencies between `lib/` subdirectories -- `lib/model/` depends on nothing; `lib/data/` depends on `model/`; `lib/analyzer/` depends on `model/`, `data/`, and `util/` -- `lib/util/` has zero internal dependencies -- `bin/main.dart` only accesses the public API entry point -- Tests use `package:test` framework, naming: `_test.dart` -- Commit messages: English, semantic (`feat:`, `fix:`, `test:`, `chore:`, `docs:`) -- Never expose internal implementation from `lib/` as public API - -For complete rules and skills, read `ai/`. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 962c6a9..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,18 +0,0 @@ -# EagleEye — GitHub Copilot - -> Read `AGENTS.md` at the start of every session for full project context. - -EagleEye is a Dart CLI tool for detecting architecture violations in Dart projects. - -## Critical Rules - -- Dart CLI only — no platform dependencies in `lib/` -- No cyclic dependencies between `lib/` subdirectories -- `lib/model/` depends on nothing; `lib/data/` depends on `model/`; `lib/analyzer/` depends on `model/` and `data/` -- `lib/util/` has zero internal dependencies -- `bin/main.dart` only accesses the public API entry point -- Tests use `package:test` framework, naming: `_test.dart` -- Commit messages: English, semantic (`feat:`, `fix:`, `test:`, `chore:`, `docs:`) -- Never expose internal implementation from `lib/` as public API - -For complete rules and skills, read `ai/`. diff --git a/ai/instructions/dart.md b/.opencode/instructions/dart.md similarity index 100% rename from ai/instructions/dart.md rename to .opencode/instructions/dart.md diff --git a/ai/module-graph.md b/.opencode/module-graph.md similarity index 100% rename from ai/module-graph.md rename to .opencode/module-graph.md diff --git a/ai/skills/architecture/SKILL.md b/.opencode/skills/architecture/SKILL.md similarity index 93% rename from ai/skills/architecture/SKILL.md rename to .opencode/skills/architecture/SKILL.md index 2b6e08d..29e6159 100644 --- a/ai/skills/architecture/SKILL.md +++ b/.opencode/skills/architecture/SKILL.md @@ -1,7 +1,6 @@ --- name: architecture -description: Verify architectural consistency and dependency rules in EagleEye -trigger: when the user asks about architecture, rules, conventions, or what is/isn't allowed in the codebase +description: Verify architectural consistency and dependency rules in EagleEye. Use when the user asks about architecture, rules, conventions, or what is/isn't allowed in the codebase. --- # EagleEye — Architectural Rules & Conventions diff --git a/ai/skills/documentation-review/SKILL.md b/.opencode/skills/documentation-review/SKILL.md similarity index 94% rename from ai/skills/documentation-review/SKILL.md rename to .opencode/skills/documentation-review/SKILL.md index ca658aa..cd4033a 100644 --- a/ai/skills/documentation-review/SKILL.md +++ b/.opencode/skills/documentation-review/SKILL.md @@ -1,7 +1,6 @@ --- name: documentation-review -description: Validate EagleEye documentation matches actual implementation -trigger: when the user asks to review documentation, check for outdated docs, or verify docs match code +description: Validate EagleEye documentation matches actual implementation. Use when the user asks to review documentation, check for outdated docs, or verify docs match code. --- # Documentation Review for EagleEye diff --git a/ai/skills/generate-tests/SKILL.md b/.opencode/skills/generate-tests/SKILL.md similarity index 90% rename from ai/skills/generate-tests/SKILL.md rename to .opencode/skills/generate-tests/SKILL.md index 7f9db1b..1a4165d 100644 --- a/ai/skills/generate-tests/SKILL.md +++ b/.opencode/skills/generate-tests/SKILL.md @@ -1,7 +1,6 @@ --- name: generate-tests -description: Generate tests following project conventions -trigger: when the user asks to write tests, review test files, or generate new test cases +description: Generate tests following project conventions. Use when the user asks to write tests, review test files, or generate new test cases. --- # Generate Tests diff --git a/ai/skills/minimum-requirements/SKILL.md b/.opencode/skills/minimum-requirements/SKILL.md similarity index 89% rename from ai/skills/minimum-requirements/SKILL.md rename to .opencode/skills/minimum-requirements/SKILL.md index 3769b47..7df210d 100644 --- a/ai/skills/minimum-requirements/SKILL.md +++ b/.opencode/skills/minimum-requirements/SKILL.md @@ -1,7 +1,6 @@ --- name: minimum-requirements -description: Determine minimum requirements to consume the EagleEye library -trigger: when the user asks about minimum SDK version, requirements, or compatible Dart versions +description: Determine minimum requirements to consume the EagleEye library. Use when the user asks about minimum SDK version, requirements, or compatible Dart versions. --- # Minimum Requirements for EagleEye diff --git a/ai/skills/review-pr/SKILL.md b/.opencode/skills/review-pr/SKILL.md similarity index 89% rename from ai/skills/review-pr/SKILL.md rename to .opencode/skills/review-pr/SKILL.md index de7a6e6..5b113f2 100644 --- a/ai/skills/review-pr/SKILL.md +++ b/.opencode/skills/review-pr/SKILL.md @@ -1,7 +1,6 @@ --- name: review-pr -description: Review an EagleEye pull request checking architecture, tests, docs, and completeness -trigger: when the user asks to review a PR, check a pull request, or validate changes +description: Review an EagleEye pull request checking architecture, tests, docs, and completeness. Use when the user asks to review a PR, check a pull request, or validate changes. --- # EagleEye PR Review Checklist diff --git a/ai/skills/run-build/SKILL.md b/.opencode/skills/run-build/SKILL.md similarity index 90% rename from ai/skills/run-build/SKILL.md rename to .opencode/skills/run-build/SKILL.md index 3891323..6b54489 100644 --- a/ai/skills/run-build/SKILL.md +++ b/.opencode/skills/run-build/SKILL.md @@ -1,7 +1,6 @@ --- name: run-build -description: Run the EagleEye build and tests -trigger: when the user asks to build, compile, run tests, or validate the project +description: Run the EagleEye build and tests. Use when the user asks to build, compile, run tests, or validate the project. --- # Running the EagleEye Build diff --git a/ai/skills/testing/SKILL.md b/.opencode/skills/testing/SKILL.md similarity index 93% rename from ai/skills/testing/SKILL.md rename to .opencode/skills/testing/SKILL.md index f992103..8226977 100644 --- a/ai/skills/testing/SKILL.md +++ b/.opencode/skills/testing/SKILL.md @@ -1,7 +1,6 @@ --- name: testing -description: Generate tests for EagleEye following project conventions -trigger: when the user asks to write tests, review test files, or generate new test cases +description: Generate tests for EagleEye following project conventions. Use when the user asks to write tests, review test files, or generate new test cases. --- # Testing in EagleEye diff --git a/ai/skills/trigger-release/SKILL.md b/.opencode/skills/trigger-release/SKILL.md similarity index 91% rename from ai/skills/trigger-release/SKILL.md rename to .opencode/skills/trigger-release/SKILL.md index 7c6b158..74f69b3 100644 --- a/ai/skills/trigger-release/SKILL.md +++ b/.opencode/skills/trigger-release/SKILL.md @@ -1,7 +1,6 @@ --- name: trigger-release -description: Automate the EagleEye release process — update changelog, bump version, tag, and push -trigger: when the user asks to trigger a release, cut a release, or release a new version +description: Automate the EagleEye release process — update changelog, bump version, tag, and push. Use when the user asks to trigger a release, cut a release, or release a new version. --- # Trigger Release diff --git a/ai/skills/validate-architecture/SKILL.md b/.opencode/skills/validate-architecture/SKILL.md similarity index 94% rename from ai/skills/validate-architecture/SKILL.md rename to .opencode/skills/validate-architecture/SKILL.md index c099c94..a65d8e3 100644 --- a/ai/skills/validate-architecture/SKILL.md +++ b/.opencode/skills/validate-architecture/SKILL.md @@ -1,7 +1,6 @@ --- name: validate-architecture -description: Verify architectural consistency and dependency rules -trigger: when the user asks about architecture, rules, conventions, or dependency validation +description: Verify architectural consistency and dependency rules. Use when the user asks about architecture, rules, conventions, or dependency validation. --- # Validate Architecture diff --git a/AGENTS.md b/AGENTS.md index 569822f..eb3d52b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,85 +1,73 @@ # EagleEye -## Project Overview +Dart CLI that detects architecture violations in Dart projects by scanning +`lib/` imports against rules in `eagle_eye_config.json`. +Docs: https://codandotv.github.io/eagle-eye/ · Package: `eagle_eye` on pub.dev. -EagleEye is a Dart CLI tool for detecting architecture violations in Dart projects. It analyzes Dart project files against configurable architectural rules to enforce dependency boundaries and layering. +## Commands -- **Library type:** Dart Package (CLI tool) -- **Primary language:** Dart ^3.8.0 -- **Runtime/platform:** Dart CLI -- **Repository:** https://github.com/CodandoTV/eagle-eye -- **Documentation:** https://codandotv.github.io/eagle-eye/ +Always run from the repo root. -## Build System +```bash +dart pub get # install deps +dart analyze # static analysis (fails build on lint errors) +dart test # run all tests +dart test test/model/eagle_eye_config_item_test.dart # single file +dart test -n "returns null" # single test by name +dart run eagle_eye:main # run the CLI against the current project +``` -- **Dart SDK constraint:** ^3.8.0 (defined in `pubspec.yaml:7`) -- **Dependencies defined in:** `pubspec.yaml` -- **Build and release commands:** - - `dart pub get` — Install dependencies - - `dart analyze` — Static analysis - - `dart test` — Run tests - - `dart run eagle_eye:main` — Run the CLI tool locally - - `dart pub publish` — Publish to pub.dev +CI (`pr.yml`) runs `dart test` then `dart analyze` on PRs to `main`; both must +pass. Run `dart analyze && dart test` after any change before calling it done. -## Project Structure +## Linting is strict — most common agent mistake -``` -bin/ - main.dart — CLI entry point -lib/ - analyzer/ — Core analysis engine - checker/ — Rule checkers - data/ — Data access layer - model/ — Domain models - exceptions/ — Custom exceptions - util/ — Utilities -test/ - analyzer/ — Analyzer tests - checker/ — Rule checker tests - model/ — Model tests -doc/ — MkDocs documentation source - index.md — Documentation home - img/ — Documentation images -example/ - README.md — Example usage guide -``` +`analysis_options.yaml` includes `flutter_lints` and escalates the following to +**errors** (not warnings), so `dart analyze` fails on any of them: -## Version Management +- `public_member_api_docs` — every public member in `lib/` needs a `///` doc comment +- `lines_longer_than_80_chars` +- `prefer_single_quotes` +- `always_use_package_imports` — no relative imports inside `lib/` +- `directives_ordering` +- `always_declare_return_types`, `prefer_final_fields`, `prefer_const_constructors` +- `annotate_overrides`, `curly_braces_in_flow_control_structures`, `file_names` +- `avoid_print`, `avoid_relative_lib_imports`, `avoid_multiple_declarations_per_line` -- **Version:** Defined in `pubspec.yaml` under the `version` field -- Current version: `2.0.2` +## Architecture -## Documentation +Public entry point is `lib/eagle_eye_launcher.dart` +(`EagleEyeLauncher().launchEagleEye()`); `bin/main.dart` only calls it. -- **Generator:** Zensical -- **Configuration:** `mkdocs.yml` -- **Source directory:** `doc/` -- **Theme:** Material for MkDocs (via Zensical) -- **Live site:** https://codandotv.github.io/eagle-eye/ +Layer rules (enforced by convention, checked in `.opencode/module-graph.md`): -## Distribution +- `lib/model/` — zero internal deps +- `lib/util/` — zero internal deps +- `lib/data/` — may depend on `model/` +- `lib/analyzer/` + `lib/analyzer/checker/` — may depend on `model/`, `data/`, `util/` +- no cycles; `bin/` must only touch the public entry point -- **pub.dev package URL:** https://pub.dev/packages/eagle_eye -- **Package name:** `eagle_eye` +## Runtime behavior -## Git Workflow +`launchEagleEye()` reads `eagle_eye_config.json` from the project root, gets the +app name from `pubspec.yaml`, scans `lib/`, and `exit(1)` on violations or +missing config/app name. Config is a JSON array of items with keys: +`filePattern`, `name`, `dependenciesAllowed`, `forbiddenDependencies`, +`exclusiveDependencies` (these were renamed in 2.0.0). -- **Default branch:** `main` -- **Release process:** - 1. Update version in `pubspec.yaml` - 2. Update `CHANGELOG.md` - 3. Commit changes - 4. Tag with `vX.Y.Z` - 5. Push commits and tags - 6. Publish to pub.dev (`dart pub publish`) -- **Git tag strategy:** `vX.Y.Z` (e.g., `v2.0.2`) -- **Versioning strategy:** Semantic Versioning (SemVer) +## Version & release -## Available Skills +- Version lives in `pubspec.yaml` (currently `2.0.4`) and `CHANGELOG.md`. +- Release = bump version + changelog, commit, tag `vX.Y.Z`, push. The tag + triggers `publish.yml` (pub.dev via OIDC). Use the `trigger-release` skill. -Before starting any task: -1. List all directories in `ai/skills/` -2. Identify which one covers the current task -3. Read that file in full before proceeding +## AI context -Each skill file is a self-contained guide. Never skip this step — the skill may contain patterns, checklists, or constraints that are not obvious from the code alone. +- `AGENTS.md` is the single source of truth; opencode loads it via + `opencode.json`. +- Skills live in `.opencode/skills/` and are auto-loaded by opencode's native + skill scan. Before a task, check for a matching skill (e.g. `testing`, + `review-pr`, `trigger-release`, `architecture`) and read it in full. +- Warning: `.opencode/instructions/dart.md` references class/file paths that no + longer exist (e.g. `lib/analyzer/analyzer.dart`, `checker/checker.dart`). + Trust the actual `lib/` tree over it. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index ed1d77a..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -# EagleEye — Claude Code - -> Read `AGENTS.md` at the start of every session for full project context. - -EagleEye is a Dart CLI tool for detecting architecture violations in Dart projects. diff --git a/README-AI-CONTEXT.md b/README-AI-CONTEXT.md deleted file mode 100644 index c7d8f22..0000000 --- a/README-AI-CONTEXT.md +++ /dev/null @@ -1,69 +0,0 @@ -# AI Context Structure - -EagleEye uses multiple AI coding tools (OpenCode, Claude Code, GitHub Copilot, Gemini, Cursor, etc.). Instead of maintaining separate context files for each tool — which would quickly diverge — all tools point to the same source. - -``` -📄 AGENTS.md ← single source of truth (master initializer) -📁 ai/ ← detailed context: instructions, skills, module graph -``` - -```mermaid -graph LR - OPENCODE["OpenCode
opencode.json → AGENTS.md"] - CLAUDE["Claude Code
CLAUDE.md"] - COPILOT["GitHub Copilot
.github/copilot-instructions.md"] - CURSOR["Cursor
.cursorrules"] - GEMINI["Gemini
.gemini/context.md"] - CODEX["Codex / OpenAI
(reads natively)"] - - AG["📄 AGENTS.md
Master Initializer"] - AI["📁 ai/
Detailed Context"] - - OPENCODE -->|reads| AG - CLAUDE -->|reads| AG - COPILOT -->|reads| AG - CURSOR -->|reads| AG - GEMINI -->|reads| AG - CODEX -->|reads| AG - - AG -->|references| AI -``` - -## ai/ structure - -``` -ai/ - module-graph.md ← module dependency graph - instructions/ - dart.md ← Dart CLI patterns and conventions - skills/ - architecture/ - SKILL.md ← architectural rules and code conventions - documentation-review/ - SKILL.md ← docs validation - generate-tests/ - SKILL.md ← test generation - minimum-requirements/ - SKILL.md ← version requirements - trigger-release/ - SKILL.md ← automated release trigger - review-pr/ - SKILL.md ← PR review checklist - run-build/ - SKILL.md ← how to build and test - testing/ - SKILL.md ← testing strategies - validate-architecture/ - SKILL.md ← architectural validation -``` - -## Tool-specific config - -| Location | Tool | Purpose | -|---|---|---| -| `AGENTS.md` | All tools | Master initializer — single source of truth | -| `CLAUDE.md` | Claude Code | Entry point → reads `AGENTS.md` | -| `.gemini/context.md` | Gemini | Entry point → reads `AGENTS.md` | -| `.github/copilot-instructions.md` | GitHub Copilot | Entry point → reads `AGENTS.md` | -| `.cursorrules` | Cursor | Entry point → reads `AGENTS.md` | -| `opencode.json` → `AGENTS.md` | OpenCode | Reads `AGENTS.md` and `ai/skills/` | diff --git a/SETUP_PROMPT.md b/SETUP_PROMPT.md index 0cb894e..ac6c6aa 100644 --- a/SETUP_PROMPT.md +++ b/SETUP_PROMPT.md @@ -38,7 +38,7 @@ Create the file at the **project root** (same directory as `pubspec.yaml`). The | `filePattern` | string | Wildcard pattern matching file paths. `*` becomes `.*` regex. | | `dependenciesAllowed` | boolean | If `false`, matched files must have **zero** internal imports. | | `forbiddenDependencies` | string[] | Matched files **must not** import anything matching these patterns. | -| `exclusiveDependencies` | string[] | Matched files can **only** import things matching these patterns. | +| `exclusiveDependencies` | string[] | Matched files can **only** import things matching **any** of these patterns (or nothing at all). | | `name` | string | Optional human-readable name (shown in violation output). | ### Architecture-specific rule templates diff --git a/doc/1-getting-started.md b/doc/1-getting-started.md index 199a3fe..f178dd9 100644 --- a/doc/1-getting-started.md +++ b/doc/1-getting-started.md @@ -34,7 +34,7 @@ Create this file at the **project root** (same directory as `pubspec.yaml`). It | `filePattern` | string | Wildcard pattern matching file paths. `*` becomes `.*` regex. | | `dependenciesAllowed` | boolean | If `false`, matched files must have **zero** internal imports. | | `forbiddenDependencies` | string[] | Matched files **must not** import anything matching these patterns. | -| `exclusiveDependencies` | string[] | Matched files can **only** import things matching these patterns. | +| `exclusiveDependencies` | string[] | Matched files can **only** import things matching **any** of these patterns (or nothing at all). | | `name` | string | Optional human-readable name (shown in violation output). | ### Architecture-specific templates diff --git a/doc/2-existing-rules.md b/doc/2-existing-rules.md index 291b2ea..9d1bb43 100644 --- a/doc/2-existing-rules.md +++ b/doc/2-existing-rules.md @@ -42,7 +42,7 @@ In this case, all files that has the suffix `viewmodel.dart` should not depend o }, ``` -The `Exclusive Dependencies Rule` specifies that some files should depend only on certain files. For example: +The `Exclusive Dependencies Rule` specifies that some files should depend only on certain files. An import is allowed when it matches **any** of the listed patterns, and a file that imports nothing is also allowed. For example: ```json // your-project-root/eagle_eye_config.json @@ -54,6 +54,18 @@ The `Exclusive Dependencies Rule` specifies that some files should depend only o In this case, the our repositories should depend only on data sources. +You can also list several allowed patterns: + +```json +// your-project-root/eagle_eye_config.json +{ + "filePattern": "*screen.dart", + "exclusiveDependencies": ["*_viewmodel.dart", "*_widget.dart"] +}, +``` + +In this case, screens may depend on view models, on widgets, on both, or on nothing — but never on anything else. + ## Dependencies Allowed Rule !!! warning "Deprecated - Only available before v2.0.0" diff --git a/lib/analyzer/checker/exclusive_dependencies_rule_checker.dart b/lib/analyzer/checker/exclusive_dependencies_rule_checker.dart index e8ca56d..7a9b307 100644 --- a/lib/analyzer/checker/exclusive_dependencies_rule_checker.dart +++ b/lib/analyzer/checker/exclusive_dependencies_rule_checker.dart @@ -2,8 +2,9 @@ import 'package:eagle_eye/analyzer/regex_helper.dart'; /// Validates whether a file imports only allowed dependencies. /// -/// Tests each import against a list of exclusive (allow-only) patterns. -/// Returns a description string if a violation is found. +/// An import is valid when it matches **any** of the exclusive (allow-only) +/// patterns. A file that imports nothing is also valid, since there is no +/// import to check. Returns a description string if a violation is found. class ExclusiveDependenciesRuleChecker { /// Helper used for regex-based pattern matching. RegexHelper regexHelper; @@ -15,8 +16,8 @@ class ExclusiveDependenciesRuleChecker { /// Checks if [importDirective] conforms to the specified /// [justWithPatterns] for the given [filePath]. /// - /// Returns a violation description if the import does not match any - /// allowed pattern, or `null` if it passes validation. + /// Returns `null` as soon as the import matches any allowed pattern, or a + /// violation description if it matches none of them. String? check({ required List justWithPatterns, required String importDirective, @@ -27,10 +28,10 @@ class ExclusiveDependenciesRuleChecker { importDirective, justWithItem, ); - if (matches == false) { - return '$filePath should depends only on $justWithPatterns'; + if (matches == true) { + return null; } } - return null; + return '$filePath should depend only on $justWithPatterns'; } } diff --git a/opencode.json b/opencode.json index 43f3153..ec39661 100644 --- a/opencode.json +++ b/opencode.json @@ -1,7 +1,4 @@ { "$schema": "https://opencode.ai/config.json", - "instructions": ["AGENTS.md"], - "skills": { - "paths": ["ai/skills"] - } + "instructions": ["AGENTS.md"] } \ No newline at end of file diff --git a/test/analyzer/checker/exclusive_dependencies_rule_checker_test.dart b/test/analyzer/checker/exclusive_dependencies_rule_checker_test.dart index 7d468d7..1609fb0 100644 --- a/test/analyzer/checker/exclusive_dependencies_rule_checker_test.dart +++ b/test/analyzer/checker/exclusive_dependencies_rule_checker_test.dart @@ -16,7 +16,7 @@ void main() { expect( result, - 'lib/my_screen.dart should depends only on [*repository.dart]', + 'lib/my_screen.dart should depend only on [*repository.dart]', ); }); @@ -32,5 +32,37 @@ void main() { expect(result, isNull); }); + + test('returns null when the import matches the second of many patterns', + () { + final regexHelper = RegexHelper(); + final checker = ExclusiveDependenciesRuleChecker(regexHelper); + + final result = checker.check( + justWithPatterns: ['*_viewmodel.dart', '*_widget.dart'], + importDirective: 'my_widget.dart', + filePath: 'lib/my_screen.dart', + ); + + expect(result, isNull); + }); + + test('returns description when the import matches none of many patterns', + () { + final regexHelper = RegexHelper(); + final checker = ExclusiveDependenciesRuleChecker(regexHelper); + + final result = checker.check( + justWithPatterns: ['*_viewmodel.dart', '*_widget.dart'], + importDirective: 'my_helper.dart', + filePath: 'lib/my_screen.dart', + ); + + expect( + result, + 'lib/my_screen.dart should depend only on ' + '[*_viewmodel.dart, *_widget.dart]', + ); + }); }); }