11 Commits

Author SHA1 Message Date
Hoai Nam a9a609bdcc Merge pull request #11 from unclecatvn/staging
chore: Release version 1.0.10 with updates for Odoo agents
2026-05-08 11:44:49 +07:00
UncleCat 56935b49a8 chore: Release version 1.0.10 with updates for Odoo agents
- Made Odoo agents version-aware (17 / 18 / 19) to load the appropriate reference pack automatically.
- Fixed broken guide paths in `odoo-code-review` to ensure correct navigation.
2026-05-08 11:44:07 +07:00
Hoai Nam b1a046d51e Merge pull request #10 from sjusamed/docs/version-resolution-changelog
docs: document version-aware Odoo agents in README and CHANGELOG
2026-05-08 11:29:49 +07:00
HoaiNam f8223189af chore: Update header image for improved visual presentation 2026-04-26 12:01:38 +07:00
Hoai Nam 6e54395980 Merge pull request #8 from sjusamed/fix/broken-skill-paths
fix(odoo-code-review): correct broken reference paths
2026-04-26 11:43:50 +07:00
Sebastien J 926bc1d6b0 docs: document version-aware Odoo agents in README and CHANGELOG
- README: add "Targeting an Odoo version" subsection under Agents
  explaining the four-level resolution order; note version-awareness
  in the Odoo agents table.
- CHANGELOG: replace the [Unreleased] TODO placeholder with concrete
  entries covering the path fixes (#8) and version-aware agents (#9).

Refs: #7
2026-04-24 15:52:41 -04:00
Sebastien J a44d689fe1 feat(agents): make odoo-code-review and odoo-code-tracer version-aware
Both agents previously hard-coded Odoo 18 in their description,
instructions, and guide references. A user targeting Odoo 17 or 19
would load 18-specific rules (`<tree>` vs `<list>`, `group_operator=`
vs `aggregator=`, etc.) and get incorrect reviews / traces.

Changes:
- Add a "Resolve the target Odoo version" step to each agent with a
  four-level fallback: explicit arg → project config
  (.odoo-version / .claude/odoo.json / package.json / pyproject.toml)
  → __manifest__.py heuristic → default to latest (19.0).
- All guide references now use `skills/odoo-${ODOO_VERSION}/references/
  odoo-${ODOO_MAJOR}-*-guide.md`, substituted at invocation time.
- Extract version-distinguishing rules (list tag, attrs syntax,
  aggregator parameter, optional `_name` in v19, etc.) into a new
  `api-highlights.md` under each of
  skills/odoo-17.0/, skills/odoo-18.0/, skills/odoo-19.0/references/.
- Update the README 'Odoo 18 conventions' example comment to cover
  17 / 18 / 19 since the pattern is identical across those versions.

Supported version matrix: 17.0, 18.0, 19.0. Older versions are out
of scope.

Refs: #7
2026-04-24 15:39:59 -04:00
Sebastien J 87e2b1bad7 fix(odoo-code-review): correct broken reference paths
The agent referenced `skills/odoo/18.0/` (wrong separator) and
`dev/odoo-18-*-guide.md` (wrong subdir). The actual layout is
`skills/odoo-18.0/references/odoo-18-*-guide.md`, so every guide
link in the agent was unresolvable.

Content remains pinned to Odoo 18; version-awareness is a follow-up
(see #7).
2026-04-24 15:20:44 -04:00
HoaiNam c79ad3faff docs: Revise README.md for enhanced clarity and structure
- Expanded the introduction with a new description of Agent Skills and its benefits.
- Added a detailed comparison table illustrating the advantages of using Agent Skills.
- Included a real-world example to demonstrate practical application.
- Improved section headings for better readability and organization.
- Updated the "Supported IDEs" section for consistency in formatting.
2026-04-23 13:09:10 +07:00
HoaiNam 942b531bba chore: Remove outdated skill entry from plugin.json
- Removed "skills/visual-explainer/SKILL.md" from the plugin.json file to streamline the skills list and eliminate deprecated references.
2026-04-23 02:17:44 +07:00
HoaiNam a27fa4dfc7 chore: Update README and workflows for improved documentation and CI processes
- Added Odoo 17 guides section to README.md for better navigation.
- Updated documentation metrics in README.md to reflect the increase to 55,000+ lines.
- Enhanced .claude-plugin/plugin.json to include new skills for Odoo 17, 18, and 19.
- Introduced new CI workflow in ci.yml for validating JSON manifests and YAML files.
- Removed outdated auto-tag and GitHub release workflows, consolidating release processes into a new release.yml workflow.
2026-04-23 02:13:46 +07:00
16 changed files with 786 additions and 661 deletions
+7 -7
View File
@@ -16,17 +16,17 @@
"agents/odoo-code-tracer/SKILL.md"
],
"skills": [
"skills/odoo/18.0/SKILL.md",
"skills/brainstorming/SKILL.md",
"skills/code-review/SKILL.md",
"skills/dtg-base/SKILL.md",
"skills/mcp-builder/SKILL.md",
"skills/odoo-17.0/SKILL.md",
"skills/odoo-18.0/SKILL.md",
"skills/odoo-19.0/SKILL.md",
"skills/payment-integration/SKILL.md",
"skills/slide/SKILL.md",
"skills/writing-skills/SKILL.md"
],
"commands": [
"commands/brainstorm.md",
"commands/code-reviewer.md",
"commands/execute-plan.md",
"commands/write-plan.md"
],
"rules": [
"rules/coding-style.md",
"rules/security.md"
-114
View File
@@ -1,114 +0,0 @@
name: Auto tag and release from package.json
on:
push:
branches:
- "main"
paths:
- "package.json"
- "CHANGELOG.md"
permissions:
contents: write
concurrency:
group: auto-tag-${{ github.ref }}
cancel-in-progress: true
jobs:
tag-and-release:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[skip tag]') }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect version bump
id: ver
run: |
set -euo pipefail
CUR="$(node -p "require('./package.json').version")"
PREV="$(git show HEAD~1:package.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "")"
echo "current=$CUR" >> "$GITHUB_OUTPUT"
echo "previous=$PREV" >> "$GITHUB_OUTPUT"
if [ "$CUR" != "$PREV" ]; then
echo "bumped=true" >> "$GITHUB_OUTPUT"
else
echo "bumped=false" >> "$GITHUB_OUTPUT"
fi
- name: Skip if no version change
if: steps.ver.outputs.bumped != 'true'
run: echo "Version unchanged vs previous commit; skipping."
- name: Tag, push, and create GitHub Release
if: steps.ver.outputs.bumped == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
V="${{ steps.ver.outputs.current }}"
TAG="v${V}"
git fetch --tags --force
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists locally; skipping."
exit 0
fi
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
echo "Tag $TAG already exists on remote; skipping."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"
export TAG V
python - <<'PY'
import os
import re
import sys
from pathlib import Path
tag = os.environ["TAG"]
version = os.environ["V"]
changelog_path = Path("CHANGELOG.md")
if not changelog_path.exists():
print("ERROR: CHANGELOG.md not found at repository root.", file=sys.stderr)
sys.exit(1)
content = changelog_path.read_text(encoding="utf-8")
lines = content.splitlines()
start = None
for i, line in enumerate(lines):
if re.match(r"^##\s+\[(?:v)?%s\]\s*$" % re.escape(version), line) or re.match(
r"^##\s+(?:v)?%s\s*$" % re.escape(version), line
) or re.match(r"^##\s+\[(?:v)?%s\]\s*$" % re.escape(tag), line) or re.match(
r"^##\s+(?:v)?%s\s*$" % re.escape(tag), line
):
start = i
break
if start is None:
print(
f"ERROR: Cannot find changelog section for {tag}. Add heading like '## [{version}]' in CHANGELOG.md.",
file=sys.stderr,
)
sys.exit(1)
end = len(lines)
for i in range(start + 1, len(lines)):
if re.match(r"^##\s+", lines[i]):
end = i
break
section = "\n".join(lines[start:end]).strip() + "\n"
Path("release-notes.md").write_text(section, encoding="utf-8")
PY
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TAG" --notes-file release-notes.md
else
gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
fi
+98
View File
@@ -0,0 +1,98 @@
name: CI
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Validate JSON manifests
run: |
set -euo pipefail
for f in .claude-plugin/plugin.json .claude-plugin/marketplace.json package.json; do
if [ -f "$f" ]; then
node -e "JSON.parse(require('fs').readFileSync('$f','utf8'))" \
&& echo "ok $f" \
|| { echo "bad $f"; exit 1; }
fi
done
- name: Validate workflow YAML
run: |
python3 -c "
import sys, glob
try:
import yaml
except ImportError:
import subprocess
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q', 'pyyaml'])
import yaml
bad = 0
for path in glob.glob('.github/workflows/*.yml'):
try:
yaml.safe_load(open(path))
print(f'ok {path}')
except Exception as e:
print(f'bad {path}: {e}')
bad += 1
sys.exit(1 if bad else 0)
"
- name: Run skill and manifest validator
run: npm test
changelog-guard:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Require CHANGELOG section when package.json version changes
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
if ! git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q '^package.json$'; then
echo "package.json unchanged; skipping changelog guard."
exit 0
fi
CUR="$(node -p "require('./package.json').version")"
PREV="$(git show "$BASE_SHA":package.json 2>/dev/null \
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "")"
if [ "$CUR" = "$PREV" ]; then
echo "package.json changed but version unchanged; skipping."
exit 0
fi
echo "Version bump detected: $PREV -> $CUR"
if grep -Eq "^##\s+\[?v?${CUR//./\\.}\]?\s*$" CHANGELOG.md; then
echo "ok CHANGELOG.md has section for v$CUR"
else
echo "::error::CHANGELOG.md missing section for v$CUR (expected '## [$CUR]')"
exit 1
fi
-79
View File
@@ -1,79 +0,0 @@
name: GitHub Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build release notes from CHANGELOG.md
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
export TAG VERSION
python - <<'PY'
import os
import re
import sys
from pathlib import Path
tag = os.environ["TAG"]
version = os.environ["VERSION"]
changelog_path = Path("CHANGELOG.md")
if not changelog_path.exists():
print("ERROR: CHANGELOG.md not found at repository root.", file=sys.stderr)
sys.exit(1)
content = changelog_path.read_text(encoding="utf-8")
lines = content.splitlines()
start = None
for i, line in enumerate(lines):
if re.match(r"^##\s+\[(?:v)?%s\]\s*$" % re.escape(version), line) or re.match(
r"^##\s+(?:v)?%s\s*$" % re.escape(version), line
) or re.match(r"^##\s+\[(?:v)?%s\]\s*$" % re.escape(tag), line) or re.match(
r"^##\s+(?:v)?%s\s*$" % re.escape(tag), line
):
start = i
break
if start is None:
print(
f"ERROR: Cannot find changelog section for {tag}. Add heading like '## [{version}]' in CHANGELOG.md.",
file=sys.stderr,
)
sys.exit(1)
end = len(lines)
for i in range(start + 1, len(lines)):
if re.match(r"^##\s+", lines[i]):
end = i
break
section = "\n".join(lines[start:end]).strip() + "\n"
Path("release-notes.md").write_text(section, encoding="utf-8")
PY
- name: Create or update release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TAG" --notes-file release-notes.md
else
gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
fi
+122
View File
@@ -0,0 +1,122 @@
name: Release
on:
push:
branches:
- main
paths:
- package.json
- CHANGELOG.md
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[skip release]') }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Detect version bump
id: ver
run: |
set -euo pipefail
CUR="$(node -p "require('./package.json').version")"
PREV="$(git show HEAD~1:package.json 2>/dev/null \
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "")"
echo "current=$CUR" >> "$GITHUB_OUTPUT"
echo "previous=$PREV" >> "$GITHUB_OUTPUT"
if [ "$CUR" != "$PREV" ] && [ -n "$CUR" ]; then
echo "bumped=true" >> "$GITHUB_OUTPUT"
else
echo "bumped=false" >> "$GITHUB_OUTPUT"
fi
- name: Skip if no version change
if: steps.ver.outputs.bumped != 'true'
run: echo "Version unchanged (${{ steps.ver.outputs.current }}); skipping release."
- name: Validate repo before release
if: steps.ver.outputs.bumped == 'true'
run: npm test
- name: Build release notes from CHANGELOG.md
if: steps.ver.outputs.bumped == 'true'
env:
VERSION: ${{ steps.ver.outputs.current }}
run: |
set -euo pipefail
python3 - <<'PY'
import os, re, sys
from pathlib import Path
version = os.environ["VERSION"]
tag = f"v{version}"
changelog = Path("CHANGELOG.md")
if not changelog.exists():
sys.exit("ERROR: CHANGELOG.md not found at repository root.")
lines = changelog.read_text(encoding="utf-8").splitlines()
patterns = [
re.compile(rf"^##\s+\[(?:v)?{re.escape(version)}\]\s*$"),
re.compile(rf"^##\s+(?:v)?{re.escape(version)}\s*$"),
]
start = next(
(i for i, line in enumerate(lines) if any(p.match(line) for p in patterns)),
None,
)
if start is None:
sys.exit(
f"ERROR: No section for {tag} in CHANGELOG.md. "
f"Add a heading like '## [{version}]'."
)
end = len(lines)
for i in range(start + 1, len(lines)):
if re.match(r"^##\s+", lines[i]):
end = i
break
notes = "\n".join(lines[start + 1 : end]).strip() + "\n"
Path("release-notes.md").write_text(notes, encoding="utf-8")
print(f"Wrote release notes for {tag} ({end - start - 1} lines).")
PY
- name: Create tag and GitHub release
if: steps.ver.outputs.bumped == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.ver.outputs.current }}
run: |
set -euo pipefail
TAG="v${VERSION}"
git fetch --tags --force
if git rev-parse "$TAG" >/dev/null 2>&1 \
|| git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
echo "Tag $TAG already exists; updating release notes only."
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"
fi
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TAG" --notes-file release-notes.md
else
gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
fi
+19 -2
View File
@@ -2,10 +2,27 @@
All notable changes to this project will be documented in this file.
## [Unreleased]
## [1.0.10]
### Release Description
Made the Odoo agents version-aware (17 / 18 / 19) so they load the right reference pack automatically instead of always citing Odoo 18 APIs. Also fixed broken guide paths in `odoo-code-review`.
### Added
- TODO: Add upcoming changes here.
- `skills/odoo-17.0/references/api-highlights.md`, `skills/odoo-18.0/references/api-highlights.md`, `skills/odoo-19.0/references/api-highlights.md` — per-version, version-distinguishing rulesets (list tag, attrs syntax, aggregator parameter, optional `_name` in v19, etc.) that the agents load alongside the general checklist.
- Target-version resolution step in `agents/odoo-code-review/SKILL.md` and `agents/odoo-code-tracer/SKILL.md` with a four-level fallback: explicit argument → project config (`.odoo-version`, `.claude/odoo.json`, `package.json`, `pyproject.toml`) → `__manifest__.py` heuristic → default to latest (`19.0`).
- README section "Targeting an Odoo version" documenting the resolution order.
### Changed
- `agents/odoo-code-review/SKILL.md` rewritten version-neutral. Every guide reference now uses `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-*-guide.md` placeholders resolved at invocation time.
- `agents/odoo-code-tracer/SKILL.md` generalised: agent description, "Specific Patterns" section, and "Related Skills" no longer hard-code Odoo 18.
- `README.md` Odoo example comment generalised from "Odoo 18 conventions" to "Odoo conventions (17 / 18 / 19)" since the pattern is identical across supported versions.
### Fixed
- `agents/odoo-code-review/SKILL.md` guide paths: `skills/odoo/18.0/``skills/odoo-18.0/`, and `dev/odoo-18-*-guide.md``references/odoo-18-*-guide.md` (the original paths did not exist in the repo).
### Notes
- Supported version matrix: **17.0, 18.0, 19.0**. Older versions are out of scope.
- Addresses #7.
## [1.0.9]
+155 -38
View File
@@ -1,6 +1,39 @@
<div align="center">
# Agent Skills
![Agent Skills Hero](lib/image/header.png)
![Agent Skills Hero](lib/image/header-new.png)
**Curated AI skill packs for Odoo, payments, and MCP — 55k+ lines of framework expertise for your AI coding assistant.**
[![npm version](https://img.shields.io/npm/v/@unclecat/agent-skills-cli.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@unclecat/agent-skills-cli)
[![npm downloads](https://img.shields.io/npm/dm/@unclecat/agent-skills-cli.svg?style=flat-square&color=blue)](https://www.npmjs.com/package/@unclecat/agent-skills-cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/unclecatvn/agent-skills?style=flat-square&color=yellow)](https://github.com/unclecatvn/agent-skills/stargazers)
[![GitHub last commit](https://img.shields.io/github/last-commit/unclecatvn/agent-skills?style=flat-square)](https://github.com/unclecatvn/agent-skills/commits/main)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/unclecatvn/agent-skills/pulls)
[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
</div>
---
## Table of Contents
- [What is Agent Skills?](#what-is-agent-skills)
- [Why use it?](#why-use-it)
- [Quick Start](#quick-start)
- [Real-World Example](#real-world-example)
- [What's Inside?](#whats-inside)
- [Skills — Framework Documentation](#skills--framework-documentation)
- [Agents — Autonomous Reviewers](#agents--autonomous-reviewers)
- [Rules — Coding Standards](#rules--coding-standards)
- [Project Structure](#project-structure)
- [Supported IDEs](#supported-ides)
- [How It Works](#how-it-works)
- [Stats](#stats)
- [Contributing](#contributing)
- [Links](#links)
---
@@ -8,20 +41,25 @@
**Agent Skills** is a collection of documentation and specialized agents that supercharge AI coding assistants like Cursor, Claude Code, Windsurf, and Aider.
Think of it as a "knowledge pack" - when you add Agent Skills to your project, your AI assistant gains access to thousands of lines of curated technical expertise about specific frameworks and technologies. This means better code suggestions, fewer mistakes, and more helpful responses.
Think of it as a **"knowledge pack"** — when you add Agent Skills to your project, your AI assistant gains access to thousands of lines of curated technical expertise about specific frameworks and technologies. This means better code suggestions, fewer mistakes, and more helpful responses.
### Why use it?
---
- **Generic AI assistants** give you general programming advice
- **AI assistants with Agent Skills** give you framework-specific, best-practice guidance
## Why use it?
For example, instead of just getting "how to write a Python function," you get "how to write an Odoo model following Odoo 18.0 conventions with proper ORM usage."
| Without Agent Skills | With Agent Skills |
|---|---|
| Generic "how to write a Python function" | Framework-specific "how to write an Odoo 18 model with proper ORM patterns" |
| AI guesses at framework conventions | AI follows documented best practices |
| You re-explain project context every session | Context lives in the repo — AI reads it automatically |
| Subtle bugs from outdated or mixed-version advice | Version-pinned guides (Odoo 17 / 18 / 19) |
| Generic security suggestions | Enforced security rules for enterprise applications |
---
## Quick Start
Get started in 30 seconds with NPX (recommended):
Get started in 30 seconds with NPX:
```bash
# Add Agent Skills to your current project
@@ -32,9 +70,63 @@ That's it! Your AI assistant will now have access to all the skills in this repo
---
## Real-World Example
**Prompt:**
> *"Add a computed field `total_with_tax` to `sale.order` that sums line totals plus VAT."*
<table>
<tr>
<th>Without Agent Skills</th>
<th>With Agent Skills</th>
</tr>
<tr>
<td valign="top">
```python
# Generic guess — may use
# wrong API for your Odoo version
total_with_tax = fields.Float(
compute='_compute_total'
)
def _compute_total(self):
for rec in self:
rec.total_with_tax = sum(
l.price * 1.1
for l in rec.order_line
)
```
</td>
<td valign="top">
```python
# Odoo conventions (17 / 18 / 19):
# Monetary + @api.depends + store
total_with_tax = fields.Monetary(
compute='_compute_total_with_tax',
store=True,
currency_field='currency_id',
)
@api.depends('order_line.price_total')
def _compute_total_with_tax(self):
for order in self:
order.total_with_tax = sum(
order.order_line.mapped('price_total')
)
```
</td>
</tr>
</table>
---
## What's Inside?
### Skills - Framework Documentation
### Skills Framework Documentation
In-depth guides written specifically for AI consumption:
@@ -50,19 +142,29 @@ In-depth guides written specifically for AI consumption:
| **[Writing Skills](skills/writing-skills/)** | Creating and editing AI skills (structure, evals, quality) |
| **[MCP Builder](skills/mcp-builder/)** | Building Model Context Protocol servers |
| **[Slide (AI Vibe Slides)](skills/slide/)** | Self-contained HTML/React slide decks for fullscreen presentation |
| **[Visual Explainer](skills/visual-explainer/)** | Self-contained HTML for diagrams, diff/plan review, tables, and visual explanations |
### Agents - Autonomous Reviewers
### Agents Autonomous Reviewers
Specialized agents that act as senior technical leads:
| Agent | What it does |
|-------|--------------|
| **[Odoo Code Review](agents/odoo-code-review/SKILL.md)** | Reviews Odoo code with scoring (110) and structured feedback |
| **[Odoo Code Tracer](agents/odoo-code-tracer/SKILL.md)** | Traces execution flow from an entry point through the call graph |
| **[Odoo Code Review](agents/odoo-code-review/SKILL.md)** | Reviews Odoo code with scoring and structured feedback. Version-aware (17 / 18 / 19). |
| **[Odoo Code Tracer](agents/odoo-code-tracer/SKILL.md)** | Traces execution flow from an entry point through the call graph. Version-aware (17 / 18 / 19). |
| **[Planner](agents/planner.md)** | Breaks down complex features into actionable implementation steps |
### Rules - Coding Standards
#### Targeting an Odoo version
The Odoo agents automatically pick the right reference pack (`skills/odoo-17.0/`, `odoo-18.0/`, or `odoo-19.0/`). Resolution order:
1. **Explicit argument** passed to the agent (`odoo_version: "19.0"`).
2. **Project config**, in order: `.odoo-version` file at the repo root, `odoo_version` in `.claude/odoo.json`, `odoo.version` in `package.json`, or `tool.odoo.version` in `pyproject.toml`.
3. **Manifest heuristic** — the dominant major version found in workspace `__manifest__.py` files.
4. **Fallback** — latest supported (`19.0`). The agent states the assumption in its output.
Per-version rule deltas (e.g. `<tree>` vs `<list>`, `group_operator=` vs `aggregator=`, optional `_name` in v19) live in each pack's `references/api-highlights.md`.
### Rules — Coding Standards
Enforced patterns for consistent, secure code:
@@ -78,6 +180,7 @@ Enforced patterns for consistent, secure code:
```
agent-skills/
├── skills/
│ ├── odoo-17.0/ # Odoo 17 guides
│ ├── odoo-18.0/ # Odoo 18 guides
│ ├── odoo-19.0/ # Odoo 19 guides
│ ├── dtg-base/ # DTGBase utilities
@@ -86,37 +189,42 @@ agent-skills/
│ ├── brainstorming/ # Ideation and spec review
│ ├── writing-skills/ # Authoring AI skills
│ ├── mcp-builder/ # MCP servers
── slide/ # HTML/React slide decks
│ └── visual-explainer/ # HTML diagrams and visual explanations
├── agents/ # Odoo reviewers + planner
├── rules/ # Coding style and security
└── lib/ # Shared assets (e.g. images)
── slide/ # HTML/React slide decks
├── agents/ # Odoo reviewers + planner
├── rules/ # Coding style and security
├── bin/ # CLI entry point
└── lib/ # Shared assets (e.g. images)
```
---
## Supported IDEs
Agent Skills works with popular AI-powered IDEs:
Agent Skills works with popular AI-powered IDEs via `npx skills add`:
- **Cursor** - Rules, remote rules, or `npx skills add`
- **Claude Code** - Native skill support
- **Windsurf** - Compatible
- **Aider** - Compatible
- **Cursor** Rules, remote rules
- **Claude Code** Native skill support
- **Windsurf** Compatible
- **Aider** Compatible
---
## How It Works
```mermaid
graph LR
A[Your AI Assistant] --> B[Reads Agent Skills]
B --> C[Framework Knowledge]
B --> D[Best Practices]
B --> E[Code Patterns]
C --> F[Better Code Suggestions]
D --> F
E --> F
flowchart LR
A[👤 Developer] -->|writes prompt| B[🤖 AI Assistant]
B -->|reads| C[📚 Agent Skills]
C --> D[Framework Knowledge]
C --> E[Best Practices]
C --> F[Security Rules]
D --> G[✨ Better Code]
E --> G
F --> G
G -->|returns| A
style C fill:#4f46e5,stroke:#312e81,color:#fff
style G fill:#10b981,stroke:#064e3b,color:#fff
```
1. You add Agent Skills to your project
@@ -130,8 +238,8 @@ graph LR
| Metric | Value |
|--------|-------|
| Documentation | 10,000+ lines |
| Skill packs | 11 (Odoo 17.0, 18.0, 19.0, DTG Base, Payment, Code Review, Brainstorming, Writing Skills, MCP Builder, Slide, Visual Explainer) |
| Documentation | 55,000+ lines |
| Skill packs | 10 (Odoo 17.0, 18.0, 19.0, DTG Base, Payment, Code Review, Brainstorming, Writing Skills, MCP Builder, Slide) |
| Agents | 3 (Odoo Code Review, Odoo Code Tracer, Planner) |
| License | MIT |
@@ -141,13 +249,17 @@ graph LR
We welcome contributions! Here's how you can help:
- **Add new skills** - Create documentation for other frameworks
- **Improve existing docs** - Fix errors, add examples
- **Create agents** - Build specialized reviewers or planners
- **Report issues** - Let us know what's missing or broken
- **Add new skills** Create documentation for other frameworks
- **Improve existing docs** Fix errors, add examples
- **Create agents** Build specialized reviewers or planners
- **Report issues** Let us know what's missing or broken
Open an issue or discussion on GitHub if you want to propose changes or new skills.
[![Contributors](https://img.shields.io/github/contributors/unclecatvn/agent-skills?style=flat-square)](https://github.com/unclecatvn/agent-skills/graphs/contributors)
[![Open Issues](https://img.shields.io/github/issues/unclecatvn/agent-skills?style=flat-square)](https://github.com/unclecatvn/agent-skills/issues)
[![Open PRs](https://img.shields.io/github/issues-pr/unclecatvn/agent-skills?style=flat-square)](https://github.com/unclecatvn/agent-skills/pulls)
---
## Links
@@ -155,9 +267,14 @@ Open an issue or discussion on GitHub if you want to propose changes or new skil
- [Issues](https://github.com/unclecatvn/agent-skills/issues)
- [Discussions](https://github.com/unclecatvn/agent-skills/discussions)
- [Releases](https://github.com/unclecatvn/agent-skills/releases)
- [npm Package](https://www.npmjs.com/package/@unclecat/agent-skills-cli)
---
_If you find this project helpful, please consider giving it a star!_
<div align="center">
_If you find this project helpful, please consider giving it a ⭐ star!_
[![Star History Chart](https://api.star-history.com/svg?repos=unclecatvn/agent-skills&type=Date)](https://star-history.com/#unclecatvn/agent-skills&Date)
</div>
+116 -217
View File
@@ -1,318 +1,217 @@
---
name: odoo-code-review
description: Review Odoo code for correctness, security, performance, and Odoo 18 standards. Use when reviewing Odoo modules, diffs, or pull requests; produce a scored report with weighted criteria.
description: Review Odoo code for correctness, security, performance, and version-specific standards (Odoo 17, 18, or 19). Use when reviewing Odoo modules, diffs, or pull requests; produce a scored report with weighted criteria.
---
# Odoo Code Review
## Objective
Review Odoo code changes against clear criteria, identify risks, and score using a weighted scale from an Odoo 18 expert perspective.
Review Odoo code changes against clear criteria, identify risks, and score using a weighted scale from an Odoo-expert perspective — using the reference pack that matches the target Odoo version.
## Resolve the target Odoo version
Before reviewing, resolve `ODOO_VERSION` (one of `17.0`, `18.0`, `19.0`) in this order. Stop at the first one that succeeds:
1. **Explicit argument** passed to the agent invocation (e.g. `odoo_version: "19.0"`).
2. **Project config**, in this order:
- `.odoo-version` file at the repo root (contents: e.g. `19.0`).
- `odoo_version` key in `.claude/odoo.json`.
- `odoo.version` key in `package.json` or `tool.odoo.version` in `pyproject.toml`.
3. **Manifest heuristic** — scan workspace `__manifest__.py` files for the `'version'` key. Use the dominant major version (e.g. `18.0.1.0.0``18.0`).
4. **Fallback** — default to `19.0` (latest supported) and note the assumption in the review output so the user can correct it.
Derive `ODOO_MAJOR` from `ODOO_VERSION` by stripping `.0` (e.g. `18.0``18`). All guide paths below use these placeholders.
Supported versions: **17.0, 18.0, 19.0**. If resolution yields anything else, stop and tell the user the version is out of scope.
## Pre-review Requirements
- Read `skills/odoo/18.0/SKILL.md` as the master index for all Odoo 18 guides.
- Read relevant guides from `skills/odoo/18.0/dev/` based on change scope:
- **Models/ORM**: `odoo-18-model-guide.md`
- **Fields**: `odoo-18-field-guide.md`
- **Decorators**: `odoo-18-decorator-guide.md`
- **Performance**: `odoo-18-performance-guide.md`
- **Views/XML**: `odoo-18-view-guide.md`
- **Security**: `odoo-18-security-guide.md`
- **Controllers**: `odoo-18-controller-guide.md`
- **Transactions**: `odoo-18-transaction-guide.md`
- **Mixins**: `odoo-18-mixins-guide.md` (mail.thread, activities)
- **Testing**: `odoo-18-testing-guide.md`
- **Migration**: `odoo-18-migration-guide.md`
- **Actions**: `odoo-18-actions-guide.md`
- **Data Files**: `odoo-18-data-guide.md`
- **Manifest**: `odoo-18-manifest-guide.md`
- Read `skills/odoo-${ODOO_VERSION}/SKILL.md` as the master index for the resolved version's guides.
- Read `skills/odoo-${ODOO_VERSION}/references/api-highlights.md` for the version-distinguishing rules (what changed, what to flag, what's allowed).
- Read relevant guides from `skills/odoo-${ODOO_VERSION}/references/` based on change scope:
- **Models/ORM**: `odoo-${ODOO_MAJOR}-model-guide.md`
- **Fields**: `odoo-${ODOO_MAJOR}-field-guide.md`
- **Decorators**: `odoo-${ODOO_MAJOR}-decorator-guide.md`
- **Performance**: `odoo-${ODOO_MAJOR}-performance-guide.md`
- **Views/XML**: `odoo-${ODOO_MAJOR}-view-guide.md`
- **Security**: `odoo-${ODOO_MAJOR}-security-guide.md`
- **Controllers**: `odoo-${ODOO_MAJOR}-controller-guide.md`
- **Transactions**: `odoo-${ODOO_MAJOR}-transaction-guide.md`
- **Mixins**: `odoo-${ODOO_MAJOR}-mixins-guide.md` (mail.thread, activities)
- **Testing**: `odoo-${ODOO_MAJOR}-testing-guide.md`
- **Migration**: `odoo-${ODOO_MAJOR}-migration-guide.md`
- **Actions**: `odoo-${ODOO_MAJOR}-actions-guide.md`
- **Data Files**: `odoo-${ODOO_MAJOR}-data-guide.md`
- **Manifest**: `odoo-${ODOO_MAJOR}-manifest-guide.md`
- Identify scope: module, file, and change context.
- Master Odoo 18 API changes: `<list>` instead of `<tree>`, `@api.ondelete`, etc.
- Apply the version-distinguishing rules from `api-highlights.md` (e.g. `<tree>` vs `<list>`, `group_operator=` vs `aggregator=`, optional `_name` in v19, etc.).
## Expert Review Process
1. **Scope**: Identify change scope, objectives, and key risks
2. **ORM & Model Methods**: Search patterns, CRUD operations, recordset operations
3. **Field Definitions**: Field types, computed fields, relational field parameters
4. **API Decorators**: @api.depends, @api.constrains, @api.ondelete (Odoo 18!)
4. **API Decorators**: `@api.depends`, `@api.constrains`, `@api.ondelete`, `@api.model_create_multi`
5. **Performance**: N+1 detection, batch operations, field selection
6. **Transaction Management**: Savepoints, UniqueViolation, serialization
7. **Views & XML**: Odoo 18 tags (`<list>`), inheritance, structure
8. **Security**: ACL, record rules, exceptions, sudo usage
6. **Transaction Management**: Savepoints, `UniqueViolation`, serialization
7. **Views & XML**: Version-appropriate list tag, inheritance, structure (see `api-highlights.md`)
8. **Security**: ACL, record rules, exceptions, `sudo()` usage
9. **Controllers**: Auth types, CSRF protection, routing
10. **Mixins**: mail.thread, mail.activity.mixin, mail.alias.mixin usage
11. **Testing**: Test coverage, proper test cases, @tagged decorators
10. **Mixins**: `mail.thread`, `mail.activity.mixin`, `mail.alias.mixin` usage
11. **Testing**: Test coverage, proper test cases, `@tagged` decorators
12. **Migration**: Migration scripts, data migration patterns
13. **Actions**: Window actions, server actions, cron jobs
14. **Data Files**: XML/CSV data structure, noupdate, shortcuts
14. **Data Files**: XML/CSV data structure, `noupdate`, shortcuts
15. **Manifest**: Dependencies, external deps, hooks, assets
## Odoo 18 Complete Checklist
## Complete Checklist
Rules below are version-neutral unless they reference `api-highlights.md`. Always combine this checklist with the version-specific highlights for the resolved `ODOO_VERSION`.
### ORM & Model Methods (30%)
-**DO NOT** use `search()` inside loop (N+1 anti-pattern)
-**DO NOT** use `search()` inside a loop (N+1 anti-pattern)
- ✅ Use `search_read()` when dict output needed
- ✅ Use `read_group()` for aggregate queries
- ✅ Use `IN` domain instead of search in loop: `[('order_id', 'in', orders.ids)]`
- ✅ Batch `create([{...}, {...}])` for multiple records
- ✅ Use `recordset.write()` instead of loop
- ✅ Use `recordset.unlink()` instead of loop
-Use `mapped()` instead of list comprehension
- ✅ Use `filtered()` before operations
- ✅ Use `exists()` to filter non-existing records
-`@api.model_create_multi` on `create()` overrides (see `api-highlights.md` for version-specific enforcement)
### Field Definitions (15%)
- `Many2one` has `ondelete` parameter (`cascade`, `restrict`, `set null`)
- `Monetary` has `currency_field` parameter
- `One2many` has `inverse_name` parameter
- **DO NOT** use `Float` for currency (use `Monetary`)
-**DO NOT** use `<tree>` in Odoo 18 (use `<list>`)
- ✅ Computed fields have `store=True` if searchable/groupable needed
-`@api.depends` includes ALL dependencies with dotted paths
### Views & XML (15%)
- Use the list tag appropriate to `ODOO_VERSION` (see `api-highlights.md`: `<tree>` in 17, `<list>` in 18+).
- Use direct-expression attrs (`invisible="..."`, `readonly="..."`, `required="..."`) — legacy `attrs=`/`states=` are rejected in 17+.
- Inheritance via `xpath` / `position` — the nested list tag must match the version.
- Avoid duplicate `name=` attributes in records.
### API Decorators (15%)
- `@api.depends` uses dotted paths for related fields: `@api.depends('partner_id.email')`
- **DO NOT** use dotted paths in `@api.constrains` (only simple field names)
- `@api.ondelete(at_uninstall=False)` instead of overriding `unlink()` for validation (Odoo 18!)
- `@api.constrains` raises `ValidationError`
-`@api.model_create_multi` for batch create (Odoo 18)
### Fields (15%)
- `Monetary` with `currency_field`
- `Many2one` with `ondelete`
- Computed field with `store=True` if filtered/searched
- Aggregation parameter: `group_operator=` (v17) vs `aggregator=` (v18+) — see `api-highlights.md`.
### Performance (20%)
- **DO NOT** `search()` in loop
- **DO NOT** `browse()` in loop
- **DO NOT** `create()` in loop
- **DO NOT** `write()` in loop
-**DO NOT** `unlink()` in loop
- ✅ Use prefetch (automatic) for related field access
- ✅ Use `search_read()` to fetch specific fields
- ✅ Use `bin_size=True` for binary fields
- ✅ Use advisory locks for concurrent operations
### Decorators (10%)
- `@api.depends` with complete dotted paths
- `@api.constrains` for invariants
- `@api.ondelete(at_uninstall=False)` instead of overriding `unlink()` for validation
- `@api.model_create_multi` for batch create
### Transaction Management (10%)
- ✅ Use `with self.env.cr.savepoint():` for error isolation
- **DO NOT** continue after UniqueViolation without savepoint
- Use advisory locks to prevent serialization errors
- ✅ Group identical updates to minimize conflicts
### Performance (10%)
- Avoid N+1 in loops
- Prefer `read_group()` / `search_read()` over per-record fetches
- Use `prefetch_fields` thoughtfully
### Views & XML (5%)
- ✅ Use `<list>` instead of `<tree>` (Odoo 18!)
- ✅ Use `decoration-*` for row styling
- ✅ Use `xpath` or shorthand with `position` for inheritance
- ✅ Proper `inherit_id` reference
### Transactions (5%)
- `savepoint` around recoverable failures
- Handle `UniqueViolation` explicitly
- Advisory locks for cross-record serialization
### Security (5%)
- ✅ Has `ir.model.access.csv` file with proper permissions
- ✅ Use `UserError` for business logic errors
- ✅ Use `ValidationError` for constraint violations
- ✅ Use `AccessError` for permission issues
-**DO NOT** raise generic `Exception`
- ✅ Record rules defined with proper domain_force
- Specific exceptions: `UserError`, `ValidationError`, `AccessError`
- No bare `except Exception`
- `sudo()` used narrowly with justification
### Controllers (5%)
- ✅ Use correct `auth` type (`user`, `public`, `none`)
- ✅ Use `auth='none'` for truly public endpoints (webhooks)
- ✅ CSRF enabled for POST (default)
-`csrf=False` only for external webhooks
### Controllers (3%)
- Correct `auth=` (`user`, `public`, `none`)
- `csrf=False` only with justification
- `type='json'` vs `type='http'` matches the client
### Mixins (Additional Check)
- `mail.thread` properly configured with `tracking=True` on tracked fields
- `mail.activity.mixin` used for activity-enabled models
- `mail.alias.mixin` properly configured with alias fields
-`utm.mixin` for campaign tracking when applicable
- ✅ Proper message_post usage, not direct chatter manipulation
### Mixins (3%)
- `mail.thread` with proper tracking fields
- `mail.activity.mixin` for activities
- `mail.alias.mixin` with alias fields
### Testing (Additional Check)
- Tests cover new functionality
- Proper use of `@tagged` decorators (standard, post_install, etc.)
- ✅ TransactionCase for model tests, HttpCase for web tests
- ✅ Test data properly isolated
- ✅ Query count assertions for performance-critical code
### Testing (2%)
- Tests for new functionality
- Proper use of `@tagged`
- Query count assertions for hot paths
### Migration (Additional Check)
- ✅ Migration scripts in `migrations/{version}/` directory
- ✅ Pre-migration scripts for data cleanup
- ✅ Post-migration scripts for data migration
- ✅ Uses hooks (pre_init, post_init, uninstall) appropriately
- ✅ Idempotent migration scripts
### Manifest & Data (2%)
- All dependencies declared
- External deps listed
- Hooks wired correctly
- `noupdate="1"` for reference data
## Anti-Patterns to Detect
## Scoring
| Anti-Pattern | Consequence | Fix |
|--------------|-------------|-----|
| `search()` in loop | N+1 queries | Use `search_read()` with `IN` domain |
| `create()` in loop | N INSERT statements | Batch: `create([{...}, {...}])` |
| `write()` in loop | N UPDATE statements | `records.write({...})` |
| `unlink()` in loop | N DELETE statements | `records.unlink()` |
| Override `unlink()` for validation | Breaks module uninstall | Use `@api.ondelete(at_uninstall=False)` |
| `@api.depends('a')` then access `a.b` | N queries | Add `@api.depends('a.b')` |
| `@api.constrains('a.b')` | Not supported | Use only `@api.constrains('a')` |
| `<tree>` in Odoo 18 | Deprecated | Use `<list>` |
| `Float` for currency | Precision issues | Use `Monetary` |
| Missing `ondelete` on Many2one | Orphan records | Add `ondelete='cascade/restrict'` |
| Generic `Exception` | Poor UX | Use `UserError`, `ValidationError` |
| Continue after UniqueViolation without savepoint | Transaction aborted | Use `with self.env.cr.savepoint():` |
| Direct chatter manipulation instead of message_post | Breaks mail.thread features | Use `message_post()` with proper subtype |
| Missing `tracking=True` on tracked fields | No field tracking in chatter | Add `tracking=True` to field definition |
| Tests without `@tagged` decorators | Wrong test environment | Add `@tagged('standard')`, `@tagged('post_install')` |
| Non-idempotent migration script | Fails on re-run | Use `if not field_exists:` checks |
| Missing `noupdate="1"` on reference data | Data overwritten on update | Add `noupdate="1"` to reference records |
| Cron without `interval_number` and `interval_type` | Never runs | Add proper interval configuration |
## Scoring Scale (Weighted)
**Criteria** (score 1-10):
- **ORM & Model Methods** (28%)
- **Field Definitions** (14%)
- **API Decorators** (14%)
- **Performance** (18%)
- **Transaction Management** (10%)
- **Views & XML** (4%)
- **Security** (6%)
- **Controllers** (6%)
**Total calculation**:
```
total = 0.28*orm + 0.14*fields + 0.14*decorators + 0.18*performance + 0.10*transaction + 0.04*views + 0.06*security + 0.06*controllers
```
**Score anchors**:
- **9-10**: Excellent, no significant risks, follows all best practices
- **7-8**: Good, minor issues or improvements possible
- **5-6**: Average, clear risks to address, has anti-patterns
- **3-4**: Poor, serious errors or regression-prone
- **1-2**: Very poor, cannot merge, violates critical patterns
## Report Format (Required)
```
## Quick Summary
- [1-2 sentences summarizing key points]
## Overall Score
- Total: X.X/10
- Formula: 0.28*ORM + 0.14*Fields + 0.14*Decorators + 0.18*Perf + 0.10*Trans + 0.04*Views + 0.06*Sec + 0.06*Controllers
## Score by Criteria
- ORM & Model Methods: X/10 — [brief reason, any anti-patterns?]
- Field Definitions: X/10 — [brief reason]
- API Decorators: X/10 — [brief reason, check @api.ondelete, dotted paths]
- Performance: X/10 — [brief reason, any N+1?]
- Transaction Management: X/10 — [brief reason, savepoints correct?]
- Views & XML: X/10 — [brief reason, using <list>?]
- Security: X/10 — [brief reason]
- Controllers: X/10 — [brief reason]
## Key Findings (high → low priority)
### 🔴 Critical (Must Fix)
- [Severity] Brief description + consequence + fix suggestion
- Code reference: `path/file.py:XX`
### 🟡 Major (Should Fix)
- [Severity] Brief description + consequence + fix suggestion
- Code reference: `path/file.py:XX`
### 🔵 Minor (Nice to Have)
- [Severity] Brief description + improvement suggestion
## Positive Patterns Found
- ✅ [Good pattern found] - Line XX
## Recommendations
- [Specific, clear improvements, in priority order]
## Testing
- Ran: [if any, state commands]
- Missing: [tests missing or not run, N+1 scenarios]
```
## Response Rules
- Prioritize error and risk detection first, then suggestions
- If no significant issues, clearly state "No findings"
- Cite correct file and code when needed: `path/to/file.py:XX`
- State assumptions when information is missing (don't guess)
- Focus on Odoo-specific patterns, not generic Python advice
- Provide code examples for complex issues
- Reference Odoo documentation when applicable
Weight each section per the percentages above. Total out of 100. Report:
- Score per section with brief justification.
- Blocking issues (must fix before merge).
- Non-blocking suggestions.
- Explicitly name the resolved `ODOO_VERSION` at the top of the report.
## Deep Dive Checks
When reviewing, thoroughly check:
When reviewing, thoroughly check (references below use `${ODOO_MAJOR}` — substitute the resolved value):
1. **Does @api.depends have complete dependencies?**
1. **Does `@api.depends` have complete dependencies?**
- Check dotted paths: `partner_id.email` instead of just `partner_id`
- Missing dependencies cause N queries
- Reference: `dev/odoo-18-decorator-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-decorator-guide.md`
2. **Are there N+1 queries?**
- Loop with `search()`, `browse()`, `read()` inside
- Solution: `search_read()` with `IN` domain or `read_group()`
- Reference: `dev/odoo-18-performance-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-performance-guide.md`
3. **Are there batch operations?**
- `create()`, `write()`, `unlink()` in loop
- Solution: Batch operations on recordset
- Reference: `dev/odoo-18-performance-guide.md`
- Solution: batch operations on recordset
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-performance-guide.md`
4. **Is transaction safe?**
- UniqueViolation handling without savepoint
- `UniqueViolation` handling without savepoint
- Concurrent updates without advisory lock
- Reference: `dev/odoo-18-transaction-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-transaction-guide.md`
5. **Are Odoo 18 patterns correct?**
- Use `<list>` instead of `<tree>`
- Use `@api.ondelete()` instead of overriding `unlink()`
- Use `@api.model_create_multi` for batch create
- Reference: `dev/odoo-18-view-guide.md`
5. **Are version-specific patterns correct?**
- List tag, attrs syntax, aggregation parameter, optional `_name` (v19).
- Reference: `skills/odoo-${ODOO_VERSION}/references/api-highlights.md` + `odoo-${ODOO_MAJOR}-view-guide.md`
6. **Are field definitions correct?**
- `Monetary` with `currency_field`
- `Many2one` with `ondelete`
- Computed field with `store=True` if needed
- Reference: `dev/odoo-18-field-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-field-guide.md`
7. **Is exception handling correct?**
- `UserError`, `ValidationError`, `AccessError`
- No generic `Exception`
- Reference: `dev/odoo-18-security-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-security-guide.md`
8. **Are mixins properly configured?**
- `mail.thread` with proper tracking fields
- `mail.activity.mixin` for activities
- `mail.alias.mixin` with alias fields
- Reference: `dev/odoo-18-mixins-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-mixins-guide.md`
9. **Is testing adequate?**
- Tests for new functionality
- Proper use of `@tagged` decorators
- Query count assertions for performance
- Reference: `dev/odoo-18-testing-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-testing-guide.md`
10. **Are migrations handled correctly?**
- Proper migration script location
- Pre/post migration scripts
- Idempotent operations
- Reference: `dev/odoo-18-migration-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-migration-guide.md`
11. **Are actions properly defined?**
- Window actions with correct context
- Server actions for automation
- Cron jobs with proper intervals
- Reference: `dev/odoo-18-actions-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-actions-guide.md`
12. **Are data files correct?**
- Proper XML record structure
- `noupdate="1"` for reference data
- CSV data properly formatted
- Reference: `dev/odoo-18-data-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-data-guide.md`
13. **Is manifest correct?**
- All dependencies declared
- External dependencies listed
- Hooks properly configured
- Reference: `dev/odoo-18-manifest-guide.md`
- Reference: `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-manifest-guide.md`
+20 -4
View File
@@ -8,7 +8,20 @@ is_background: false
# Odoo Code Tracer Agent
You are an expert Odoo 18 code execution tracer. Your mission is to trace code flow from start to finish, identifying every function call, override, and execution path.
You are an expert Odoo code execution tracer (Odoo 17, 18, or 19). Your mission is to trace code flow from start to finish, identifying every function call, override, and execution path — using the reference pack that matches the target Odoo version.
## Resolve the target Odoo version
Before tracing, resolve `ODOO_VERSION` (one of `17.0`, `18.0`, `19.0`) in this order. Stop at the first one that succeeds:
1. **Explicit argument** passed to the agent invocation (e.g. `odoo_version: "19.0"`).
2. **Project config**: `.odoo-version` file at the repo root, `odoo_version` in `.claude/odoo.json`, `odoo.version` in `package.json`, or `tool.odoo.version` in `pyproject.toml`.
3. **Manifest heuristic**: scan workspace `__manifest__.py` files for the `'version'` key — use the dominant major.
4. **Fallback**: default to `19.0` and note the assumption in your trace output.
Derive `ODOO_MAJOR` from `ODOO_VERSION` (e.g. `18.0``18`). Supported: **17.0, 18.0, 19.0** — anything else is out of scope.
Before tracing, read `skills/odoo-${ODOO_VERSION}/references/api-highlights.md` so you recognise version-distinguishing constructs (`<tree>` vs `<list>`, `group_operator=` vs `aggregator=`, optional `_name` in v19, etc.) as you follow the code.
## Objective
@@ -71,7 +84,9 @@ While tracing, identify:
- **Inheritance complexity**: Deep override chains
- **Side effects**: Emails sent, notifications created, external calls
## Odoo 18 Specific Patterns
## Odoo Patterns (Version-Aware)
The patterns below are structural and apply across all supported versions. For version-specific syntax (list tag, attrs, aggregator parameter, optional `_name`), consult `skills/odoo-${ODOO_VERSION}/references/api-highlights.md` while tracing.
### Model Inheritance Tracing
@@ -306,5 +321,6 @@ graph TD
This tracer works best when combined with:
- `odoo-code-review`: For scoring traced code
- `odoo-18` guides: For understanding Odoo patterns
- `odoo-18-performance-guide`: For analyzing query patterns
- `skills/odoo-${ODOO_VERSION}/` guides: for understanding Odoo patterns at the resolved version
- `skills/odoo-${ODOO_VERSION}/references/odoo-${ODOO_MAJOR}-performance-guide.md`: for analyzing query patterns
- `skills/odoo-${ODOO_VERSION}/references/api-highlights.md`: for version-distinguishing syntax
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 MiB

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@unclecat/agent-skills-cli",
"version": "1.0.9",
"version": "1.0.10",
"description": "CLI and docs for installing agent skills by version.",
"bin": {
"agent-skills": "bin/agent-skills.js"
@@ -0,0 +1,41 @@
---
name: odoo-17-api-highlights
description: Version-distinguishing API patterns for Odoo 17. Read this when the target version is 17.0 so the reviewer/tracer applies the right rules.
---
# Odoo 17 API Highlights
Use this file as the version-specific ruleset when the resolved Odoo version is `17.0`. It supplements — not replaces — the general review checklist.
## Views
- **List view tag: `<tree>`** — Odoo 17 still uses `<tree>`. The rename to `<list>` happens in 18.
- Applies everywhere: view records, `xpath` expressions, action `view_mode="tree,form"`.
- **Legacy `attrs=` / `states=` are rejected by the view validator in 17.** Use direct-expression attributes:
- `attrs="{'invisible': [('state','=','done')]}"``invisible="state == 'done'"`
- `attrs="{'readonly': [('locked','=',True)]}"``readonly="locked"`
- `states="draft,confirmed"``invisible="state not in ('draft','confirmed')"`
- Reference: `references/odoo-17-view-guide.md`.
## Fields
- **Aggregation parameter: `group_operator=`** (numeric fields default to `'sum'`). The parameter is renamed to `aggregator=` in later versions — in v17 source, `aggregator=` will fail or be silently ignored.
- Reference: `references/odoo-17-field-guide.md`.
## Decorators
- **`@api.model_create_multi`** is required when overriding `create()`. Do not rely on the `@api.model` fallback.
- **`@api.ondelete(at_uninstall=False)`** is available (since 15) and preferred over overriding `unlink()` for validation.
- Reference: `references/odoo-17-decorator-guide.md`.
## Frontend
- OWL 2.8 is the frontend framework version shipped with Odoo 17.
## Quick review checks (v17-specific)
- ❌ `<list>` tag (belongs in 18+) — flag as wrong version.
- ❌ `attrs="..."` / `states="..."` — must be rewritten to direct expressions.
- ❌ `aggregator=` — use `group_operator=` in 17.
- ✅ `@api.model_create_multi` on `create()` overrides.
- ✅ `@api.ondelete` for delete validation.
@@ -0,0 +1,36 @@
---
name: odoo-18-api-highlights
description: Version-distinguishing API patterns for Odoo 18. Read this when the target version is 18.0 so the reviewer/tracer applies the right rules.
---
# Odoo 18 API Highlights
Use this file as the version-specific ruleset when the resolved Odoo version is `18.0`. It supplements — not replaces — the general review checklist.
## Views
- **List view tag: `<list>`**`<tree>` is deprecated in 18. Use `<list>` everywhere, including `xpath` expressions and action `view_mode="list,form"`.
- **Direct-expression attrs only** — legacy `attrs=` / `states=` are rejected (carried from 17). Use `invisible="..."`, `readonly="..."`, `required="..."`.
- Reference: `references/odoo-18-view-guide.md`.
## Fields
- **Aggregation parameter: `aggregator=`** (replaces `group_operator=` from v17). Numeric fields default to `'sum'`.
- Reference: `references/odoo-18-field-guide.md`.
## Decorators
- **`@api.ondelete(at_uninstall=False)`** — preferred over overriding `unlink()` for validation. Overriding `unlink()` for checks breaks module uninstallation.
- **`@api.model_create_multi`** — overriding `create()` without it emits a deprecation warning in 18.
- Reference: `references/odoo-18-decorator-guide.md`.
## Quick review checks (v18-specific)
- ❌ `<tree>` tag — must be `<list>` in 18.
- ❌ `attrs="..."` / `states="..."` — rewrite to direct expressions.
- ❌ `group_operator=` — use `aggregator=` in 18.
- ❌ Overriding `unlink()` for validation — use `@api.ondelete`.
- ❌ Overriding `create()` without `@api.model_create_multi`.
- ✅ `<list>` in view records, xpath, and action `view_mode`.
- ✅ `@api.ondelete(at_uninstall=False)` for delete rules.
- ✅ `@api.model_create_multi` for batch create.
@@ -0,0 +1,35 @@
---
name: odoo-19-api-highlights
description: Version-distinguishing API patterns for Odoo 19. Read this when the target version is 19.0 so the reviewer/tracer applies the right rules.
---
# Odoo 19 API Highlights
Use this file as the version-specific ruleset when the resolved Odoo version is `19.0`. It supplements — not replaces — the general review checklist. Everything from 18 applies unless noted below.
## Models
- **`_name` is optional** — Odoo 19 derives it automatically from the CamelCase class name (each capital letter → `.` separator):
- `ResPartner``res.partner`
- `SaleOrder``sale.order`
- `MyModel``my.model`
- **`_sql_constraints`** — the constraint name can be omitted; Odoo auto-generates a unique name based on model + attribute.
- Reference: `references/odoo-19-model-guide.md`.
## Views
- Same as 18: `<list>` tag, direct-expression attrs. Reference: `references/odoo-19-view-guide.md`.
## Fields
- Same as 18: `aggregator=` for aggregation. Reference: `references/odoo-19-field-guide.md`.
## Decorators
- Same as 18: `@api.ondelete`, `@api.model_create_multi`. `@api.returns` usage patterns are expanded in the v19 guide. Reference: `references/odoo-19-decorator-guide.md`.
## Quick review checks (v19-specific)
- ✅ `_name` may be omitted when the CamelCase class name maps correctly — don't flag as missing.
- ✅ Unnamed `_sql_constraints` are valid — don't flag as missing name.
- All 18 rules still apply (`<list>`, direct-expression attrs, `aggregator=`, `@api.ondelete`, `@api.model_create_multi`).
+136 -199
View File
@@ -1,219 +1,156 @@
#!/usr/bin/env node
/**
* Test Script - Verify Claude's access to skills and guides
* Structural validation for the agent-skills repo.
*
* This script tests:
* 1. What documentation is loaded in settings.json
* 2. Whether guide files exist
* 3. Estimated token count for each guide
* Checks:
* 1. Every skills/* and agents/* folder has a SKILL.md with
* valid `name` and `description` in its YAML frontmatter.
* 2. Every component path listed in .claude-plugin/plugin.json exists.
* 3. package.json version has a matching section in CHANGELOG.md
* (skipped if the version is still 0.x or under [Unreleased]).
*
* Exit code 0 on success, 1 on any failure.
*/
const fs = require('fs');
const path = require('path');
const fs = require("fs");
const path = require("path");
// Detect if running from agent-skills or parent directory
const RUN_DIR = process.cwd();
const PARENT_DIR = path.dirname(RUN_DIR);
const ROOT = path.resolve(__dirname, "..");
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const RESET = "\x1b[0m";
// Check both directories for .claude/settings.json
// Prefer parent directory's settings (main project config)
const parentSettings = path.join(PARENT_DIR, '.claude', 'settings.json');
const localSettings = path.join(RUN_DIR, '.claude', 'settings.json');
const errors = [];
const warnings = [];
const BASE_DIR = fs.existsSync(parentSettings) ? PARENT_DIR : RUN_DIR;
const SETTINGS_PATH = path.join(BASE_DIR, '.claude', 'settings.json');
const SKILLS_BASE = path.join(BASE_DIR, 'agent-skills');
const GUIDES_DIR = path.join(SKILLS_BASE, 'skills/odoo/18.0/dev');
// ANSI colors
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
bold: '\x1b[1m'
};
function log(color, ...args) {
console.log(color + args.join(' ') + colors.reset);
function fail(msg) {
errors.push(msg);
console.log(`${RED}${RESET} ${msg}`);
}
function checkFile(filePath) {
try {
const stats = fs.statSync(filePath);
const content = fs.readFileSync(filePath, 'utf8');
return {
exists: true,
size: stats.size,
lines: content.split('\n').length,
chars: content.length,
// Rough estimate: 1 token ≈ 4 characters
estimatedTokens: Math.ceil(content.length / 4)
};
} catch {
return { exists: false };
function warn(msg) {
warnings.push(msg);
console.log(`${YELLOW}!${RESET} ${msg}`);
}
function ok(msg) {
console.log(`${GREEN}${RESET} ${msg}`);
}
function readFrontmatter(filePath) {
const text = fs.readFileSync(filePath, "utf8");
if (!text.startsWith("---")) return null;
const end = text.indexOf("\n---", 3);
if (end === -1) return null;
const body = text.slice(3, end).replace(/^\r?\n/, "");
const fields = {};
for (const line of body.split(/\r?\n/)) {
const m = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line);
if (m) fields[m[1]] = m[2].trim();
}
return fields;
}
function getGuideList() {
const guides = [
{ name: 'Actions', file: 'odoo-18-actions-guide.md', priority: 'medium' },
{ name: 'Controller', file: 'odoo-18-controller-guide.md', priority: 'low' },
{ name: 'Data', file: 'odoo-18-data-guide.md', priority: 'medium' },
{ name: 'Decorator', file: 'odoo-18-decorator-guide.md', priority: 'high' },
{ name: 'Development', file: 'odoo-18-development-guide.md', priority: 'high' },
{ name: 'Field', file: 'odoo-18-field-guide.md', priority: 'high' },
{ name: 'Manifest', file: 'odoo-18-manifest-guide.md', priority: 'medium' },
{ name: 'Mixins', file: 'odoo-18-mixins-guide.md', priority: 'medium' },
{ name: 'Model', file: 'odoo-18-model-guide.md', priority: 'high' },
{ name: 'Migration', file: 'odoo-18-migration-guide.md', priority: 'low' },
{ name: 'OWL', file: 'odoo-18-owl-guide.md', priority: 'low' },
{ name: 'Performance', file: 'odoo-18-performance-guide.md', priority: 'high' },
{ name: 'Reports', file: 'odoo-18-reports-guide.md', priority: 'low' },
{ name: 'Security', file: 'odoo-18-security-guide.md', priority: 'high' },
{ name: 'Testing', file: 'odoo-18-testing-guide.md', priority: 'medium' },
{ name: 'Transaction', file: 'odoo-18-transaction-guide.md', priority: 'medium' },
{ name: 'Translation', file: 'odoo-18-translation-guide.md', priority: 'low' },
{ name: 'View', file: 'odoo-18-view-guide.md', priority: 'medium' }
];
return guides;
}
function validateSkillDir(dir, label) {
const entries = fs
.readdirSync(dir, { withFileTypes: true })
.filter((e) => e.isDirectory());
async function main() {
console.clear();
log(colors.cyan, '\n╔════════════════════════════════════════════════════════════╗');
log(colors.cyan, '║ Claude Skills & Guides Access Test ║');
log(colors.cyan, '╚════════════════════════════════════════════════════════════╝\n');
// 1. Check settings.json
log(colors.bold, '📋 Step 1: Checking .claude/settings.json');
log(colors.blue, '─'.repeat(60));
const settings = checkFile(SETTINGS_PATH);
if (!settings.exists) {
log(colors.red, ' ❌ settings.json not found!');
if (entries.length === 0) {
warn(`${label}/ is empty`);
return;
}
let settingsData;
try {
settingsData = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
log(colors.green, ` ✅ settings.json found (${settings.lines} lines)`);
} catch (err) {
log(colors.red, ` ❌ Failed to parse settings.json: ${err.message}`);
return;
}
// 2. Check what's in documentation
log(colors.bold, '\n📚 Step 2: Checking documentation array');
log(colors.blue, '─'.repeat(60));
const docs = settingsData.documentation || [];
if (docs.length === 0) {
log(colors.yellow, ' ⚠️ No documentation configured');
} else {
log(colors.green, `${docs.length} file(s) in documentation:\n`);
let totalTokens = 0;
docs.forEach((docPath, idx) => {
// Resolve documentation paths relative to BASE_DIR (where settings.json is)
const fullPath = path.join(BASE_DIR, docPath);
const info = checkFile(fullPath);
if (info.exists) {
const icon = docPath.includes('SKILL.md') ? '📑' : '📄';
log(colors.reset, ` ${idx + 1}. ${icon} ${path.basename(docPath)}`);
log(colors.reset, ` Path: ${docPath}`);
log(colors.reset, ` Tokens: ~${info.estimatedTokens.toLocaleString()}`);
totalTokens += info.estimatedTokens;
console.log('');
} else {
log(colors.red, ` ${idx + 1}. ❌ ${docPath} (NOT FOUND)`);
}
});
log(colors.cyan, ` 📊 Total estimated tokens: ~${totalTokens.toLocaleString()}\n`);
}
// 3. Check all guide files
log(colors.bold, '📁 Step 3: Scanning all guide files');
log(colors.blue, '─'.repeat(60));
const guides = getGuideList();
const loadedGuides = docs.map(d => path.basename(d));
console.log('');
console.log(' Priority Legend:');
log(colors.green, ' 🟢 HIGH - Essential for daily work');
log(colors.yellow, ' 🟡 MEDIUM - Frequently used');
log(colors.red, ' 🔴 LOW - Occasionally used');
console.log('');
console.log(' Status Legend:');
log(colors.green, ' ✅ LOADED - In documentation, auto-loaded');
log(colors.yellow, ' ⚠️ AVAILABLE - Exists, Claude must Read manually');
log(colors.red, ' ❌ MISSING - File not found');
console.log('');
let loadedCount = 0;
let availableCount = 0;
let missingCount = 0;
guides.forEach(guide => {
const fullPath = path.join(GUIDES_DIR, guide.file);
const info = checkFile(fullPath);
const isLoaded = loadedGuides.includes(guide.file);
if (!info.exists) {
log(colors.red, ` ❌ [${guide.priority.toUpperCase()}] ${guide.name}`);
log(colors.red, ` File: ${guide.file} - NOT FOUND`);
missingCount++;
} else if (isLoaded) {
log(colors.green, ` ✅ [${guide.priority.toUpperCase()}] ${guide.name}`);
log(colors.green, ` ~${info.estimatedTokens.toLocaleString()} tokens - AUTO-LOADED`);
loadedCount++;
} else {
log(colors.yellow, ` ⚠️ [${guide.priority.toUpperCase()}] ${guide.name}`);
log(colors.yellow, ` ~${info.estimatedTokens.toLocaleString()} tokens - NEEDS READ`);
availableCount++;
for (const entry of entries) {
const skillPath = path.join(dir, entry.name, "SKILL.md");
const rel = path.relative(ROOT, skillPath);
if (!fs.existsSync(skillPath)) {
fail(`missing ${rel}`);
continue;
}
});
// 4. Summary
log(colors.bold, '\n📊 Summary');
log(colors.blue, '─'.repeat(60));
log(colors.green, ` ✅ Auto-loaded (in documentation): ${loadedCount}/${guides.length}`);
log(colors.yellow, ` ⚠️ Available (needs Read tool): ${availableCount}/${guides.length}`);
if (missingCount > 0) {
log(colors.red, ` ❌ Missing: ${missingCount}/${guides.length}`);
const fm = readFrontmatter(skillPath);
if (!fm) {
fail(`${rel}: no YAML frontmatter`);
continue;
}
if (!fm.name) fail(`${rel}: frontmatter missing 'name'`);
if (!fm.description) fail(`${rel}: frontmatter missing 'description'`);
if (fm.name && fm.description) ok(rel);
}
// 5. Recommendations
log(colors.bold, '\n💡 Recommendations');
log(colors.blue, '─'.repeat(60));
const highPriorityNotLoaded = guides.filter(
g => g.priority === 'high' && !loadedGuides.includes(g.file)
);
if (loadedCount >= 5) {
log(colors.green, ' ✅ Good coverage! Core guides are loaded.');
} else {
log(colors.yellow, ' ⚠️ Consider adding more HIGH priority guides.');
}
console.log('');
log(colors.cyan, ' Test Questions for Claude:\n');
log(colors.reset, ' 1. "Tạo computed field với @api.depends trong Odoo 18"');
log(colors.reset, ' → Needs: decorator-guide.md\n');
log(colors.reset, ' 2. "Fix N+1 query khi search trong loop"');
log(colors.reset, ' → Needs: performance-guide.md\n');
log(colors.reset, ' 3. "Tạo ir.model.access.csv cho model mới"');
log(colors.reset, ' → Needs: security-guide.md\n');
log(colors.bold, '\n✨ Test complete!\n');
}
main().catch(err => {
console.error('Error:', err);
process.exit(1);
});
function validatePluginManifest() {
const manifestPath = path.join(ROOT, ".claude-plugin", "plugin.json");
if (!fs.existsSync(manifestPath)) {
warn(".claude-plugin/plugin.json not found — skipping manifest check");
return;
}
let manifest;
try {
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
} catch (err) {
fail(`plugin.json: invalid JSON (${err.message})`);
return;
}
const components = manifest.components || {};
for (const [kind, list] of Object.entries(components)) {
if (!Array.isArray(list)) continue;
for (const relPath of list) {
const abs = path.join(ROOT, relPath);
if (!fs.existsSync(abs)) {
fail(`plugin.json: ${kind} path does not exist: ${relPath}`);
} else {
ok(`plugin.json/${kind}: ${relPath}`);
}
}
}
}
function validateChangelog() {
const pkgPath = path.join(ROOT, "package.json");
const changelogPath = path.join(ROOT, "CHANGELOG.md");
if (!fs.existsSync(pkgPath) || !fs.existsSync(changelogPath)) return;
const version = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
if (!version) return;
const changelog = fs.readFileSync(changelogPath, "utf8");
const re = new RegExp(
`^##\\s+\\[?v?${version.replace(/\./g, "\\.")}\\]?\\s*$`,
"m"
);
if (re.test(changelog)) {
ok(`CHANGELOG.md has section for v${version}`);
} else {
fail(
`CHANGELOG.md: missing section for v${version} (expected '## [${version}]')`
);
}
}
function main() {
console.log("Validating skills/");
validateSkillDir(path.join(ROOT, "skills"), "skills");
console.log("\nValidating agents/");
validateSkillDir(path.join(ROOT, "agents"), "agents");
console.log("\nValidating plugin manifest");
validatePluginManifest();
console.log("\nValidating CHANGELOG");
validateChangelog();
console.log("");
if (errors.length > 0) {
console.log(`${RED}${errors.length} error(s)${RESET}`);
process.exit(1);
}
if (warnings.length > 0) {
console.log(`${YELLOW}${warnings.length} warning(s)${RESET}`);
}
console.log(`${GREEN}All checks passed.${RESET}`);
}
main();