Releases: iplweb/claude-skills
2026.05.6 — django-extract-app plugin
2026.05.6 — 2026-05-11
New plugin release: django-extract-app adds a guided workflow for extracting a Django app embedded in a monolithic project into a standalone reusable package. Designed to chain into the existing readme-guardian + oss-github-publisher pipeline so the extracted package ships with the same level of polish as a greenfield OSS project.
Added — django-extract-app plugin (two skills)
-
django-extract-app— the main extraction skill. 10-step procedure, one commit per step in the new package's repo:- Reconnaissance — locates the app inside the monolith, confirms it's a real Django app (
apps.py/INSTALLED_APPSmembership / settings module detection), inventories source files (models / views / admin / signals / commands / templatetags / templates / static / migrations / existing tests). - Extractability audit — eight independent checks, each producing OK / WARN / BLOCK: cross-app FKs + dynamic
apps.get_modelrefs, cross-app imports,settings.Xusages (becomes the package's settings contract), migrationdependencies=[...]cross-app deps,reverse()+{% url %}cross-app coupling, template{% extends %}/{% include %}cross-app,@receiver(..., sender=other_app.X)signals,admin.site.registerof cross-app models. ≥1 BLOCK pauses and asks the user to abort or knowingly continue. - Decisions (via
AskUserQuestion, one at a time) — OSS vs internal package, PyPI distribution name (suggestsdjango-<foo>), importable name, target package path, git history strategy (cleaninitorgit filter-repo --subdirectory-filterwith preflight check), DB engine in tests (auto-detects Postgres-specific features likeArrayField,JSONFieldPG ops,django.contrib.postgres,pg_trgmand recommendspytest-testcontainers-django; defaults to SQLite otherwise), example/demo project (default Yes for apps with views/urls/admin/templates), license choice. - Repo bootstrap —
mkdir+git init(orgit filter-repoon a clone of the monolith), copies app source intosrc/<importable>/, first commit"Initial extraction from <monolith> @ <sha>". pyproject.toml+ uv — detects third-party imports vs stdlib/Django/same-package, generates a complete[project]block (Django × Python matrix fromreadme-guardian's canonical table — 5.2 LTS + 6.0 as of 2026-05-08),[tool.setuptools.packages.find]forsrc/layout,[tool.pytest.ini_options]withDJANGO_SETTINGS_MODULE = "tests.settings", runsuv sync --all-extras.- Test scaffolding with TDD stubs — generates
tests/settings.py(SQLite or Postgres-via-testcontainers fixture),conftest.py, optionaltests/urls.py, and per-construct test stubs (test_models.py,test_views.py,test_admin.py,test_signals.py,test_commands.py,test_templatetags.py,test_apps.py) — every stubpytest.skip("TODO: ...")so failure-to-fill-in is visible. Existing tests from the monolith are migrated with import paths rewritten (from <monolith>.<app>.X→from <importable>.X); cross-app imports get flagged as TODO comments rather than auto-resolved. - GitHub Actions —
.github/workflows/tests.ymlwithinclude:-style Django × Python matrix (5.2 LTS / 6.0 × Python 3.10–3.14, intersected withreadme-guardian's canonical compatibility table), separate lint job (ruff check + format, non-blocking), optionalexample-checkjob for the demo project (manage.py check+migrate --run-syncdb). Postgres mode adds adocker infosmoke step (testcontainers manages its own container). - Tooling —
.pre-commit-config.yaml(ruff lint E/F/W + ruff-format +pyupgrade --py310-plusderived fromrequires-python+django-upgrade --target-version 5.2derived from Django floor),LICENSE(MIT default),.gitignore(Python + Django + uv),README.mdstarter (install /INSTALLED_APPS/ URLs / requirements / settings contract / dev setup),CHANGELOG.mdin Keep-a-Changelog format. - Optional example/demo project —
example/withmanage.py+ minimalsettings.py(SQLite + admin) +urls.pywiring the package's URLs. Doubles as a CI smoke test for the package. - Local verification + chaining — runs
uv sync && uv run pytest && uv run pre-commit run --all-files && uv build && uv run --with twine twine check dist/*, surfaces any failure to the user (no silent auto-fix). Then asks (default Yes) whether to chain into/readme-guardian:readme-guardianand/oss-github-publisher:oss-github-publisher, both invoked via theSkilltool in the new package's directory. Final report enumerates commits, audit findings, verification results, and next-steps (push to GitHub, publish to PyPI, run the cleanup sub-skill).
- Reconnaissance — locates the app inside the monolith, confirms it's a real Django app (
-
django-extract-app-cleanup— phase-2 sub-skill, invoked separately after the package is published or installed editable. Wires the new package back into the monolith and removes the original app directory, in two commits with three verifications:- Wiring — adds the package as a dependency via one of four strategies (
AskUserQuestion): PyPI released (uv sync-immediately), PyPI placeholder (added with TODO, no install until publish), editable local install (uv add --editable <path>), or git URL with explicit@ref. Verifies the package imports andmanage.py checkpasses BEFORE removing the original app — so removal can't strand the monolith if wiring is broken. - Removal — before deletion, runs a migration safety check: compares the AppConfig label between monolith and installed package, and diffs the migration filename sets in
<monolith>/<app>/migrations/vs the installed package's<importable>/migrations/. If either mismatches, stops without deleting. Otherwisegit rm -r <app>/, then re-verifies all three:manage.py check,makemigrations --check --dry-run(the critical drift detector — if this finds anything, the source-of-truth has diverged and the user must resolve manually, not paper over with a fresh migration the package won't have), and the monolith's full test suite.
- Wiring — adds the package as a dependency via one of four strategies (
Added — repository
- New plugin entry in
marketplace.jsonandREADME.md(plugin table, Python project lifecycle skill graph with extraction sub-graph, install + usage sections, cross-references). - Cross-reference:
django-extract-appreads the canonical Django × Python compatibility matrix fromreadme-guardian's SKILL.md — same single source of truth aspython-upgrade-package, no duplicated copies of the table.
Iron laws encoded in the extraction skill
- Never modify the monolith in the main skill. Read-only access only. Wiring + removal happens in the separate cleanup sub-skill, with a clean working-tree precondition.
- One commit per step in the new repo. Every step is independently revertible.
- Every decision goes through
AskUserQuestion. No silent defaults for naming, location, history strategy, DB engine, license, or example project. - Audit BLOCKers stop the workflow. A blocking issue triggers an explicit abort/continue prompt — never silently shipping a known-broken package.
2026.05.5 — Security hardening
Security hardening release. Three iterative codex self-reviews surfaced 9 sandbox/secret-handling issues in code-review-opencode (and a few in adjacent skills). Every finding reproduced empirically before fixing — git diff --no-index /etc/hosts /dev/null, git log --output=PATH, malicious-filename command injection via xargs -I {}, and nested .env pathspec leakage all confirmed live. Round 3 returned with no new findings against fixed surfaces.
Fixed (security — code-review-opencode)
- Bash
cat/find/grep/rg/head/tail/wcwildcards removed. Previous policy pairedread.*.env: denywithbash."cat *": "allow"— model couldcat .envstraight through bash, bypassing read-tool path policy. opencode's nativeread/glob/greptools (which respect path policy) handle file access now. git *wildcards removed. Blanket"git *": "allow"accepted destructive verbs (git checkout HEAD~5 -- /etc/passwd,git clean -fdx,git reset --hard,git config user.email evil@x). Whitelist now lists only explicit read-only verbs.git diffandgit diff *removed.git diff --no-index FILE1 FILE2reads any user-readable path, fully bypassingexternal_directory: deny. Verified:git diff --no-index /etc/hosts /dev/nulldumps/etc/hosts. Diff foruncommitted/commitmodes is pre-computed in the wrapper.git log *andgit show *removed. Both verbs accept--output=PATHwhich writes a file — bypasseseditpolicy. Verified live. No-args forms still allowed.git branch *andgit tag *removed. Wildcards accepted mutating forms (-D foo,-d v1,new-name) modifying.git/. No-args list-only forms still allowed.- Command injection in untracked-file pre-compute.
xargs -I {} sh -c '... cat -- \"{}\"'literal-substituted filenames into thesh -ctemplate, so$(touch /tmp/INJECTED)executed before reachingsh. Verified: old pattern created/tmp/INJECTED, new pattern (NUL-delimitedwhile read -d ''loop) doesn't. New loop also doesn'tcatcontent — opencode reads each file via its ownreadtool under deny policy. .envpathspec was root-only.:(exclude).envexcluded./envonly;sub/.env/sub/.env.localslipped through. Replaced with:(exclude,glob)**/.env*patterns covering all nesting depths.- Tracked-diff exclusion extended to all secret families. Was filtering
.env*only; now mirrors fullreaddenylist (*.pem,*.key,id_rsa*,id_dsa*,id_ed25519*,id_ecdsa*,*.p12,*.pfx,*.keystore,*.jks,credentials.json,*credentials*.json,service-account*.json,.npmrc,.pypirc,.netrc) — root + every nested directory. Smoke-tested: 16 secret files across 3 directory levels, all filtered, only benign tracked change remains. - Read-tool denylist was root-only. Patterns like
id_rsa,.npmrc,service-account*.jsonmatched only repo root because opencode pattern matching doesn't cross/with a single*. Every secret pattern is now duplicated — once bare (root) and once with**/prefix (any depth).
Fixed (other skills)
github-build-fixer— short-poll loop now waits. Step 6'sfor _ in 1..6; do gh run list ...; donehad no delay, so it fired 6 queries in well under a second and returned "No run found". Addedsleep 5. Step 1 sleep prohibition clarified — covers long CI watch (usegh run watch), not short post-push materialization wait.readme-guardian— Django × Python matrix updated to 2026-05-08. Was missing Django 6.0 and treated 4.2 LTS as still in extended support (expired Apr 2026). Verified against djangoproject.com: only 5.2 LTS (Python 3.10–3.14) and 6.0 (Python 3.12–3.14) are upstream-supported; 4.2/5.0/5.1 are EOL. Snapshot dated.python-upgrade-package— Django matrix examples propagated. Sample CI matrix, classifiers, Django badge, and "drop EOL versions" guidance updated to drop 4.2/5.0/5.1 and add 6.0. Canonical table still lives inreadme-guardian.python-upgrade-package— Djangoconftest.pyfixed. Sample assignedsettings.DJANGO_SETTINGS_MODULE = ...on theLazySettingsproxy — silently does nothing useful, surfaces asImproperlyConfiguredon nextdjango.setup(). Replaced withos.environ.setdefault(...);pyproject.toml [tool.pytest.ini_options]form promoted as preferred.
🤖 Generated with Claude Code