ccteams 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/bin/ccteams.js +65 -5
- package/lib/update-check.js +168 -0
- package/package.json +1 -1
- package/teams/django/agents/django-builder.md +58 -0
- package/teams/django/agents/django-reviewer.md +61 -0
- package/teams/django/agents/drf-api.md +52 -0
- package/teams/django/orchestration.md +28 -0
- package/teams/django/team.json +6 -0
package/README.md
CHANGED
|
@@ -95,9 +95,11 @@ ccteams ships with these teams out of the box. Each is a builder + reviewer pair
|
|
|
95
95
|
| `generalist` | Stack-agnostic, end-to-end feature team: scope → design → build → QA → ship. Use when no stack-specific team fits or for general cross-stack work. |
|
|
96
96
|
| `next-ts` | Next.js (App Router) + TypeScript + Tailwind — RSC, Server Actions, type-safe data fetching, accessible UI. |
|
|
97
97
|
| `frontend` | Framework-agnostic UI/UX and accessibility — UI work that isn't Next.js-specific, or focused on a11y/responsive/UX quality. |
|
|
98
|
+
| `react-native` | Expo + React Native (TypeScript) mobile apps — screens, navigation, data fetching, plus a native-decisions advisor (Expo/EAS/config plugins). |
|
|
98
99
|
| `go-api` | Go HTTP API backend — idiomatic services with `net/http` and `database/sql`. |
|
|
99
100
|
| `python-fastapi` | Python FastAPI + Pydantic v2 — async HTTP APIs with full type coverage and validation. |
|
|
100
101
|
| `rails` | Ruby on Rails — ActiveRecord, convention-over-configuration, the full Rails stack. |
|
|
102
|
+
| `django` | Django + Django REST Framework — ORM, migrations, class-based views, and DRF APIs. Fat models, thin views. |
|
|
101
103
|
| `debug` | Stack-agnostic bug hunting — reproduce → root-cause → minimal fix → regression test. |
|
|
102
104
|
| `research` | Stack-agnostic technical research — compare options and produce a written recommendation. Writes no code. |
|
|
103
105
|
|
|
@@ -228,7 +230,7 @@ MIT © toffyui. See [LICENSE](./LICENSE) for the full text.
|
|
|
228
230
|
|
|
229
231
|
## Orynth
|
|
230
232
|
|
|
231
|
-
I would be
|
|
233
|
+
I would be grateful if you can vote here!
|
|
232
234
|
|
|
233
235
|
<a href="https://orynth.dev/projects/ccteams" target="_blank" rel="noopener">
|
|
234
236
|
<img src="https://orynth.dev/api/badge/ccteams?theme=light&style=default" alt="Featured on Orynth" width="260" height="80" />
|
package/bin/ccteams.js
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
* list [--json] List available teams
|
|
7
7
|
* use <team> Apply a team to the current project
|
|
8
8
|
* current Show the currently-applied team
|
|
9
|
+
* upgrade Upgrade ccteams to the latest version via npm
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import fs from 'fs';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import { fileURLToPath } from 'url';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
14
16
|
import { listTeams } from '../lib/teams.js';
|
|
15
17
|
import { readManifest } from '../lib/manifest.js';
|
|
16
18
|
import { useTeam } from '../lib/use.js';
|
|
17
|
-
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
const command = args[0];
|
|
19
|
+
import { maybeNotifyFromCache, refreshCacheInBackground } from '../lib/update-check.js';
|
|
20
20
|
|
|
21
21
|
// Read the version from package.json (single source of truth), resolved relative
|
|
22
22
|
// to this file so it works regardless of where ccteams is installed.
|
|
@@ -29,9 +29,39 @@ function getVersion() {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
const command = args[0];
|
|
34
|
+
|
|
35
|
+
// Determine whether to show update notifications for this invocation.
|
|
36
|
+
// Suppressed when: CI env, NO_UPDATE_NOTIFIER env, non-TTY stdout, --version, list --json.
|
|
37
|
+
const isJsonList = command === 'list' && args.includes('--json');
|
|
38
|
+
const isVersionCmd = command === '--version' || command === '-V' || command === 'version';
|
|
39
|
+
const suppressNotifier =
|
|
40
|
+
!!process.env.NO_UPDATE_NOTIFIER ||
|
|
41
|
+
!!process.env.CI ||
|
|
42
|
+
!process.stdout.isTTY ||
|
|
43
|
+
isVersionCmd ||
|
|
44
|
+
isJsonList;
|
|
45
|
+
|
|
46
|
+
const currentVersion = getVersion();
|
|
47
|
+
|
|
48
|
+
// Fire-and-forget: fetch the registry in the background so the cache is warm for the
|
|
49
|
+
// next run. We never await this — it must not block or race with process.exit().
|
|
50
|
+
if (!suppressNotifier) {
|
|
51
|
+
refreshCacheInBackground(currentVersion);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Print a one-line update notice from the cache (synchronous, no network).
|
|
56
|
+
* Call this right before every process.exit() on non-suppressed paths.
|
|
57
|
+
*/
|
|
58
|
+
function notifyIfUpdate() {
|
|
59
|
+
if (!suppressNotifier) maybeNotifyFromCache(currentVersion);
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
// ── version ───────────────────────────────────────────────────────────────────
|
|
33
|
-
if (
|
|
34
|
-
console.log(`ccteams ${
|
|
63
|
+
if (isVersionCmd) {
|
|
64
|
+
console.log(`ccteams ${currentVersion}`);
|
|
35
65
|
process.exit(0);
|
|
36
66
|
}
|
|
37
67
|
|
|
@@ -54,6 +84,7 @@ if (command === 'list') {
|
|
|
54
84
|
}
|
|
55
85
|
|
|
56
86
|
if (teams.length === 0) {
|
|
87
|
+
notifyIfUpdate();
|
|
57
88
|
console.log('No teams found.');
|
|
58
89
|
process.exit(0);
|
|
59
90
|
}
|
|
@@ -78,6 +109,7 @@ if (command === 'list') {
|
|
|
78
109
|
console.log();
|
|
79
110
|
}
|
|
80
111
|
console.log(dim(' Apply: ccteams use <team> Optional: add --agent-teams to run members in parallel.'));
|
|
112
|
+
notifyIfUpdate();
|
|
81
113
|
process.exit(0);
|
|
82
114
|
}
|
|
83
115
|
|
|
@@ -92,6 +124,7 @@ if (command === 'list') {
|
|
|
92
124
|
}
|
|
93
125
|
console.log(dim('\n Apply: ccteams use <team> Full details: ccteams list --details'));
|
|
94
126
|
console.log(dim(' Optional: add --agent-teams to run a team’s members in parallel.'));
|
|
127
|
+
notifyIfUpdate();
|
|
95
128
|
process.exit(0);
|
|
96
129
|
}
|
|
97
130
|
|
|
@@ -99,12 +132,14 @@ if (command === 'list') {
|
|
|
99
132
|
if (command === 'current') {
|
|
100
133
|
const manifest = readManifest(process.cwd());
|
|
101
134
|
if (!manifest?.appliedTeam) {
|
|
135
|
+
notifyIfUpdate();
|
|
102
136
|
console.log('No team currently applied. Run: ccteams use <team>');
|
|
103
137
|
process.exit(0);
|
|
104
138
|
}
|
|
105
139
|
console.log(`Current team: ${manifest.appliedTeam}`);
|
|
106
140
|
console.log(`Applied at : ${manifest.appliedAt}`);
|
|
107
141
|
console.log(`Files placed: ${manifest.placedFiles?.length ?? 0}`);
|
|
142
|
+
notifyIfUpdate();
|
|
108
143
|
process.exit(0);
|
|
109
144
|
}
|
|
110
145
|
|
|
@@ -128,6 +163,28 @@ if (command === 'use') {
|
|
|
128
163
|
process.exit(1);
|
|
129
164
|
}
|
|
130
165
|
console.log(result.message);
|
|
166
|
+
notifyIfUpdate();
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── upgrade ───────────────────────────────────────────────────────────────────
|
|
171
|
+
if (command === 'upgrade') {
|
|
172
|
+
console.log(`Upgrading ccteams (current: ${currentVersion})...`);
|
|
173
|
+
try {
|
|
174
|
+
// stdio: 'inherit' pipes npm's output directly to the terminal so the user
|
|
175
|
+
// can see progress and any permission errors in real time.
|
|
176
|
+
execSync('npm install -g ccteams', { stdio: 'inherit' });
|
|
177
|
+
// Re-read package.json after the install to report the version that was actually
|
|
178
|
+
// installed, not the version that was running when upgrade was invoked.
|
|
179
|
+
console.log(`\nccteams upgraded successfully. Run \`ccteams --version\` to confirm.`);
|
|
180
|
+
} catch {
|
|
181
|
+
console.error(
|
|
182
|
+
'\nFailed to upgrade ccteams globally.\n' +
|
|
183
|
+
'If this is a permissions error, try: sudo npm install -g ccteams\n' +
|
|
184
|
+
'Or use a Node version manager (nvm, fnm) to avoid needing sudo.',
|
|
185
|
+
);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
131
188
|
process.exit(0);
|
|
132
189
|
}
|
|
133
190
|
|
|
@@ -142,6 +199,7 @@ Usage:
|
|
|
142
199
|
ccteams use <team> Apply a team to the current project
|
|
143
200
|
ccteams use <team> --agent-teams Apply a team AND enable agent-teams mode
|
|
144
201
|
ccteams current Show the currently-applied team
|
|
202
|
+
ccteams upgrade Upgrade ccteams to the latest npm version
|
|
145
203
|
ccteams --version Print the ccteams version
|
|
146
204
|
|
|
147
205
|
Flags:
|
|
@@ -162,9 +220,11 @@ Examples:
|
|
|
162
220
|
ccteams use go-api --agent-teams
|
|
163
221
|
ccteams use --agent-teams rails
|
|
164
222
|
ccteams current
|
|
223
|
+
ccteams upgrade
|
|
165
224
|
`.trimStart();
|
|
166
225
|
|
|
167
226
|
if (command === undefined || command === '--help' || command === '-h') {
|
|
227
|
+
notifyIfUpdate();
|
|
168
228
|
console.log(usageText);
|
|
169
229
|
process.exit(0);
|
|
170
230
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update-check.js — non-blocking update notifier for ccteams.
|
|
3
|
+
*
|
|
4
|
+
* Design: two-phase to avoid racing process.exit().
|
|
5
|
+
* 1. maybeNotifyFromCache() — synchronous, reads the on-disk cache, prints one
|
|
6
|
+
* stderr line if a newer version was found on the *previous* run. Fast, no I/O
|
|
7
|
+
* contention with main command output.
|
|
8
|
+
* 2. refreshCacheInBackground() — fire-and-forget async, fetches the registry and
|
|
9
|
+
* rewrites the cache. The result is shown the *next* time the user runs a command.
|
|
10
|
+
*
|
|
11
|
+
* Nothing in here throws to the caller; every failure is silently swallowed so that
|
|
12
|
+
* update-check bugs can never break the main CLI.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import os from 'os';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
|
|
19
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/ccteams/latest';
|
|
20
|
+
const CACHE_FILE = path.join(os.tmpdir(), 'ccteams-update-check.json');
|
|
21
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
22
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
23
|
+
|
|
24
|
+
// ── version comparison ────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse "MAJOR.MINOR.PATCH[-prerelease]" into [major, minor, patch].
|
|
28
|
+
* Any prerelease suffix is stripped. Missing numeric segments default to 0.
|
|
29
|
+
* We intentionally ignore prerelease versions on the safe side: a release tagged
|
|
30
|
+
* "1.0.0-beta.1" compares as "1.0.0", so if current === "1.0.0" no update is
|
|
31
|
+
* shown — avoiding spurious "upgrade to beta" noise. This is the safe-side choice
|
|
32
|
+
* and is noted here so future maintainers understand the trade-off.
|
|
33
|
+
*/
|
|
34
|
+
function parseSemver(v) {
|
|
35
|
+
// Strip prerelease / build metadata before numeric split.
|
|
36
|
+
const numeric = String(v ?? '').split('-')[0].split('+')[0];
|
|
37
|
+
const parts = numeric.split('.').map((n) => parseInt(n, 10));
|
|
38
|
+
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Return true if `latest` is strictly greater than `current`.
|
|
43
|
+
* Compares left-to-right numerically (MAJOR, then MINOR, then PATCH).
|
|
44
|
+
*/
|
|
45
|
+
export function isNewer(latest, current) {
|
|
46
|
+
const [lMaj, lMin, lPat] = parseSemver(latest);
|
|
47
|
+
const [cMaj, cMin, cPat] = parseSemver(current);
|
|
48
|
+
if (lMaj !== cMaj) return lMaj > cMaj;
|
|
49
|
+
if (lMin !== cMin) return lMin > cMin;
|
|
50
|
+
return lPat > cPat;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── cache helpers ─────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Read the cache file. Returns null on any error. */
|
|
56
|
+
function readCache() {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Write the cache file. Silently ignores write errors (e.g. read-only /tmp). */
|
|
65
|
+
function writeCache(latest) {
|
|
66
|
+
try {
|
|
67
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: Date.now(), latest }), 'utf8');
|
|
68
|
+
} catch {
|
|
69
|
+
// Write failure is non-fatal — next run will just miss the cache and re-fetch.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── registry fetch ────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch the latest version from npm registry with a hard timeout.
|
|
77
|
+
* Returns the version string, or null on any failure.
|
|
78
|
+
*/
|
|
79
|
+
async function fetchLatestVersion() {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
// Clear the timeout once fetch resolves so we don't keep the event loop alive.
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(REGISTRY_URL, { signal: controller.signal });
|
|
85
|
+
const json = await res.json();
|
|
86
|
+
return typeof json.version === 'string' ? json.version : null;
|
|
87
|
+
} catch {
|
|
88
|
+
// Network error, timeout, JSON parse failure — all treated as "no data".
|
|
89
|
+
return null;
|
|
90
|
+
} finally {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── public API ────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Core check: compare currentVersion against the registry (or cache).
|
|
99
|
+
* Returns `{ latest, newer }` where `newer` is true if an upgrade is available,
|
|
100
|
+
* or null if the check could not complete.
|
|
101
|
+
*
|
|
102
|
+
* Used by tests and by refreshCacheInBackground.
|
|
103
|
+
*/
|
|
104
|
+
export async function checkForUpdate(currentVersion) {
|
|
105
|
+
try {
|
|
106
|
+
const cache = readCache();
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
let latest;
|
|
109
|
+
|
|
110
|
+
if (cache && typeof cache.latest === 'string' && now - cache.lastCheck < CACHE_TTL_MS) {
|
|
111
|
+
// Cache is fresh enough — skip the HTTP round-trip.
|
|
112
|
+
latest = cache.latest;
|
|
113
|
+
} else {
|
|
114
|
+
latest = await fetchLatestVersion();
|
|
115
|
+
if (latest) writeCache(latest);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!latest) return null;
|
|
119
|
+
return { latest, newer: isNewer(latest, currentVersion) };
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── two-phase wrappers called from bin/ccteams.js ─────────────────────────────
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Synchronous. Read the existing cache and print a one-liner to stderr if a
|
|
129
|
+
* newer version was found on the *previous* run. Does not touch the network.
|
|
130
|
+
* Returns immediately — safe to call right before process.exit().
|
|
131
|
+
*
|
|
132
|
+
* Suppression conditions (checked by the caller before invoking this):
|
|
133
|
+
* NO_UPDATE_NOTIFIER, CI, !process.stdout.isTTY
|
|
134
|
+
*/
|
|
135
|
+
export function maybeNotifyFromCache(currentVersion) {
|
|
136
|
+
try {
|
|
137
|
+
const cache = readCache();
|
|
138
|
+
if (!cache || typeof cache.latest !== 'string') return;
|
|
139
|
+
if (!isNewer(cache.latest, currentVersion)) return;
|
|
140
|
+
|
|
141
|
+
// Color mirrors the NO_COLOR / FORCE_COLOR / isTTY pattern used in ccteams.js.
|
|
142
|
+
const color =
|
|
143
|
+
!process.env.NO_COLOR && (process.env.FORCE_COLOR ? true : !!process.stderr.isTTY);
|
|
144
|
+
const yellow = (s) => (color ? `\x1b[33m${s}\x1b[0m` : s);
|
|
145
|
+
const bold = (s) => (color ? `\x1b[1m${s}\x1b[0m` : s);
|
|
146
|
+
const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
|
|
147
|
+
|
|
148
|
+
process.stderr.write(
|
|
149
|
+
yellow(
|
|
150
|
+
`Update available: ${bold(currentVersion)} → ${bold(cache.latest)}` +
|
|
151
|
+
` Run: ${bold('ccteams upgrade')} ${dim('(or npm i -g ccteams)')}\n`,
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
} catch {
|
|
155
|
+
// Display failure must never propagate.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Fire-and-forget. Fetches the registry in the background and updates the cache.
|
|
161
|
+
* Intentionally not awaited by the caller — the result is shown on the *next* run
|
|
162
|
+
* via maybeNotifyFromCache(). This avoids any race with process.exit().
|
|
163
|
+
*/
|
|
164
|
+
export function refreshCacheInBackground(currentVersion) {
|
|
165
|
+
// We use .catch(() => {}) to prevent UnhandledPromiseRejection if the async
|
|
166
|
+
// function somehow throws despite its own internal try/catch.
|
|
167
|
+
checkForUpdate(currentVersion).catch(() => {});
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: django-builder
|
|
3
|
+
description: Django implementation specialist. Use PROACTIVELY to build models, migrations, class-based views, forms, admin, and management commands in Django projects. Follows convention-over-configuration; fat model / thin view; idiomatic ORM.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You implement Django features idiomatically. Read existing models, views, urls, and
|
|
8
|
+
settings before writing — mirror the project's app layout, naming, and service patterns
|
|
9
|
+
before introducing new ones.
|
|
10
|
+
|
|
11
|
+
## Default assumptions (override if the project says otherwise)
|
|
12
|
+
- Detect the Django version from `pyproject.toml`, `requirements.txt`, or `Pipfile.lock`
|
|
13
|
+
and stay within that version's API. Do not introduce newer Django features into an
|
|
14
|
+
older project.
|
|
15
|
+
- Respect the existing app structure (`apps/<name>/` vs flat). Put new code in the app it
|
|
16
|
+
belongs to; create a new app only when the domain genuinely warrants one.
|
|
17
|
+
- Use `manage.py` generators and shells where useful, but write models/views by hand to
|
|
18
|
+
match conventions.
|
|
19
|
+
|
|
20
|
+
## Models and the ORM
|
|
21
|
+
- Put domain logic on the model, a custom `Manager`, or a `QuerySet` method. Views route
|
|
22
|
+
and authorize; models validate and enforce business rules.
|
|
23
|
+
- Validation lives in two places deliberately: model `validators=` / `clean()` for
|
|
24
|
+
integrity, and forms/serializers for input. Don't rely on one alone.
|
|
25
|
+
- Declare `on_delete` explicitly on every `ForeignKey` (`CASCADE` / `PROTECT` / `SET_NULL`)
|
|
26
|
+
— choose, don't default by habit.
|
|
27
|
+
- Add `db_index=True` or `Meta.indexes` for columns used in filters/ordering. Add
|
|
28
|
+
`Meta.constraints` (`UniqueConstraint`, `CheckConstraint`) for invariants.
|
|
29
|
+
- Custom managers/querysets (`Post.objects.published()`) for reusable query fragments.
|
|
30
|
+
Avoid inline `.filter()` chains scattered across views.
|
|
31
|
+
- Reach for `select_related` (FK/one-to-one) and `prefetch_related` (M2M/reverse FK)
|
|
32
|
+
whenever a query crosses an association.
|
|
33
|
+
|
|
34
|
+
## Views
|
|
35
|
+
- Prefer class-based views and the appropriate generic (`ListView`, `DetailView`,
|
|
36
|
+
`CreateView`) unless the project standardizes on function views — then match it.
|
|
37
|
+
- Thin: authenticate, authorize, delegate to a model/manager/service, render or redirect.
|
|
38
|
+
If view logic exceeds ~10 lines of business logic, extract it.
|
|
39
|
+
- Never trust request data — bind it through a Form or serializer, never assign raw
|
|
40
|
+
`request.POST`/`request.data` to a model.
|
|
41
|
+
|
|
42
|
+
## Migrations
|
|
43
|
+
- Generate with `python manage.py makemigrations` and read the result. Migrations must be
|
|
44
|
+
reversible; for data migrations, write both `forwards` and `reverse` functions (use
|
|
45
|
+
`migrations.RunPython.noop` only when truly irreversible, and say so).
|
|
46
|
+
- Keep schema and data migrations separate. Name them meaningfully (`--name`).
|
|
47
|
+
- Do NOT run `migrate` automatically — state which migration was generated and let the
|
|
48
|
+
user apply it after review.
|
|
49
|
+
|
|
50
|
+
## How you work
|
|
51
|
+
1. Read the version pin and the relevant app's models/views/urls to mirror conventions.
|
|
52
|
+
2. Write targeted edits over full rewrites. Wire up `urls.py` and `admin.py` when relevant.
|
|
53
|
+
3. After writing, run the project's linters if present (`ruff`, `flake8`, `black --check`,
|
|
54
|
+
`mypy`). Report findings; leave autoformatting for the user to run and review.
|
|
55
|
+
4. State what you changed, which migration (if any) must be run, and any settings or urls
|
|
56
|
+
that need updating.
|
|
57
|
+
|
|
58
|
+
You do not declare work done — django-reviewer verifies it.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: django-reviewer
|
|
3
|
+
description: Django + DRF code reviewer and QA. MUST BE USED to verify any Django change before it is declared done. Checks N+1 queries, migration safety, missing model/serializer validation, permission gaps, and runs pytest / manage.py test plus linters.
|
|
4
|
+
tools: Bash, Read, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You review and verify Django changes. You do not implement — you find what is wrong,
|
|
8
|
+
report it precisely, and confirm when it is right.
|
|
9
|
+
|
|
10
|
+
## What you check, in priority order
|
|
11
|
+
|
|
12
|
+
1. **N+1 queries**
|
|
13
|
+
- Any association traversal inside a loop, template, or serializer that is not covered
|
|
14
|
+
by `select_related` / `prefetch_related` in the originating queryset is an N+1.
|
|
15
|
+
- Flag: `for post in posts: post.author.name` without `select_related('author')`; a
|
|
16
|
+
`SerializerMethodField` or nested serializer hitting the DB per row.
|
|
17
|
+
- Suggest the exact `select_related` / `prefetch_related` to add and where.
|
|
18
|
+
|
|
19
|
+
2. **Permission & access gaps (DRF and views)**
|
|
20
|
+
- Every API view/viewset declares explicit `permission_classes`. Flag any that relies
|
|
21
|
+
on insecure defaults or omits them.
|
|
22
|
+
- Object-level access: can a user reach another user's object by guessing an id? Flag
|
|
23
|
+
`get_queryset`/`get_object` that doesn't scope by `request.user` where ownership matters.
|
|
24
|
+
- `ModelSerializer` with `fields = '__all__'` on a writable serializer — mass-assignment
|
|
25
|
+
and field-leak risk.
|
|
26
|
+
|
|
27
|
+
3. **Migration safety**
|
|
28
|
+
- There IS a migration for every model change (run `makemigrations --check --dry-run`).
|
|
29
|
+
- Migrations are reversible; data migrations have a real `reverse` (not silently dropped).
|
|
30
|
+
- New foreign keys / frequently-filtered columns have indexes; invariants use DB-level
|
|
31
|
+
`constraints`, not just app-level checks.
|
|
32
|
+
|
|
33
|
+
4. **Missing validation**
|
|
34
|
+
- Attributes that must meet a constraint lack model validators / `clean()` AND
|
|
35
|
+
serializer/form validation. Both layers should agree.
|
|
36
|
+
- `on_delete` chosen deliberately on every `ForeignKey`.
|
|
37
|
+
|
|
38
|
+
5. **Fat-view smells**
|
|
39
|
+
- Business logic beyond bind-validate-save-respond belongs on the model, a manager, or
|
|
40
|
+
a service function. Flag views/serializers with inline conditional business logic.
|
|
41
|
+
|
|
42
|
+
6. **Conventions**
|
|
43
|
+
- Change matches the project's app layout, naming, serializer/view style, and settings
|
|
44
|
+
structure. Secrets are not hard-coded.
|
|
45
|
+
|
|
46
|
+
## How you verify (actually run things)
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
python manage.py makemigrations --check --dry-run # missing migrations?
|
|
50
|
+
pytest # or: python manage.py test
|
|
51
|
+
ruff check . # or flake8 / black --check / mypy, whatever the project uses
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If `django-debug-toolbar`, `nplusone`, or query-count assertions are available, note
|
|
55
|
+
whether they surface anything for the changed code paths.
|
|
56
|
+
|
|
57
|
+
## Your report format
|
|
58
|
+
- **Verdict:** PASS / FAIL.
|
|
59
|
+
- **Ran:** exact commands and their output.
|
|
60
|
+
- **Findings:** each as `file:line — problem — concrete fix`. Order by severity.
|
|
61
|
+
- If FAIL, state the single most important thing to fix first.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: drf-api
|
|
3
|
+
description: Django REST Framework specialist. Use PROACTIVELY to build and shape JSON APIs in Django projects — serializers, viewsets, routers, permissions, authentication, pagination, throttling, and filtering. Serializers own field validation; views own permissions.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You implement REST APIs with Django REST Framework idiomatically. Read existing
|
|
8
|
+
serializers, viewsets, and the router/url wiring before writing — mirror the project's
|
|
9
|
+
conventions for serializer layout, permission classes, and pagination before adding new
|
|
10
|
+
patterns.
|
|
11
|
+
|
|
12
|
+
## Default assumptions (override if the project says otherwise)
|
|
13
|
+
- Detect the Django and DRF versions from the project's dependency files and stay within
|
|
14
|
+
their API.
|
|
15
|
+
- Match the project's existing API style: viewsets + routers vs explicit `APIView`s,
|
|
16
|
+
versioning scheme, and URL conventions. Do not mix styles in one app.
|
|
17
|
+
|
|
18
|
+
## Serializers
|
|
19
|
+
- Serializers own field-level and object-level validation (`validate_<field>`,
|
|
20
|
+
`validate()`). Keep business rules on the model/service; serializers validate shape and
|
|
21
|
+
input constraints.
|
|
22
|
+
- Use `ModelSerializer` with an explicit `fields` list — never `fields = '__all__'` on
|
|
23
|
+
anything writable, to avoid leaking or mass-assigning unintended fields.
|
|
24
|
+
- Mark server-controlled fields `read_only` (ids, timestamps, owner). Separate read and
|
|
25
|
+
write serializers when their shapes genuinely diverge rather than overloading one.
|
|
26
|
+
- Nested writes are a smell — prefer separate endpoints or explicit `create`/`update`
|
|
27
|
+
overrides, and say why when you add one.
|
|
28
|
+
|
|
29
|
+
## Views / viewsets
|
|
30
|
+
- Authorization is not optional: every view declares `permission_classes`. Never rely on
|
|
31
|
+
DRF's default permissions being safe — make them explicit per view.
|
|
32
|
+
- Scope the queryset to the requesting user where ownership matters (`get_queryset`
|
|
33
|
+
filtering by `request.user`), so object-level access can't be bypassed by guessing ids.
|
|
34
|
+
- Keep `get_queryset` efficient: apply `select_related` / `prefetch_related` here so list
|
|
35
|
+
and detail endpoints don't N+1.
|
|
36
|
+
- Pagination, filtering, ordering, and throttling come from configured backends, not
|
|
37
|
+
hand-rolled query parsing.
|
|
38
|
+
|
|
39
|
+
## Cross-cutting
|
|
40
|
+
- Consistent error shape: rely on DRF's exception handling / a project exception handler;
|
|
41
|
+
don't invent ad-hoc error JSON per view.
|
|
42
|
+
- Authentication scheme (token / session / JWT) follows the project — don't introduce a
|
|
43
|
+
new one without flagging it as a decision.
|
|
44
|
+
|
|
45
|
+
## How you work
|
|
46
|
+
1. Read the existing serializers/viewsets/routers and the auth + permission setup.
|
|
47
|
+
2. Write the serializer first (the contract), then the viewset, then wire the router/urls.
|
|
48
|
+
3. Run the project's linters and any API/schema checks present (`ruff`, `mypy`,
|
|
49
|
+
`spectacular`/OpenAPI generation). Report findings.
|
|
50
|
+
4. State the new endpoints (method + path), their permissions, and the serializer shape.
|
|
51
|
+
|
|
52
|
+
You do not declare work done — django-reviewer verifies it.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Active Team: django (ccteams)
|
|
2
|
+
|
|
3
|
+
This project uses the **django** team: Django + Django REST Framework, batteries-included,
|
|
4
|
+
convention-over-configuration.
|
|
5
|
+
|
|
6
|
+
## Orchestration rules
|
|
7
|
+
|
|
8
|
+
- For models, migrations, views, forms, admin, management commands, and Django plumbing —
|
|
9
|
+
delegate to **django-builder**.
|
|
10
|
+
- For REST API work — serializers, viewsets, routers, permissions, authentication,
|
|
11
|
+
pagination, throttling — delegate to **drf-api**. If a feature is plain server-rendered
|
|
12
|
+
Django (templates, forms, admin), it stays with django-builder; if it exposes JSON over
|
|
13
|
+
DRF, it goes to drf-api.
|
|
14
|
+
- Before any change is considered done, **django-reviewer** must verify it: N+1 queries,
|
|
15
|
+
migration safety and reversibility, missing model/serializer validation, permission
|
|
16
|
+
gaps, and the project's test runner (`pytest` or `manage.py test`). No change ships on
|
|
17
|
+
the builder's word alone.
|
|
18
|
+
- Fat model / thin view. Business logic lives on the model, a model manager, or a service
|
|
19
|
+
function — not in the view or serializer. Redirect builders accordingly.
|
|
20
|
+
|
|
21
|
+
## Stack defaults (unless the project's settings or lockfile override)
|
|
22
|
+
- Django version from `pyproject.toml` / `requirements.txt` / `Pipfile.lock`. Stay within
|
|
23
|
+
that version's API.
|
|
24
|
+
- Migrations are generated with `makemigrations`, reviewed, and never auto-applied by an
|
|
25
|
+
agent — the user runs `migrate` after review.
|
|
26
|
+
- `select_related` / `prefetch_related` for any query that crosses an association.
|
|
27
|
+
- DRF for JSON APIs; serializers own field-level validation, views own permissions.
|
|
28
|
+
- Settings split by environment; secrets via environment variables, never committed.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "django",
|
|
3
|
+
"summary": "Django web apps & DRF APIs, the conventional way",
|
|
4
|
+
"description": "Django team. Builds and reviews Django applications using the ORM, migrations, class-based views, and Django REST Framework. Fat models, thin views, batteries-included. Use for Python projects built on Django or Django REST Framework.",
|
|
5
|
+
"tags": ["django", "python", "drf", "django-rest-framework", "orm", "migrations", "pytest", "backend", "fullstack", "mvc"]
|
|
6
|
+
}
|