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 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 greateful if you can vote here!
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 (command === '--version' || command === '-V' || command === 'version') {
34
- console.log(`ccteams ${getVersion()}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccteams",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Apply and switch pre-built teams of Claude Code subagents from the command line",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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
+ }