-
Notifications
You must be signed in to change notification settings - Fork 21
chore(memory): seeder for global dev-prohibition rules into Ralph recall #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
15cbb7a
22b63ca
2495ac8
63ab9fc
5a8296c
4c8c024
f11880e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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" | ||
| } | ||
| ] |
| 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" | ||
|
|
||
| if [[ "$MODE" == "--apply" ]]; then | ||
| echo "==> Refreshing the read-only GREEN projection in MEMORY.md" | ||
| python3 "$PROJECT_MEMORY" --apply | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the user has configured the dedicated memory-tree location with 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 | ||
| 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" |
| 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}" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a first run where
~/.ralph/procedural/seed-dev-prohibitions.jsonexists but~/.ralph/cache/has not been created yet, this apply path fails after the nodes are processed:migrate_rules_to_nodes.pywritesrejects_path.parent / "migrated-nodes.jsonl"without creating the parent when there are no rejects. The four expected rules are valid, so this common path raisesFileNotFoundErrorinstead 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 👍 / 👎.