Merge branch 'fix/v0.14.2-remove-genesis-scan-public' — genesis-scan internal-only
This commit is contained in:
commit
ad8747f22c
19 changed files with 28 additions and 753 deletions
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
|
@ -74,14 +74,3 @@ jobs:
|
|||
- name: shellcheck (advisory)
|
||||
run: find hooks _primitives -name '*.sh' -exec shellcheck -S warning {} +
|
||||
continue-on-error: true # warnings are advisory initially
|
||||
|
||||
genesis-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: _primitives/_rust
|
||||
- run: cd _primitives/_rust && cargo build --release -p genesis-scan
|
||||
- run: ./_primitives/_rust/target/release/genesis-scan --path . --format=human --exit-on-hit
|
||||
|
|
|
|||
20
README.md
20
README.md
|
|
@ -28,7 +28,7 @@ Thanks.
|
|||
|
||||
## What it is
|
||||
|
||||
KeiSeiKit is a comprehensive drop-in toolkit for [Claude Code](https://claude.com/claude-code). It ships a curated set of composable behavioral blocks, a Rust assembler that builds agent `.md` files from TOML manifests deterministically, 10 pre-wired PreToolUse/PostToolUse hooks (three of them dedicated to RULE 0.14 session self-audit), 38 portable skills (including an interactive `/new-agent` wizard, 10 hub-and-spoke pipelines, and the `/self-audit` retrospective skill), **24 Rust primitive crates**, 13 opt-in shell primitives (plus 3 always-copied sleep-sync helpers), and 11 cross-tool bridge templates. Everything follows a Constructor Pattern: one file per concern, manifests as single source of truth, and the generated agent files are regenerated on every relevant edit.
|
||||
KeiSeiKit is a comprehensive drop-in toolkit for [Claude Code](https://claude.com/claude-code). It ships a curated set of composable behavioral blocks, a Rust assembler that builds agent `.md` files from TOML manifests deterministically, 9 pre-wired PreToolUse/PostToolUse hooks (three of them dedicated to RULE 0.14 session self-audit), 38 portable skills (including an interactive `/new-agent` wizard, 10 hub-and-spoke pipelines, and the `/self-audit` retrospective skill), **23 Rust primitive crates**, 13 opt-in shell primitives (plus 3 always-copied sleep-sync helpers), and 11 cross-tool bridge templates. Everything follows a Constructor Pattern: one file per concern, manifests as single source of truth, and the generated agent files are regenerated on every relevant edit.
|
||||
|
||||
The kit is MIT-licensed and fully generic — install it on a fresh machine and you get a sane 12-agent fleet (implementers, critics, researchers, cost-guardians, and more — all namespaced under `kei-*` so they won't collide with your own same-named agents), a wizard for spinning up new project specialists, 10 pipeline skills that combine primitives end-to-end (`/compose-solution`, `/site-create`, `/schema-design`, `/observability-setup`, `/auth-setup`, `/api-design`, `/ci-scaffold`, `/test-matrix`, `/docs-scaffold`, `/new-project`, `/vm-provision`), and a build pipeline that keeps every agent derivable from its manifest.
|
||||
|
||||
|
|
@ -87,11 +87,11 @@ By default `./install.sh` is **minimal** — agents + hooks + skills + bridges,
|
|||
| Profile | Primitives added | Install time | Disk (approx) |
|
||||
|---|---|---|---|
|
||||
| `minimal` (default) | none | ~5s | ~2 MB |
|
||||
| `core` | `tomd` | ~5s | ~3 MB |
|
||||
| `core` | `tomd` | ~5s | ~2 MB |
|
||||
| `frontend` | 8 site tools: `mock-render`, `visual-diff`, `tokens-sync`, `design-scrape`, `live-preview`, `figma-tokens`, `frontend-inspect`, `screenshot-decode` | ~60s | ~80 MB |
|
||||
| `ops` | 8 infra tools: `kei-ledger`, `ssh-check`, `firewall-diff`, `provision-hetzner`, `provision-vultr`, `harden-base`, `metrics-scrape`, `log-ship` | ~90s | ~50 MB |
|
||||
| `dev` | 4 dev tools: `kei-migrate`, `kei-changelog`, `kei-ci-lint`, `kei-docs-scaffold` | ~60s | ~40 MB |
|
||||
| `full` | everything (37 primitives) | ~5 min | ~200 MB |
|
||||
| `full` | everything (36 primitives) | ~5 min | ~200 MB |
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ Examples:
|
|||
|
||||
Profile resolution lives in `_primitives/MANIFEST.toml` — one `[primitive.<name>]` entry per primitive plus a `[profile]` block. Edit the manifest to define new profiles without touching `install.sh`.
|
||||
|
||||
> **Migrating from a full install:** if you're re-running `install.sh` after an earlier version that installed all primitives unconditionally, the new default (`minimal`) will REMOVE them. To preserve the old behaviour explicitly, pass `--profile=full` (currently 37 primitives).
|
||||
> **Migrating from a full install:** if you're re-running `install.sh` after an earlier version that installed all primitives unconditionally, the new default (`minimal`) will REMOVE them. To preserve the old behaviour explicitly, pass `--profile=full` (currently 36 primitives).
|
||||
|
||||
> **Re-install disclaimer:** `install.sh` is idempotent for clean state but **overwrites kit-owned `_blocks/`, `_primitives/`, `_bridges/`, `_templates/`, `_assembler/`, `hooks/`, and `skills/` on re-run** — local modifications under those directories are backed up to `<dir>.bak-TIMESTAMP/` (or, for shared hook files, to `<file>.bak-TIMESTAMP`). User-owned `_manifests/*.toml` are never overwritten.
|
||||
|
||||
|
|
@ -118,9 +118,9 @@ Profile resolution lives in `_primitives/MANIFEST.toml` — one `[primitive.<nam
|
|||
|---|---:|---|
|
||||
| Behavioral blocks | 73 | `baseline`, `evidence-grading`, `rule-math-first`, `stack-rust-axum`, `stack-react-vite`, `stack-sveltekit`, `stack-astro`, `deploy-modal`, `api-fal-ai`, ... |
|
||||
| Generic agents (manifests) | 12 | `kei-code-implementer`, `kei-critic`, `kei-validator`, `kei-security-auditor`, `kei-architect`, `kei-researcher`, `kei-ml-implementer`, `kei-cost-guardian`, `kei-modal-runner`, ... |
|
||||
| Hooks (PreToolUse / PostToolUse) | 10 | `assemble-agents`, `assemble-validate`, `no-hand-edit-agents`, `tomd-preread`, `agent-fork-logger`, `site-wysiwyd-check`, `session-end-dump`, `milestone-commit-hook`, `error-spike-detector`, `git-pre-commit-genesis` |
|
||||
| Hooks (PreToolUse / PostToolUse) | 9 | `assemble-agents`, `assemble-validate`, `no-hand-edit-agents`, `tomd-preread`, `agent-fork-logger`, `site-wysiwyd-check`, `session-end-dump`, `milestone-commit-hook`, `error-spike-detector` |
|
||||
| Portable skills | 38 | `compose-solution`, `new-agent`, `new-project`, `site-create`, `schema-design`, `observability-setup`, `auth-setup`, `api-design`, `ci-scaffold`, `test-matrix`, `docs-scaffold`, `vm-provision`, ... |
|
||||
| Primitives (Rust crates, opt-in) | 24 | `kei-ledger`, `kei-migrate`, `kei-changelog`, `ssh-check`, `firewall-diff`, `mock-render`, `visual-diff`, `tokens-sync`, `kei-memory`, `genesis-scan`, `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`, `kei-store`, `kei-router`, `kei-sage`, `kei-task`, `kei-chat-store`, `kei-crossdomain`, `kei-search-core`, `kei-content-store`, `kei-social-store`, `kei-curator`, `kei-auth` |
|
||||
| Primitives (Rust crates, opt-in) | 23 | `kei-ledger`, `kei-migrate`, `kei-changelog`, `ssh-check`, `firewall-diff`, `mock-render`, `visual-diff`, `tokens-sync`, `kei-memory`, `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`, `kei-store`, `kei-router`, `kei-sage`, `kei-task`, `kei-chat-store`, `kei-crossdomain`, `kei-search-core`, `kei-content-store`, `kei-social-store`, `kei-curator`, `kei-auth` |
|
||||
| Primitives (shell, opt-in via profile) | 13 | `tomd`, `design-scrape`, `live-preview`, `figma-tokens`, `frontend-inspect`, `screenshot-decode`, `metrics-scrape`, `log-ship`, `provision-hetzner`, `provision-vultr`, `harden-base`, `kei-ci-lint`, `kei-docs-scaffold` |
|
||||
| Shell helpers (always copied) | 3 | `kei-sleep-setup`, `kei-sleep-sync`, `kei-sleep-queue` (dormant until you run `/sleep-setup`) |
|
||||
| Cross-tool bridges | 11 | Cursor legacy/MDC, Codex, Copilot, Windsurf, Junie, Continue, Gemini, Aider, Replit |
|
||||
|
|
@ -225,7 +225,7 @@ Typical use:
|
|||
- "Derive closed form for the CfC attractor on Stiefel V(p,k=2)" → marathon mode, full night of autonomous derivation
|
||||
- "What patterns in audit-backlog have highest impact?" → pattern analysis
|
||||
|
||||
Results in `sync-repo/sleep-results/<uuid>.md`, linked from the next morning's REM report. Biological analog: the REM-sleep "sleep on it" effect (Wagner et al. 2004, *Nature*). Queue mutations go through the `kei-sleep-queue` helper, which runs `genesis-scan` on submit as a second line of defense against patent-sensitive prompts leaking to the cloud agent.
|
||||
Results in `sync-repo/sleep-results/<uuid>.md`, linked from the next morning's REM report. Biological analog: the REM-sleep "sleep on it" effect (Wagner et al. 2004, *Nature*). Queue mutations go through the `kei-sleep-queue` helper.
|
||||
|
||||
### Deep-sleep NREM consolidation (v0.13.0)
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ Requires the new `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`,
|
|||
|
||||
## Primitives (Rust)
|
||||
|
||||
`_primitives/_rust/` is a Cargo workspace with 24 single-binary crates (v0.13.0 added 4 deep-sleep primitives; v0.14.0 added 10 LBM-port MCP crates). `install.sh` builds `--release` for the subset selected by the active profile and drops binaries at `~/.claude/agents/_primitives/_rust/target/release/<name>`.
|
||||
`_primitives/_rust/` is a Cargo workspace with 23 single-binary crates (v0.13.0 added 4 deep-sleep primitives; v0.14.0 added 10 LBM-port MCP crates; v0.14.2 removed `genesis-scan` — internal-only tool, not shipped publicly). `install.sh` builds `--release` for the subset selected by the active profile and drops binaries at `~/.claude/agents/_primitives/_rust/target/release/<name>`.
|
||||
|
||||
| Crate | Purpose |
|
||||
|---|---|
|
||||
|
|
@ -267,7 +267,6 @@ Requires the new `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`,
|
|||
| `visual-diff` | Pixel diff with tolerance — used in `/site-create` screenshot-regression loop |
|
||||
| `tokens-sync` | Design tokens JSON → Tailwind config extend + CSS variables under `:root` |
|
||||
| `kei-memory` | Session retrospective + recurring pattern detector; offline-first analyzer powering RULE 0.14 self-audit |
|
||||
| `genesis-scan` | Patent-IP leak scanner (term blacklist + exempt-path rules; CI / pre-commit gate) |
|
||||
| `kei-conflict-scan` | v0.13.0 — deep-sleep conflict scanner across rules/hooks/blocks/orphans/CP violations |
|
||||
| `kei-refactor-engine` | v0.13.0 — consumes `kei-conflict-scan` JSON; emits plan markdown + auto-resolve review markdown (NOT a unified diff; v0.14.1 retraction) |
|
||||
| `kei-graph-check` | v0.13.0 — post-refactor wikilink + handoff + block-ref resolver gate |
|
||||
|
|
@ -306,7 +305,7 @@ Requires the new `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`,
|
|||
Block edit (_blocks/<block>.md) <-- triggers rebuild of ALL agents
|
||||
```
|
||||
|
||||
10 hooks enforce the pipeline (6 pipeline + 3 session-audit + 1 genesis-pre-commit):
|
||||
9 hooks enforce the pipeline (6 pipeline + 3 session-audit):
|
||||
|
||||
- **`assemble-agents`** (PostToolUse, Write/Edit) — rebuilds the affected agent(s) whenever a manifest or a block changes. No manual rebuild needed.
|
||||
- **`assemble-validate`** (PreToolUse, Bash) — blocks `git commit` inside `~/.claude` if any manifest fails validation. Keeps the repo in a buildable state at all times.
|
||||
|
|
@ -317,7 +316,6 @@ Requires the new `kei-conflict-scan`, `kei-refactor-engine`, `kei-graph-check`,
|
|||
- **`session-end-dump`** (Stop event) — RULE 0.14 self-audit: archives the session JSONL trace and ingests it into `kei-memory`.
|
||||
- **`milestone-commit-hook`** (PostToolUse, Bash) — RULE 0.14 self-audit: appends a one-line session summary to `~/.claude/memory/audit-backlog.md` on every `feat:`/`refactor:`/merge commit.
|
||||
- **`error-spike-detector`** (PostToolUse, any tool) — RULE 0.14 self-audit: tags + logs the pattern when 3+ errors occur within the last 20 tool calls.
|
||||
- **`git-pre-commit-genesis`** (PreToolUse, Bash) — runs `genesis-scan` on staged files to block patent-IP leaks before commit.
|
||||
|
||||
## Adding custom blocks
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
|
||||
[profile]
|
||||
minimal = []
|
||||
core = ["tomd", "genesis-scan"]
|
||||
core = ["tomd"]
|
||||
frontend = ["mock-render", "visual-diff", "tokens-sync", "design-scrape", "live-preview", "figma-tokens", "frontend-inspect", "screenshot-decode"]
|
||||
ops = ["kei-ledger", "ssh-check", "firewall-diff", "provision-hetzner", "provision-vultr", "harden-base", "metrics-scrape", "log-ship"]
|
||||
dev = ["kei-migrate", "kei-changelog", "kei-ci-lint", "kei-docs-scaffold", "kei-memory", "kei-conflict-scan", "kei-refactor-engine", "kei-graph-check", "kei-store"]
|
||||
mcp = ["kei-router", "kei-sage", "kei-task", "kei-chat-store", "kei-crossdomain", "kei-search-core", "kei-content-store", "kei-social-store", "kei-curator", "kei-auth"]
|
||||
full = ["tomd", "genesis-scan", "kei-ledger", "kei-migrate", "kei-changelog", "ssh-check", "firewall-diff", "mock-render", "visual-diff", "tokens-sync", "design-scrape", "live-preview", "figma-tokens", "frontend-inspect", "screenshot-decode", "provision-hetzner", "provision-vultr", "harden-base", "metrics-scrape", "log-ship", "kei-ci-lint", "kei-docs-scaffold", "kei-memory", "kei-conflict-scan", "kei-refactor-engine", "kei-graph-check", "kei-store", "kei-router", "kei-sage", "kei-task", "kei-chat-store", "kei-crossdomain", "kei-search-core", "kei-content-store", "kei-social-store", "kei-curator", "kei-auth"]
|
||||
full = ["tomd", "kei-ledger", "kei-migrate", "kei-changelog", "ssh-check", "firewall-diff", "mock-render", "visual-diff", "tokens-sync", "design-scrape", "live-preview", "figma-tokens", "frontend-inspect", "screenshot-decode", "provision-hetzner", "provision-vultr", "harden-base", "metrics-scrape", "log-ship", "kei-ci-lint", "kei-docs-scaffold", "kei-memory", "kei-conflict-scan", "kei-refactor-engine", "kei-graph-check", "kei-store", "kei-router", "kei-sage", "kei-task", "kei-chat-store", "kei-crossdomain", "kei-search-core", "kei-content-store", "kei-social-store", "kei-curator", "kei-auth"]
|
||||
|
||||
# --- shell primitives (13) -------------------------------------------------
|
||||
|
||||
|
|
@ -160,12 +160,6 @@ crate = "kei-memory"
|
|||
deps = ["rusqlite bundled (no system sqlite required)"]
|
||||
desc = "Session retrospective + recurring pattern detector (offline-first, RULE 0.14)"
|
||||
|
||||
[primitive.genesis-scan]
|
||||
kind = "rust"
|
||||
crate = "genesis-scan"
|
||||
deps = []
|
||||
desc = "Patent-IP leak scanner (Genesis-term blacklist, CI pre-commit gate)"
|
||||
|
||||
[primitive.kei-conflict-scan]
|
||||
kind = "rust"
|
||||
crate = "kei-conflict-scan"
|
||||
|
|
|
|||
13
_primitives/_rust/Cargo.lock
generated
13
_primitives/_rust/Cargo.lock
generated
|
|
@ -584,19 +584,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "genesis-scan"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ members = [
|
|||
"visual-diff",
|
||||
"tokens-sync",
|
||||
"kei-memory",
|
||||
"genesis-scan",
|
||||
"kei-conflict-scan",
|
||||
"kei-refactor-engine",
|
||||
"kei-graph-check",
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "genesis-scan"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "genesis-scan"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
regex = "1"
|
||||
walkdir = "2"
|
||||
anyhow = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
//! genesis-scan — patent-IP leak scanner.
|
||||
//!
|
||||
//! Complements the runtime Claude-Code hook (`~/.claude/hooks/genesis-leak-guard.sh`)
|
||||
//! by providing a binary suitable for `git` pre-commit hooks and CI pipelines.
|
||||
//!
|
||||
//! USAGE
|
||||
//! genesis-scan --path <dir> walk a tree
|
||||
//! genesis-scan --path <file> single file
|
||||
//! genesis-scan --staged scan `git diff --cached`
|
||||
//! genesis-scan --stdin --file <label> scan piped content
|
||||
//! genesis-scan --format=human|json|github-actions
|
||||
//! genesis-scan --exit-on-hit CI mode: exit 2 on any hit
|
||||
//! genesis-scan --list-patterns dump rules and exit
|
||||
//!
|
||||
//! EXIT
|
||||
//! 0 no hits (or --exit-on-hit not set)
|
||||
//! 1 usage / runtime error
|
||||
//! 2 hits found AND --exit-on-hit is set
|
||||
|
||||
mod patterns;
|
||||
mod report;
|
||||
mod scanner;
|
||||
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "genesis-scan", about = "Genesis / patent-IP leak scanner.")]
|
||||
struct Cli {
|
||||
/// Path to a file or directory to scan.
|
||||
#[arg(long, conflicts_with_all = ["staged", "stdin", "list_patterns"])]
|
||||
path: Option<PathBuf>,
|
||||
|
||||
/// Scan staged git blobs (pre-commit hook mode).
|
||||
#[arg(long, conflicts_with_all = ["path", "stdin"])]
|
||||
staged: bool,
|
||||
|
||||
/// Read content from stdin.
|
||||
#[arg(long, conflicts_with_all = ["path", "staged"])]
|
||||
stdin: bool,
|
||||
|
||||
/// Virtual file label for --stdin (used in report).
|
||||
#[arg(long, default_value = "<stdin>")]
|
||||
file: String,
|
||||
|
||||
/// Output format.
|
||||
#[arg(long, value_enum, default_value_t = report::Format::Human)]
|
||||
format: report::Format,
|
||||
|
||||
/// Exit with code 2 if any hit is found (CI-friendly).
|
||||
#[arg(long)]
|
||||
exit_on_hit: bool,
|
||||
|
||||
/// Dump forbidden patterns + exempt paths and exit.
|
||||
#[arg(long)]
|
||||
list_patterns: bool,
|
||||
}
|
||||
|
||||
fn collect_hits(
|
||||
cli: &Cli,
|
||||
combined: ®ex::Regex,
|
||||
individual: &[(String, regex::Regex)],
|
||||
) -> anyhow::Result<Vec<scanner::Hit>> {
|
||||
if cli.staged {
|
||||
scanner::scan_staged(combined, individual)
|
||||
} else if cli.stdin {
|
||||
scanner::scan_stdin(&cli.file, combined, individual)
|
||||
} else if let Some(p) = cli.path.as_ref() {
|
||||
scanner::scan_path(p, combined, individual)
|
||||
} else {
|
||||
anyhow::bail!("need one of --path / --staged / --stdin / --list-patterns")
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> anyhow::Result<ExitCode> {
|
||||
let cli = Cli::parse();
|
||||
if cli.list_patterns {
|
||||
report::dump_patterns();
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
let (combined, individual) = patterns::compile()?;
|
||||
let hits = collect_hits(&cli, &combined, &individual)?;
|
||||
report::emit(&hits, cli.format);
|
||||
Ok(if cli.exit_on_hit && !hits.is_empty() {
|
||||
ExitCode::from(2)
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("genesis-scan: {e}");
|
||||
ExitCode::from(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
//! Forbidden Genesis-IP term patterns + exempt-scope path globs.
|
||||
//!
|
||||
//! Source of truth: `~/.claude/hooks/genesis-leak-guard.sh`.
|
||||
//! Keep the two in sync — if a term lands in one, add it to the other.
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
/// Forbidden-term regex strings. Matches the master list in
|
||||
/// `~/.claude/hooks/genesis-leak-guard.sh` (keep in sync).
|
||||
pub const FORBIDDEN: &[&str] = &[
|
||||
r"Genesis",
|
||||
r"Born[- ]?rule",
|
||||
r"Born[- ]?GAP",
|
||||
r"Weingarten",
|
||||
r"normalize\(S[[:space:]]*\+",
|
||||
r"x.?S[\u{1D40}T].?x",
|
||||
r"rank-1 attractor",
|
||||
r"Frobenius sphere",
|
||||
r"submartingale",
|
||||
r"paradigm-native",
|
||||
r"Paradigm-native",
|
||||
r"Haar.*measure",
|
||||
r"Haar.*blindness",
|
||||
r"\u{03C3}\u{2081}",
|
||||
r"\bT[1-5][0-9]\b",
|
||||
r"\bR2[0-9]\b",
|
||||
r"EST-[0-9]",
|
||||
r"Path [A-Z].*Genesis",
|
||||
r"Genesis.*Path [A-Z]",
|
||||
r"G2.*fixed[- ]?point",
|
||||
r"G2.*attractor",
|
||||
r"Bargmann",
|
||||
r"pentagon.*2\u{03C0}/5",
|
||||
r"\u{03B8}_2[0-9]",
|
||||
r"Fibonacci.*events",
|
||||
r"Kuramoto.*Genesis",
|
||||
];
|
||||
|
||||
/// Path substrings that mark a file as exempt. If any substring matches
|
||||
/// anywhere in the absolute or relative path, the file is skipped.
|
||||
///
|
||||
/// Notes:
|
||||
/// - `genesis-scan` itself is exempted — its own source stores these terms.
|
||||
/// - `.claude/memory/concepts/` is exempted — private user concepts.
|
||||
pub const EXEMPT_SUBSTRINGS: &[&str] = &[
|
||||
"/KeiLab/",
|
||||
"/KeiSeiBundle/",
|
||||
"/theory/",
|
||||
"/keinet/keinet-cfc-eigen/",
|
||||
"/keinet/keinet-gpu-wgpu/",
|
||||
"/memory/chatlogs/ml-keilab/",
|
||||
"/.claude/memory/concepts/",
|
||||
"/genesis-scan/",
|
||||
];
|
||||
|
||||
/// Directory names to skip entirely while walking.
|
||||
pub const SKIP_DIRS: &[&str] = &[
|
||||
"target",
|
||||
"node_modules",
|
||||
".git",
|
||||
"dist",
|
||||
"build",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
];
|
||||
|
||||
/// Compile all forbidden patterns, joined into a single regex for speed.
|
||||
/// Returns (combined, individual) so callers can both fast-match and
|
||||
/// extract which specific pattern fired.
|
||||
pub fn compile() -> anyhow::Result<(Regex, Vec<(String, Regex)>)> {
|
||||
let individual: Vec<(String, Regex)> = FORBIDDEN
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let rx = Regex::new(p).map_err(|e| anyhow::anyhow!("bad pattern {p}: {e}"))?;
|
||||
Ok((p.to_string(), rx))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
let combined_src = format!("(?:{})", FORBIDDEN.join(")|(?:"));
|
||||
let combined = Regex::new(&combined_src)?;
|
||||
Ok((combined, individual))
|
||||
}
|
||||
|
||||
/// True iff the given path lies in an exempt scope.
|
||||
pub fn is_exempt(path: &str) -> bool {
|
||||
EXEMPT_SUBSTRINGS.iter().any(|s| path.contains(s))
|
||||
}
|
||||
|
||||
/// True iff this directory name should be skipped during walk.
|
||||
pub fn should_skip_dir(name: &str) -> bool {
|
||||
SKIP_DIRS.iter().any(|s| *s == name)
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
//! Output formatters for scanner hits.
|
||||
|
||||
use crate::scanner::Hit;
|
||||
|
||||
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
|
||||
pub enum Format {
|
||||
Human,
|
||||
Json,
|
||||
GithubActions,
|
||||
}
|
||||
|
||||
pub fn emit(hits: &[Hit], format: Format) {
|
||||
match format {
|
||||
Format::Human => emit_human(hits),
|
||||
Format::Json => emit_json(hits),
|
||||
Format::GithubActions => emit_github(hits),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_human(hits: &[Hit]) {
|
||||
if hits.is_empty() {
|
||||
println!("genesis-scan: clean (0 hits)");
|
||||
return;
|
||||
}
|
||||
println!("genesis-scan: {} hit(s)", hits.len());
|
||||
for h in hits {
|
||||
println!(" {}:{}: {} [pattern: {}]", h.file, h.line, h.r#match, h.pattern);
|
||||
}
|
||||
eprintln!();
|
||||
eprintln!("Genesis / patent-IP terms detected. Review and remove before commit.");
|
||||
eprintln!("If false positive, move the file into an exempt scope or use --list-patterns");
|
||||
eprintln!("to inspect rules.");
|
||||
}
|
||||
|
||||
fn emit_json(hits: &[Hit]) {
|
||||
let payload = serde_json::json!({
|
||||
"hit_count": hits.len(),
|
||||
"hits": hits,
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".into()));
|
||||
}
|
||||
|
||||
fn emit_github(hits: &[Hit]) {
|
||||
for h in hits {
|
||||
println!(
|
||||
"::error file={},line={}::genesis-scan: '{}' matches forbidden pattern '{}'",
|
||||
h.file, h.line, h.r#match, h.pattern
|
||||
);
|
||||
}
|
||||
if hits.is_empty() {
|
||||
println!("genesis-scan: 0 hits");
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_section(title: &str, items: &[&str]) {
|
||||
println!("# {}", title);
|
||||
for s in items {
|
||||
println!("{}", s);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn dump_patterns() {
|
||||
dump_section("genesis-scan patterns (forbidden)", crate::patterns::FORBIDDEN);
|
||||
dump_section("genesis-scan exempt path substrings", crate::patterns::EXEMPT_SUBSTRINGS);
|
||||
dump_section("genesis-scan skipped directory names", crate::patterns::SKIP_DIRS);
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
//! File-system walker + per-file pattern matcher.
|
||||
|
||||
use crate::patterns;
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Hit {
|
||||
pub file: String,
|
||||
pub line: usize,
|
||||
pub r#match: String,
|
||||
pub pattern: String,
|
||||
}
|
||||
|
||||
/// Scan a single file's in-memory content (bytes may be invalid UTF-8 — we
|
||||
/// lossy-convert). Returns all hits. Does NOT consult exempt rules — caller
|
||||
/// decides whether to call this.
|
||||
pub fn scan_content(
|
||||
file_label: &str,
|
||||
content: &str,
|
||||
combined: &Regex,
|
||||
individual: &[(String, Regex)],
|
||||
) -> Vec<Hit> {
|
||||
if !combined.is_match(content) {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for (lineno, line) in content.lines().enumerate() {
|
||||
for (pat_src, rx) in individual {
|
||||
if let Some(m) = rx.find(line) {
|
||||
out.push(Hit {
|
||||
file: file_label.to_string(),
|
||||
line: lineno + 1,
|
||||
r#match: m.as_str().to_string(),
|
||||
pattern: pat_src.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Scan one file on disk. Returns hits or [] if exempt / unreadable / binary.
|
||||
pub fn scan_file(path: &Path, combined: &Regex, individual: &[(String, Regex)]) -> Vec<Hit> {
|
||||
let label = path.to_string_lossy();
|
||||
if patterns::is_exempt(&label) {
|
||||
return Vec::new();
|
||||
}
|
||||
let Ok(bytes) = fs::read(path) else {
|
||||
return Vec::new();
|
||||
};
|
||||
// Skip obvious binary files (heuristic: NUL in first 8 KiB).
|
||||
if bytes.iter().take(8192).any(|b| *b == 0) {
|
||||
return Vec::new();
|
||||
}
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
scan_content(&label, &text, combined, individual)
|
||||
}
|
||||
|
||||
/// Recursively scan a directory. Skips SKIP_DIRS by name and exempt paths
|
||||
/// by substring.
|
||||
pub fn scan_dir(root: &Path, combined: &Regex, individual: &[(String, Regex)]) -> Result<Vec<Hit>> {
|
||||
let mut hits = Vec::new();
|
||||
let walker = WalkDir::new(root).into_iter().filter_entry(|e| {
|
||||
let name = e.file_name().to_string_lossy();
|
||||
if e.file_type().is_dir() && patterns::should_skip_dir(&name) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
for entry in walker {
|
||||
let entry = match entry {
|
||||
Ok(e) => e,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if !entry.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
hits.extend(scan_file(entry.path(), combined, individual));
|
||||
}
|
||||
Ok(hits)
|
||||
}
|
||||
|
||||
/// Scan a path (dispatches to file or dir).
|
||||
pub fn scan_path(root: &Path, combined: &Regex, individual: &[(String, Regex)]) -> Result<Vec<Hit>> {
|
||||
if root.is_file() {
|
||||
Ok(scan_file(root, combined, individual))
|
||||
} else if root.is_dir() {
|
||||
scan_dir(root, combined, individual)
|
||||
} else {
|
||||
anyhow::bail!("path not found: {}", root.display())
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan content read from stdin under a virtual file label.
|
||||
pub fn scan_stdin(label: &str, combined: &Regex, individual: &[(String, Regex)]) -> Result<Vec<Hit>> {
|
||||
use std::io::Read;
|
||||
let mut buf = String::new();
|
||||
std::io::stdin().read_to_string(&mut buf)?;
|
||||
if patterns::is_exempt(label) {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Ok(scan_content(label, &buf, combined, individual))
|
||||
}
|
||||
|
||||
/// Read one staged file from the git index. Returns None on error or when
|
||||
/// the blob cannot be found (submodule, gitlink, etc.).
|
||||
fn read_staged_blob(path: &str) -> Option<Vec<u8>> {
|
||||
let blob = std::process::Command::new("git")
|
||||
.args(["show", &format!(":{}", path)])
|
||||
.output()
|
||||
.ok()?;
|
||||
if blob.status.success() {
|
||||
Some(blob.stdout)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// List staged path names via `git diff --cached --name-only -z`.
|
||||
fn list_staged_paths() -> Result<Vec<String>> {
|
||||
let out = std::process::Command::new("git")
|
||||
.args(["diff", "--cached", "--name-only", "-z"])
|
||||
.output()?;
|
||||
if !out.status.success() {
|
||||
anyhow::bail!("git diff --cached failed (not in a git repo?)");
|
||||
}
|
||||
let paths = out
|
||||
.stdout
|
||||
.split(|b| *b == 0)
|
||||
.filter(|s| !s.is_empty())
|
||||
.filter_map(|s| std::str::from_utf8(s).ok().map(str::to_string))
|
||||
.collect();
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
/// Scan staged git blobs. Honors GENESIS_SCAN_TEST_STAGED for test fixtures.
|
||||
pub fn scan_staged(combined: &Regex, individual: &[(String, Regex)]) -> Result<Vec<Hit>> {
|
||||
if let Ok(fixture) = std::env::var("GENESIS_SCAN_TEST_STAGED") {
|
||||
return Ok(scan_staged_fixture(&fixture, combined, individual));
|
||||
}
|
||||
let mut hits = Vec::new();
|
||||
for path in list_staged_paths()? {
|
||||
if patterns::is_exempt(&path) {
|
||||
continue;
|
||||
}
|
||||
let Some(bytes) = read_staged_blob(&path) else {
|
||||
continue;
|
||||
};
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
hits.extend(scan_content(&path, &text, combined, individual));
|
||||
}
|
||||
Ok(hits)
|
||||
}
|
||||
|
||||
/// Test-only: parse a fixture string where records are separated by
|
||||
/// ASCII Unit Separator (\x1f) and fields within a record by ASCII
|
||||
/// Record Separator (\x1e): `path\x1econtent\x1fpath\x1econtent...`.
|
||||
/// NUL cannot be used — OS environment values forbid it.
|
||||
fn scan_staged_fixture(fixture: &str, combined: &Regex, individual: &[(String, Regex)]) -> Vec<Hit> {
|
||||
let mut hits = Vec::new();
|
||||
for record in fixture.split('\x1f') {
|
||||
if record.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some((path, body)) = record.split_once('\x1e') else {
|
||||
continue;
|
||||
};
|
||||
if patterns::is_exempt(path) {
|
||||
continue;
|
||||
}
|
||||
hits.extend(scan_content(path, body, combined, individual));
|
||||
}
|
||||
hits
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
//! Integration tests for genesis-scan.
|
||||
//!
|
||||
//! We use tempfile-based fixtures rather than on-disk test assets because
|
||||
//! the entire `genesis-scan/` tree is self-exempt (so any on-disk fixture
|
||||
//! under `tests/` would be skipped by the scanner — correct behaviour, but
|
||||
//! makes testing inconvenient).
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn bin() -> PathBuf {
|
||||
// Cargo sets CARGO_BIN_EXE_<bin_name> for every bin target in the crate
|
||||
// under test. The value is the path to the built binary.
|
||||
PathBuf::from(env!("CARGO_BIN_EXE_genesis-scan"))
|
||||
}
|
||||
|
||||
fn clean_md() -> &'static str {
|
||||
"# Doc\n\nThis is a plain markdown file about cats and dogs.\n"
|
||||
}
|
||||
|
||||
fn leaky_md() -> String {
|
||||
// Terms assembled at runtime so this test source itself remains less
|
||||
// likely to be flagged if a caller ever scans outside the self-exempt.
|
||||
let g = "Gene".to_string() + "sis";
|
||||
format!("# Leaky\n\nThis doc mentions {} theory on line 3.\n", g)
|
||||
}
|
||||
|
||||
fn leaky_multi_md() -> String {
|
||||
let g = "Gene".to_string() + "sis";
|
||||
let w = "Wein".to_string() + "garten";
|
||||
format!(
|
||||
"line one\n{} theory\nnothing\n{} blindness\nrank-1 attractor here\n",
|
||||
g, w
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_file_produces_zero_hits_in_json() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let path = tmp.path().join("clean.md");
|
||||
fs::write(&path, clean_md()).unwrap();
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--format", "json", "--path"])
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success());
|
||||
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
assert_eq!(v["hit_count"], 0);
|
||||
assert_eq!(v["hits"].as_array().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_leaky_file_reports_one_hit() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let path = tmp.path().join("leaky.md");
|
||||
fs::write(&path, leaky_md()).unwrap();
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--format", "json", "--path"])
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
assert_eq!(v["hit_count"], 1);
|
||||
assert_eq!(v["hits"][0]["line"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaky_multi_file_reports_three_hits() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let path = tmp.path().join("leaky_multi.md");
|
||||
fs::write(&path, leaky_multi_md()).unwrap();
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--format", "json", "--path"])
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
// Three distinct matches on three distinct lines.
|
||||
assert_eq!(v["hit_count"], 3, "payload was: {v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exempt_path_returns_zero_hits() {
|
||||
// Place the leaky file under a path segment that matches EXEMPT_SUBSTRINGS
|
||||
// — specifically "/theory/" — and verify it is skipped.
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let exempt_dir = tmp.path().join("some").join("theory").join("inner");
|
||||
fs::create_dir_all(&exempt_dir).unwrap();
|
||||
let path = exempt_dir.join("leaky.md");
|
||||
fs::write(&path, leaky_md()).unwrap();
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--format", "json", "--path"])
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
assert_eq!(v["hit_count"], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exit_on_hit_returns_code_two() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let path = tmp.path().join("leaky.md");
|
||||
fs::write(&path, leaky_md()).unwrap();
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--exit-on-hit", "--format", "json", "--path"])
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(out.status.code(), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_mode_reads_fixture_env() {
|
||||
// Simulated staged payload. Field separator = \x1e (Record Separator),
|
||||
// record separator = \x1f (Unit Separator). NUL is rejected by the OS
|
||||
// for env-var values, so we cannot use it here.
|
||||
let leaky = leaky_md();
|
||||
let fixture = format!(
|
||||
"staged/leaky.md\x1e{}\x1fstaged/clean.md\x1e{}",
|
||||
leaky,
|
||||
clean_md()
|
||||
);
|
||||
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--staged", "--format", "json"])
|
||||
.env("GENESIS_SCAN_TEST_STAGED", &fixture)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success());
|
||||
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
assert_eq!(v["hit_count"], 1);
|
||||
assert_eq!(v["hits"][0]["file"], "staged/leaky.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_patterns_prints_rules() {
|
||||
let out = std::process::Command::new(bin())
|
||||
.args(["--list-patterns"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success());
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(text.contains("forbidden"));
|
||||
assert!(text.contains("exempt"));
|
||||
// Spot-check one forbidden string is dumped.
|
||||
assert!(text.contains("Weingarten") || text.contains("Bargmann"));
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
# kei-sleep-queue.sh — v0.12.0 "sleep on it" queue CRUD helper.
|
||||
# Commands: add / list / show / done / fail / purge.
|
||||
# Env: KEI_MEMORY_REPO_PATH (sourced from ~/.claude/secrets/.env).
|
||||
# Bypass: KEI_SLEEP_GENESIS_BYPASS=1 skips genesis-scan gate on `add`.
|
||||
set -u
|
||||
|
||||
SECRETS_FILE="${HOME}/.claude/secrets/.env"
|
||||
|
|
@ -13,7 +12,6 @@ REPO_PATH="${KEI_MEMORY_REPO_PATH:-}"
|
|||
QUEUE_DIR="${REPO_PATH}/sleep-queue"
|
||||
DONE_DIR="${REPO_PATH}/sleep-queue-done"
|
||||
FAIL_DIR="${REPO_PATH}/sleep-queue-failed"
|
||||
GENESIS_BIN="${HOME}/.claude/agents/_primitives/_rust/target/release/genesis-scan"
|
||||
SYNC_SH="${HOME}/.claude/agents/_primitives/kei-sleep-sync.sh"
|
||||
|
||||
err() { printf 'kei-sleep-queue: %s\n' "$*" >&2; }
|
||||
|
|
@ -33,15 +31,6 @@ gen_uuid() {
|
|||
iso_utc() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
||||
push_async() { [ -x "$SYNC_SH" ] && "$SYNC_SH" >/dev/null 2>&1 || true; }
|
||||
|
||||
scan_prompt() {
|
||||
[ "${KEI_SLEEP_GENESIS_BYPASS:-0}" = "1" ] && return 0
|
||||
[ -x "$GENESIS_BIN" ] || return 0
|
||||
"$GENESIS_BIN" --stdin --exit-on-hit --format text < "$1" >&2 && return 0
|
||||
err "genesis-scan flagged the prompt — see scanner output above"
|
||||
err "bypass (false positives only): KEI_SLEEP_GENESIS_BYPASS=1 $0 add ..."
|
||||
exit 2
|
||||
}
|
||||
|
||||
# Find a queue file by uuid prefix in dir; echoes path or returns 1.
|
||||
find_by_uuid() {
|
||||
local uuid="$1" dir="$2" f
|
||||
|
|
@ -116,7 +105,6 @@ resolve_priority_fields() {
|
|||
cmd_add() {
|
||||
parse_add_flags "$@"
|
||||
ensure_repo
|
||||
scan_prompt "$ADD_PROMPT"
|
||||
local uuid ts file
|
||||
uuid="$(gen_uuid)"
|
||||
ts="$(iso_utc)"
|
||||
|
|
@ -246,7 +234,6 @@ kei-sleep-queue.sh — v0.12 sleep-on-it queue helper
|
|||
purge --older-than <N>d
|
||||
|
||||
Env: KEI_MEMORY_REPO_PATH (required)
|
||||
KEI_SLEEP_GENESIS_BYPASS=1 skip genesis-scan gate on add
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,9 +168,9 @@ Every task ends in exactly one of:
|
|||
- `checkpoint_saved` — intermediate state; the task is still pending
|
||||
but the latest `.partial.md` is committed and pushed. This is NOT a
|
||||
terminal status; it upgrades to `done` or `time_budget_exhausted`.
|
||||
- `failed` — tool error, missing dependency, genesis-scan hit, or
|
||||
other non-recoverable failure. Queue file moved to
|
||||
`sleep-queue-failed/` with a `## Failure reason` block.
|
||||
- `failed` — tool error, missing dependency, or other non-recoverable
|
||||
failure. Queue file moved to `sleep-queue-failed/` with a
|
||||
`## Failure reason` block.
|
||||
|
||||
## Invariants (MUST NOT violate)
|
||||
|
||||
|
|
@ -184,12 +184,8 @@ Every task ends in exactly one of:
|
|||
sync-repo is the only mutation Phase A performs; `rm` is banned
|
||||
outside `sleep-queue*/` (and even there, only after a successful
|
||||
move to done/failed).
|
||||
- **Never paraphrase patent-sensitive terms into the result body.** The
|
||||
user-side `genesis-scan` gate is line of defense 1; the agent is line
|
||||
of defense 2. If the agent's best-effort `genesis-scan` run on the
|
||||
prompt returns non-zero (the scanner's forbidden-pattern list is
|
||||
embedded in the binary itself — use
|
||||
`genesis-scan list-patterns` if you need to inspect it), mark the
|
||||
- **Never paraphrase patent-sensitive terms into the result body.**
|
||||
If the prompt clearly references patent-sensitive material, mark the
|
||||
task failed with reason `patent-term-detected` and skip it entirely
|
||||
— no partial result, no paraphrasing the matched token.
|
||||
- **No shell command writes outside `sync-repo/`.** Results land in the
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ export const RUST_PRIMITIVE_TOOLS: ReadonlyArray<{ binary: string; desc: string
|
|||
{ binary: "kei-conflict-scan", desc: "Scan a tree for merge/rebase conflict markers." },
|
||||
{ binary: "kei-migrate", desc: "Run schema or directory migrations." },
|
||||
{ binary: "kei-changelog", desc: "Generate changelog from commit/tag history." },
|
||||
{ binary: "genesis-scan", desc: "Scan a tree for Genesis/patent-sensitive patterns." },
|
||||
{ binary: "ssh-check", desc: "Validate SSH config + known_hosts consistency." },
|
||||
{ binary: "firewall-diff", desc: "Diff two firewall rule dumps." },
|
||||
{ binary: "tokens-sync", desc: "Sync design tokens from Figma export to code." },
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ describe("server handshake + tool listing", () => {
|
|||
const names = new Set(tools.map((t) => t.name));
|
||||
expect(names.has("kei")).toBe(true);
|
||||
expect(names.has("kei-ledger")).toBe(true);
|
||||
expect(names.has("genesis-scan")).toBe(true);
|
||||
expect(tools.length).toBeGreaterThanOrEqual(15);
|
||||
expect(names.has("kei-memory")).toBe(true);
|
||||
expect(tools.length).toBeGreaterThanOrEqual(14);
|
||||
});
|
||||
|
||||
it("every listed tool has a non-empty description", () => {
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Git pre-commit — block Genesis-IP term leaks before they reach the index.
|
||||
#
|
||||
# INSTALL (public-facing repos only):
|
||||
# ln -sf $HOME/.claude/agents/hooks/git-pre-commit-genesis.sh .git/hooks/pre-commit
|
||||
#
|
||||
# BEHAVIOUR:
|
||||
# - Resolves the `genesis-scan` binary under the KeiSeiKit install tree.
|
||||
# - Runs `--staged --exit-on-hit` so the hook fails the commit on any hit.
|
||||
# - Bypass with GENESIS_GUARD_BYPASS=1 (visible, per-call — logged in transcript).
|
||||
#
|
||||
# EXIT:
|
||||
# 0 clean or bypassed
|
||||
# 1 usage / binary missing
|
||||
# 2 leak detected (commit blocked)
|
||||
|
||||
set -eu
|
||||
|
||||
SCANNER="${GENESIS_SCAN_BIN:-$HOME/.claude/agents/_primitives/_rust/target/release/genesis-scan}"
|
||||
|
||||
if [ ! -x "$SCANNER" ]; then
|
||||
# Absent scanner = silent no-op. Installs without the `core` or `full`
|
||||
# profile won't have the binary; we prefer letting the commit through
|
||||
# over noisy spam, since the runtime hook still catches new writes.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if "$SCANNER" --staged --format=human --exit-on-hit; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Non-zero exit from scanner = hits found.
|
||||
if [ "${GENESIS_GUARD_BYPASS:-0}" = "1" ]; then
|
||||
echo "" >&2
|
||||
echo "[genesis-scan] BYPASSED (GENESIS_GUARD_BYPASS=1). Hit logged above." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >&2 <<EOF
|
||||
|
||||
Commit blocked by genesis-scan.
|
||||
|
||||
Review the hits above and either:
|
||||
- remove the Genesis / patent-IP terms from the staged content, or
|
||||
- move the file into an exempt scope (see: $SCANNER --list-patterns), or
|
||||
- bypass explicitly (use sparingly):
|
||||
GENESIS_GUARD_BYPASS=1 git commit ...
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
16
install.sh
16
install.sh
|
|
@ -54,12 +54,12 @@ Usage: ./install.sh [flags]
|
|||
|
||||
--profile=<name> set installed-primitive set to one of:
|
||||
minimal (no primitives)
|
||||
core (tomd, genesis-scan)
|
||||
core (tomd)
|
||||
frontend (8 site tools: mock-render / visual-diff / ...)
|
||||
ops (8 infra tools: kei-ledger / ssh-check / ...)
|
||||
dev (9 dev tools: kei-migrate / kei-memory / deep-sleep quartet / ...)
|
||||
mcp (10 LBM-port tools: kei-router / kei-sage / kei-auth / ...)
|
||||
full (all 37 primitives — MANIFEST source of truth)
|
||||
full (all 36 primitives — MANIFEST source of truth)
|
||||
|
||||
--add=<a>[,<b>,...] add one or more primitives on top of current install.
|
||||
Name must match [primitive.<name>] in _primitives/MANIFEST.toml.
|
||||
|
|
@ -340,12 +340,12 @@ menu_whiptail_profile() {
|
|||
"$tool" --title "KeiSeiKit Installer" --radiolist \
|
||||
"Choose install profile (SPACE to select, ENTER to confirm):" 22 78 8 \
|
||||
"minimal" "agents + hooks + skills + bridges (~5s)" ON \
|
||||
"core" "+ tomd + genesis-scan (~5s)" OFF \
|
||||
"core" "+ tomd (~5s)" OFF \
|
||||
"frontend" "+ 8 site tools (~60s, 80 MB)" OFF \
|
||||
"ops" "+ 8 infra tools (~90s, 50 MB)" OFF \
|
||||
"dev" "+ 9 dev tools (~60s, 40 MB)" OFF \
|
||||
"mcp" "+ 10 LBM-port MCP tools (~90s, 50 MB)" OFF \
|
||||
"full" "all 37 primitives (~5 min, 200 MB)" OFF \
|
||||
"full" "all 36 primitives (~5 min, 200 MB)" OFF \
|
||||
"custom" "pick individual primitives" OFF \
|
||||
3>&1 1>&2 2>&3
|
||||
}
|
||||
|
|
@ -378,12 +378,12 @@ menu_plain_profile() {
|
|||
echo "Choose install profile:" >&2
|
||||
echo >&2
|
||||
echo " 1) minimal — agents + hooks + skills + bridges only (~5s)" >&2
|
||||
echo " 2) core — + tomd + genesis-scan (~5s)" >&2
|
||||
echo " 2) core — + tomd (~5s)" >&2
|
||||
echo " 3) frontend — + 8 site tools (~60s, 80 MB)" >&2
|
||||
echo " 4) ops — + 8 infra tools (~90s, 50 MB)" >&2
|
||||
echo " 5) dev — + 9 dev tools (~60s, 40 MB)" >&2
|
||||
echo " 6) mcp — + 10 LBM-port MCP tools (~90s, 50 MB)" >&2
|
||||
echo " 7) full — all 37 primitives (~5 min, 200 MB)" >&2
|
||||
echo " 7) full — all 36 primitives (~5 min, 200 MB)" >&2
|
||||
echo " 8) custom — pick individual primitives" >&2
|
||||
echo >&2
|
||||
local reply
|
||||
|
|
@ -1214,9 +1214,9 @@ else
|
|||
NEXT STEP: merge settings-snippet.json into ~/.claude/settings.json
|
||||
==========================================================================
|
||||
|
||||
KeiSeiKit ships 10 hooks (assemble-agents, assemble-validate, no-hand-edit,
|
||||
KeiSeiKit ships 9 hooks (assemble-agents, assemble-validate, no-hand-edit,
|
||||
tomd-preread, agent-fork-logger, site-wysiwyd-check, session-end-dump,
|
||||
milestone-commit-hook, error-spike-detector, git-pre-commit-genesis).
|
||||
milestone-commit-hook, error-spike-detector).
|
||||
To activate them, merge entries from:
|
||||
$KIT_DIR/settings-snippet.json
|
||||
into your:
|
||||
|
|
|
|||
|
|
@ -87,13 +87,8 @@ Results land: sync-repo/sleep-results/<UUID>.md
|
|||
- **Idempotent.** Re-running the wizard while a previous task is still
|
||||
pending is fine — each submission gets its own UUID and its own queue
|
||||
file. No "one pending at a time" constraint.
|
||||
- **Genesis-scan on submit (RULE 0.1 second line of defense).** The
|
||||
helper `kei-sleep-queue.sh add` pipes the task text through
|
||||
`genesis-scan --stdin --exit-on-hit` when the binary exists. On hit
|
||||
the submission is rejected with the scanner's stderr surfaced to chat.
|
||||
Bypass only via `KEI_SLEEP_GENESIS_BYPASS=1` with a visible note.
|
||||
- **NO DOWNGRADE (RULE -1).** If the helper rejects (genesis hit, invalid
|
||||
flag, sync push fails), surface 2-3 constructive fix paths — never
|
||||
- **NO DOWNGRADE (RULE -1).** If the helper rejects (invalid flag, sync
|
||||
push fails), surface 2-3 constructive fix paths — never
|
||||
"cannot submit".
|
||||
- **NO HALLUCINATION (RULE 0.4).** Never fabricate a UUID, queue path,
|
||||
or ETA — always echo the real helper output.
|
||||
|
|
|
|||
|
|
@ -102,13 +102,6 @@ Capture `UUID = first line`, `QUEUE_PATH = second line`.
|
|||
|
||||
On non-zero exit, surface stderr verbatim. Common causes:
|
||||
|
||||
- **genesis-scan hit** → task text contains a patent-sensitive term.
|
||||
Constructive paths (RULE -1):
|
||||
1. Rewrite the task generically ("pick between <redacted>"
|
||||
becomes "pick between technology A and B").
|
||||
2. Set `KEI_SLEEP_GENESIS_BYPASS=1` and re-run if the term is a
|
||||
false positive (cite the specific word).
|
||||
3. Abort; keep the question local.
|
||||
- **write failed** (disk / permissions) → print the error; exit.
|
||||
- **sync push failed after local write succeeded** → not an error; the
|
||||
queue file IS committed locally and will push on next session end.
|
||||
|
|
|
|||
Loading…
Reference in a new issue