Skip to content
Merged
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
456 changes: 73 additions & 383 deletions .claude/skills/adversarial/SKILL.md

Large diffs are not rendered by default.

File renamed without changes.
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ jobs:
run: |
# Check hooks exist
test -d .claude/hooks
# Verify all hooks have .sh extension
# Verify all hooks use a supported runtime extension.
# Hooks are polyglot: Bash (.sh), Python (.py), and Node (.js/.mjs).
for hook in .claude/hooks/*; do
if [ -f "$hook" ] && [[ ! "$hook" == *.sh ]]; then
echo "ERROR: Hook $hook missing .sh extension"
exit 1
fi
[ -f "$hook" ] || continue
case "$hook" in
*.sh|*.py|*.js|*.mjs) ;;
*)
echo "ERROR: Hook $hook has unsupported extension (expected .sh, .py, .js, or .mjs)"
exit 1
;;
esac
done

- name: Validate skills structure
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ tmp-review/
.claude/quality-results
.claude/progress.md
.claude/plan-state.json
.claude/tasks.json
.claude/state/
.claude/snapshots/
.claude/backups/
Expand Down
46 changes: 46 additions & 0 deletions scripts/memory/seed-data/dev-prohibitions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[
{
"rule_id": "dev-no-placeholders",
"behavior": "ABSOLUTE PROHIBITION: Never write placeholder code in ANY development. No stubs, dummy values, TODO/FIXME-as-implementation, foo/bar/example.com, 'pass'/'...'/'not implemented' as final state. If you cannot complete it, STOP and ask the user. Detection duty: if you find a placeholder in ANY file (even outside the current analysis/PR/task scope) you MUST run git blame on the offending lines and report it.",
"trigger": "Any code authoring, review, analysis, or PR in any project",
"domain": "development",
"confidence": 1.0,
"usage_count": 1,
"severity": "critical",
"tags": ["prohibition", "placeholders", "quality", "git-blame", "global"],
"source_repo": "user-global-rules"
},
{
"rule_id": "dev-no-unrequested-fallbacks",
"behavior": "ABSOLUTE PROHIBITION: Never add fallback logic UNLESS the user requests it explicitly and clearly. No error-swallowing try/except, no 'value || default' masking failures, no silent catch-and-degrade. Fail loud and fail fast by default. Detection duty: if you find an unrequested fallback in ANY file (even outside the current analysis/PR/task scope) you MUST run git blame on the offending lines and report what failure it is masking.",
"trigger": "Any code authoring, review, analysis, or PR in any project",
"domain": "development",
"confidence": 1.0,
"usage_count": 1,
"severity": "critical",
"tags": ["prohibition", "fallbacks", "fail-fast", "fail-loud", "git-blame", "global"],
"source_repo": "user-global-rules"
},
{
"rule_id": "dev-no-production-code-for-tests",
"behavior": "ABSOLUTE PROHIBITION: Never add, weaken, or special-case PRODUCTION code for the sole purpose of making a test pass. No hardcoding the asserted value, no 'if testMode' branches, no special-casing test inputs. If a test fails, first verify its expectation is correct: fix the test if it is wrong; fix production only for a REAL requirement. Detection duty: if you find production code shaped only to pass a test in ANY file (even outside the current scope) you MUST run git blame and report which test it was gaming.",
"trigger": "Any test-driven change, code authoring, review, analysis, or PR in any project",
"domain": "testing",
"confidence": 1.0,
"usage_count": 1,
"severity": "critical",
"tags": ["prohibition", "testing", "no-test-gaming", "git-blame", "global"],
"source_repo": "user-global-rules"
},
{
"rule_id": "testing-fail-loud-fail-fast",
"behavior": "MANDATORY: ALL tests (unit AND e2e) MUST fail loud / fail fast. A failing condition MUST make the test FAIL loudly and immediately; a test that cannot reach a real assertion MUST fail, never pass and never silently skip. Forbidden: error-swallowing try/catch, soft asserts that only log, 'assert True', '|| true'/continue-on-error/'set +e' masking, retry loops hiding flakiness, broad skip/xfail. E2E on local minikube (special force): if minikube is down, namespace missing, image pull failed, pod not Ready, or service unreachable -> the test MUST FAIL LOUDLY with the concrete reason; NEVER silently skip, NEVER fall back to a mock, NEVER pretend success; readiness waits MUST have a bounded timeout that FAILS with pod/service diagnostics. Detection duty: if you find a test masking failure in ANY file (even outside the current scope) you MUST run git blame and report what failure it masks.",
"trigger": "Authoring, reviewing, or running unit/e2e tests; CI test steps; minikube/k8s e2e suites",
"domain": "testing",
"confidence": 1.0,
"usage_count": 1,
"severity": "critical",
"tags": ["prohibition", "testing", "e2e", "minikube", "fail-fast", "fail-loud", "git-blame", "global"],
"source_repo": "user-global-rules"
}
]
67 changes: 67 additions & 0 deletions scripts/memory/seed-dev-prohibitions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
#
# seed-dev-prohibitions.sh — Graduate the 4 global dev-prohibition rules into the
# Ralph Memory Tree (recall_v2 / Codex recall) for the CURRENT project.
#
# The Memory Tree is PROJECT-SCOPED by design (recall_v2 hard-rejects nodes from a
# different project_id — this is intentional per-project isolation, not a bug).
# Run this from inside any repo where you want Codex recall to surface these rules.
# Global coverage (all projects) is already provided by ~/.claude/CLAUDE.md and
# ~/.claude/rules/proven/*.md — this script only adds the in-repo recall layer.
#
# The rule definitions ship in-repo at scripts/memory/seed-data/dev-prohibitions.json
# so the seeder is reproducible in any clone. Override with SEED_FILE=<path> if needed.
#
# Idempotent: node ids are deterministic (derived from rule_id); re-running updates
# nodes in place, never duplicates.
#
# Usage:
# scripts/memory/seed-dev-prohibitions.sh # apply into current repo's tree
# scripts/memory/seed-dev-prohibitions.sh --dry-run # validate only, write nothing
# SEED_FILE=/path/to/rules.json scripts/memory/seed-dev-prohibitions.sh # custom seed
#
set -euo pipefail
umask 077

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
SEED_FILE="${SEED_FILE:-$SCRIPT_DIR/seed-data/dev-prohibitions.json}"

MIGRATE="$SCRIPT_DIR/migrate_rules_to_nodes.py"
PROJECT_MEMORY="$SCRIPT_DIR/project_memory.py"

# Validate arguments first (fail fast on misuse, before touching anything).
MODE="--apply"
case "${1:-}" in
""|--apply) MODE="--apply" ;;
--dry-run) MODE="--dry-run" ;;
*) echo "FATAL: unknown argument: ${1}. Usage: $(basename "$0") [--apply|--dry-run]" >&2; exit 1 ;;
esac

# Fail loud and fast if any prerequisite is missing — never pretend success.
[[ -f "$SEED_FILE" ]] || { echo "FATAL: seed file not found: $SEED_FILE" >&2; exit 1; }
[[ -f "$MIGRATE" ]] || { echo "FATAL: migrator not found: $MIGRATE" >&2; exit 1; }
[[ -f "$PROJECT_MEMORY" ]] || { echo "FATAL: project_memory not found: $PROJECT_MEMORY" >&2; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "FATAL: python3 not on PATH" >&2; exit 1; }

echo "==> Migrating dev-prohibition rules into the Ralph Memory Tree ($MODE)"
python3 "$MIGRATE" --rules "$SEED_FILE" "$MODE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Create the cache directory before invoking the migrator

On a first run where ~/.ralph/procedural/seed-dev-prohibitions.json exists but ~/.ralph/cache/ has not been created yet, this apply path fails after the nodes are processed: migrate_rules_to_nodes.py writes rejects_path.parent / "migrated-nodes.jsonl" without creating the parent when there are no rejects. The four expected rules are valid, so this common path raises FileNotFoundError instead of completing the one-command seeding; ensure the cache directory exists or pass a rejects path whose parent is created before calling the migrator.

Useful? React with 👍 / 👎.


if [[ "$MODE" == "--apply" ]]; then
echo "==> Refreshing the read-only GREEN projection in MEMORY.md"
python3 "$PROJECT_MEMORY" --apply

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the same Ralph home for projection and recall

If the user has configured the dedicated memory-tree location with RALPH_MEMORY_HOME, the migration above writes via TreeStore() into that directory, but this projection command (and the recall verification below) default to RALPH_HOME/~/.ralph instead. In that environment the script successfully creates the nodes in one tree, then projects zero nodes and fails verification from another tree; pass the resolved Ralph memory home consistently to migrate_rules_to_nodes.py, project_memory.py, and recall_v2.py.

Useful? React with 👍 / 👎.


echo "==> Verifying recall_v2 surfaces the new nodes"
RECALL="$SCRIPT_DIR/recall_v2.py"
[[ -f "$RECALL" ]] || { echo "FATAL: recall_v2 not found: $RECALL" >&2; exit 1; }
MISSING=0
for q in "placeholder in code" "unrequested fallback error handling" \
"production code to pass a test" "e2e test minikube fail loud fail fast"; do
if ! python3 "$RECALL" --query "$q" --limit 3 \
| grep -qE "rule_(dev-no-|testing-fail-loud)"; then
echo "FAIL: no dev-prohibition node recalled for query: '$q'" >&2
MISSING=1
fi
done
[[ "$MISSING" -eq 0 ]] || { echo "FATAL: recall verification failed — nodes not surfaced" >&2; exit 1; }
echo "OK: all 4 dev-prohibition rules are recallable in this project's tree."
fi
60 changes: 60 additions & 0 deletions tests/memory/test-seed-dev-prohibitions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
#
# test-seed-dev-prohibitions.sh — fail-loud contract tests for the
# dev-prohibitions seeder (scripts/memory/seed-dev-prohibitions.sh).
#
# Verifies the guard rails a reader cannot trust by eye: misuse and missing
# inputs MUST exit non-zero with a clear FATAL message, and the in-repo seed
# fixture MUST be valid. This test is itself fail-loud: any broken contract
# exits 1.
#
set -euo pipefail

REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." >/dev/null 2>&1 && pwd)"
SEEDER="$REPO_ROOT/scripts/memory/seed-dev-prohibitions.sh"
SEED_JSON="$REPO_ROOT/scripts/memory/seed-data/dev-prohibitions.json"

PASS=0
FAIL=0
pass() { printf ' PASS: %s\n' "$1"; PASS=$((PASS + 1)); }
fail() { printf ' FAIL: %s\n' "$1" >&2; FAIL=$((FAIL + 1)); }

[[ -f "$SEEDER" ]] || { echo "FATAL: seeder not found: $SEEDER" >&2; exit 1; }

# --- Test 1: missing seed file must fail loud ---------------------------------
rc=0
out="$(SEED_FILE=/nonexistent/seed.json bash "$SEEDER" --dry-run 2>&1)" || rc=$?
if [[ "$rc" -ne 0 ]] && grep -q "FATAL: seed file not found" <<<"$out"; then
pass "missing seed file exits non-zero with FATAL (rc=$rc)"
else
fail "missing seed file did not fail loud (rc=$rc): $out"
fi

# --- Test 2: unknown argument must fail loud (before any prerequisite check) ---
rc=0
out="$(bash "$SEEDER" --bogus-arg 2>&1)" || rc=$?
if [[ "$rc" -ne 0 ]] && grep -q "FATAL: unknown argument" <<<"$out"; then
pass "unknown argument exits non-zero with FATAL (rc=$rc)"
else
fail "unknown argument did not fail loud (rc=$rc): $out"
fi

# --- Test 3: in-repo seed fixture is valid JSON with the 4 expected rules ------
expected="dev-no-placeholders dev-no-production-code-for-tests dev-no-unrequested-fallbacks testing-fail-loud-fail-fast "
if [[ ! -f "$SEED_JSON" ]]; then
fail "in-repo seed fixture missing: $SEED_JSON"
elif ! command -v jq >/dev/null 2>&1; then
fail "jq not available — cannot validate seed fixture"
else
ids="$(jq -r '.[].rule_id' "$SEED_JSON" | sort | tr '\n' ' ')"
if [[ "$ids" == "$expected" ]]; then
pass "in-repo seed fixture is valid and has the 4 expected rule ids"
else
fail "seed fixture rule-id mismatch: got [$ids] expected [$expected]"
fi
fi

echo
printf 'Results: %d passed, %d failed\n' "$PASS" "$FAIL"
[[ "$FAIL" -eq 0 ]] || exit 1
echo "All seeder contract tests passed"
29 changes: 29 additions & 0 deletions tests/memory/test_seed_dev_prohibitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Pytest wrapper so the seeder's fail-loud contract test runs under
`pytest tests/` in CI.

The substantive assertions live in the sibling bash script
``test-seed-dev-prohibitions.sh`` (it drives the bash seeder). Pytest only
collects ``test_*.py`` files, so without this wrapper the contract test is
never executed by CI. This wrapper runs it and fails loudly if any contract
is violated.
"""
import subprocess
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
CONTRACT_TEST = REPO_ROOT / "tests" / "memory" / "test-seed-dev-prohibitions.sh"


def test_seed_dev_prohibitions_contract():
"""Run the bash contract test; a non-zero exit (any broken contract) fails here."""
assert CONTRACT_TEST.is_file(), f"contract test missing: {CONTRACT_TEST}"
result = subprocess.run(
["bash", str(CONTRACT_TEST)],
capture_output=True,
text=True,
cwd=str(REPO_ROOT),
)
assert result.returncode == 0, (
f"seeder contract test failed (exit {result.returncode}):\n"
f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
)
4 changes: 2 additions & 2 deletions tests/test_v2.25_search_hierarchy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ section "Skills & Documentation Tests"
# =============================================================================

echo "Test 20: Context7 usage skill exists..."
if [[ -f "$PROJECT_ROOT/.claude/skills/context7-usage/skill.md" ]]; then
if [[ -f "$PROJECT_ROOT/.claude/skills/context7-usage/SKILL.md" ]]; then
pass "context7-usage skill exists"
else
fail "context7-usage skill missing"
fi

echo "Test 21: Context7 skill has decision tree..."
if grep -q 'Decision Tree\|decision tree' "$PROJECT_ROOT/.claude/skills/context7-usage/skill.md"; then
if grep -q 'Decision Tree\|decision tree' "$PROJECT_ROOT/.claude/skills/context7-usage/SKILL.md"; then
pass "context7-usage has decision tree"
else
fail "context7-usage missing decision tree"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_v2.26_prefix_commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ test_task_visualizer_exists

# Test 31: task-visualizer has skill.md
test_task_visualizer_skill() {
if [ -f "$PROJECT_ROOT/.claude/skills/task-visualizer/skill.md" ]; then
if [ -f "$PROJECT_ROOT/.claude/skills/task-visualizer/SKILL.md" ]; then
test_pass "task-visualizer skill.md exists"
else
test_fail "task-visualizer skill.md does not exist"
Expand Down
Loading