mirror of
https://github.com/unclecatvn/agent-skills.git
synced 2026-09-14 20:53:17 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9a609bdcc | |||
| 56935b49a8 | |||
| b1a046d51e | |||
| f8223189af | |||
| 6e54395980 | |||
| 926bc1d6b0 | |||
| a44d689fe1 | |||
| 87e2b1bad7 | |||
| c79ad3faff | |||
| 942b531bba | |||
| a27fa4dfc7 |
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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]
|
||||
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
<div align="center">
|
||||
|
||||
# Agent Skills
|
||||
|
||||

|
||||

|
||||
|
||||
**Curated AI skill packs for Odoo, payments, and MCP — 55k+ lines of framework expertise for your AI coding assistant.**
|
||||
|
||||
[](https://www.npmjs.com/package/@unclecat/agent-skills-cli)
|
||||
[](https://www.npmjs.com/package/@unclecat/agent-skills-cli)
|
||||
[](LICENSE)
|
||||
[](https://github.com/unclecatvn/agent-skills/stargazers)
|
||||
[](https://github.com/unclecatvn/agent-skills/commits/main)
|
||||
[](https://github.com/unclecatvn/agent-skills/pulls)
|
||||
[](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 (1–10) 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.
|
||||
|
||||
[](https://github.com/unclecatvn/agent-skills/graphs/contributors)
|
||||
[](https://github.com/unclecatvn/agent-skills/issues)
|
||||
[](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!_
|
||||
|
||||
[](https://star-history.com/#unclecatvn/agent-skills&Date)
|
||||
|
||||
</div>
|
||||
|
||||
+116
-217
@@ -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`
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user