diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 07fa1654..236ff51e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -92,6 +92,13 @@ Control (report < 0.85) → Human review required full copy of OikosBot once lived in `bots/sustainabot/`; it was extracted to `hyperpolymath/oikosbot` and the slot reset to a placeholder. `oikos` is a separate DSL (`hyperpolymath/oikos-economics-accounting-dsl`). +7. **Mark intentional mass-deletions.** The Repo Integrity Guard + (`.github/workflows/repo-integrity-guard.yml`) fails any change that deletes + more than `MAX_DELETIONS` (50) tracked files vs. base — it exists because + `main` was once silently gutted from 1777 files to 2. For a *deliberate* + large removal, include the literal marker `[mass-delete-ok]` in a commit + message in the range, **or** in the PR title/body. Never weaken the guard or + trim its critical-file list just to get a change through. ## Repo health diff --git a/.github/workflows/repo-integrity-guard.yml b/.github/workflows/repo-integrity-guard.yml index 4702b0c7..8d10cc47 100644 --- a/.github/workflows/repo-integrity-guard.yml +++ b/.github/workflows/repo-integrity-guard.yml @@ -65,6 +65,9 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} BEFORE_SHA: ${{ github.event.before }} HEAD_SHA: ${{ github.sha }} + # Untrusted PR text — consumed only via env + grep -F (never eval'd). + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} run: | set -euo pipefail @@ -86,14 +89,18 @@ jobs: echo "Deleted tracked files vs $base: $deleted (limit ${MAX_DELETIONS})" # Intentional large removals: put [mass-delete-ok] in any commit - # message in the range (or the PR title/body). + # message in the range, or in the PR title/body. if git log --format='%B' "${base}..${HEAD_SHA}" | grep -qF '[mass-delete-ok]'; then - echo "Override marker [mass-delete-ok] present — tripwire bypassed by intent." + echo "Override marker [mass-delete-ok] present in a commit message — tripwire bypassed by intent." + exit 0 + fi + if printf '%s\n%s\n' "${PR_TITLE:-}" "${PR_BODY:-}" | grep -qF '[mass-delete-ok]'; then + echo "Override marker [mass-delete-ok] present in the PR title/body — tripwire bypassed by intent." exit 0 fi if [ "$deleted" -gt "${MAX_DELETIONS}" ]; then - echo "::error::$deleted files deleted vs base (limit ${MAX_DELETIONS}). If intentional, add [mass-delete-ok] to a commit message in this change. Refusing to let main be silently gutted again." + echo "::error::$deleted files deleted vs base (limit ${MAX_DELETIONS}). If intentional, add [mass-delete-ok] to a commit message OR the PR title/body. Refusing to let main be silently gutted again." git diff --diff-filter=D --name-only "$base" "$HEAD_SHA" | head -40 | sed 's/^/ deleted: /' exit 1 fi diff --git a/bots/rhodibot/README.adoc b/bots/rhodibot/README.adoc index 5851d7e6..b923496b 100644 --- a/bots/rhodibot/README.adoc +++ b/bots/rhodibot/README.adoc @@ -287,9 +287,9 @@ Rhodibot is a Tier 1 Verifier in the gitbot-fleet hierarchy: | Tier 1 Verifier | Sibling (mathematical/formal verification) -| https://github.com/hyperpolymath/sustainabot[sustainabot] +| link:../sustainabot/README.adoc[sustainabot] | Tier 1 Verifier -| Sibling (ecological/economic standards) +| Sibling (ecological/economic standards) — reserved fleet slot, _not_ the standalone OikosBot (`hyperpolymath/oikosbot`) | glambot | Tier 2 diff --git a/shared-context/src/bot.rs b/shared-context/src/bot.rs index 7d563b96..d8df54e2 100644 --- a/shared-context/src/bot.rs +++ b/shared-context/src/bot.rs @@ -30,6 +30,12 @@ pub enum BotId { Cipherbot, /// Targeted audit bot wrapping panic-attack static analysis Panicbot, + /// External ecological/economic code-analysis App (OikosBot, + /// `hyperpolymath/oikosbot`) that publishes findings via the optional + /// `oikosbot-fleet` bridge. NOT the reserved `Sustainabot` fleet slot and + /// NOT a fleet-managed roster bot — like `Custom`, it is deliberately + /// excluded from `all()` so the coordinator never dispatches it. + Oikosbot, /// Custom/external bot Custom(u32), } @@ -48,6 +54,7 @@ impl fmt::Display for BotId { BotId::Accessibilitybot => write!(f, "accessibilitybot"), BotId::Cipherbot => write!(f, "cipherbot"), BotId::Panicbot => write!(f, "panicbot"), + BotId::Oikosbot => write!(f, "oikosbot"), BotId::Custom(id) => write!(f, "custom-{}", id), } } @@ -57,7 +64,7 @@ impl BotId { /// Get the tier this bot belongs to pub fn tier(&self) -> Tier { match self { - BotId::Rhodibot | BotId::Echidnabot | BotId::Sustainabot | BotId::Panicbot => Tier::Verifier, + BotId::Rhodibot | BotId::Echidnabot | BotId::Sustainabot | BotId::Oikosbot | BotId::Panicbot => Tier::Verifier, BotId::Glambot | BotId::Seambot | BotId::Finishbot | BotId::Accessibilitybot => Tier::Finisher, BotId::Cipherbot => Tier::Specialist, BotId::RobotRepoAutomaton => Tier::Executor, @@ -99,6 +106,7 @@ impl BotId { "accessibilitybot" | "accessibility-bot" => Some(BotId::Accessibilitybot), "cipherbot" | "cipher-bot" => Some(BotId::Cipherbot), "panicbot" | "panic-bot" => Some(BotId::Panicbot), + "oikosbot" | "oikos-bot" => Some(BotId::Oikosbot), _ => None, } } @@ -354,6 +362,19 @@ impl BotInfo { can_fix: false, depends_on: vec![BotId::Rhodibot], }, + BotId::Oikosbot => Self { + id, + name: "OikosBot".to_string(), + description: "External ecological/economic code-analysis App (hyperpolymath/oikosbot) that publishes via the optional oikosbot-fleet bridge — distinct from the reserved Sustainabot slot".to_string(), + version: "0.1.0".to_string(), + categories: vec![ + "sustainability".to_string(), + "ecological".to_string(), + "economic".to_string(), + ], + can_fix: false, + depends_on: vec![], + }, BotId::Custom(_) => Self { id, name: "Custom Bot".to_string(),