Agent SkillsWingedGuardian/GENesis-AGI › genesis-development

genesis-development

GitHub

用于开发、调试和重构 Genesis 框架本身的技能,涵盖源码修改、运行时连接及内部工具构建。

.claude/skills/genesis-development/SKILL.md WingedGuardian/GENesis-AGI

Trigger Scenarios

修改 src/ 或 tests/ 下的文件 修复 Genesis 启动问题 添加新的 MCP 工具或能力

Install

npx skills add WingedGuardian/GENesis-AGI --skill genesis-development -g -y
More Options

Non-standard path

npx skills add https://github.com/WingedGuardian/GENesis-AGI/tree/main/.claude/skills/genesis-development -g -y

Use without installing

npx skills use WingedGuardian/GENesis-AGI@genesis-development

指定 Agent (Claude Code)

npx skills add WingedGuardian/GENesis-AGI --skill genesis-development -a claude-code -g -y

安装 repo 全部 skill

npx skills add WingedGuardian/GENesis-AGI --all -g -y

预览 repo 内 skill

npx skills add WingedGuardian/GENesis-AGI --list

SKILL.md

Frontmatter
{
    "name": "genesis-development",
    "phase": 10,
    "consumer": "cc_foreground",
    "skill_type": "workflow",
    "description": "This skill should be used when developing, debugging, refactoring, or building Genesis itself — tasks like \"fix this in Genesis\", \"add a new MCP tool\", \"wire up the runtime\", \"Genesis won't start\", \"create a worktree\", \"debug the bridge\", or \"add a capability\". Applies to any task modifying files under src\/, .claude\/, or tests\/. Do NOT load for Genesis-as-tool work (\"summarize this\", \"write a LinkedIn post\", \"research X\") or general questions unrelated to Genesis internals.\n"
}

Load Gate

Before reading any reference, confirm the task is Genesis-development, not Genesis-as-tool. If uncertain, ask the user: "Are we modifying Genesis itself, or using Genesis for something else?"

On-Load Mindset

Internalize these immediately when this skill fires — they shape how to work from the start, not just what to check before commit.

Wiring Discipline

Every new component needs at least one call site in the actual runtime path. Apply this 4-level verification taxonomy:

  1. Exists — file/function present. Proves nothing.
  2. Substantive — tests pass, handles happy + error. No runtime proof.
  3. Wired — live call site, import chain unbroken. Minimum for "done."
  4. Data-Flow Verified — real data flows end-to-end. Required for critical paths.

Mark nothing "done" below Level 3.

GROUNDWORK Code Is NOT Dead Code

Code tagged # GROUNDWORK(feature-id): why is intentional future investment. Never delete or refactor it as dead code. Only remove when the feature is fully active or the user explicitly cancels it.

Architecture Review

For medium-to-large Genesis work (3+ files, new components, wiring changes), dispatch a genesis-architect subagent before implementation to check dependencies, edge cases, and DRY violations. Small targeted changes skip this.

Timeout Policy

The burden of proof is on you to justify why a timeout should exist. Do not default to "add a timeout for safety." Instead:

  1. Identify the specific failure mode. What hangs? Why? Is there evidence this actually happens, or is it speculative?
  2. Justify the specific value. Why this number and not another? What legitimate work would be killed at a lower value?
  3. If you have no strong justification for a specific value, default to 2 hours (7200s). This is the project floor — generous enough to never interfere with legitimate work while preventing permanent resource lockout from truly hung processes.
  4. Surface the request to the user with the value, the failure mode, and the evidence. Never add a timeout as a "small improvement" or "defense in depth."

Timeouts on reflections, CC calls, cognitive paths, and long-thinking work fight Genesis instead of helping it — they cap legitimate long thinking and add speculative defense against rare hangs. The exception is raw subprocess calls with no external watchdog (e.g., deterministic executor steps), where a hung process blocks shared resources (executor semaphore) with no other recovery mechanism.

The Bash TOOL's own timeout is separate — default 120000ms, HARD CEILING 600000ms (10 min). An inner timeout N … INSIDE the command does NOT extend it: the tool wrapper SIGTERMs the whole call at its own timeout param (default 120000ms → exit 143). To allow longer, set that timeout PARAMETER explicitly — but you cannot exceed 600000ms. A larger value does not buy more time; the call still dies at 10 minutes (MEASURED 2026-08-27: timeout: 1600000 was killed at exactly 10m 0s). Anything that might run past ten minutes therefore has only ONE correct form — run_in_background: true. Treat "raise the timeout" as a fix that tops out, not one that scales.

For long or unbounded work — above all deploys (scripts/update.sh, bootstrap.sh, host-setup.sh: container align + guardian redeploy + host update-node/update-cc, run sequentially — routinely exceed 600s and so CANNOT be done in the foreground at any timeout value) — run via run_in_background: true (harness-tracked, notifies on completion, no timeout ceiling), NEVER a foreground timeout or nohup … & (detached but untracked → no completion signal, so you end up hand-polling anyway). update.sh has SIGTERM/INT rollback traps, so a mid-run kill is not a no-op — verify state (server active, no mid-rebase, pin, both CC versions) before re-running; it is idempotent. (A non-blocking PreToolUse advisory hook, .claude/hooks/cc-deploy-timeout-guard, nudges toward this when a deploy is run in the foreground.)

Verify Outcomes, Not Just Tests

ruff check . && pytest -v is the minimum bar, not the finish line. After tests pass, verify the actual end-to-end outcome the change delivers. Diff behavior between main and your changes when relevant. For wiring changes: verify the init/bootstrap order passes the right values at runtime, not just that parameters exist. For notification changes: verify the notification actually arrives. Ask: "If the system restarts right now, will this actually work?" If you can't answer yes with evidence, you're not done.

Verify in the REAL runtime context, not a shell proxy. "Works when I run it" is not "works where it runs." Same code + same uid ≠ same context: a long-running systemd service (genesis-server, guardian) differs from your interactive shell in mount namespace, seccomp, NoNewPrivileges, dropped caps, and — the one that bites — ptrace//proc access. Anything that reads /proc/<other-pid>/{environ,mem,stat}, another process's env, sockets, or namespaced/hardened resources MUST be verified by hitting the live endpoint (curl the real server) or running inside the real service — not python -c in your shell. (Origin, 2026-08: the CC-slot stale-code badge read /proc/<pid>/environ, which succeeds in a shell but returns EACCES under the server's ProtectSystem=strict sandbox — so enumerate_cc_slots() returned 0 in-server and three features shipped green but inert since July; the module's docstring claim "same-uid reads succeed" was a shell-tested falsehood. The fix routed around the ptrace-gated read entirely.)

Acceptance Bar + Measured Rate — the primary methodology

Use this as often as it applies. It is the default way to build anything here, not a special-occasion technique. Unit tests prove the code does what you wrote. This proves the thing actually WORKS — with numbers and a denominator.

Two artifacts, both produced BEFORE shipping:

1. The acceptance bar — replay the real defect. Take the actual failure that motivated the work and run it through the new thing. If it does not catch/fix the case it exists for, it does not ship, however elegant it is and however green the suite is. Reconstruct the real case (from git history, from the transcript, from live data) rather than a stylised approximation — a synthetic case can pass while the real shape does not.

2. The measured rate — run it against real data and produce a number. A claim like "low false-positive rate" or "it should be fine in practice" is not evidence. Run the thing over a real corpus — recent commits, live rows, real traffic, historical transcripts — and report k/N (x.x%). A number without a denominator is not a measurement. Then look at the individual hits and say honestly which are real signal and which are noise; a "false-positive rate" that turns out to be mostly true positives is a fire rate, and saying so is part of the result.

The measurement is a GATE, not a footnote. Decide the acceptable threshold BEFORE measuring, and if the number misses it, tighten and re-measure rather than shipping with a caveat. When you tighten, re-run the acceptance bar in the same breath — a filter that improves the rate by breaking the thing you built it for has made it worse, and only running both together catches that.

Worked example (2026-08-27/28, an orphaned-literal detector). The tool itself was still on an unmerged branch when this was written, so treat the first two bullets as a method illustration; the figures in the paragraph after them are derived from this repository's own history and can be re-derived.

  • Acceptance: replayed a real review defect; the detector named the exact sibling file. PASS.
  • Measured, false-positive side: 1/151 real file-edits fired (0.7%). The one hit was an identifier rather than prose, so the filter was tightened to require interior whitespace, re-measured at 0/151 (0.0%), and the acceptance replay was re-run to confirm the tightening had not blinded it.

That example then became a lesson against itself, which is why it is kept. Everything above measures FALSE POSITIVES, and 0/151 reads as though the tightening were free. It was not. Measuring the other direction needs INDEPENDENT ground truth rather than the tool's own criterion — otherwise you grade the tool on its own definition of success. Here that meant mining history for a literal removed from one file and then removed AGAIN from a second file in a later commit: the repo itself recording that the first fix left a sibling. Over 1,584 commits that yields 95 verified cases (6.0% of commits) — and the interior-whitespace filter that scored so well on precision excludes 48 of those 95 (51%) by construction. The number that actually decided the design was recall against a budget cap: 28/47 in-scope cases caught (60%) with a cap of 6 literals per edit, versus 47/47 (100%) with the cap lifted — every one of the 19 misses was that single cap, and lifting it recovered all of them.

The rule that generalises: a rate measured on one side of a tradeoff is half a measurement. A precision number with no recall number cannot distinguish a good filter from a blind one, and the side you did not measure is the side that will be wrong. Decide which direction matters for the thing you are building, and measure that one first.

This also catches a specific self-deception. A first prototype of that same detector used a regex and reported "2 findings" while silently skipping the entire class it was built for (the pattern excluded backslashes; every prompt string ends in \n). The acceptance replay is what exposed it. A matcher that finds nothing is indistinguishable from a matcher that looks at nothing — only replaying a known-positive tells them apart.

A blocked compound command loses EVERYTHING in it

A PreToolUse block kills the whole Bash call, not the offending part — so a guard firing on step 3 also silently discards steps 1 and 2, while the error text talks only about step 3. This repo has many blocking guards (review_enforcement_commit, full_suite_guard, concurrent_test_guard, git_push_guard, the destructive/protected-path guards), so it is not rare.

Never chain a state-changing step with a step that can be blocked. Keep cd, heredocs, file writes and restore-from-backup in their own invocation, separate from test runs, commits, pushes, or anything a guard inspects.

After any block, verify state before continuing. Run pwd, and re-check the file you believed you wrote. Do not assume the earlier half of the command ran. Prefer git -C <literal path> over a persistent cd, so a lost cd cannot silently redirect later commands. Do the same for scripts by spelling them $ROOT/scripts/… (ROOT="$(git rev-parse --show-toplevel)"), which is what .claude/commands/deep-review.md requires — a bare relative scripts/… resolves against whatever the cwd drifted to.

But the path is not what decides which worktree a script acts on. The PROCESS CWD is. Some scripts derive their target from the directory they run in — review_state.py's evidence-path and mark resolve the worktree via git rev-parse with the inherited cwd, and never read sys.argv[0]. So run those FROM the worktree they are about, in their own invocation, however you spell the path. Get this wrong and review evidence is written under another worktree's key, and the depth gate then blocks on a file you never wrote.

MEASURED 2026-08-28, with controls in both directions: from one cwd, the relative, absolute and $ROOT/-prefixed spellings of review_state.py all printed the SAME evidence path; the same absolute path run from three different cwds printed three DIFFERENT ones. Path form: no effect. Cwd: decisive.

Measured cost in one session: four heredocs that never wrote, a restore-from-backup that never ran (leaving a file deliberately regressed), and a cd that never happened — so edits landed in the wrong worktree and had to be reverted as cross-branch contamination. Guard false-positives make this worse: shell_parse mis-parses backslash line-continuations and quoted heredoc bodies, so legitimate commands get blocked too.

Instance-Fix vs Class-Fix Gate

When a mechanism failed to write or propagate something (a memory, a directive, a config row, a status flag), hand-writing the missing artifact is a data repair — it mitigates ONE instance on ONE install. It is never the fix. Before reporting anything as "fixed", classify it:

  • Data repair — you wrote the artifact the mechanism should have written. Label it "data repair" explicitly, and in the same session either fix the mechanism or get the user's explicit deferral (recorded as a follow-up). Never report a data repair as "fixed".
  • Class fix — you changed the mechanism so the artifact is written correctly on every install, going forward, with a test proving it.

The test: "If a fresh install hits the same situation tomorrow, does my change help them?" If the answer is no, you have repaired data, not fixed anything. (Origin: 2026-07-17 — a stale-decision recurrence was "fixed" with a hand-written memory + directive; the propagation mechanism that failed to write them stayed broken.)

Debugging Discipline (phase-gated)

Adapted from superpowers systematic-debugging. "Find root causes" is a value; these are the GATES that make it enforceable:

  • Iron Law: no fix proposals until root-cause investigation completes. Investigation (read the full error, reproduce, check recent changes, gather evidence) is a phase that FINISHES before any fix is proposed — never propose fixes in the same breath as the symptom. "It's probably X, let me fix that" = investigation skipped.
  • Fix-attempt cap: 3 failed fixes → STOP and question the architecture. The debugging twin of the review escalation cap, with the same mechanics (count attempts visibly; the cap consumes standing approval). Each failed fix revealing a new problem in a different place is not bad luck — it is the signature of a wrong architecture or a wrong problem statement. Do not attempt fix #4; bring the pattern to the user.
  • Boundary instrumentation for multi-component failures. When the path crosses components (hook → server → engine; CI → build → deploy), don't reason about where it breaks — LOG entry/exit at each boundary, run ONCE, and let the evidence localize the failing component before investigating it. (The "starved vs broken" check — inputs before regression-hunting — is the special case of this.)
  • Read the reference implementation COMPLETELY before deriving from it. When a change must mirror what another subsystem does (what routing consumes, what the runtime resolves, what a protocol expects), read that subsystem's path END-TO-END first and derive from its own code/loader — never incrementally guess-and-patch toward it. Incremental spec discovery is how a review loop runs 7 rounds. (Origin: PR #1281 — the onboarding floor's key list was wrong three times until it was derived from the router's own load_config + call-site chains.)
  • Spec required-sets: enforce the WHOLE set at once, and lock it with ONE test. The spec-facing corollary of the rule above. When a change must make code satisfy a canonical spec's required-set — a prompt's required JSON fields, a validator's mandatory keys, an allow-list — read the spec's required list in full and enforce ALL of it in one move; then write a single test asserting the required block == the complete canonical set (not one assertion per field). Adding only the field a reviewer just flagged leaves the next one for the next round, and a per-field test goes green while the next missing field ships. (Origin: PR #1333 — _SALVAGE_PROMPT's Required block was completed one field at a time across three Codex rounds — cognitive_state_update → confidence → observations — for what REFLECTION_DEEP.md declares as one closed set {observations, confidence, cognitive_state_update}.)
  • Condition-based waiting. When a fix or test must wait for a state change, poll the CONDITION (with a bounded deadline), never sleep an arbitrary duration — arbitrary sleeps are flaky under load and slow everywhere else. This complements the Timeout Policy (which governs the values).

Test-First Discipline

Adapted from superpowers test-driven-development, scoped to where it pays:

  • Bug fixes: failing reproduction test FIRST — always. Before touching the code, write the minimal test that reproduces the bug and WATCH IT FAIL for the expected reason. Then fix; the same test proves the fix and pins the regression. A repro test written after the fix proves nothing (it never caught the bug).
  • Verify-RED, always and everywhere. Any new test must be seen to FAIL (correctly) at least once — via the bug, a reverted fix, or a deliberately broken assertion — before its green is trusted. A test that has only ever passed may be testing nothing; a whole suite passing every review round while a reviewer keeps finding real spec bugs is the tell that the tests encode the same wrong spec as the code.
  • A RED that comes back GREEN has AT LEAST six causes, and "the test is vacuous" is the LAST one to reach for. In rough order of how often they actually occur:
    1. The run never EXECUTED — a guard refused it, a lock held it, the tool timed out — so there is no result at all. This is the CONFIDENT FALSE NEGATIVE: a sweep reporting "all mutations survived" is far more often a sweep that never ran. Make the runner ABORT when the test command emits no result line, and treat a SKIPPED/deselected line FOR THE TEST UNDER VERIFICATION the same way — it is a result line, and it still means nothing ran. Scope that check to the target: a suite carrying legitimate skipif tests emits SKIPPED lines on every healthy run, so a runner that aborts on ANY of them refuses every run — and the agent then either sits blocked or starts stripping skip markers to unblock itself.
    2. The MUTATION silently failed to apply — the auto-formatter reflows lines and a str.replace() anchor written from memory then matches nothing.
    3. The test ran against a DIFFERENT COPY of the code — an installed package shadowing the source tree, a stale .pyc, the wrong virtualenv, or (measured, this session) a path relative to a process whose cwd had moved to another worktree. The mutation applied, the run happened, the test is sound, and none of the other causes fits.
    4. The mutation was BEHAVIOURALLY NULL — it applied and parses, so every postcondition below passes, but it changed no behaviour: swapped operands that commute, an edit inside a dead branch, a type annotation Python does not enforce (also measured this session). The remedy is a different MUTATION, not a different test.
    5. A SIBLING LAYER still enforces the invariant, so the green is correct. When two layers produce the same behaviour, mutate the WHOLE mechanism, not one of its halves. (The duplicated-layer anecdote in the review-loop section is the worked example.)
    6. A SIBLING TEST LEAKED STATE that masks the mutation — an undone monkeypatch, a stray os.environ entry, a mutated singleton or a module-level cache — so the mutated path is never reached in THIS run. It matches none of the five above: the run executed, the mutation applied and is not behaviourally null, the code is the right copy, and no production layer is enforcing anything. MEASURED in this repo: a leaked disable lever made the very lock under test a no-op, and five real failures read as a story about the mechanism instead. The remedy is test ISOLATION (an autouse fixture that clears the lever) — NOT a different mutation, and NOT a different test. Only after all six: the test is vacuous. The list is ordered and still not closed — if none of them fits, the vacuous conclusion is UNPROVEN rather than established: look for the cause you have not modelled before rewriting a test that may be sound, because rewriting a sound test is the expensive mistake here. The PRINCIPLE, which is what to remember: every injection must prove it applied, by its own postcondition, before any result is read as RED. Two corollaries follow, and both have bitten: prove it against the value THAT injection was handed, never against the pristine original — with two or more edits, the first edit keeps a whole-file mutated != original true while a later one silently misses its anchor, so the partial mutation reads as complete (assert the anchor matched the expected number of times); and prove it with the mutated file's OWN parser — compile(src, path, "exec") for Python (NOT ast.parse: that only builds a tree, so it accepts context-invalid constructs like a return moved outside a function or a break outside a loop, and the SyntaxError then surfaces at COLLECTION, where a nonzero exit reads as a successful RED), bash -n for shell — since an invalid mutation breaks collection and reads as a successful RED, while the wrong language's parser rejects a valid mutation and hides a real survivor. Restore from a file copy taken beforehand, never git checkout — the work is uncommitted — and make the restore ATTEMPT unconditional ON THE RUN'S OUTCOME (trap restore EXIT, a finally:), never the tail of an && chain. Unconditional means it always RUNS, not that it always OVERWRITES: what it writes is still gated on the hash check below. A trap that restores blindly is the very thing that destroys a concurrent edit. The expected outcome here is a NONZERO exit, and under set -e a trailing restore is exactly the statement that never runs (the out=$(cmd) entry in Common Traps is the same mechanism), so the shape that reads as careful leaves a deliberately-broken file in an uncommitted worktree on the ordinary path — as well as on interruption or a tool timeout. Take that copy ONCE for the whole sweep and refuse to start EACH cycle if the file already differs from it — a copy re-taken immediately before each mutation makes the check vacuous, since the file trivially matches a copy a moment old; the baseline exists to catch a PREVIOUS cycle that failed to restore, or a concurrent edit. Verify the restore by hash rather than assuming it. And restore ONLY what you broke: compare against the hash the MUTATION wrote before overwriting, because between the mutation and the handler another session, agent or formatter may have edited that file, and a blind snapshot restore silently destroys their uncommitted work — a final hash check does not catch this, it only confirms the overwrite succeeded. If the file no longer matches what the mutation wrote, PRESERVE it and report the conflict instead. The cleanest way to avoid the window entirely is to mutate inside an isolated worktree nobody else is editing.
  • Vacuous-test shapes to check for by name (a list of the common ones, not a definition). The most frequent in practice is the one that never ran at all: a test SKIPPED by a marker, or deselected by a -k filter or a wrong path, which reports SKIPPED or "no tests ran" and never goes red — check the count, not just the absence of failures. Beyond that, a test is vacuous when: its assertion is ALSO true on the success path (assert x.blocked is False where a successful call also returns False — assert the fact that DISTINGUISHES them); its setup short-circuits the path it names (passing an explicit argument the code prefers over the env var under test); or its fixture never creates the shape it claims (a bash -c 'sleep 30 # marker' decoy exec-replaces itself and loses the marker from its argv — add a guard-the-guard assert that the fixture really has the property). Ask of every new test: would this still pass if the mechanism it names were deleted?
  • Contested/subtle specs: write the expectations first. When what-should- happen is itself under discussion (which keys count, which states clear an alarm), enumerate the expectation table as failing tests BEFORE implementing — it forces the spec question to surface at design time instead of review round 4.
  • Corpus replay cannot find a false-POSITIVE class — generate the matrix. Replaying real recorded inputs proves only that shapes you have ALREADY run still behave; it is structurally blind to a shape you have never typed. A guard change measured "0 false positives across 18k real commands" and still hard-blocked an ordinary command, because the corpus happened to contain no instance of the one shape that mattered: a construct the parser mis-handles that ALSO leaves the guard with no parsed segment. For any classifier or guard, enumerate the CROSS PRODUCT of the axes that actually drive the decision — {operation} × {the constructs your parser can mis-segment} × {evidence present, absent} × {interactive, unattended} — and assert the invariant per cell, so an untested cell fails loudly instead of silently. Keep the corpus replay as the realism check; the generated matrix is the coverage check FOR THE MODEL YOU DECLARED, which is the most it can be — a construct you never thought of has no cell to skip and so passes in silence, which is the same false confidence one level up. Naming it "the coverage check" without that qualifier is the trap. Discovering new axes is a different instrument: differential or property testing against a canonical parser, which fails on shapes nobody enumerated. Skip a cell only with an explicit reason recorded in the skip, since a silent skip and a hole look identical.
  • Anti-patterns (binding): never assert on a mock's behavior when the real code path can run; never add test-only methods/branches to production classes; fakes implement the real contract (real method names, real return types — import them). Test setup so complex it needs its own debugging = the design is too coupled; fix the design.

Code Intelligence — pick the right lane

Serena (Python LSP) is always live — it parses current files per query, so it's the default for symbol/reference/impact questions ("who calls X", "what breaks if I change Z") and never goes stale. CBM gives the architecture/graph overview. GitNexus does what neither can — multi-hop blast radius, execution flows, route/tool maps, coupling/community analysis — but it is snapshot- based: its answers are only correct when the index matches the working tree, and it drifts after you pull merged PRs (its reindex fires on local commit, not on pull). So reach for GitNexus deliberately for its unique views, and run gitnexus analyze first when freshness matters; for live "who calls this" during active editing, prefer Serena. There is no "always run impact before every edit" mandate — that just gates work behind a tool that's stale-by-design.

  • Blast radius / impact: Serena find_referencing_symbols (live) for the direct caller set; GitNexus impact <symbol> (reindex first) for multi-hop + affected processes/risk. Use the full UID if ambiguous (Method:path/file.py:Class.method#N).
  • Unfamiliar code: gitnexus context <symbol> or browse gitnexus://repo/GENesis-AGI/processes (when fresh).
  • Custom questions: gitnexus cypher — LadybugDB uses CodeRelation with a type property for edges, not Neo4j-style named edge labels.

Full syntax and Cypher examples: .claude/docs/code-intelligence-guide.md; tool-selection decision matrix: .claude/docs/code-intelligence.md

Common Traps

  • Fail-closed data access. A data-access boundary must RAISE (or return a clearly-typed "unknown/unavailable") on missing scope or an unavailable dependency — it must NEVER silently return the wrong data, the singleton's data, or an empty result that reads as "all clear". A monitoring/consistency check whose dependency (Qdrant, FTS, a remote) is down reports unknown, never healthy/degraded — a dependency outage that masquerades as data corruption (or as cleanliness) is worse than a loud error. Prefer a helper that raises over one that swallows (the batch_retrieve_point_ids-raises vs batch_retrieve_vectors-swallows split exists for exactly this). Origin: the home-anchored-DB reads that silently returned no data from an empty worktree path, and the memory-integrity checker (2026-07).

  • Ego sessions are ACTIVE. src/genesis/ego/ is live (v3.0a11). Two egos: user ego (CEO, Opus) and Genesis ego (COO, Sonnet). Both run on adaptive cadence via the awareness loop. Changes here are production changes.

  • DB path confusion. genesis.db is at ~/genesis/data/genesis.db, NOT ~/genesis/genesis.db. Use genesis.env.genesis_db_path().

  • Column names. Use db_schema MCP before assuming column names. The DB has 60+ tables.

  • Signal collectors. Phase 1 built stubs; Phase 6 replaced some with real implementations. Code that looks complete may not produce signals.

  • Capabilities manifest. ~/.genesis/capabilities.json is write-once at bootstrap, not dynamic. New capabilities need registration in _CAPABILITY_DESCRIPTIONS in src/genesis/runtime/_capabilities.py AND a bootstrap init step.

  • APScheduler IntervalTrigger resets on restart. IntervalTrigger counts from server startup, not from last successful run. If the server restarts more frequently than the interval, the job never fires. Use CronTrigger for anything longer than a few hours. Bit us with user_model_evolution (48h interval, daily restarts).

  • Silent skips are banned (provision-or-surface). A setup/resilience feature that gracefully skips on a missing prerequisite (a package, a host knob) must either PROVISION the prerequisite (bootstrap.sh / host-setup.sh / a guardian reconciler) or register an effective-fact in infra_profile that the awareness posture check (awareness/loop.py::_check_infra_protection_posture) reads — so an unprotected box raises a standing alert instead of staying silent. A graceful skip with neither = a box that runs unprotected with zero signal (a sibling install ran weeks without swap/systemd-oomd until a memory spike wedged it, 2026-07). Guardrail: tests/test_awareness/test_infra_protection_posture.py.

  • Modules are NEVER subsystems. A capability module (src/genesis/modules/**, an external pluggable capability — "hands, not brain", see modules/base.py) is not an internal Genesis subsystem (memory, reflection, ego, triage, autonomy, sentinel). Module memory writes must never set a source_subsystem value — that tag means "internal decisional output, exclude from default recall", which is wrong for module output. This is enforced mechanically: any .store() under modules/** passing source_subsystem is a hard CI failure in tests/test_memory/test_store_subsystem_coverage.py, which also forces every new memory-writer to either tag itself or be explicitly classified as user-context. _KNOWN_SUBSYSTEMS (memory/retrieval.py) is the authoritative subsystem list; adding a module name to it is a category error.

  • Destructive data migrations must reconcile cross-store mirror fields. When a cleanup/backfill deletes data in one store (e.g. Qdrant vectors) but another store mirrors that data's existence (e.g. memory_metadata.embedding_status), the delete MUST also fix the mirror field. A deleted vector left as embedding_status='embedded' is a field that lies, and that lie is not cosmetic if any code path reads it — MemoryStore._mark_superseded gates an update_payload on embedding_status != 'fts5_only' and would fire a doomed write on the now-deleted point. Before assuming a stale field is harmless, grep for its reads, not just its writes. (Bit us in the source_subsystem purge, #918; fixed by #921 Step 2c — reconcile tagged rows to fts5_only.)

  • immutable=1 reads miss WAL-resident writes. A read-only sqlite3 connection opened with file:...?immutable=1 reads only the main db file and ignores the -wal, so a change you JUST committed (still un-checkpointed) is invisible — you get a false-negative "the write didn't land." To verify a live write, use ?mode=ro (WAL-aware) or query through the server/CRUD path; reserve immutable=1 for historical read-only sampling where a little staleness is fine. (A reconcile UPDATE read clean under mode=ro but appeared unchanged under immutable=1.)

  • Never hand-roll gh/bash/CLI argv parsing inside a hook or security gate. Re-implementing shell/gh command-line semantics by hand (regex, manual token walking) creates an effectively UNBOUNDED adversarial-divergence tail: a good reviewer (Codex especially) will keep surfacing real gaps from true semantics — --repo/-R/-Rvalue, GH_REPO, cd, &&, ||, --body-file -, duplicate flags, URL-vs-branch targets, enterprise hosts, --help, nested bash -c — and each named fix ships the next round's bug. This is the mechanism behind the measured Codex review-loop (an internal analysis of recent review-looping PRs: first-round findings were real catchable bugs, but later rounds were dominated by fix-churn on the hand-rolled parser itself — no finite pre-push audit bounds that tail). The cure is architectural, not "review harder": bind atomically to the real tool (e.g. gh pr merge --match-head-commit) and use a canonical parser (shlex/bashlex) — never bespoke semantics. Choose the fail direction PER BOUNDARY by consequence: the shared parser must DEGRADE gracefully (fail-open, never crash — that is shell_parse.py's stated contract), while each security-critical caller (merge/push authorization) treats an unparseable command as a block (fail-closed THERE). A parser-wide absolute fail-closed is wrong — it would deny legitimate uncommon commands without closing evasion paths. Same family as the canonical-parser lesson (regex→yaml, #1393). Loci today: scripts/hooks/shell_parse.py + scripts/hooks/git_push_guard.py.

    The boundary of the class — read this BEFORE you reason yourself out of it. The tar pit is NOT "who tokenizes the string." Delegating tokenization to shell_parse and asking git for repo state does NOT exempt a guard: if the guard's CORRECTNESS depends on modeling what a git command WILL DO — which flags force, which operands are paths vs refs, which modes destroy, which repo is targeted — it is argv→EFFECT mapping, and that mapping is the same unbounded open-set surface as raw string parsing. This was reasoned around once already (2026-08-23, PR #1432): the guard used the canonical tokenizer and probed live git state, the author concluded "so it's not hand-rolling," and Codex returned 13 real findings (10 P1) — every one of them living in the argv→effect layer. The first architect finding of that shape (a separated global value-flag bypass) was the CLASS signal and got instance-patched; the next round found the rest of the class. n=1 IS the signal: any reviewer finding that exposes a semantic-modeling gap in a guard means STOP and re-architect — never patch the named instance.

    Decision test (verbatim, apply before shipping any guard): could a git flag you've never heard of change your guard's verdict? If yes, your claim is open-set — redesign to closed-set token claims (exact-form whitelists / literal token blocks) or to RECOVERABILITY (snapshot-then-allow, where a miss degrades to the status quo instead of a broken guarantee). Do not ship the open-set version and plan to harden it later; the review loop IS the hardening loop, one bug per round, and it does not converge.

    Three corollaries, each bought with a non-converging review loop.

    (a) NEVER normalize the command text before a blind-spot probe. A probe whose whole job is "notice that this text is unparseable" must read the RAW command. Preprocessing it can only ever DELETE the evidence the probe exists to find. MEASURED: a normalization step added in good faith — to stop a class of ordinary command from prompting — turned that shape into a silent ALLOW on two independent guards. Ordinary, not adversarial: the shape was one a developer writes without thinking, and the command really executed (verified against a shimmed binary, so the proof was execution rather than parse). The normalizer removed the very evidence the guard keyed on, and its own model of the shell's comment syntax was narrower than the shell's, so it could delete executed code as well. It then turned out not to be load-bearing at all: every case cited to justify it was one where the parser already resolved the operation, so its branch never ran. The fix was a DELETION, and it closed both defects at once. When a guard loop will not converge, look for the component that MODELS shell semantics and remove it — refining it is the loop.

    Scope this to a probe. Normalizing before a tokenizer whose tokens you are about to use is a different act with a different fail direction, and a sibling guard does exactly that, deliberately. Before calling any such site an instance of this rule, the question to answer is whether the normalization CAN change the tokenizability verdict or hide a target — and that is a possibility question, so a count cannot answer it. Demand a STRUCTURAL argument. The model is already in the repo, at scripts/hooks/destructive_command_guard.py:101-120: its replacements delete a line continuation — which is what the shell does with it — or insert whitespace and separators; none introduces a quote or an escape, so none can corrupt the quote balance shlex decides on. (The deletion removes a backslash, but a backslash-newline is a continuation, not an escape the shell keeps; the one place the shell does keep it, inside single quotes, is an over-block the guard states rather than hides.) That is a cannot; a corpus run only ever yields did not, here, and by the rule above it is structurally blind to the shape nobody typed. Run the corpus as corroboration, never as the proof, and pair it with a control that DOES flip — an unflipped corpus and an inert measurement look identical.

    State the direction too, not just a total — and then check the direction on the token you did not think of. The same sibling's fold used to split a word the shell joins, and the paragraph that stood here said the verdict could therefore only move toward refusing. That was measured on path operands and was false on the option token: the split could hide the flags, and a spelling the shell runs as a recursive-force removal of a protected path was allowed. A reviewer found it from the diff; the earlier audit had split every position of a path and never the option, and its zero came from benign traffic with no true-positive control. The fold now deletes the sequence, which is what the shell does, and the guard is asserted to give the same answer for the continued and the joined spelling. The general lesson is the one above: the examples you enumerate are a sample, and a direction claim needs the cell that would falsify it.

    Deliberately stated without the triggering shapes. A guard's defeat conditions are not a teaching aid, and this file is public.

    (b) When the parse is unreliable, ASK — do not BLOCK. A hard block forces a surgically precise trigger, and precision is exactly what an unreliable parse cannot deliver. Measured over five review rounds: every narrowing conjunct became a new way to STARVE the trigger (an over-strip that ate the evidence; a decoy segment that stood the net down), while every widening hard-blocked benign shapes (git status # don't commit yet was refused). Emitting a PreToolUse ask inverts the cost of being wrong — a false positive is one confirmation, a miss is the pre-existing status quo — which is what lets the trigger stay broad instead of clever. Measured prompt rate after widening: 0.43% of ~19k real commands.

    This does NOT loosen the fail-closed mandate above, and the two are easy to read as contradicting each other. Rule (b) is scoped to the git-operation blind-spot net — a guard whose trigger is deliberately broad and whose false positive is one confirmation. It is NOT a template for every guard: the protected-paths and destructive-command guards hard-block an unreliable parse even when a person is present, by design, because their false negative is an irreplaceable path or a broad recursive removal and their false positive is a rewrite. Within the net, the two rules are scoped by who is present: ask is the interactive form of the refusal, and where a session is unattended fail-closed governs and (c) applies. The operation proceeds unverified in neither case.

    This is the shipped shape now, not an aspiration, and one distinction inside it must stay visible. The shared parser still degrades to a naive split with NO failure signal, so a caller that only asks "did I get a matching segment?" allows. What closes the hole is a separate conjunction AT THE CALLER — no matching segment, AND the raw text is un-tokenizable, AND it names a gated operation — which yields ask interactively and a refusal when unattended. The parser's contract did not change and must not: it degrades, the caller chooses the fail direction, exactly as the mandate above requires.

    Earlier revisions of this passage described that net in the present tense while it was still unmerged. Both directions of that error are worth naming, because fixing one produces the other: an unbuilt mechanism written as shipped, and then — once it does ship — a hedge left standing that now understates the tree. A status sentence in a durable document is a claim with a date on it. Re-check it against the code whenever the surrounding work lands, not only when it is first written.

    Corollaries of the corollaries, each measured: a net that returns inline PRE-EMPTS every gate below it (a hard block with no other backstop was observed downgrading to a prompt) — set a reason and DEFER it to the tail where the other decisions are resolved. And an invariant pair of the form "never silently allowed" + "never hard blocked" is satisfied BY a block→ask downgrade, so pin the verdict EXACTLY wherever a hard block is the contract. Read "never hard blocked" here only as the shape of the trap — as an actual invariant it is false unscoped, and (c) below replaces it.

    (c) The ask-cost argument does NOT survive the move to a refusal. Rule (b) buys its broad trigger with "a false positive costs one confirmation" — and that is true only where someone can confirm. An unattended session has nobody to answer, so the obvious completion of (b) is a deny leg for that path, and that is where it goes wrong: the SAME broad predicate whose errors were cheap now produces unappealable refusals of ordinary work. MEASURED: sharing one predicate across both legs refused routine, entirely benign commands in unattended sessions, in the one failure direction the design had been chosen to avoid.

    Narrowing the refusal predicate is the obvious repair and it does not converge, for a reason worth stating precisely, because the imprecise version of it is false. The claim is NOT that no raw-text rule could ever work — the raw text does carry quote and comment syntax, and a complete canonical parser could read it, which is what the canonical-parser rule above tells you to reach for. The claim is bounded to a predicate built on the SAME degraded parse that failed: at that point the guard cannot say whether an occurrence of a gated verb is executed or merely quoted, commented, or documented, and that inability is the premise of the net existing. A predicate with no more information than the failure itself cannot both refuse the hidden operation and permit the inert mention. Escaping that needs a different information source — a fuller parser, or enforcement at the execution boundary, where mentions are never classified at all — not a cleverer rule over the same text.

    Three narrowing rounds on one predicate is the signature to STOP and make the policy decision explicitly. The decision taken on the guard this was learned from, and since shipped: the unattended path KEEPS refusing, and the invariant gets scoped rather than deleted. "A benign shape is never hard blocked" was simply false as written; what is true and testable is that it is never hard blocked where a human can approve, and where no one can, the refusal carries an ACTIONABLE stderr — the cause, plus a route that gives the gate MORE information rather than less.

    That last qualifier is load-bearing and the sloppy version of this sentence is a bypass instruction. Telling an operator to find "a rephrasing that avoids the predicate" invites mutating raw text until a degraded predicate stops matching, while the same unverified operation still runs — the fail-closed mandate defeated by its own error message. Only two routes are legitimate, and neither is an evasion. If the text is PROSE that merely mentions a gated verb, take it out of a shell command altogether — write the file with an editor tool — because it was never an operation to gate and never should have been parsed as one. If it IS the operation, express it so the parser can actually read it, which does not dodge the gate but submits to it. A rewrite that suppresses the trigger while still performing the operation is the one thing such a message must never suggest, and a message is not "actionable" if that is what it teaches. (Actionability here is a courtesy to the operator, NOT the RECOVERABILITY of the Decision test above, which is about a MISS degrading to the status quo. Same word, opposite failure direction; do not satisfy the Decision test by printing a nicer error.) State the narrower invariant; do not leave the false one standing, and do not delete the guarantee that still holds.

    Two things keep this from reading as licence. The open-set imprecision is tolerable here ONLY because the verdict is ask or deny and never allow — an imprecise predicate that cannot authorize anything does not violate the Decision test, while the same predicate wired to an allow would. And a broad regex over raw text is admissible ONLY as a mention scan whose outcome is an ask where a person is present and a refusal where none is — never an allow; the moment it maps argv to an effect and authorizes on the result, it is the hand-rolled-parser tar pit the mandate above forbids.

    A measurement informed this rather than settling it, and it was afterwards WITHDRAWN — which is the more useful half of the story. The claim was that across a sample of unattended sessions the leg had never fired in either direction. It does not survive: the transcripts it counted no longer existed when someone went to re-derive it, and the sample was far too small to carry a word as strong as "never" even while they did. Absence in a small sample is the expected observation for a rare event, not evidence the event cannot happen; the honest reading is that the leg's rate is LOW, which is a different claim and a weaker one. What was actually being chosen was which promise the suite should make, not which incident to prevent — and that conclusion never rested on the number, which is why it outlived it.

    A note on every number in these three rules, including the ones above. They come from one operator's local session transcripts at one date, and no corpus or harness is checked in, so a reader cannot reproduce or falsify them — the differing denominators are different harvests, not one corpus quoted three ways. That is not a hypothetical weakness: one of them was withdrawn the first time anyone tried, for exactly that reason. Treat them as the scale at which something was observed, never as a published result. A rule that only holds at someone else's numbers is not a rule; each of these should stand on its stated mechanism alone — and if one ever seems to DEPEND on a figure, that dependency is the defect to fix, not the figure to defend.

  • out=$(cmd) under set -e swallows the failure path — and NO linter catches it. An assignment whose value is a command substitution INHERITS that substitution's exit status, so under set -euo pipefail a bare out=$(cmd) followed by rc=$? never reaches the rc=$? when cmd fails: errexit fires first. Any error handling keyed on rc is dead code on exactly the path it was written for. Two properties make this vicious: it is invisible (the function just stops, printing nothing), and it hides behind call sites — f || echo … / if ! f disable errexit INSIDE the function, so the bug stays latent until someone writes the first bare call. shellcheck 0.9.0 does not flag it at any severity, including -o all (SC2155 is the different local x=$(cmd) declare-and-assign case; measured 2026-08-27 — no other linter was tested, and a future shellcheck could add it). Always write rc=0; out=$(cmd) || rc=$? — but declare local on its own line first: local out=$(cmd) || rc=$? NEVER fires, because local is a command and the compound takes local's status (0), not the substitution's. MEASURED: the split form yields rc=100, the inline form yields rc=0 and the caller proceeds as if the command succeeded — strictly WORSE than the bug this entry describes, since it converts a loud abort into a silent false success. The same applies inside the error handler: with set -o pipefail, x=$(… | grep -v … | tail -1) aborts when grep matches nothing, so a ${x:-fallback} default written for that very case never runs — guard it with || x="". Origin: scripts/lib/cc_version.sh (caught only by adversarial review), then three instances found in one scripts/bootstrap.sh function whose whole diagnostic block was unreachable. Pinned by tests/test_scripts/test_bootstrap_guards.py::test_install_pkg_*.

Iterative-Refinement Discipline

AI refinement cycles degrade code they were asked to "improve" — validation gets stripped, types relaxed, function scope widened. Published measurements show vague improvement prompts degrade security fastest across iterations. Three binding rules:

  1. Iterate with scoped, explicit prompts ("fix the race in X by serializing on Y"), never "improve/clean up/make robust".
  2. Be security-explicit when touching validation, auth, or boundaries — state what must not be weakened.
  3. Diff each refinement for what it REMOVED (constraints, guards, type enforcement), not just what it added.

Full failure-mode taxonomy + ordered audit passes: references/ai-code-audit.md.

Anti-Rationalization

These are excuses sessions use to skip discipline. If you catch yourself thinking any of these, STOP — you are rationalizing a shortcut.

Rationalization Why it's wrong
"This is just a simple fix, no tests needed" Simple fixes break complex systems. The Qdrant regression was a "simple fix." Write the test.
"I already know what this function does" You haven't read the implementation. Docstrings lie. Read the actual code.
"Tests pass, so we're done" Tests verify what they cover, not the outcome. Verify actual end-to-end behavior.
"I'll clean this up in the next commit" Next commit never comes in autonomous sessions. Do it now or create a follow-up.
"This file is too large to read fully" Read the relevant section. Partial reads lead to partial understanding and wrong fixes.
"The linter is happy, ship it" Linters catch syntax, not logic. Clean lint with broken behavior is worse than a warning with correct behavior.
"This change is low-risk, no impact analysis needed" Your confidence is based on what you know; checking callers reveals what you don't. Serena find_referencing_symbols is live — run it. For multi-hop blast radius, gitnexus analyze then impact.
"I can skip the worktree, I'll be quick" Concurrent session safety exists because "quick" commits have destroyed work before. Always worktree.
"The error is transient, retry will fix it" Diagnose first. Retrying a misdiagnosed error wastes tokens and masks root causes.
"I'll add the follow-up later" Follow-ups not created in-session are lost. Create it now while context is fresh.
"I don't need a skill for this" If a skill exists, use it. The using-superpowers Red Flags table exists for this exact rationalization.
"This review round is the same class, it doesn't really count" The visible round counter decides what counts, not you. Update it every cycle; at the cap, STOP.
"The user already said proceed, so I can keep looping" The escalation/fix-attempt caps CONSUME standing approval. Round 4+ (or fix #4) on an old instruction is a violation, not obedience.
"I can read the summary instead of the source" Summaries lose context. If you're about to change code, read the code, not the description of it.
"The missing data was the problem — I wrote it, so it's fixed" The mechanism that failed to write it is the problem. Hand-written artifacts are data repair, not a fix (see Instance-Fix vs Class-Fix Gate).
"I'll just add the field the reviewer flagged" A spec's required-set is closed — derive and enforce ALL of it at once, with one test locking the whole set, or the next round finds the next missing field (see Debugging Discipline: spec required-sets).

Code Discovery

Use the right tool for how you're exploring:

  • Architecture overview — CBM get_architecture(aspects=["overview"])
  • Finding symbols — CBM search_graph(name_pattern="...") or Serena find_symbol
  • Call tracing — CBM trace_path(function_name="...") or Serena find_referencing_symbols
  • Impact / blast radius — Serena find_referencing_symbols (live caller set); GitNexus impact (reindex first) for multi-hop + affected processes
  • Config/doc/non-code files — Grep/Read directly

Full decision matrix: .claude/docs/code-intelligence.md

Auditing Existing Capabilities — enumerate, don't spot-check

Before claiming Genesis "lacks X", "needs to add X", or is "weaker than at X" — or before any competitive/architecture comparison — verify by ENUMERATION, not a spot-check. Auditing a symbol is not auditing the stack, and a negative from a positive search is not evidence of absence:

  1. Enumerate the subsystem's full module inventory before concluding anything is absent.
  2. Trace the call graph BOTH directions — mechanisms often live in the wrapper/caller layer, not the first symbol (CRAG lives in the MCP recall wrapper, not retrieval.py; the reranker is applied by the caller).
  3. Grep by CONCEPT with several synonyms, not one symbol.
  4. Verify built/enabled/disabled against RUNTIME state (env gates, server logs), not code presence.
  5. Multi-path systems → coverage matrix (N entry points × M mechanisms); hot auto-fired paths often carry a thinner stack than the deep path — a gradient, not an absence.
  6. Confidence is capped by enumeration completeness.

A 2026-06-30 competitive audit wrongly claimed Genesis lacked CRAG, scope-before-rank, and a live reranker — all three had already shipped. Full protocol: procedure codebase_audit / CC memory audit-enumerate-not-spotcheck. For "does Genesis already have X", consult the subsystem map (docs/architecture/CURRENT.md, via the subsystem-map skill) FIRST; references/codebase-map.md stays the package-level structural companion.

Adaptive Review Protocol

Choose the review level proportional to the change:

Change type Review level Examples
Docs / text / comments None Markdown prose, inline comments
Simple mechanical None Variable rename, typo fix, import reorder
Small focused fix Code-reviewer agent inline Single-function bug fix, config tweak
Substantial change Code-reviewer inline + /deep-review Multi-file refactor, new MCP tool, wiring
Prompt / LLM behavior Both + extra scrutiny System prompts, skill instructions, routing

Decision criteria when ambiguous: "If the change could break a runtime path not covered by its own unit test, it needs /deep-review. If it only touches things with clear, isolated test coverage, code-reviewer inline is sufficient."

/review is not in this repo — it comes from the optional superpowers plugin. Where that plugin is installed, /review and superpowers:code-reviewer are the preferred path. Where it is not, those names do not resolve, and older text saying "Run /review" sends you after nothing — which is why the enforcement hooks now name the plugin as optional rather than assuming it. Check, don't assume, in either direction: the plugin is offered by the official marketplace, so its presence is per-install, not per-repo.

Always available, plugin or no: /deep-review (dispatches the adversarial pass AND writes the evidence marker), the built-in /code-review, and the by-hand scripts/review_state.py evidence-pathmark flow. /audit-changes is a light self-check, not a substitute for any of them.

The enforcement hooks (review_enforcement_prompt.py, review_enforcement_commit.py) still fire on every change — they are safety nets, not the decision-maker. This protocol provides the judgment framework.

Review depth is machine-checked (the front-stop)

The "substantial → adversarial /review-level audit" decision above is no longer left to judgment alone (that judgment is exactly what failed on PR #1353 — an under-depth inline pass was self-certified as sufficient). The commit gate now COMPUTES substantiality from the staged diff (review_scope.classify_change_substantiality) and BLOCKS a substantial change whose review marker is not an ADVERSARIAL audit (review_enforcement_commit.py Rule 2.5). Substantiality is a surface-area × risk model — ≥50 reviewable lines OR

1 code file OR an auth/api/migrations file OR an executable prompt/agent/skill surface (.claude/agents|commands|skills/*, src/genesis/skills/*), so the "Prompt / LLM behavior → both + extra scrutiny" row above is machine-enforced (a trivial edit to one is depth-audited). User-sovereign top-level CAPS docs (SOUL.md/USER.md/CLAUDE.md) are exempt. Clearance binds the marker to the reviewed diff's FULL content, so re-staging different content after the audit re-blocks. A precision-filtered "no findings" inline pass is FALSE CONFIDENCE for a substantial change — not clearance. Depth is override-exempt: a findings # review-override does NOT waive it; only a loud, logged # depth-ack does (the audited escape for a genuine format mismatch). "Adversarial" is verified STRUCTURALLY — a severity ladder (BLOCKER/SHOULD-FIX/NOTE, CRITICAL/HIGH/LOW, P1/P2/P3, or the CODE_AUDITOR JSON contract) + file:line engagement + substance — so run the real recall-tuned audit (genesis-architect / CODE_AUDITOR.md); do not hand-write a soft prompt.

Honest enforcement model — VERIFY IT, do not assume it. This section used to claim the enforcing teeth were "the independent cloud reviewer + a required human approval, gated by branch protection", and that the local hook was merely advisory friction. Measured on a live deploy (2026-08-27), that was false in both halves, and the correction matters because it inverts which layer you can actually rely on:

  • GET /repos/{owner}/{repo}/branches/main/protection404, not protected. The protection the claim rested on did not exist. A ruleset did — a different API, invisible to the branch-protection endpoint.
  • That ruleset required one status check (test), not the ten other blocking CI jobs. Lint, leak-detector and the rest were never server-side required.
  • It carried bypass_actors: [{actor_type: RepositoryRole, actor_id: 5 (admin), bypass_mode: always}]. A bypass entry voids the rule for that actor. The sole author is the repo admin, and the merge command this skill mandates carries --admin.

⇒ For the actor who merges, the server-side backstop was void, and the local git_push_guard.py merge gate was the only real enforcement — precisely the layer the old text told you to discount.

Two rules follow.

  1. Never state where the teeth are without querying it. Both endpoints, every time: branches/main/protection AND rulesets (then the ruleset by id, since the list view omits bypass_actors and rules). A rule with a matching bypass actor is decoration for that actor. A "required" check list is meaningless until you have compared it against the jobs that actually block.
  2. Do not weaken a local gate on the theory that something external catches it. That reasoning is only as good as the configuration you just verified, and configuration drifts silently — nothing announces a ruleset edit.

The local gate's job is still to interrupt autopilot, and it is still editable by the same author. But "editable in principle" is not "backed up in practice." Where a real external backstop exists, name it and cite the query that proved it.

Close every review with a verdict. End each review with an explicit Ready to merge: Yes | No | With fixes + a one-line reason, alongside the DONE / DONE_WITH_CONCERNS completion status. And the DON'Ts that keep a review honest: never "looks good" without reading the code; never a finding on code you did not read; never vague ("improve error handling") — always file:line + why it matters. (Deliberately NOT a "praise-first / acknowledge strengths" balance: an adversarial audit's job is to assume bugs and enumerate the class, not to reassure.)

Two protocol steps apply to every review at "Code-reviewer inline" level or above (full definitions in .claude/agents/genesis-architect.md):

  • Scope-drift check first: compare stated intent (plan file / PR description / commit messages) against git diff --stat vs the merge-base, and open the review with the Scope Check: CLEAN / DRIFT DETECTED / REQUIREMENTS MISSING + Intent/Delivered block. Informational, never blocking.
  • Completion status last: every review (and every skill workflow that concludes work) ends with exactly one of DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT — with concerns listed, or blocker + what was tried, or exactly what context is missing. Findings use the BLOCKER / SHOULD-FIX / NOTE severity ladder with per-finding confidence and the pre-emit quote gate (a finding must quote its motivating file:line or be confidence-capped).

Review-loop discipline

  • A review's findings are a SAMPLE, not a to-do list. This is the single highest-value habit in this section, and the one most often skipped. CLAUDE.md already says to treat the user's examples as a sample and enumerate the broader class — the same rule applies to REVIEWER output and is easy to miss there, because N findings look exactly like an N-item work queue. Before fixing any finding: name the CLASS it belongs to, enumerate the full population of that class (programmatically — an AST walk or a grep that lists every member, not a mental scan), and fix the population. Then the next round has nothing of that class left to find. Origin: 2026-08-27, three defect-bearing rounds on one PR where each round's fix introduced the next round's defect — one of three renderers, then one of two branches in the same function, then one direction of a two-directional boundary. Every round I fixed exactly what was named. The enumeration that finally closed it took one AST script and would have worked in round one.

  • Fixing the named instance is how a loop runs away. If two consecutive rounds each surface NEW defects, stop patching: that is the signature of instance-fixing, and the commit gate hard-blocks TWICE: first at the SECOND round (review_enforcement_commit.py mode-switch, cleared by # audit-ack), then at the third (escalation cap, # escalation-ack). Both call _deny, so the first stop arrives one round earlier than "the cap" suggests — see the two-tier table below. Switch to enumeration BEFORE the gate has to say so.

  • Run the pre-push adversarial pass with /deep-review (.claude/commands/deep-review.md): one command that dispatches a fresh-context genesis-architect (+ genesis-security-reviewer on security surfaces) over the FULL branch diff with the right SHAPE — fail-open/state/TOCTOU/ hand-rolled-parsing hunting, not a lint/secrets scan — and writes the evidence marker. This is the review that catches Round-1 bugs before Codex does; /audit-changes is only a light self-check.

  • PR review-findings status = python3 scripts/hooks/git_push_guard.py --check-pr <N> — ONLY. This runs the SAME code path as the merge gate (strict fail-closed: a failed scan is never reported clean). NEVER hand-roll a gh api pulls/N/comments query to decide whether a PR is review-clean: a wrong filter's EMPTY result reads exactly like "clean". Origin (2026-08-23, #1431/#1432): Codex authors BOTH its inline findings AND its review-summary body as chatgpt-codex-connector[bot] — the REST user.login, WITH the [bot] suffix. A hand-rolled filter keyed on a DIFFERENT login (the GraphQL app login, which is not the REST login) matched nothing, and 13 real findings (10 P1) were reported to the user as "review-clean" until the merge gate blocked. An empty result from your own query is "my query found nothing", never "no findings exist". Freshness is a SEPARATE gate, and it is NOT a blanket reviewed-SHA-equals-HEAD rule: for a hook-surface or otherwise non-trivial delta a current Codex review must COVER head (reviews-API commit_id == head, or a clean Codex re-review comment naming head), but a trivial NON-hook delta may still merge on a stale review — --check-pr reports that as codex-at-head : ok (STALE review of <sha>, delta since is trivial), a pass, not a block.

  • Hook-surface PRs merge only with a current GitHub Codex review — mechanical. A PR touching the enforcement-hook surface (the guard code itself) gets no stale-review leniency: the merge gate (1) never classifies its post-review delta as "review-trivial", and (2) refuses # stale-review-override — regardless of Codex head-freshness — unless recorded fallback-review evidence exists for the EXACT base+head (~/.genesis/override_review_evidence/<repo>__<pr>__<base12>__<sha>.txt). A current at-head Codex review does NOT substitute for that evidence: the same sigil also waives _check_base_is_default, and the evidence identity binds the BASE tip, which a head-only review cannot vouch for (a hook-surface PR retargeted to a non-default base must be re-reviewed in that base's context). The surface is defined authoritatively by _HOOK_SURFACE_PREFIXES + _HOOK_SURFACE_FILES in scripts/hooks/git_push_guard.py (hook dirs, the global bash safety hook, the review-scope/state modules, hook wiring in .claude/settings.json, and the tracked configs the hooks read) — read those constants, kept exhaustive by the TestWiredHooksFenceGuardrail test, rather than any hand-copied list. The override procedure requires the user's explicit authorization, then a fallback adversarial review (local codex exec when quota allows, else genesis-architect), evidence recorded naming the head, then the merge re-run — the gate's block message walks through it.

  • One reviewer at a time — NEVER run two review agents simultaneously. Run one reviewer (e.g. Codex), apply/verify its findings, then run the next reviewer (e.g. Claude) on the fixed code — sequential, never in parallel. The second reviewer should see the improved code, not the same unfixed diff both would otherwise review; parallel also doubles review spend per baseline. (Standing user directive.)

  • A different model is the real correctness gate; Codex is the default. A Claude reviewer shares this model's blind spots, so it clears the LOCAL depth gate but is not the cross-model gate. When the GitHub Codex reviewer is unavailable AND the install has an approved alternative external reviewer — a DIFFERENT model, with explicit per-use user approval every time — run it non-interactively over the diff with an adversarial mandate. Which reviewer that is (if any) is install-local and belongs in user-level config, not here. Unavailable is established by ASKING: comment @codex review, wait, and read the reply. An explicit usage-limits comment is unavailability. Nothing else is — silence is not, and neither is --check-pr reporting no review, which says the same thing whether the reviewer is down or was simply never triggered at this head. Nor is a codex exec quota error: that is a separate surface on separate quota. Scope what you hand it exactly as .claude/commands/deep-review.md §1 specifies. Verify it saw a diff at all: a clean verdict that does not demonstrate WHAT it reviewed is void, and a false clean from the cross-model gate is worse than no review. Do not merge on a same-model-only review.

  • Escalation cap — a HARD BLOCK at 3 rounds that each find NEW defects. A round = one review→fix→re-review iteration (local reviewer rounds and cloud-bot re-review rounds count together, per change). The cap is enforced by three mechanics, not by vibes:

    1. Visible round counter. From round 1, the plan file (or task list) carries Review rounds: N (cap 3), updated every cycle. Rounds are a tracked artifact — "it's the same class, it doesn't really count" is exactly the rationalization the counter exists to kill.
    2. The block point is BEFORE dispatching the next review. The check is "am I about to trigger round 4+?" — evaluated at the mechanical moment (the @codex review comment, the re-push, the reviewer dispatch), never after reading the next batch of findings.
    3. The cap CONSUMES standing approval. A prior "proceed", "merge when clean", or "keep going until Codex is green" is VOID once the cap fires. Continuing a round-4+ loop on an earlier instruction is a violation, not obedience — STOP, post the round ledger (round → what it found → what it cost), name the cap explicitly ("we've hit the 3-round escalation cap"), and get a FRESH decision: keep hardening, switch to a robust-by- construction redesign, narrow scope, or shelve. Tabulate findings by CLASS before fixing — but never let that change what COUNTS. Tabulate the findings with a CLASS column before fixing ANY round's findings, including round ONE. Deferring the tabulation to the second round is what spends a round discovering a shared cause that was visible in the first: if the opening review returns several instances of one generator, an instance-level first pass fixes the ones named and ships the rest as the next round's findings. The tabulation is cheap and the round it saves is not. Findings that look unrelated one at a time routinely share one generator — five across three rounds once reduced to a single defect (two layers that had to agree about every lever and could not), and patching instances twice changed nothing while deleting the second layer removed all five at once. If one class has ≥2 entries, look for the shared GENERATOR and fix that; two findings can land in one superficial class without sharing a cause, so the count is the prompt to look, not the verdict. Class grouping decides HOW you fix — never whether the round counts. The counter is deliberately class-blind: bump_review_round increments on a distinct staged diff and records no class (or reviewer) identity, and the marking rule below is finding-based — ANY new BLOCKER/SHOULD-FIX/P1/P2 makes the round defect-bearing, including the second instance of a class you have already named. Passing --clean to keep a repeat-class round off the counter is a falsification, and worse than miscounting: --clean RESETS the streak to zero, so it disarms the cap outright rather than merely under-counting it.

    A corollary that costs a round if missed: when you delete a duplicated layer, verify the class is closed by enumerating the registry for the BEHAVIOUR, not for the deleted file's NAME. A name-scoped guard beneath a class-scoped claim passes happily while a sibling oracle sits on the same event and matcher. (And if the invariant turns out not to be statically checkable, say so and narrow the test to what it can prove — a guard that cries wolf gets deleted by whoever hits it next.)

    These three are backstopped by a machine layer with two tiers, not one. The tier you hit FIRST is the one most sessions do not know exists:

    Round Gate Demands Sigil Resets counter?
    2 (cap-1) MODE-SWITCH block Stop patching the named instance. Dispatch a FRESH-CONTEXT adversarial subagent over the ENTIRE diff; READ authoritative docs/source for any domain semantics; fix the whole enumerated CLASS in one commit. # audit-ack No
    3 (cap) HARD STOP The full round-ledger stop above. # escalation-ack Yes

    The round-2 block is not the round-3 cap arriving early — it is a different instruction. It says the approach is wrong (you are fixing instances, not the class), where the cap says stop and re-decide. Acking round 2 without actually doing the fresh-context audit is how a session arrives at round 3 having learned nothing. # audit-ack attests that the audit HAPPENED; it is not a "continue" button.

    There is a separate depth tier that can fire at ANY round: a substantial change (≥50 reviewable lines OR >1 code file OR auth/api/migrations OR any prompt/agent/skill surface) whose marked review is not adversarial is blocked with # depth-ack. A mark that lands with no file:line engagement prints a WARNING and will be rejected by that gate — re-write the evidence with concrete anchors rather than acking past it.

    Backing all of it: review_state.py keeps a per-branch counter of CONSECUTIVE defect-bearing review rounds, and the commit gate (review_enforcement_commit.py) HARD-BLOCKS the commit at ESCALATION_ROUND_CAP (3) unless the command carries a deliberate trailing # escalation-ack. The counter implements the "each find NEW defects" clause literally — so when you mark a review, record its OUTCOME:

    • Review surfaced a NEW BLOCKER / SHOULD-FIX / P1 / P2 finding → mark as usual (python3 scripts/review_state.py mark --agent-output <path>) — the round counts.
    • Review surfaced no new BLOCKER/SHOULD-FIX/P1/P2 finding → add --clean (… mark --agent-output <path> --clean) — this RESETS the streak (circuit-breaker reset-on-success), so honestly-clean multi-commit development (independent clean reviews of distinct diffs) never trips the cap.

    A round is CLEAN iff the review found no BLOCKER/SHOULD-FIX/P1/P2. NOTEs, nitpicks, and dispositioned optional-hardening do NOT make a round defect-bearing — without this line a nitpick-prone reviewer would make every round "defect-bearing" and the cap collapses back into raw commit-counting. An unflagged mark counts as defect-bearing by default: a forgotten --clean gives a slightly early conscious checkpoint (the safe direction), never a silently-disabled cap. The ack is a conscious, logged act (like # review-override); adding it — or falsely passing --clean — WITHOUT the honest review result is the same violation as ignoring the prose above (the --clean flag mirrors the review record you write to the per-worktree evidence path (review_state.py evidence-path) at the same moment: falsifying one falsifies the other). Caveats: multiple findings in a single pass = one round (not an escalation); the same defect reappearing (an incomplete prior fix) is a fix-it-properly issue, not an escalation trigger. This complements the enumerate-class-then-lock convergence discipline — the cap is the escalation trigger when the class won't lock within ≤3 rounds. (Origin: PR #1281 ran ~7 reviewer rounds because a standing "proceed once clean" silently carried through rounds 4–6.)

    The Codex-round twin of the cap (git_push_guard.py _check_codex_round_escalation): the local counter above is BLIND to a loop that churns through CODEX rounds while every local review is clean — the 2026-08-12 MW-3 #1372 whack-a-mole shape (5 Codex rounds, local counter at 0, and the round-4 "fix" of a non-bug introduced the only genuine liveness bug). So gh pr comment … "@codex review" HARD-BLOCKS once the PR already carries ESCALATION_ROUND_CAP Codex reviews (counted live from the GitHub API; fail-open on any API error), until a trailing # escalation-ack. Before acking, DO THE STEP-BACK the block prints: (1) triage every open finding — {live bug | latent trap | hardening | observation}; only live bugs and cheaper-now-than-later traps may change already-reviewed code, the rest get a documented acceptance or route to the PR that owns the area; (2) fix MECHANISMS, not instances; (3) for state-machine/queue code, enumerate EVERY status value and trace the change under each (your tests encode your own state model — they can't catch states you didn't consider); (4) consider REVERTING a prior round's fix rather than patching it again; (5) escalate to the user with a minimize-change recommendation. The ack asserts that step-back happened — appending it without doing the work is the same violation as falsifying --clean.

  • External review feedback is a set of claims to VERIFY, not orders. For every bot/external finding: check it against the actual code (its stated mechanism may be wrong even when the underlying concern is real — quote the disproving file:line), check whether the "fix" breaks existing behavior or violates YAGNI, and push back with technical reasoning when it's wrong for this codebase. A finding that conflicts with the user's prior design decisions (e.g. live network calls in a hot autonomy gate, weakening an approval gate) is a STOP-and-discuss, never an auto-fix. Chasing a reviewer's green checkmark with a change you believe is wrong is a discipline failure. No performative agreement — state the verified fix, or the reasoned pushback.

  • Waiving the review GATE is not waiving the FINDINGS. When the user says "skip the review" for a trivial change, that waives the blocking ceremony (the gate and its *-override sigils — note # review-override is the one that waives the findings scan) — it NEVER licenses ignoring a reviewer's substantive findings. Read Codex's inline findings (even non-blocking P2s) BEFORE merging even when the gate is waived, and engage each on merits: verify it, then fix or consciously accept with a stated reason. Merging past unread findings on a "skip review" is a trust breach, not obedience. (Origin: #1439 merged past 3 correct Codex P2s.)

The Gate Machinery — the sequence, and why it bites

Ten enforcement layers sit between a change and main. Learning them by hitting them costs a session real time, every time. The canonical sequence:

python3 scripts/review_state.py evidence-path     # -> ~/.genesis/review_evidence/<key>.txt
# ... write the adversarial audit to exactly that path ...
git add <files>                                   # STAGE FIRST — mark hashes --cached
python3 scripts/review_state.py mark [--clean]    # --clean only if NO new BLOCKER/P1/P2
git commit -F <msg-file>                          # bare, not piped (see below)
git push                                          # approve the dialog on a branch's first push
gh pr create ...
gh pr comment <N> --body "@codex review"          # after EVERY subsequent push
python3 scripts/hooks/git_push_guard.py --check-pr <N>
gh pr merge <N> --squash --admin --match-head-commit <head>   # verbatim from --check-pr

Ordering and lifetime rules that are not obvious:

  • Stage → mark → commit. mark hashes git diff --cached. Re-staging or amending after marking invalidates it, and the check fails CLOSED.
  • Evidence expires in 30 minutes. The audit file must be recent when you mark, and the marker itself expires on the same clock. A long detour between audit and commit means re-marking.
  • evidence-path and mark key off the PROCESS cwd, not a flag. Run them from the same worktree as the commit or the key diverges silently.
  • A successful commit WIPES the marker. The next commit needs a fresh review; this is deliberate, not a bug.
  • --match-head-commit is mechanically required, not a nicety — the merge arm blocks outright when a verified head exists and the flag is absent. Copy the whole command from --check-pr output rather than reconstructing it.

Traps with a real cost, each one measured:

  • A BLOCKED Bash call runs NOTHING — including earlier && segments and heredocs. If mark && commit is blocked, the mark did not happen either. Run gate-adjacent steps as separate calls.
  • Ack sigils bind per-guard, and mostly to the LAST pipeline segment. git commit ... | tail # audit-ack puts the ack on tail. Run the commit bare. Some guards accept a sigil on any segment, others only on the offending one — do not generalise from one guard's behaviour.
  • Sigils must lead the trailing comment. The override is read from the leading run of recognised tokens, so # see audit-ack notes overrides nothing.
  • --no-verify is blocked before any override is even considered. There is no way to skip the native hooks; fix the cause.
  • Chained commits are heavily restricted — across worktrees, blocked outright; within one, later commits must be pure --amend with no intervening git command. cd "$VAR" && git commit fails closed with a branch-verification message, which reads like a branch problem and is not: use a literal path.
  • Worktree removal is not yours to do. git worktree remove is blocked; scripts/worktree_lifecycle.py owns it, with a 7-day trash bin, and reaps unchanged worktrees on a daily timer. Leave a dead worktree alone.
  • Editing a tracked git hook blocks the commit until its hash is re-recorded (scripts/update_hook_versions.sh), and editing scripts/hooks/* changes nothing until sync-hooks.sh copies it into .git/hooks/.
  • .github/** and prompt surfaces are never "docs" for substantiality purposes — they always reach the depth gate.

CI is not one check. Ten of eleven jobs block; only review-depth-check is advisory. Several are reproducible locally BEFORE pushing, which is far cheaper than a red PR:

ruff check src/ tests/ scripts/
python scripts/check_external_io.py
python scripts/check_subsystem_map.py
python scripts/check_shared_artifact_consumers.py
python scripts/check_frozen_clock.py

Do NOT run the full pytest suite locally (it is banned, and the concurrent-test guard blocks a second run anyway) — CI's test job is the blocking one.

Detecting a running pytest: match argv STRUCTURE, never a substring. Any check that greps command lines for pytest matches ITS OWN command line — pgrep -f "python -m pytest", ps | grep, and a bash case *pytest* all self-match, as does another session's wait-loop. Test argv[0]'s basename, or a python interpreter with an adjacent -m pytest.

Pre-Commit Gate

Verify before any commit:

  • git diff --cached --stat — every file in the diff belongs to your work
  • git status --short — check untracked files (should be staged or ignored)
  • Review level applied matches the adaptive protocol above
  • Staged files do not include secrets (secrets.env, .env, credentials)
  • New-Store Gate (anti-proliferation). A new persistent store — a DB table, a Qdrant collection, a file-plane under ~/.genesis/ — needs a written justification for why an existing store cannot hold it, plus a note on how it stays consistent with related stores (and its retention + backup path). The memory subsystem already sprawls across ~9 logical systems / 3 physical planes because this gate did not exist; a table that "felt cleaner" is how store #10 is born. Reuse an existing store or an existing convention (e.g. the ~/.genesis/eval/golden/ install-local golden-set convention, #1143) unless the justification is real. Prefer NOT reusing a store whose SEMANTICS differ (don't shoehorn a store-health row into the model-eval eval_runs table just because it is "a table that exists").
  • Private-data scan before every push (public repo). Grep the ENTIRE diff (git diff origin/main...HEAD) for private/identifying data — real names, company/product names, emails, IPs, private career/project specifics, verbatim user messages. Check ALL surfaces, not just prose: source comments, docstrings, and test fixtures/data are the easy misses. Use a synthetic stand-in in tests, never the real private artifact. (2026-07-01: a verbatim private DM leaked via a test docstring + a code comment after the commit message and PR body were already clean.)
  • GROUNDWORK-tagged code not accidentally deleted
  • New capabilities registered in _capabilities.py + bootstrap manifest
  • Conventional commit prefixes: feat:, fix:, refactor:, docs:, test:, chore:. Scope optional: feat(ego): add cadence manager. Subject line under 72 characters. Dominant category wins if mixed.
  • NEVER push to main or merge into main without a PR and user approval. Enforced by PreToolUse hook.
  • Targeted tests during development. Run ONLY the relevant test file(s) for your changes. NEVER run the full test suite locally — CI handles that. Check CI via gh pr checks. Bare pytest without a file path is banned.
  • Commit continuously: after every logical unit of work. Uncommitted = lost.
  • PR closes a ledger item → cite Ledger: <item-id> in the PR body (the 32-hex session_ledger row id, own line, e.g. Ledger: 71337fab…). The repo-pulse worker auto-absorbs the row with PR evidence at the next session boundary — deterministic, reversible via session_ledger_update. A bare id mention WITHOUT the Ledger: marker is context, not completion (the pulse only proposes it). Find ids via session_charter or the charter injection block.

Generalizability Gate — build for ANY install, not this one

Genesis is a public, cloneable system. Every change must work on ANY user's install, not just the machine it was written on. Standing user directive.

Hardware/scale adaptivity. Other installs have different RAM, disk, CPU count, and workload scale. Never hardcode absolute resource numbers or scale assumptions:

  • Memory/disk caps: percentage-of-available or config-derived, never fixed GB (precedent: #1029 percentage-based memory caps). Concurrency: derive from os.cpu_count()/config, never a literal core count.
  • Hard minimums are allowed but must be EXPLICIT (documented in install docs/config comments), not implicit assumptions that fail mysteriously.
  • Workload scale varies (PR velocity, table sizes, transcript sizes): enumerate with pagination/bounds and LOUD truncation markers, never silent caps (precedent: repo-pulse limit_hit).
  • Optional dependencies AND optional infrastructure (Ollama, GPU, individual API keys, a host VM/guardian, Tailscale, voice/edge hardware) must degrade gracefully behind detection/config — presence is never assumed (precedent: Ollama-optional, API_KEY_VOYAGE-gated reranker, guardian features no-op without guardian_remote.yaml).

No install-specific values in code. IPs, hostnames, usernames, absolute /home/<user> paths, GitHub slugs, timezones: these belong in generated local config (~/.genesis/config/genesis.yaml, written by setup-local-config.sh) or config overlays — never in committed code, defaults, or tests. Resolve repo paths via genesis.env.repo_root() / genesis_db_path() (GENESIS_REPO_ROOT-aware); resolve GitHub slugs LIVE (gh repo view --json nameWithOwner) — a configured slug can name a real-but-wrong repo and return plausible stale data. Shipped config defaults must work on a fresh install with ZERO overlay.

Leak-detection patterns follow the same rule — never hardcode an install's private literals into a tracked scanner. A public repo's CI grep / gitleaks rule / commit-msg hook / contribution sanitizer must ship only generic CLASS patterns (all RFC1918, IPv6 ULA per RFC 4193, /home/<user> shapes — see scripts/check_portability.sh). This install's SPECIFIC literals (its hostnames, subnets, ULA prefixes, private repo name, timezone) live only in the GENERATED ~/.genesis/release-fingerprints.txt (built by genesis.contribution.fingerprints at bootstrap; hand-edited section preserved, backed up via backup.sh) and — opt-in — the public repo's GENESIS_PRIVATE_PATTERNS Actions secret. A scanner that must exclude its own definition files from scanning is self-allowlisting a leak. See procedure public_repo_leak_detection_design.

Tenant-neutral, not tenant-shaped. Genesis is a single-user sovereign system. Build clean single-user code; do NOT pre-genericise for multi-tenancy — no tenant_id columns, ACL tables, or context objects that always resolve to one identity — absent a committed multi-tenant requirement. Premature genericisation taxes every change with abstraction for a customer that may never exist, and the reliability work that WOULD precede multi-tenancy (a fail-closed data boundary, consolidated stores, provenance) is worth doing on its own single-user merits and makes the eventual retrofit easier as a side effect. When that requirement lands, tenancy is a well-understood retrofit — not insurance to carry now.

Deploy-path answer required — "how does this reach other installs?" Every PR must have an answer for both an EXISTING install and a FRESH clone. Merged-but-undeployable-elsewhere is a bug. The standard paths:

Change type Deploy path
Runtime code git pull + server restart (update.sh does both)
DB schema additive idempotent migration — applies at restart
One-off data fix / backfill data-migration framework (post-boot, idempotent) — NEVER a hand-run script only this install executed
Config default repo config file (+ optional local overlay); works with no overlay
systemd unit / timer registered in bootstrap.sh AND the update path — never hand-systemctl enabled only here
Hooks / MCP servers land at next CC session start (note the mid-window in the PR)
Guardian / host VM update.sh redeploy (Host-Deploy Gate below)

When a change CANNOT deploy through the standard paths (one-time host action: packages, sudoers, cgroup settings, firmware), it must ship one of: (a) a gated self-heal that reconciles on a recurring tick (precedent: the guardian's swap reconcile — checks every tick, repairs config + live state, opt-out flag), or (b) an explicit, documented operator step in CHANGELOG + install docs. Silent "works here because I hand-fixed it" divergence is the failure mode this gate exists to kill — it bites hardest on guardian/host changes.

Empty-state correctness — a fresh install is state zero. Every feature must behave correctly with NO accumulated state: empty tables, no history, no cursor files, first run ever. First runs bound their own work (precedent: repo-pulse lookback_days — never "all history"); readers of possibly-absent tables degrade explicitly (precedents: dashboard charters_available: false; charter injection byte-identical when the migration hasn't applied yet). Test the zero state, not just the populated one — "works here" often means "works with two years of accumulated state."

External-tool version drift. Other installs run different versions of gh, GitNexus, Node, and Claude Code — and upgrade on their own schedule. Never key logic on one version's observed behavior without a fallback: prefer first-class config over output-patching, and keep the patch as a safety net when older versions ignore the config (precedent: .gitnexusrc

  • the strip job for rc-unaware versions); parse external-tool output fail-closed against the LIVE stream, never assumed semantics; pin versions only where the system owns the pin (cc_version.sh + cc-align).

A settings lever for every autonomous behavior. Anything that acts without a user in the loop — detached workers, scheduled jobs, auto-writes — ships its operator lever in the SAME PR: a settings domain (off | propose_only | live or equivalent) plus an env kill switch, with invalid values degrading toward LESS write authority (precedents: repo_pulse domain + GENESIS_REPO_PULSE_DISABLED; session_ledger_shadow live-coerced to shadow). Another operator must be able to turn your feature off — or cap its authority — without editing code. This is "the user decides tradeoffs" applied to every install.

Retention for every unbounded store. Any table, log, or directory that grows without bound ships its prune path in the SAME PR, wired into disk_hygiene.sh or an existing retention tick (precedents: repo-pulse 45d prune; ledger-shadow 45d prune; label-aware attention-snapshot GC). An unbounded store is a slow disk-leak on someone else's smaller disk — retention is part of the feature, not a follow-up.

Install-agnostic tests. Tests must pass on a fresh clone with no Genesis services, no live DB, no network, no gh auth, no local config: synthetic fixtures only (never real usernames/slugs/IPs — doubles as the privacy gate), injectable runners for external commands, tmp_path over real paths, no wall-clock dependence. CI on GitHub's runners IS the reference "different install" — anything a test can't exercise there needs an injectable seam, not a skip-on-my-machine guard.

Host-Deploy Gate (merged ≠ deployed)

A merged PR that touches host-deployed paths is NOT done at merge. The guardian and the host VM only pick up changes when scripts/update.sh runs — merging and walking away leaves the host running stale code indefinitely (observed live: a host guardian sat 3 PRs behind for a week because every session assumed deploy "happens somehow").

Trigger paths (match = this gate applies): src/genesis/guardian/, scripts/guardian-gateway.sh, scripts/install_guardian.sh, scripts/host-setup.sh, scripts/update.sh, scripts/lib/cc_version.sh.

After merging such a PR, in the same session:

  1. Run scripts/update.sh from ~/genesis (it redeploys the guardian when guardian-relevant paths changed and heals host/container CC + Node pin drift — including on a no-delta run).
  2. Verify the deploy landed: gateway version op reports the expected deployed_commit / CC version; guardian tick healthy in its journal.
  3. State the deploy + verification result explicitly in the wrap-up. If the deploy cannot happen this session (host unreachable), create a follow-up via follow_up_create — never leave deploy as an implicit assumption.

The reverse direction is equally binding: host VMs are deploy targets, never edit-in-place dev environments. An emergency hand-edit on a host gets a same-day PR that lands the same change at source — a host divergence that outlives its incident is a bug.

Pre-Merge Gate

Canonical pre-merge check: run python3 scripts/hooks/git_push_guard.py --check-pr <N> [--repo OWNER/REPO] BEFORE proposing a merge. It runs the SAME functions the enforcement gate uses (mergeable → CI → base-invariant → Codex-freshness → scheduled-Claude-review → review-body → inline findings), so the report and the gate can never disagree — this --check-pr read IS the mandatory pre-merge step: always run it and read the PR's automated-review comments (Codex, leak/CI, the scheduled Claude review) before any merge — never hand-roll a gh/jq review check (a hand-rolled query once used the GraphQL bot login on the REST endpoint, matched nothing, and reported "Codex clean" while P2s sat unread). When all gates pass it prints the exact atomic merge command to copy (... --match-head-commit <verified-head>); use that command verbatim.

git_push_guard.py enforces a hard gate at merge time. Beyond the review findings below, a gated gh pr merge:

  • must carry --admin (explicit approval flag) and be bound to the reviewed head via --match-head-commit (GitHub rejects it server-side if the head moved — TOCTOU defense); the --check-pr command supplies this;
  • requires Codex to have reviewed the current head — Codex does NOT auto-review a later fix-commit, so comment @codex review and wait after any push. Clean-comment freshness: a clean Codex re-review is posted as an ISSUE COMMENT ("Codex Review: Didn't find any major issues. … Reviewed commit: <sha>"), not a review object, so the reviews API never sees it. The gate now ALSO accepts that clean comment when its Reviewed commit sha names the current head — so a genuinely-clean re-review no longer false-blocks (it used to force a # stale-review-override). Fail-closed: the clean marker alone never vouches; a parseable Reviewed commit sha at head is required, and the comment must be authored by the Codex bot. Smart-delta narrowing: a STALE review passes anyway when the unreviewed delta (reviewed...head via the compare API, classified by review_scope substantiality) is provably review-trivial (docs-only / a small single-file touch-up) — the merge is then still bound to the exact head that was classified. A substantial or unclassifiable delta blocks; an ABSENT review always blocks;
  • requires every scheduled Claude review at the current head. Each scheduled Claude review (a /schedule cloud routine) posts as the repo OWNER's account and must carry a marker <!-- genesis-scheduled-review: head=<full-40-hex-sha> kind=<name> --> naming the exact head it reviewed AND which routine it is (kind). The gate blocks unless an owner-authored marker for EVERY effective required kind (_required_scheduled_review_kinds() — DEFAULT code-review + leaks; the leak/secret scanner is irreducible and always required; an install may relax the OPTIONAL kinds to ADVISORY via merge_gate.required_scheduled_reviews: [<kinds>] in local genesis.yaml) names the PR's current head — so if any required routine never ran, ran on a stale commit, or was rate-limited, the merge blocks (naming the missing kinds). An ADVISORY routine still posts its review on the PR to be read/addressed, but its absence does not block. The block message is an inventory, not a diagnosis: under each missing kind it lists EVERY marker block the scan found that names that kind, with its status, and hides nothing. Run python3 scripts/hooks/git_push_guard.py --check-pr <N> — it renders those rows, not just the summary line (whose present: none clause reads like "nothing was posted" in every case below, and is the exact wording an operator was once measured acting wrongly on). Row statuses you will see:
    • accepted at a DIFFERENT head — a routine ran, then a push moved the head. Routines are generally not re-run on a push; re-review the current head and post the marker;
    • REFUSED (at this head or another) — the body reads as carrying a blocking finding and no clean-verdict line overrides it; see the clean-verdict rule below. The message deliberately does NOT print the verdict string, because a gate that prints the line that makes it pass is explaining how to get past itself;
    • could not be counted: — a head that is not full 40-lowercase-hex, an empty or refused field value (quoted back verbatim), an author who is not the repo owner, a dismissed review, a stale unpublished draft;
    • unscoped — blocks naming no REQUIRED kind, listed and credited to nothing: guessing which review a block "meant" would steer you into attesting for one that never ran. A value carrying a /status suffix (kind=leaks/failed) lands here and is flagged as a run that reported its own failure. The one conditional is a COUNT, keyed on a fact: a kind with no OWNER-authored block at any head gets "a routine may still be in flight — waiting is the right move", because that is the only state where patience can help. A stranger's comment is not evidence about the owner's routine and never silences that note; an owner's block in ANY state (accepted elsewhere, refused, dismissed, stale draft, malformed) is, and does. Why an inventory and not a diagnosis: the previous shape picked one cause per kind and hid the rest, and every one of nine review findings across six rounds was a hidden fact — a refused [P1] on an older commit hidden behind a typo at the current one, the only evidence a review had ever run hidden by a drive-by comment. Precedence is the right shape for a VERDICT; for a REPORT, hiding a true fact is never correct. Or append # scheduled-review-override to merge anyway (the conscious "merge without the scheduled reviews" case). Head match is EXACT for the LLM marker — no delta tolerance, unlike the Codex freshness gate, which grants relief on a provably trivial delta. That asymmetry is deliberate: the Codex classifier judges code-review substantiality by file type and size, and an inferential leak lands in exactly the small doc edit it would wave through. The ONE relief the leaks kind gets is MECHANICAL, not a delta tolerance: an ACCEPTED leaks marker on an ANCESTOR commit of head satisfies the gate when the leak-detector job of the CI workflow is green at head (identity pinned to that (name, workflow) pair; _MECHANICAL_RESCAN_BY_KIND), and NEVER when any refused leaks marker — or any blocking finding the scan could not credit to a head or kind (a tie, a malformed marker) — exists anywhere in the PR (the head axis is not a time axis — a later acceptance at an older head must not outrank a refusal). --check-pr renders a carried marker as ok (leaks carried from <sha>, leak-detector green at head), never as ok (at head). Measured motive: 6 of 10 sampled multi-push PRs were blocked purely because the routine does not re-stamp after a push, and the override had become routine. The marker means "ran clean", not merely "ran": a review whose body carries a blocking finding ([P1]/HARD BLOCK/### ERROR, unless a clean verdict overrides) is rejected, and DISMISSED/PENDING(draft) reviews don't count. ALWAYS end a genuinely-clean scheduled review with an explicit verdict line (VERDICT: PASS, or PII/Secrets/Wording: CLEAN). The blocking patterns are plain substrings with no negation awareness, so prose like "not a hard block" or "no hard blocks found" TRIPS them — measured on two 2026-08-28 PRs whose markers both contained that phrase in negated prose; the one that also carried a clean-verdict line was accepted and the one without it was silently refused. Without the verdict line a clean review can be rejected on wording alone; Fail-closed: an unreadable comments/reviews fetch BLOCKS (never a false all-clear);
  • requires the PR base to equal the repo's default branch (retarget guard);
  • blocks unless mergeability is a definite MERGEABLE (a failed/unknown read does not merge).
  • the CI gate blocks red/pending checks; and — on the canonical public repo, where CI always runs — an absent CI state (a readable EMPTY check set = CI never ran, the tell of a conflicting branch or a dropped pull_request trigger) also blocks, so an un-CI'd PR can't merge. Likewise incomplete (a NON-empty rollup whose present checks are green but a REQUIRED workflow contributed no verdict — e.g. a lone green CodeQL after a workflow-specific trigger drop, or a fully-SKIPPED suite): the required identity is the rollup workflowName, config driven via merge_gate.required_ci_workflows: [<names>] in local genesis.yaml (default CI; fail-closed to the default on any malformed/empty config — there is no disable value). An UNREADABLE CI read (unknown) fails OPEN, and off the canonical repo absent/incomplete fail open too (another repo may legitimately have no CI, or a differently-named suite). Waive with # ci-override (never --admin).
  • Override sigils are split by boundary so one waiver can't silently disarm an unrelated gate: # review-override waives ONLY the finding scans (review-body + inline P1s); # stale-review-override waives ONLY the review-context gates (Codex-at-head freshness + base-invariant); # scheduled-review-override waives ONLY the scheduled-Claude-review gate; CI is # ci-override. Append several sigils in one trailing comment when several waivers are genuinely intended — but the right fix for a stale review is @codex review, not the sigil.

The review-findings gate specifically:

  1. After CI passes, the merge hook automatically checks PR comments for automated review findings (ERROR, [P1], HARD BLOCK).
  2. If review present with blocking findings → merge is BLOCKED by the hook (exit code 2). Fix the findings first.
  3. If review present with only WARNINGs/NOTEs → merge allowed.
  4. If no review comments at all (quota exhausted) → merge allowed on CI alone. Note in PR that review was quota-limited.
  5. Override: Append # review-override to the merge command to bypass the gate (e.g., gh pr merge 123 --squash --admin # review-override). The override is logged. Use only when findings are intentionally accepted.
  6. Read the PR's warning comments before merging — not just the hard gate. Beyond Codex, a structural-review bot posts under the repo-owner account (review state COMMENTED) and emits SOFT WARNINGs (PII / private-text / wording) that the hook does NOT block on and that a naive .comments scan misses. Check BOTH gh pr view N --json reviews,comments and gh api repos/<owner>/<repo>/pulls/N/comments, and address each soft warning or consciously accept it. Never merge past an unread warning.
  7. Codex findings are INLINE review comments — invisible to gh pr view. Codex's review body is boilerplate ("Here are some automated review suggestions"); its actual [P1]/[P2] findings live only at gh api repos/<slug>/pulls/N/comments. Derive <slug> live — gh repo view --json nameWithOwner --jq .nameWithOwner — NEVER hardcode it (configs name several repos; the working repo is not the org default). A 404 from that endpoint means WRONG SLUG or PR number, never "no findings" — a clean PR returns []. The merge-gate hook only blocks ERROR/[P1]/HARD BLOCK, so unread P2s pass silently (2026-07-10: 8 real P2s on the entity-layer PRs were merged past this exact way). And the two channels are INDEPENDENT: Codex can post a quota/usage-limit message as an ISSUE comment while a later @codex review trigger delivers real inline findings anyway — a quota message is evidence about that channel at that moment, never proof Codex "can't review". After time passes, re-trigger and check the INLINE endpoint before concluding quota-limited (2026-08-26: #1484's real P2 arrived inline while the issue-comment channel still showed only the earlier quota message).
  8. A CONFLICTING PR silently suppresses the whole CI suite. When a PR has a merge conflict with main, GitHub cannot build the merge ref, so pull_request-triggered workflows (the entire ci.yml suite) never run — while CodeQL still passes on the head SHA, making the check list LOOK green. A thin check list (only Analyze/CodeQL) means CHECK gh pr view N --json mergeableCONFLICTING needs the base branch merged in before any CI verdict exists at all (2026-07-16: #1089 sat conflict-suppressed through three pushes; main had moved under it via concurrent sessions). Since #1484 the merge gate ENFORCES this class mechanically on the canonical repo: a fully-empty rollup reads ci: absent and blocks, and a thin/partial rollup missing the required CI workflow reads ci: incomplete and blocks (see the CI-gate bullet above) — the trap text stays because the DIAGNOSIS (check mergeable first) is still the fastest route to the cause.

Reference Router

Read references ONLY when relevant to the specific task. Do NOT load all references on every trigger.

When you need... Read...
Subsystem purpose/maturity/do-not-touch (judgment layer) docs/architecture/CURRENT.md
Codebase structure, package map, gotchas, debugging references/codebase-map.md
Package/module/symbol navigation (progressive drill) codebase_navigate MCP tool (L0→L1→L2)
venv, DB paths, Qdrant, Ollama, network, commands references/environment.md
Worktree rules, concurrent sessions, branch naming references/worktrees.md
tracked_task, exc_info, os.killpg, logging patterns references/observability.md
V3 state, build order, GROUNDWORK, architecture docs references/architecture.md
Phase 6 contribution pipeline, sanitizer references/contribution.md
Pending work, active incidents, subsystem status references/build-state.md
Auditing/deep-reviewing AI-generated code (failure taxonomy, audit passes) references/ai-code-audit.md
Which code tool to use (CBM vs Serena vs GitNexus vs Grep) .claude/docs/code-intelligence.md

Freshness rule: On first read of codebase-map.md in a session, verify structural claims against current code. If a package status or gotcha has changed, flag to user before acting on stale assumptions. docs/architecture/CURRENT.md carries per-entry verified: stamps enforced by scripts/check_subsystem_map.py (CI subsystem-map-check) — after changing a subsystem's capabilities, update its entry and stamp.

Public Repo & Release Workflow

The public repo (GENesis-AGI) is the primary development repo. Standard open-source workflow: PRs go directly to the public repo.

  • Squash merges only — merge commits are disabled on the public repo. Always git pull --rebase origin main after merging a PR before committing locally, or push will be rejected (non-fast-forward).
  • README is public-authoritative — the public repo's README.md is hand-crafted and must NEVER be overwritten.
  • CHANGELOG audience is users — only include entries a user updating their install would care about. No internal refactors, README changes, CI tweaks, or process artifacts. Lead with the user-visible effect, not the implementation technique.
  • No sensitive data in commits — voice data, research profiles, IPs, and secrets must never enter the repo. User data lives in overlays outside the repo (e.g., ~/.claude/skills/*/, ~/.genesis/).
  • Individual campaigns are user data, not infrastructure — a campaign's name/prompt/targets/cadence live only in the campaigns DB table and the private backups repo; never hardcode them into tracked source. Unlike modules (which ship defaults under config/modules/*.yaml), campaigns ship ZERO defaults (no config/campaigns/). Only campaign infrastructure ships. Express reusable session types as generic roles (e.g. the community-responder profile), not names coupled to a live campaign. See src/genesis/campaigns/__init__.py.
  • External egress is gated; owner-facing egress is not — any autonomous send to the outside world (Discord, Medium, Twitter/X, Slack, DistributionManager.distribute) MUST route through the capability shadow-gate (autonomy/shadow_gate) before the enforce stage; the scripts/check_external_io.py CI guard backstops new endpoints. Delivery TO the owner (Telegram/voice/email-to-owner) is NEVER gated. Full contract in autonomy/shadow_gate.py.

Version History

  • 6f3310e Current 2026-09-03 01:53

    修正 review 门控规则以回应 Codex 审查发现,并将 review-gate 经验教训迁移至本技能文档。

  • 3ca56ae 2026-08-27 14:05

    新增 hooks 表面合并的安全机制,要求 Codex-at-head 并提供证据以覆盖审查,增强门禁安全性。

  • 9a68e71 2026-08-19 16:21

    新增 /deep-review 对抗性逻辑预推送命令及无手工解析规则,强化审查流程与代码鲁棒性。

  • 51253c6 2026-08-13 14:05

    强化 Codex-at-head 合并门禁逻辑:拆分覆盖信号量以隔离新鲜度与发现扫描检查;实现智能差异缩小功能以优化阻塞判断;移除已驳回评审的生效资格;改进 TOCTOU 绑定块的执行时机并补充集成测试。

  • 94be12b 2026-08-07 00:30

    通过 hooks 在 commit gate 层面机器强制执行 review 升级轮次上限(3轮),防止审查循环无限进行;修复因文档/配置跳过逻辑导致的绕过漏洞,确保限制在跳过检查前生效。

  • 7b2d218 2026-08-06 06:35

    强化Review循环限制为硬性阻断,增加调试纪律(根因分析、测试先行)和TDD规范,新增反合理化检查项。

  • 563f090 2026-08-03 13:04

    新增贡献指纹生成功能,将本地私有泄露检测模式移出受控源码,增强安全与合规性。

  • 5236453 2026-08-01 05:32

    新增审查循环纪律:限制单次仅一名审查者,并在3轮提出新缺陷后升级处理。

  • 5d12d48 2026-07-31 11:21

    新增记忆完整性检测核心(Phase 0),实现跨后端一致性检查器以发现静默数据不一致;增加配置驱动的健康探针、作业调度及仪表盘支持,提升可观测性。

  • 83a6c7f 2026-07-19 22:29

    新增实例修复与类修复的判定门控,明确数据修复不属于修复范畴;增加反合理化校验行,要求手工艺品需修复机制或显式延期。

  • f9015bb 2026-07-05 18:16

Same Skill Collection

.claude/skills/cc-update/SKILL.md
.claude/skills/code-intelligence/SKILL.md
.claude/skills/content-publish/SKILL.md
.claude/skills/genesis-voice/SKILL.md
.claude/skills/gitnexus/gitnexus-cli/SKILL.md
.claude/skills/gitnexus/gitnexus-debugging/SKILL.md
.claude/skills/gitnexus/gitnexus-exploring/SKILL.md
.claude/skills/gitnexus/gitnexus-guide/SKILL.md
.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md
.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md
.claude/skills/shelve/SKILL.md
.claude/skills/subsystem-map/SKILL.md
.claude/skills/taste/SKILL.md
.claude/skills/unshelve/SKILL.md
.claude/skills/youtube-fetch/SKILL.md
config/gstack-patches/codex-SKILL.md
src/genesis/skills/aws-fde-delivery/SKILL.md
src/genesis/skills/browser-automation/SKILL.md
src/genesis/skills/debugging/SKILL.md
src/genesis/skills/evaluate/SKILL.md
src/genesis/skills/forecasting/SKILL.md
src/genesis/skills/integrate-module/SKILL.md
src/genesis/skills/lead-generation/SKILL.md
src/genesis/skills/linkedin-comment-strategy/SKILL.md
src/genesis/skills/linkedin-content-calendar/SKILL.md
src/genesis/skills/linkedin-dm-outreach/SKILL.md
src/genesis/skills/linkedin-hook-writer/SKILL.md
src/genesis/skills/linkedin-post-writer/SKILL.md
src/genesis/skills/linkedin-profile-optimizer/SKILL.md
src/genesis/skills/obstacle-resolution/SKILL.md
src/genesis/skills/onboarding/SKILL.md
src/genesis/skills/osint/SKILL.md
src/genesis/skills/prospect-researcher/SKILL.md
src/genesis/skills/research/SKILL.md
src/genesis/skills/retrospective/SKILL.md
src/genesis/skills/stealth-browser/SKILL.md
src/genesis/skills/triage-calibration/SKILL.md
src/genesis/skills/user_evaluate/SKILL.md
src/genesis/skills/video-processing/SKILL.md
.claude/skills/deliverable-builder/SKILL.md
.claude/skills/voice-master/SKILL.md
src/genesis/skills/voice-master/SKILL.md

Metadata

Files
0
Version
6f3310e
Hash
dd468add
Indexed
2026-07-05 18:16

trang chủ - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-04 05:51
浙ICP备14020137号-1 $bản đồ khách truy cập$