Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .codex_yolo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ if [[ "${#}" -gt 0 ]]; then
diagnostics|doctor|health)
exec "${SCRIPT_DIR}/.codex_yolo_diagnostics.sh"
;;
debt)
exec "${SCRIPT_DIR}/.codex_yolo_debt.sh" "${@:2}"
;;
version)
if [[ -f "${SCRIPT_DIR}/VERSION" ]]; then
cat "${SCRIPT_DIR}/VERSION"
Expand Down Expand Up @@ -123,6 +126,7 @@ if [[ "${CODEX_SKIP_UPDATE_CHECK:-0}" != "1" ]]; then
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/.codex_yolo.Dockerfile" -o "${temp_dir}/.codex_yolo.Dockerfile" && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/.codex_yolo_entrypoint.sh" -o "${temp_dir}/.codex_yolo_entrypoint.sh" && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/.codex_yolo_diagnostics.sh" -o "${temp_dir}/.codex_yolo_diagnostics.sh" && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/.codex_yolo_debt.sh" -o "${temp_dir}/.codex_yolo_debt.sh" && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/default-AGENTS.md" -o "${temp_dir}/default-AGENTS.md" && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/.dockerignore" -o "${temp_dir}/.dockerignore" 2>/dev/null && \
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${BRANCH}/VERSION" -o "${temp_dir}/VERSION"; then
Expand All @@ -135,10 +139,12 @@ if [[ "${CODEX_SKIP_UPDATE_CHECK:-0}" != "1" ]]; then
# Install core files
chmod +x "${temp_dir}/.codex_yolo.sh"
chmod +x "${temp_dir}/.codex_yolo_diagnostics.sh"
chmod +x "${temp_dir}/.codex_yolo_debt.sh"
cp "${temp_dir}/.codex_yolo.sh" "${SCRIPT_DIR}/.codex_yolo.sh"
cp "${temp_dir}/.codex_yolo.Dockerfile" "${SCRIPT_DIR}/.codex_yolo.Dockerfile"
cp "${temp_dir}/.codex_yolo_entrypoint.sh" "${SCRIPT_DIR}/.codex_yolo_entrypoint.sh"
cp "${temp_dir}/.codex_yolo_diagnostics.sh" "${SCRIPT_DIR}/.codex_yolo_diagnostics.sh"
cp "${temp_dir}/.codex_yolo_debt.sh" "${SCRIPT_DIR}/.codex_yolo_debt.sh"
cp "${temp_dir}/default-AGENTS.md" "${SCRIPT_DIR}/default-AGENTS.md"
cp "${temp_dir}/.dockerignore" "${SCRIPT_DIR}/.dockerignore" 2>/dev/null || true
cp "${temp_dir}/VERSION" "${SCRIPT_DIR}/VERSION"
Expand Down
2 changes: 1 addition & 1 deletion .codex_yolo_completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ _codex_yolo_complete() {
prev="${COMP_WORDS[COMP_CWORD-1]}"

# codex_yolo specific commands and flags
local codex_yolo_opts="diagnostics doctor health version --version --verbose -v --pull --gh --help"
local codex_yolo_opts="diagnostics doctor health debt version --version --verbose -v --pull --gh --help"

# Common codex CLI commands (pass-through)
local codex_opts="login --help --yolo --search --device-auth"
Expand Down
1 change: 1 addition & 0 deletions .codex_yolo_completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ _codex_yolo() {
'diagnostics:Run health checks and show diagnostic information'
'doctor:Alias for diagnostics'
'health:Alias for diagnostics'
'debt:Scan the current repository for technical debt markers'
'version:Show version information'
'login:Log in to OpenAI Codex'
)
Expand Down
255 changes: 255 additions & 0 deletions .codex_yolo_debt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#!/usr/bin/env bash
set -euo pipefail

MARKER_REGEX='(^|[^[:alnum:]_])(TODO|FIXME|HACK|XXX)(\([^)]+\))?([[:space:]]*[:-]|[[:space:]]+)'

usage() {
cat <<'USAGE'
Usage: codex_yolo debt [path]

Scan the current repository (or the optional path) for common technical-debt
markers, classify each finding with deterministic Bash heuristics, assign a
priority score, and print a prioritized report.

Scanned markers:
TODO, FIXME, HACK, XXX

Categories:
bug-risk Potential correctness or stability issue
maintainability Cleanup or refactor debt in code
test-gap Missing or deferred test coverage work
docs-config-infra Documentation, configuration, or automation debt
USAGE
}

normalize_context() {
printf '%s' "$1" | tr '\t' ' ' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+/ /g'
}

resolve_scan_root() {
local requested_path="${1:-}"

if [[ -n "${requested_path}" ]]; then
if [[ ! -d "${requested_path}" ]]; then
echo "Error: path not found: ${requested_path}" >&2
exit 1
fi

(
cd "${requested_path}"
pwd
)
return
fi

if git_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
printf '%s\n' "${git_root}"
else
pwd
fi
}

scan_matches() {
find . \
\( -path './.git' \
-o -path './node_modules' \
-o -path './dist' \
-o -path './build' \
-o -path './coverage' \
-o -path './.next' \
-o -path './.venv' \
-o -path './venv' \
-o -path './tmp' \
-o -path './vendor' \
-o -path './.turbo' \
-o -path './.cache' \
\) -prune -o -type f -print0 |
while IFS= read -r -d '' file; do
grep -nHI -E "${MARKER_REGEX}" "${file}" 2>/dev/null || true
done
}

detect_marker() {
local context="$1"
local upper_context

upper_context="$(printf '%s' "${context}" | tr '[:lower:]' '[:upper:]')"
if [[ "${upper_context}" =~ ${MARKER_REGEX} ]]; then
printf '%s\n' "${BASH_REMATCH[2]}"
else
printf 'TODO\n'
fi
}

classify_category() {
local marker="$1"
local file_path="$2"
local context="$3"
local lower_path lower_context

lower_path="$(printf '%s' "${file_path}" | tr '[:upper:]' '[:lower:]')"
lower_context="$(printf '%s' "${context}" | tr '[:upper:]' '[:lower:]')"

if [[ "${lower_path}" == *test* ]] || [[ "${lower_path}" == *spec* ]] ||
[[ "${lower_context}" == *test* ]] || [[ "${lower_context}" == *coverage* ]] ||
[[ "${lower_context}" == *regression* ]] || [[ "${lower_context}" == *fixture* ]] ||
[[ "${lower_context}" == *assert* ]] || [[ "${lower_context}" == *flaky* ]]; then
printf 'test-gap\n'
return
fi

if [[ "${lower_path}" == *.md ]] || [[ "${lower_path}" == *.yml ]] || [[ "${lower_path}" == *.yaml ]] ||
[[ "${lower_path}" == *dockerfile* ]] || [[ "${lower_path}" == *config* ]] ||
[[ "${lower_path}" == *install.sh ]] || [[ "${lower_path}" == .github/workflows/* ]] ||
[[ "${lower_context}" == *docs* ]] || [[ "${lower_context}" == *readme* ]] ||
[[ "${lower_context}" == *workflow* ]] || [[ "${lower_context}" == *deploy* ]] ||
[[ "${lower_context}" == *config* ]] || [[ "${lower_context}" == *ci* ]]; then
printf 'docs-config-infra\n'
return
fi

if [[ "${marker}" == "FIXME" ]] || [[ "${marker}" == "XXX" ]] ||
[[ "${lower_context}" == *bug* ]] || [[ "${lower_context}" == *broken* ]] ||
[[ "${lower_context}" == *error* ]] || [[ "${lower_context}" == *failure* ]] ||
[[ "${lower_context}" == *failing* ]] || [[ "${lower_context}" == *crash* ]] ||
[[ "${lower_context}" == *panic* ]] || [[ "${lower_context}" == *null* ]] ||
[[ "${lower_context}" == *nil* ]] || [[ "${lower_context}" == *race* ]] ||
[[ "${lower_context}" == *unsafe* ]]; then
printf 'bug-risk\n'
return
fi

printf 'maintainability\n'
}

calculate_score() {
local marker="$1"
local category="$2"
local file_path="$3"
local context="$4"
local score=0
local lower_path lower_context
local severity_regex='critical|urgent|security|vuln|crash|panic|corrupt|deadlock|prod|production|data[[:space:]]loss'
local signal_regex='bug|broken|error|failure|failing|regression|workaround|temporary|refactor|cleanup|remove|leak|unsafe|null|nil|timeout|retry'

case "${marker}" in
FIXME) score=70 ;;
XXX) score=65 ;;
HACK) score=55 ;;
TODO) score=45 ;;
esac

case "${category}" in
bug-risk) score=$((score + 20)) ;;
test-gap) score=$((score + 15)) ;;
docs-config-infra) score=$((score + 10)) ;;
maintainability) score=$((score + 5)) ;;
esac

lower_path="$(printf '%s' "${file_path}" | tr '[:upper:]' '[:lower:]')"
lower_context="$(printf '%s' "${context}" | tr '[:upper:]' '[:lower:]')"

if [[ "${lower_context}" =~ ${severity_regex} ]]; then
score=$((score + 15))
fi

if [[ "${lower_context}" =~ ${signal_regex} ]]; then
score=$((score + 8))
fi

if [[ "${lower_path}" == src/* ]] || [[ "${lower_path}" == lib/* ]] ||
[[ "${lower_path}" == app/* ]] || [[ "${lower_path}" == cmd/* ]] ||
[[ "${lower_path}" == bin/* ]] || [[ "${lower_path}" == .github/workflows/* ]] ||
[[ "${lower_path}" == .codex_yolo.sh ]] || [[ "${lower_path}" == .codex_yolo_entrypoint.sh ]] ||
[[ "${lower_path}" == install.sh ]]; then
score=$((score + 5))
fi

if (( score > 100 )); then
score=100
fi

printf '%s\n' "${score}"
}

priority_label() {
local score="$1"

if (( score >= 85 )); then
printf 'critical\n'
elif (( score >= 70 )); then
printf 'high\n'
elif (( score >= 55 )); then
printf 'medium\n'
else
printf 'low\n'
fi
}

if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
usage
exit 0
fi

if [[ "$#" -gt 1 ]]; then
echo "Error: expected zero or one path argument" >&2
echo >&2
usage >&2
exit 1
fi

scan_root="$(resolve_scan_root "${1:-}")"
findings_file="$(mktemp)"
sorted_file="$(mktemp)"
trap 'rm -f "${findings_file}" "${sorted_file}"' EXIT

(
cd "${scan_root}"
scan_matches
) | while IFS= read -r match; do
file_path="${match%%:*}"
remainder="${match#*:}"
line_number="${remainder%%:*}"
context="${remainder#*:}"
file_path="${file_path#./}"
context="$(normalize_context "${context}")"
marker="$(detect_marker "${context}")"
category="$(classify_category "${marker}" "${file_path}" "${context}")"
score="$(calculate_score "${marker}" "${category}" "${file_path}" "${context}")"
priority="$(priority_label "${score}")"

printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"${score}" "${priority}" "${category}" "${marker}" "${file_path}" "${line_number}" "${context}" >> "${findings_file}"
done

if [[ ! -s "${findings_file}" ]]; then
echo "No technical debt markers found in ${scan_root}."
exit 0
fi

LC_ALL=C sort -t $'\t' -k1,1nr -k5,5 -k6,6n "${findings_file}" > "${sorted_file}"

total_findings="$(wc -l < "${sorted_file}" | tr -d ' ')"

echo "codex_yolo debt report"
echo "Scope: ${scan_root}"
echo "Findings: ${total_findings}"
echo "Markers: TODO, FIXME, HACK, XXX"
echo "Priority score: marker severity + category + risk keywords"
echo ""
printf '%-9s %-5s %-20s %-7s %-28s %s\n' "Priority" "Score" "Category" "Marker" "Location" "Context"
printf '%-9s %-5s %-20s %-7s %-28s %s\n' "--------" "-----" "--------" "------" "--------" "-------"

while IFS=$'\t' read -r score priority category marker file_path line_number context; do
printf '%-9s %-5s %-20s %-7s %-28s %s\n' \
"${priority}" "${score}" "${category}" "${marker}" "${file_path}:${line_number}" "${context}"
done < "${sorted_file}"

echo ""
echo "Category counts:"
for category in bug-risk maintainability test-gap docs-config-infra; do
count="$(awk -F '\t' -v wanted="${category}" '$3 == wanted { total += 1 } END { print total + 0 }' "${sorted_file}")"
if [[ "${count}" != "0" ]]; then
echo " ${category}: ${count}"
fi
done
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
bash -n .codex_yolo.sh
bash -n .codex_yolo_entrypoint.sh
bash -n .codex_yolo_diagnostics.sh
bash -n .codex_yolo_debt.sh
bash -n install.sh

- name: Run integration tests
Expand Down Expand Up @@ -88,6 +89,7 @@ jobs:
# Check that shell scripts are executable
test -x .codex_yolo.sh || (echo ".codex_yolo.sh not executable" && exit 1)
test -x .codex_yolo_diagnostics.sh || (echo ".codex_yolo_diagnostics.sh not executable" && exit 1)
test -x .codex_yolo_debt.sh || (echo ".codex_yolo_debt.sh not executable" && exit 1)
test -x install.sh || (echo "install.sh not executable" && exit 1)
test -x tests/integration_tests.sh || (echo "integration_tests.sh not executable" && exit 1)

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to codex_yolo will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Technical Debt Report**: New `codex_yolo debt` host-side command for fast repository triage
- Scans `TODO`, `FIXME`, `HACK`, and `XXX` markers
- Classifies findings into bug-risk, maintainability, test-gap, and docs/config/infra buckets
- Assigns deterministic priority scores and sorts the report by severity
- Runs without building or starting the Docker container

### Changed
- Installer and auto-update flows now download `.codex_yolo_debt.sh`
- Shell completions now expose `debt` as a first-class subcommand
- Integration tests and CI syntax checks now cover the debt classifier helper

## [1.1.0] - 2026-01-31

### Added - Product Perspective
Expand Down
22 changes: 22 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,27 @@ codex_yolo version
codex_yolo --version
```

### Technical Debt Triage
```bash
# Scan the current repository for technical debt markers
codex_yolo debt

# Scan a different checkout or subdirectory
codex_yolo debt ../another-repo
```

The debt report scans for `TODO`, `FIXME`, `HACK`, and `XXX`, then classifies
each finding into one of four buckets:

- `bug-risk`
- `maintainability`
- `test-gap`
- `docs-config-infra`

Scores are deterministic and based on marker type, file path, and keywords in
the surrounding text. Treat the output as prioritized triage rather than a
complete architectural assessment.

### Common Issues

#### "Docker daemon not running"
Expand Down Expand Up @@ -276,5 +297,6 @@ CODEX_BASE_IMAGE=node:18-slim codex_yolo

- Run `codex_yolo --help` for Codex CLI help
- Run `codex_yolo diagnostics` for system health check
- Run `codex_yolo debt` for a host-side technical debt report
- Visit: https://github.com/laurenceputra/codex_yolo
- Report issues: https://github.com/laurenceputra/codex_yolo/issues
Loading
Loading