pedantic-coder/skills/import-discipline/SKILL.md
This skill should be used when the user is organizing imports, writing require/use/include statements, fixing import ordering, dealing with circular dependencies, or when a file has disordered, ungrouped, or inconsistent imports. Covers import grouping, sorting, separation, and the rule that imports are the table of contents for a file.
npx skillsauth add oborchers/fractional-cto import-disciplineInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The import block is the first thing a reader sees after the module docstring. It answers the most fundamental question about any file: what does this depend on? A disordered import block is like a book with a scrambled table of contents — it forces the reader to scan every line to understand the file's dependencies. This is not a cosmetic concern. It is a readability failure.
The rules are simple, universal, and non-negotiable: group by origin, separate groups with blank lines, alphabetize within groups, import only what you use, never import everything.
Every language has the same conceptual hierarchy. Adapt the specifics; preserve the structure.
Group 1: Standard library / built-in modules
(blank line)
Group 2: Third-party / external packages
(blank line)
Group 3: Internal / project-level modules
(blank line)
Group 4: Relative / sibling imports
Rules that apply everywhere:
Python's import conventions are well-established. Tools like isort and ruff enforce them automatically — but you must understand the rules to configure the tools correctly and to catch what they miss.
Grouping order:
# 1. __future__ imports (always first, always alone)
from __future__ import annotations
# 2. Standard library
import os
import sys
from collections import defaultdict
from pathlib import Path
# 3. Third-party packages
import httpx
import pydantic
from fastapi import FastAPI, HTTPException
from sqlalchemy import Column, Integer, String
# 4. Internal / project modules
from myapp.config import Settings
from myapp.database import get_session
from myapp.models import User
# 5. Relative / sibling imports
from .exceptions import NotFoundError
from .schemas import UserCreate, UserResponse
Critical Python rules:
from __future__ import annotations is always the very first import. No exceptions.import os (module import) vs from os import path (name import): prefer module imports for standard library to keep the namespace explicit. os.path.join() is clearer about origin than path.join().from module import * is banned. It pollutes the namespace, makes it impossible to trace where a name came from, and breaks static analysis.__all__ in __init__.py to declare the public API explicitly. Every name in __all__ must be deliberately chosen.ruff rule I (isort-compatible) or standalone isort with profile = "black".TypeScript imports have more variety (node builtins, npm packages, workspace packages, path aliases, relative paths) but the same grouping principle applies.
Grouping order:
// 1. Node built-in modules
import fs from "node:fs";
import path from "node:path";
// 2. External packages (npm)
import express from "express";
import { z } from "zod";
// 3. Organization-scoped packages
import { logger } from "@myorg/logger";
import { validateRequest } from "@myorg/middleware";
// 4. Internal path aliases (@/ or ~/)
import { db } from "@/database";
import { UserModel } from "@/models/user";
import { config } from "@/config";
// 5. Relative imports
import { NotFoundError } from "./errors";
import { userSchema } from "./schemas";
import type { UserCreateInput } from "./types";
Critical TypeScript rules:
node: prefix: import fs from "node:fs", not import fs from "fs". The prefix eliminates ambiguity with npm packages that shadow built-in names.type imports use import type { ... } — this is not optional when verbatimModuleSyntax is enabled, and it signals to the reader (and bundler) that the import is erased at runtime.import { everything } from "./index") are acceptable for re-export modules but dangerous when they pull in large dependency trees. If your barrel import causes circular dependencies or bloated bundles, import from the specific file.import/order rule or eslint-plugin-simple-import-sort.Go's import conventions are the strictest and the best-tooled. goimports handles formatting automatically, but you must still structure imports correctly for readability.
Grouping order:
import (
// 1. Standard library
"context"
"fmt"
"net/http"
"time"
// 2. External packages
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
// 3. Internal packages
"myapp/internal/config"
"myapp/internal/database"
"myapp/internal/models"
)
Critical Go rules:
goimports (or gofumpt which includes it) to auto-format. But verify it groups correctly — some versions do not separate internal from external.import . "testing") are banned outside of test files using frameworks that require them (and even then, use sparingly).import _ "database/sql") are only acceptable for side-effect registration (e.g., database drivers). Always add a comment explaining why: import _ "github.com/lib/pq" // register PostgreSQL driver.import pg "github.com/jackc/pgx/v5" when two packages have the same name.Inline imports (importing inside a function instead of at the top of the file) are a code smell by default. They break the "table of contents" contract — the reader cannot see all dependencies at a glance.
Acceptable ONLY in these cases:
Documented circular dependency. Two modules that must reference each other, where a top-level import creates a cycle. The inline import breaks the cycle. Always add a comment:
def get_user_orders(user_id: str) -> list:
# Inline import to break circular dependency: models -> services -> models
from myapp.services.orders import OrderService
return OrderService.find_by_user(user_id)
Conditional heavy import. A module that is expensive to load (e.g., ML model, large data file) and only needed in a rarely-executed code path:
def generate_report():
# Lazy import: pandas is only needed for reporting, not on every request
import pandas as pd
...
Optional dependency. A feature that works with or without a package:
try:
import orjson as json # Faster JSON if available
except ImportError:
import json
In all three cases, the comment explaining WHY is mandatory. An inline import without an explanation is a violation.
from module import * is banned. In every language. Without exception.
Why:
# BAD — where does Request come from? Flask? Django? A local module?
from flask import *
from myapp.models import *
# GOOD — every dependency is traceable
from flask import Flask, Request, jsonify
from myapp.models import User, Order
// BAD
export * from "./models";
export * from "./utils";
// If models and utils both export "validate", which one wins?
// GOOD — explicit re-exports
export { User, Order } from "./models";
export { formatDate, parseId } from "./utils";
Centralize public APIs through index files, but make them explicit.
# myapp/models/__init__.py
from .user import User
from .order import Order
from .product import Product
__all__ = ["Order", "Product", "User"] # Alphabetized. Always.
// models/index.ts
export { Order } from "./order";
export { Product } from "./product";
export { User } from "./user";
// Alphabetized. Explicit. No star re-exports.
Delete them. Immediately. Not after the feature is done. Not when the linter runs. Now.
An unused import is:
"But I might need it later" is not a reason. Version control exists. Import it again when you need it. The 3 seconds of re-typing are cheaper than the permanent tax of a false dependency.
Working implementations in examples/:
examples/import-ordering.md — Multi-language examples (Python, TypeScript, Go) showing properly organized imports with correct grouping, separation, alphabetization, and common violations fixed.When writing or reviewing import blocks:
from x import *, export * from)from __future__ is the very first import if presentnode: prefix; type-only imports use import typeimport _) have a comment explaining the side effect__init__.py / index.ts with explicit names, not star exports__all__ (Python) and export statements are alphabetizedtools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used by the plan-verifier agent and the /plan-verify command to audit a drafted master plan against a fixed checklist. Covers universal-core completeness, the v0.3.0+ no-tables-for-phases-or-questions rule, trigger-based section-coverage gaps, phase actionability (heading + per-phase TL;DR + bulleted scope + exit criteria), the v0.3.1+ per-phase TL;DR requirement, the v0.3.2+ plain-bullet scope shape (legacy `- [ ]`/`- [x]` accepted silently), the v0.3.3+ context-block shape (plan-level `**TL;DR:**` + bulleted metadata, legacy `>` blockquote accepted silently), integer phase numbering enforcement, dependency traceability, citation resolution, callout/evidence convention compliance, Open Questions placement, and the one-PR-per-master-plan rule. Single-owner of the audit checklist.
tools
This skill should be used when authoring, reviewing, or modifying a multi-phase master planning document via the planning-tools plugin (especially the /plan-master and /plan-verify commands). Codifies the universal core sections, trigger-based optional sections, integer-only phase numbering, Open Questions placement, one-PR-per-plan rule, status conventions, evidence attribution, callouts, cross-reference formats, the v0.3.0 list-shape mandate (phases and questions are heading + bulleted list, never markdown tables), the v0.3.1 per-phase TL;DR requirement (1–3 sentence what/why summary under each phase heading for glance-ability), the v0.3.2 plain-bullet scope shape (`- <action>` items, no `- [ ]` checkboxes — the phase status emoji is the sole tick signal), and the v0.3.3 context-block shape (a plan-level `**TL;DR:**` + a bulleted metadata list instead of a `>` blockquote; legacy blockquote blocks accepted silently). Project-agnostic — no ticket-prefix or plan-type taxonomy.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.