Skip to content

Instantly share code, notes, and snippets.

@robertchong
Last active May 6, 2026 14:25
Show Gist options
  • Select an option

  • Save robertchong/eb42b09e866ee0d283530bc22664dbfc to your computer and use it in GitHub Desktop.

Select an option

Save robertchong/eb42b09e866ee0d283530bc22664dbfc to your computer and use it in GitHub Desktop.
Copilot Instructions inspired by Andrej Karpathy's CLAUDE.md

GitHub Copilot Instructions

Behavioral guidelines for AI-assisted coding in this repository. Based on the principles from Andrej Karpathy's CLAUDE.md.

Tradeoff: These guidelines bias toward caution over speed. For trivial one-liner changes, use judgment.


1. Think Before Coding

Don't assume. Don't hide confusion. Surface tradeoffs.

Before implementing:

  • State your assumptions explicitly. If uncertain, ask.
  • If multiple interpretations exist, present them — don't pick silently.
  • If a simpler approach exists, say so. Push back when warranted.
  • If something is unclear, stop. Name what's confusing. Ask.

2. Simplicity First

Minimum code that solves the problem. Nothing speculative.

  • No features beyond what was asked.
  • No abstractions for single-use code.
  • No "flexibility" or "configurability" that wasn't requested.
  • No error handling for impossible scenarios.
  • If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.


3. Surgical Changes

Touch only what you must. Clean up only your own mess.

When editing existing code:

  • Don't "improve" adjacent code, comments, or formatting.
  • Don't refactor things that aren't broken.
  • Match existing style, even if you'd do it differently.
  • If you notice unrelated dead code, mention it — don't delete it.

When your changes create orphans:

  • Remove imports/variables/functions that your changes made unused.
  • Don't remove pre-existing dead code unless asked.

The test: every changed line should trace directly to the user's request.


4. Goal-Driven Execution

Define success criteria. Loop until verified.

Transform tasks into verifiable goals:

  • "Add validation" → "Write tests for invalid inputs, then make them pass"
  • "Fix the bug" → "Write a test that reproduces it, then make it pass"
  • "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:

1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.


5. Bash / Shell Conventions

This is primarily a macOS utility scripts repository. The following patterns are found throughout the codebase.

Shebangs

The codebase uses #!/usr/bin/env bash for portable scripts and #!/bin/zsh only for macOS-specific system scripts (e.g. those touching Keychain, LaunchD, or Zsh-only builtins). Plain #!/bin/sh appears in older uninstall scripts.

Error handling

Scripts open with set -euo pipefail — exit on error, treat unset variables as errors, and propagate pipe failures. This is standard across all bash scripts in the repo.

Output and colors

The codebase uses ANSI escape codes for color-coded output:

GREEN='\033[0;32m'
RED='\033[1;91m'
RESET='\033[0m'

Timestamps are formatted as date "+%Y-%m-%d @ %H:%M:%S". Log files live under ~/bin/ (e.g. ~/bin/brew_update_logs.txt).

Visual separators

80-character dash lines (---...---) are used as visual section boundaries in concatenated output and in long scripts. The pattern is:

printf '%0.s-' {1..80}; echo

Argument parsing

Scripts use manual case "$1" in switching or while [[ $# -gt 0 ]] loops rather than getopts. Long and short flag pairings (-f|--force, -q|--quiet, -n|--dry-run) are the standard form.

Temporary files and cleanup

Temp directories are created with mktemp -d and cleaned up via trap:

TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

Environment variable controls

Optional debug or artifact-retention behavior is gated by env vars (KEEP=1, DEBUG_SPLIT=1) rather than hardcoded flags, keeping the default execution clean.

Privileged operations

sudo is used only where strictly required. Scripts that need it state so at the top in a comment and prompt the user before proceeding.

Interactive menus

Destructive operations (e.g. wiping metadata, uninstalling software) use an interactive menu with a confirmation step before executing. The pattern is a select or read -p prompt.

New bash script skeleton

When creating a new bash script, the codebase structure looks like this:

#!/usr/bin/env bash
set -euo pipefail

# ── Colors ────────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
RED='\033[1;91m'
RESET='\033[0m'

# ── Usage ─────────────────────────────────────────────────────────────────────
usage() {
    echo "Usage: $(basename "$0") [options]"
    echo ""
    echo "Options:"
    echo "  -f, --force    Overwrite existing files"
    echo "  -q, --quiet    Suppress output"
    echo "  -n, --dry-run  Show what would happen without doing it"
    echo "  -h, --help     Show this help message"
    exit 0
}

# ── Main ──────────────────────────────────────────────────────────────────────
main() {
    # parse args
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -f|--force)   FORCE=1  ; shift ;;
            -q|--quiet)   QUIET=1  ; shift ;;
            -n|--dry-run) DRY_RUN=1; shift ;;
            -h|--help)    usage ;;
            *) echo -e "${RED}Unknown option: $1${RESET}"; usage ;;
        esac
    done
}

main "$@"

Dos and don'ts for new bash scripts:

  • Do gate optional behavior behind env vars (KEEP=1) or flags, not hardcoded values.
  • Do use mktemp -d + trap for any temp directory.
  • Do show a confirmation prompt before any destructive action.
  • Don't use ls to iterate files — use find or globs.
  • Don't parse ls output.
  • Don't use cat file | grep — use grep file directly.

6. Python Conventions

The Python scripts in this repo are small, self-contained utilities (networking tools, a Streamlit UI). They do not use argparse or complex CLI frameworks.

Argument parsing

Scripts use sys.argv with direct index access for simple positional arguments. Error messages are printed to stderr and the script exits with code 1 on bad input.

I/O multiplexing

Telnet/socket scripts use select.select() for bidirectional non-blocking I/O rather than threading.

Error handling

try/except blocks catch specific exceptions and print colored fallback messages. Bare except: clauses are not used.

New Python script skeleton

#!/usr/bin/env python3
"""One-line description of what this script does."""

import sys

GREEN = "\033[0;32m"
RED = "\033[1;91m"
RESET = "\033[0m"


def main():
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <arg>", file=sys.stderr)
        sys.exit(1)

    # ... implementation


if __name__ == "__main__":
    main()

Dos and don'ts for new Python scripts:

  • Do keep scripts self-contained with no external config file dependencies.
  • Do use sys.exit(1) on failure and sys.exit(0) (or implicit) on success.
  • Don't add argparse unless the script has more than 3 flags.
  • Don't import third-party packages without documenting the install step in a comment.

7. Java / Spring Boot Conventions

Target

Java 17 LTS. Maven (pom.xml) is the build tool. The framework is Spring Boot.

mvn clean install   # full build
mvn test            # run tests only
mvn spring-boot:run # run the application locally

Style

The codebase follows Oracle standard Java conventions:

  • 4-space indentation (no tabs).
  • Allman brace style: opening { on a new line for class and method declarations.
  • camelCase for fields and methods, PascalCase for classes and interfaces.
  • UPPER_SNAKE_CASE for constants.
  • Maximum line length of 120 characters.

Naming

Construct Pattern Example
Interface Noun UserService
Implementation <Interface>Impl UserServiceImpl
Controller <Domain>Controller UserController
Repository <Domain>Repository UserRepository
Test class <Subject>Test UserServiceTest

Spring Boot patterns

The codebase uses constructor injection, not field @Autowired:

@Service
public class UserServiceImpl implements UserService
{
    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository)
    {
        this.userRepository = userRepository;
    }
}

Configuration lives in application.properties or application.yml. Properties are bound via @ConfigurationProperties for grouped config rather than scattering @Value annotations.

Error handling

Checked exceptions are used for recoverable errors (e.g. resource not found). RuntimeException subclasses are used for programming errors. Empty catch blocks are not used — at minimum, log the exception.

Records and Optional

Java record types are used for pure data holders (DTOs, value objects). Optional<T> is the return type for service-layer methods that may return no result; it is not used for fields or method parameters.

New Java class skeleton

package com.example.demo.service;

import java.util.Optional;

public class ExampleServiceImpl implements ExampleService
{
    private final ExampleRepository exampleRepository;

    public ExampleServiceImpl(ExampleRepository exampleRepository)
    {
        this.exampleRepository = exampleRepository;
    }

    @Override
    public Optional<ExampleDto> findById(Long id)
    {
        return exampleRepository.findById(id)
                .map(ExampleDto::fromEntity);
    }
}

Tests

Tests use JUnit 5 (@Test, @BeforeEach, @ExtendWith) and Mockito for mocking. Integration tests use @SpringBootTest. Test methods are named methodName_stateUnderTest_expectedBehavior:

@Test
void findById_whenIdExists_returnsDto()
{
    // arrange
    // act
    // assert
}

Dos and don'ts for new Java code:

  • Do use constructor injection — never field @Autowired.
  • Do use record for DTOs and value objects.
  • Do return Optional<T> from service methods that may find nothing.
  • Don't create static utility classes with only static methods unless the class is truly stateless and shared.
  • Don't leave empty catch blocks — log or rethrow.
  • Don't use @Value for more than 2 properties in a class — use @ConfigurationProperties instead.

These guidelines are working if: diffs are minimal and traceable, rewrites due to overcomplication are rare, and clarifying questions come before implementation rather than after mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment