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.
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.
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.
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.
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.
This is primarily a macOS utility scripts repository. The following patterns are found throughout the codebase.
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.
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.
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).
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}; echoScripts 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.
Temp directories are created with mktemp -d and cleaned up via trap:
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXITOptional debug or artifact-retention behavior is gated by env vars
(KEEP=1, DEBUG_SPLIT=1) rather than hardcoded flags, keeping the default
execution clean.
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.
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.
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+trapfor any temp directory. - Do show a confirmation prompt before any destructive action.
- Don't use
lsto iterate files — usefindor globs. - Don't parse
lsoutput. - Don't use
cat file | grep— usegrep filedirectly.
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.
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.
Telnet/socket scripts use select.select() for bidirectional non-blocking I/O
rather than threading.
try/except blocks catch specific exceptions and print colored fallback
messages. Bare except: clauses are not used.
#!/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 andsys.exit(0)(or implicit) on success. - Don't add
argparseunless the script has more than 3 flags. - Don't import third-party packages without documenting the install step in a comment.
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
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. camelCasefor fields and methods,PascalCasefor classes and interfaces.UPPER_SNAKE_CASEfor constants.- Maximum line length of 120 characters.
| Construct | Pattern | Example |
|---|---|---|
| Interface | Noun | UserService |
| Implementation | <Interface>Impl |
UserServiceImpl |
| Controller | <Domain>Controller |
UserController |
| Repository | <Domain>Repository |
UserRepository |
| Test class | <Subject>Test |
UserServiceTest |
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.
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.
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.
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 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
recordfor 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
catchblocks — log or rethrow. - Don't use
@Valuefor more than 2 properties in a class — use@ConfigurationPropertiesinstead.
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.