diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..7d656b6 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "hyperlex", + "displayName": "Hyperlex", + "version": "0.4.2", + "description": "Memetic emergence analysis: slang detection, lineage matching, virality and hyperstition scoring, receipt-backed forecasts, and settled Brier calibration. Never invents Brier scores; settlement stays a human step.", + "author": { + "name": "Applied Alchemy Labs / Hermes" + }, + "homepage": "https://github.com/Zero-State-LLC/Hyperlex", + "repository": "https://github.com/Zero-State-LLC/Hyperlex", + "license": "LicenseRef-Zero-State-Proprietary-1.0", + "keywords": [ + "memetics", + "slang", + "hyperstition", + "virality", + "lineage", + "calibration", + "brier", + "mutation" + ] +} diff --git a/.claude/skills/hyperlex-analyze/SKILL.md b/.claude/skills/hyperlex-analyze/SKILL.md new file mode 100644 index 0000000..b1f4048 --- /dev/null +++ b/.claude/skills/hyperlex-analyze/SKILL.md @@ -0,0 +1,29 @@ +--- +name: hyperlex-analyze +description: Analyze pasted text or a JSON ingest file with the Hyperlex CLI. Prefer offline route. +when_to_use: User pastes slang/text to analyze, or points at a file for Hyperlex analyze. +allowed-tools: Bash Read +arguments: [input] +--- + +# /hyperlex-analyze + +Analyze text or a file. Prefer `pipeline` / `run` when the user wants a receipt + forecasts. Use `analyze` for analysis-only. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +# pasted text +$HLX analyze "$ARGUMENTS" --route offline +# full packet (receipt + forecasts) +$HLX pipeline "$ARGUMENTS" --route offline +# ingest JSON file +$HLX analyze --input "$ARGUMENTS" --route offline +``` + +## Expected + +JSON result. `provenance.brier` is `null`. Lineage / virality / mutation_trace only when the CLI attaches them. + +## Fail-closed + +Do not invent labels or Brier. If the file is missing, say so. Do not settle. diff --git a/.claude/skills/hyperlex-demo/SKILL.md b/.claude/skills/hyperlex-demo/SKILL.md new file mode 100644 index 0000000..0c44440 --- /dev/null +++ b/.claude/skills/hyperlex-demo/SKILL.md @@ -0,0 +1,29 @@ +--- +name: hyperlex-demo +description: Run the Hyperlex offline demo and summarize receipt labels. No API keys. Never invent Brier. +when_to_use: User asks for a Hyperlex demo, first success path, or a sample receipt. +allowed-tools: Bash Read +--- + +# /hyperlex-demo + +Run the offline demo. Summarize labels from the CLI output. Do not invent scores. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX demo +# repo checkout: python3 scripts/hyperlex.py demo +# wrapper: bash scripts/claude_hlx.sh demo +``` + +## Expected + +- Exit `0`, `ok: true` +- `brier` and `provenance_brier` are `null` +- Receipt path under the demo out dir or `~/.hyperlex/receipts/` +- Known atoms often match a lineage family (`rizz` → `brainrot-aura`) +- Repeat OBSERVED / INFERRED / SPECULATIVE — do not upgrade them + +## Fail-closed + +If the CLI fails, report the error. Do not fabricate a receipt or Brier. Do not settle. diff --git a/.claude/skills/hyperlex-pending/SKILL.md b/.claude/skills/hyperlex-pending/SKILL.md new file mode 100644 index 0000000..b939415 --- /dev/null +++ b/.claude/skills/hyperlex-pending/SKILL.md @@ -0,0 +1,24 @@ +--- +name: hyperlex-pending +description: List open Hyperlex forecasts from the score log. Start of the calibration loop. +when_to_use: User asks what forecasts are open, pending, or ready to settle. +allowed-tools: Bash Read +--- + +# /hyperlex-pending + +List unsettled forecasts. This is the start of the calibration loop — not settlement. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX pending +$HLX pending --limit 20 +``` + +## Expected + +JSON list of open forecast ids (default log: `~/.hyperlex/score_log.jsonl`). Empty list is valid. + +## Fail-closed + +Do not invent forecast ids. Do not compute Brier here. Next step is human: `/hyperlex-settle` after they choose TRUE|FALSE|VOID|CONFLICT. diff --git a/.claude/skills/hyperlex-scan/SKILL.md b/.claude/skills/hyperlex-scan/SKILL.md new file mode 100644 index 0000000..44d9dc7 --- /dev/null +++ b/.claude/skills/hyperlex-scan/SKILL.md @@ -0,0 +1,28 @@ +--- +name: hyperlex-scan +description: LIVE_EMERGENCE_SCAN helper. Pass a query (or comma-separated queries). Offline-first. +when_to_use: User wants a Hyperlex scan, live-emergence scan, or multi-query monitor run. +allowed-tools: Bash Read +arguments: [query] +--- + +# /hyperlex-scan + +Multi-query LIVE_EMERGENCE_SCAN. Prefer `--route offline` unless the user allows network. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX scan --query "$ARGUMENTS" --route offline --receipt --forecasts --append-log +# several terms: +$HLX scan --queries "rizz,locked in" --route offline --receipt --forecasts --append-log +# bundled config (repo / installed skill): +$HLX scan --config "${HYPERLEX_SKILL_DIR:-.}/examples/cron/scan-queries.json" --route offline --receipt --forecasts +``` + +## Expected + +JSON scan summary. Per-query receipts when `--receipt` is set. Forecasts have `brier: null` until settle. + +## Fail-closed + +Do not register cron. `risk-schedule` is advisory only. Do not invent Brier. Do not auto-settle. diff --git a/.claude/skills/hyperlex-settle/SKILL.md b/.claude/skills/hyperlex-settle/SKILL.md new file mode 100644 index 0000000..974f8f7 --- /dev/null +++ b/.claude/skills/hyperlex-settle/SKILL.md @@ -0,0 +1,29 @@ +--- +name: hyperlex-settle +description: Coach Hyperlex settlement. Human must supply forecast-id and decision. Never invent outcomes or Brier. +when_to_use: User wants to settle a forecast or close the calibration loop. +allowed-tools: Bash Read +--- + +# /hyperlex-settle + +Settlement is an **explicit human step**. Do not invent TRUE/FALSE/VOID/CONFLICT. Do not invent Brier. + +1. `$HLX pending` — show open ids. +2. Ask the operator for `--forecast-id` and `--decision`. +3. Only after they choose, run: + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX settle --forecast-id --decision TRUE +# FALSE | VOID | CONFLICT +$HLX score-series --mean-shift --verify-chain +``` + +## Expected + +Settle appends the score log. `score-series` is the only place a numeric Brier is real. Empty series → `NOT_COMPUTABLE`. + +## Fail-closed + +If the operator has not chosen a decision, stop. Coach the flags. Do not auto-settle. Do not guess outcomes. diff --git a/.claude/skills/hyperlex-wizard/SKILL.md b/.claude/skills/hyperlex-wizard/SKILL.md new file mode 100644 index 0000000..158b429 --- /dev/null +++ b/.claude/skills/hyperlex-wizard/SKILL.md @@ -0,0 +1,26 @@ +--- +name: hyperlex-wizard +description: Week-one Hyperlex onboarding via wizard --auto. Offline. Never auto-settles. +when_to_use: User is new to Hyperlex, asks to get started, or wants the guided operator path. +allowed-tools: Bash Read +--- + +# /hyperlex-wizard + +Week-one guided path. Prefer `--auto` in Claude (non-interactive). + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX wizard --auto +$HLX wizard --auto --query "" +``` + +## Expected + +Steps: `env_intro` → `doctor` → `demo` → `first_pipeline` → `calibration_coach` → `score_series_hint` → `handoff`. + +Summarize those steps. Open analysis stays `brier: null`. Show any open forecasts from the wizard / `$HLX pending`. + +## Fail-closed + +Do not auto-settle. Ask the operator for TRUE|FALSE|VOID, then coach `$HLX settle`. Do not invent Brier. diff --git a/.claude/skills/hyperlex/SKILL.md b/.claude/skills/hyperlex/SKILL.md new file mode 100644 index 0000000..1ed2af1 --- /dev/null +++ b/.claude/skills/hyperlex/SKILL.md @@ -0,0 +1,43 @@ +--- +name: hyperlex +description: > + Main Hyperlex contract in Claude Code. Use for slang, memetics, lineage, + virality, hyperstition, receipts, forecasts, and settlement coaching. + Prefer the CLI. Not for jailbreak / wrap composition. +when_to_use: > + User asks for Hyperlex, slang analysis, memetic emergence, lineage, + Brier settlement, or cultural-signal receipts. +allowed-tools: Bash Read Grep +--- + +# Hyperlex (project skill) + +Read the repo-root [SKILL.md](../../../SKILL.md) for the full Hermes + Claude +contract. This file is a thin pointer so Claude Code can invoke `/hyperlex` +when this checkout is the project. Do not duplicate engine logic here. + +## Invoke + +```bash +export HYPERLEX_SKILL_DIR="${HYPERLEX_SKILL_DIR:-$PWD}" +export HERMES_SKILL_DIR="${HERMES_SKILL_DIR:-$HYPERLEX_SKILL_DIR}" +export HLX="${HLX:-python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py}" +$HLX check +$HLX doctor +$HLX demo +$HLX wizard --auto +$HLX pipeline "" --route offline +``` + +Installed personal skill: + +```bash +export HLX="python3 $HOME/.claude/skills/hyperlex/scripts/hyperlex.py" +# or: bash "$HOME/.claude/skills/hyperlex/scripts/claude_hlx.sh" +``` + +## Fail-closed + +- Never invent a numeric Brier. Open analysis keeps `brier: null`. +- Never auto-settle. Ask for TRUE|FALSE|VOID|CONFLICT, then run `settle`. +- Prefer Bash CLI over guessing labels. Repeat OBSERVED / INFERRED / SPECULATIVE. diff --git a/.gitignore b/.gitignore index adec837..6adaf42 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ build/ !docs/command-router.v1.json !data/backfill/**/*.json !data/phylogeny/**/*.json +!.claude-plugin/plugin.json # Temp / build output tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f1646..99e2a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +- **Claude Code host (additive):** `.claude-plugin/plugin.json`, project + `CLAUDE.md`, slash helpers (`.claude/skills/` + plugin `commands/`), + `install.sh --claude` / `--claude-plugin`, `scripts/claude_hlx.sh`, + `docs/claude-skill.md`, `docs/claude-runtime-contract.md`, + `references/claude-runtime-contract.md`. + `doctor` reports `CLAUDE_OK` / `CLAUDE_MISSING` (missing does not fail). + Hermes install paths unchanged. - CLI `wizard` + package `hyperlex.wizard`: week-one Hermes guided path (`--auto` / interactive); never auto-settles; offline-first; SKILL.md procedure - **SIGNAL REPORT parity (Companion adaptation):** `result.v1` extended with optional `provenance.seed`, `analysis.compression_metrics`, `analysis.symbolic_role`, `analysis.propagation_vector`, `analysis.slang_family_tree`, `analysis.signal_report` (schema + package-local copy). Builder: `src/hyperlex/analysis/signal_report.py`. Wired into `detect_memetic_patterns` (attach + seed header). Docs: `docs/superpowers/specs/2026-08-06-signal-report-adaptation.md`. All new fields optional and fail-open. Brier remains null on open analysis. diff --git a/CLAUDE.md b/CLAUDE.md index 0390e52..c1c0f5d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,2 +1,14 @@ Follow [AGENTS.md](AGENTS.md). That file is the only contributor and agent instruction source in this repository. For a non-trivial change, read the active intent.md before editing. If none exists, draft intent.md and wait for human accept. + +--- + +## Claude Code host (additive) + +When using Claude Code as an additional host (Hermes remains primary), see +[`docs/claude-skill.md`](docs/claude-skill.md) and +[`references/claude-runtime-contract.md`](references/claude-runtime-contract.md). +Install helpers: `bash install.sh --claude` / `--claude-plugin`. +Wrapper: `bash scripts/claude_hlx.sh `. + +AGENTS.md remains the only contributor/agent instruction source for this org repo. diff --git a/QUICKSTART.md b/QUICKSTART.md index 68ccfa1..f628d0c 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -27,6 +27,20 @@ $HLX wizard --auto $HLX commands ``` +## Claude Code + +```bash +bash install.sh --claude +export HYPERLEX_SKILL_DIR="${HOME}/.claude/skills/hyperlex" +export HLX="python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py" +$HLX demo +$HLX wizard --auto +``` + +Personal skill path: `~/.claude/skills/hyperlex`. Plugin path: +`~/.claude/plugins/hyperlex` (`--claude-plugin`). Project helpers live under +`.claude/skills/`. Settlement stays a human step. Guide: [docs/claude-skill.md](docs/claude-skill.md). + ## Daily path ```bash diff --git a/README.md b/README.md index 4d2189e..eebb920 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,21 @@ Deep dive: [docs/operator-loop.md](./docs/operator-loop.md) · [docs/commands.md --- + +## Claude Code (additive host) + +Hermes remains the primary skill host. Claude Code is optional: + +```bash +bash install.sh --claude +export HYPERLEX_SKILL_DIR="${HOME}/.claude/skills/hyperlex" +export HLX="python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py" +$HLX doctor # CLAUDE_OK / CLAUDE_MISSING (missing does not fail) +``` + +See [`docs/claude-skill.md`](docs/claude-skill.md) and [`docs/claude-runtime-contract.md`](docs/claude-runtime-contract.md). + + ## CLI surface **Prefer** diff --git a/SKILL.md b/SKILL.md index d0e24b0..7d318ae 100644 --- a/SKILL.md +++ b/SKILL.md @@ -10,6 +10,14 @@ description: > ai-native slang, brainrot, and receipt-backed cultural signal scans. Not for general web research (use agent-reach), product audits (neon-genie), cinematic work (kubrick), or jailbreak / wrap composition. +when_to_use: > + Invoke for slang detection, memetic emergence, lineage matching, + hyperstition or virality scoring, forecast settlement, or mutation + traces. Prefer the Hyperlex CLI over inventing scores. +allowed-tools: + - Bash + - Read + - Grep version: 0.4.2 author: Applied Alchemy Labs / Hermes license: LicenseRef-Zero-State-Proprietary-1.0 @@ -30,6 +38,11 @@ metadata: - Mutation category: analysis related_skills: [] + claude: + hosts: [claude-code] + personal_skill: ~/.claude/skills/hyperlex + plugin_dir: ~/.claude/plugins/hyperlex + plugin_manifest: .claude-plugin/plugin.json openclaw: requires: bins: [python3] @@ -115,6 +128,47 @@ python3 "$HOME/.hermes/skills/hyperlex/scripts/hyperlex.py" check python3 "$HOME/.hermes/skills/hyperlex/scripts/hyperlex.py" smoke ``` +Claude Code personal skill (Hermes install still runs; this flag is additive): + +```bash +bash install.sh --claude --dry-run +bash install.sh --claude +# ~/.claude/skills/hyperlex + sibling slash helpers +# optional plugin tree: bash install.sh --claude-plugin +``` + +## Claude Code + +Claude is an additional host. Hermes remains the primary skill surface. +Prefer the Bash CLI. Do not invent scores. Do not auto-settle. + +```bash +export HYPERLEX_SKILL_DIR="${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}" +export HERMES_SKILL_DIR="${HERMES_SKILL_DIR:-$HYPERLEX_SKILL_DIR}" +export HLX="${HLX:-python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py}" +# wrapper: bash scripts/claude_hlx.sh +# after pip: hyperlex +``` + +When to call: + +| Need | Command | +|------|---------| +| First success | `$HLX demo` | +| Week-one path | `$HLX wizard --auto` | +| Analyze + receipt | `$HLX pipeline "" --route offline` | +| Multi-query scan | `$HLX scan --query "" --route offline --receipt --forecasts` | +| Open forecasts | `$HLX pending` | +| Close a forecast | `$HLX settle --forecast-id --decision TRUE` after the human chooses | + +Slash helpers (project `.claude/skills/` or plugin `commands/`): `/hyperlex`, +`/hyperlex-demo`, `/hyperlex-wizard`, `/hyperlex-scan`, `/hyperlex-analyze`, +`/hyperlex-pending`, `/hyperlex-settle`. + +Fail-closed: never invent Brier; never auto-settle; repeat OBSERVED / INFERRED / +SPECULATIVE. Docs: `docs/claude-skill.md`, `CLAUDE.md` (project only — plugin +context does not load `CLAUDE.md`). + ## Commands (prefer simplified path) ```bash @@ -270,10 +324,12 @@ PYTHONPATH="$HERMES_SKILL_DIR/src" python3 -m hyperlex mutation trace "it's givi Successful packaging: - `~/.hermes/skills/hyperlex/SKILL.md` exists +- optional Claude: `~/.claude/skills/hyperlex/SKILL.md` after `install.sh --claude` - `check` returns `"ok": true` - `smoke` writes a receipt under `out/smoke/` - Open analysis has `"brier": null` - Mutation trace packets have `"forecast_eligible": false` +- `doctor` reports `CLAUDE_OK` or `CLAUDE_MISSING` (missing does not fail the skill) ## Design references @@ -287,6 +343,7 @@ Successful packaging: - `examples/slang-families/` — Mermaid + HTML family diagrams - `data/backfill/2026/` — YTD slang packs - `references/hermes-runtime-contract.md` — path / authority policy +- `references/claude-runtime-contract.md` — Claude Code paths / env / CLI - `references/source-and-upgrades.md` — install identity, two-rename limits, lock recovery ## Security diff --git a/STATUS.md b/STATUS.md index 63ea392..1ce0377 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,6 +3,7 @@ **Version:** 0.4.0 **Posture:** Hermes skill (Python package repo) **Install:** `bash install.sh` → `~/.hermes/skills/hyperlex` +**Claude (optional):** `bash install.sh --claude` → `~/.claude/skills/hyperlex` **Track:** Phases 0–4 complete · Phase 5.0–5.3 · Pages static run history · Hallmark desk UI ## Health diff --git a/commands/hyperlex-analyze.md b/commands/hyperlex-analyze.md new file mode 100644 index 0000000..867f0d3 --- /dev/null +++ b/commands/hyperlex-analyze.md @@ -0,0 +1,23 @@ +--- +description: Analyze pasted text or a JSON ingest file with the Hyperlex CLI. Prefer offline route. +allowed-tools: Bash Read +--- + +# /hyperlex-analyze + +Analyze text or a file. Prefer `pipeline` / `run` when the user wants a receipt + forecasts. Use `analyze` for analysis-only. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX analyze "$ARGUMENTS" --route offline +$HLX pipeline "$ARGUMENTS" --route offline +$HLX analyze --input "$ARGUMENTS" --route offline +``` + +## Expected + +JSON result. `provenance.brier` is `null`. Lineage / virality / mutation_trace only when the CLI attaches them. + +## Fail-closed + +Do not invent labels or Brier. If the file is missing, say so. Do not settle. diff --git a/commands/hyperlex-demo.md b/commands/hyperlex-demo.md new file mode 100644 index 0000000..3e87c61 --- /dev/null +++ b/commands/hyperlex-demo.md @@ -0,0 +1,27 @@ +--- +description: Run the Hyperlex offline demo and summarize receipt labels. No API keys. Never invent Brier. +allowed-tools: Bash Read +--- + +# /hyperlex-demo + +Run the offline demo. Summarize labels from the CLI output. Do not invent scores. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX demo +# repo checkout: python3 scripts/hyperlex.py demo +# wrapper: bash scripts/claude_hlx.sh demo +``` + +## Expected + +- Exit `0`, `ok: true` +- `brier` and `provenance_brier` are `null` +- Receipt path under the demo out dir or `~/.hyperlex/receipts/` +- Known atoms often match a lineage family (`rizz` → `brainrot-aura`) +- Repeat OBSERVED / INFERRED / SPECULATIVE — do not upgrade them + +## Fail-closed + +If the CLI fails, report the error. Do not fabricate a receipt or Brier. Do not settle. diff --git a/commands/hyperlex-pending.md b/commands/hyperlex-pending.md new file mode 100644 index 0000000..af7af01 --- /dev/null +++ b/commands/hyperlex-pending.md @@ -0,0 +1,22 @@ +--- +description: List open Hyperlex forecasts from the score log. Start of the calibration loop. +allowed-tools: Bash Read +--- + +# /hyperlex-pending + +List unsettled forecasts. This is the start of the calibration loop — not settlement. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX pending +$HLX pending --limit 20 +``` + +## Expected + +JSON list of open forecast ids (default log: `~/.hyperlex/score_log.jsonl`). Empty list is valid. + +## Fail-closed + +Do not invent forecast ids. Do not compute Brier here. Next step is human: `/hyperlex-settle` after they choose TRUE|FALSE|VOID|CONFLICT. diff --git a/commands/hyperlex-scan.md b/commands/hyperlex-scan.md new file mode 100644 index 0000000..548c9ea --- /dev/null +++ b/commands/hyperlex-scan.md @@ -0,0 +1,23 @@ +--- +description: LIVE_EMERGENCE_SCAN helper. Pass a query (or comma-separated queries). Offline-first. +allowed-tools: Bash Read +--- + +# /hyperlex-scan + +Multi-query LIVE_EMERGENCE_SCAN. Prefer `--route offline` unless the user allows network. + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX scan --query "$ARGUMENTS" --route offline --receipt --forecasts --append-log +$HLX scan --queries "rizz,locked in" --route offline --receipt --forecasts --append-log +$HLX scan --config "${HYPERLEX_SKILL_DIR:-.}/examples/cron/scan-queries.json" --route offline --receipt --forecasts +``` + +## Expected + +JSON scan summary. Per-query receipts when `--receipt` is set. Forecasts have `brier: null` until settle. + +## Fail-closed + +Do not register cron. `risk-schedule` is advisory only. Do not invent Brier. Do not auto-settle. diff --git a/commands/hyperlex-settle.md b/commands/hyperlex-settle.md new file mode 100644 index 0000000..938c36b --- /dev/null +++ b/commands/hyperlex-settle.md @@ -0,0 +1,27 @@ +--- +description: Coach Hyperlex settlement. Human must supply forecast-id and decision. Never invent outcomes or Brier. +allowed-tools: Bash Read +--- + +# /hyperlex-settle + +Settlement is an **explicit human step**. Do not invent TRUE/FALSE/VOID/CONFLICT. Do not invent Brier. + +1. `$HLX pending` — show open ids. +2. Ask the operator for `--forecast-id` and `--decision`. +3. Only after they choose, run: + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX settle --forecast-id --decision TRUE +# FALSE | VOID | CONFLICT +$HLX score-series --mean-shift --verify-chain +``` + +## Expected + +Settle appends the score log. `score-series` is the only place a numeric Brier is real. Empty series → `NOT_COMPUTABLE`. + +## Fail-closed + +If the operator has not chosen a decision, stop. Coach the flags. Do not auto-settle. Do not guess outcomes. diff --git a/commands/hyperlex-wizard.md b/commands/hyperlex-wizard.md new file mode 100644 index 0000000..4d2a031 --- /dev/null +++ b/commands/hyperlex-wizard.md @@ -0,0 +1,24 @@ +--- +description: Week-one Hyperlex onboarding via wizard --auto. Offline. Never auto-settles. +allowed-tools: Bash Read +--- + +# /hyperlex-wizard + +Week-one guided path. Prefer `--auto` in Claude (non-interactive). + +```bash +: "${HLX:=python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.claude/skills/hyperlex}}/scripts/hyperlex.py}" +$HLX wizard --auto +$HLX wizard --auto --query "" +``` + +## Expected + +Steps: `env_intro` → `doctor` → `demo` → `first_pipeline` → `calibration_coach` → `score_series_hint` → `handoff`. + +Summarize those steps. Open analysis stays `brier: null`. Show any open forecasts from the wizard / `$HLX pending`. + +## Fail-closed + +Do not auto-settle. Ask the operator for TRUE|FALSE|VOID, then coach `$HLX settle`. Do not invent Brier. diff --git a/docs/README.md b/docs/README.md index d5716d1..ceaf187 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,6 +8,7 @@ Hyperlex is a **Hermes skill** (Python package repo). Relevant Abraxas wire shap ## Core - [Hermes skill model](./hermes-skill.md) +- [Claude Code skill + plugin](./claude-skill.md) - [Public API v1 freeze](./api-v1.md) - [Brier & Calibration](./brier-calibration.md) - [Rune / Signal Relay](./rune-signal-relay.md) @@ -35,4 +36,5 @@ Hyperlex is a **Hermes skill** (Python package repo). Relevant Abraxas wire shap ## References - [arXiv papers](../references/arxiv_papers.md) - [Hermes runtime contract](../references/hermes-runtime-contract.md) +- [Claude Code runtime contract](claude-runtime-contract.md) diff --git a/docs/claude-runtime-contract.md b/docs/claude-runtime-contract.md new file mode 100644 index 0000000..c3acb91 --- /dev/null +++ b/docs/claude-runtime-contract.md @@ -0,0 +1,79 @@ +# Hyperlex — Claude Code runtime contract + +Published operator contract (MkDocs). The skill-tree copy lives at +`references/claude-runtime-contract.md`. + +## Identity + +| Field | Value | +|-------|--------| +| Skill / plugin name | `hyperlex` | +| Personal skill | `~/.claude/skills/hyperlex` | +| Local plugin dir | `~/.claude/plugins/hyperlex` | +| Plugin manifest | `.claude-plugin/plugin.json` | +| Entry contract | root `SKILL.md` (single-skill plugin layout) | +| CLI | `scripts/hyperlex.py` | +| Wrapper | `scripts/claude_hlx.sh` | +| Package | `src/hyperlex/` | + +Claude Code loads a root `SKILL.md` as one skill when the plugin has no +`skills/` directory and no `skills` manifest field. Slash helpers are project +skills under `.claude/skills/` and plugin commands under `commands/`. Do not +copy the engine into `skills/hyperlex/`. + +`CLAUDE.md` is project context when this repo is the working directory. It is +not plugin context. + +## Paths + +```text +Personal skill (~/.claude/skills/hyperlex/): + SKILL.md + scripts/hyperlex.py + scripts/claude_hlx.sh + src/hyperlex/ + +Local plugin (~/.claude/plugins/hyperlex/): + .claude-plugin/plugin.json + SKILL.md + commands/hyperlex-*.md + scripts/ src/ + +Project checkout: + CLAUDE.md + .claude/skills//SKILL.md +``` + +Operator data is unchanged (outside the skill tree): + +```text +~/.hyperlex/receipts/ +~/.hyperlex/score_log.jsonl +``` + +## Environment + +| Variable | Role | +|----------|------| +| `HLX` | CLI invocation string (`python3 …/scripts/hyperlex.py`) | +| `HYPERLEX_SKILL_DIR` | Claude-preferred skill root | +| `CLAUDE_SKILL_DIR` | Optional alias for `HYPERLEX_SKILL_DIR` | +| `HERMES_SKILL_DIR` | Hermes name for the same tree; Claude may reuse it | + +`scripts/claude_hlx.sh` resolves the skill dir in this order: +`HYPERLEX_SKILL_DIR`, `CLAUDE_SKILL_DIR`, `HERMES_SKILL_DIR`, this checkout, +`~/.claude/skills/hyperlex`, `~/.claude/plugins/hyperlex`, +`~/.hermes/skills/hyperlex`. Then it execs `scripts/hyperlex.py`. + +## Execution rules + +1. Prefer Bash CLI: `$HLX …` or `bash scripts/claude_hlx.sh …`. +2. Baseline work uses `--route offline` (no network, no API keys). +3. Never invent numeric Brier. Open analysis keeps `provenance.brier` null. +4. Never auto-settle. Ask for TRUE|FALSE|VOID|CONFLICT, then run `settle`. +5. `doctor` reports `CLAUDE_OK` or `CLAUDE_MISSING`. Missing does not fail Hermes. + +## Authority + +Same classes as the Hermes runtime contract (`references/hermes-runtime-contract.md`): +OBSERVED, INFERRED, SPECULATIVE, NOT_COMPUTABLE, operator settlement. diff --git a/docs/claude-skill.md b/docs/claude-skill.md new file mode 100644 index 0000000..c13c8b3 --- /dev/null +++ b/docs/claude-skill.md @@ -0,0 +1,93 @@ +# Hyperlex in Claude Code + +Hermes is the primary skill host. Claude Code is an additional host. Same CLI, +same settlement rule, same receipts. This is dual-runtime packaging — not a +rewrite. + +## Two install shapes + +| Shape | Path | What Claude loads | +|-------|------|-------------------| +| **Personal skill** | `~/.claude/skills/hyperlex/` | Directory `SKILL.md` + bundled `scripts/` / `src/` for offline CLI | +| **Local plugin** | `~/.claude/plugins/hyperlex/` | `.claude-plugin/plugin.json`, root `SKILL.md`, slash `commands/` | + +The repo itself is already a single-skill plugin: root `SKILL.md` plus +`.claude-plugin/plugin.json`. You can point Claude Code at this checkout +instead of copying it. + +Thin `hyperlex init --target claude` writes only `SKILL.md` and expects +`hyperlex` on PATH. Prefer `install.sh --claude` when you want the offline CLI +tree without a pip install. + +## Personal skill (recommended for operators) + +```bash +bash install.sh --claude --dry-run +bash install.sh --claude +export HYPERLEX_SKILL_DIR="${HOME}/.claude/skills/hyperlex" +export HERMES_SKILL_DIR="${HERMES_SKILL_DIR:-$HYPERLEX_SKILL_DIR}" +export HLX="python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py" +$HLX check && $HLX doctor && $HLX demo +``` + +`--claude` is **additive**. Hermes still installs to `~/.hermes/skills/hyperlex` +unless you pass `--target`. Sibling slash helpers land next to the personal +skill: + +```text +~/.claude/skills/hyperlex/ # main contract + CLI +~/.claude/skills/hyperlex-demo/ +~/.claude/skills/hyperlex-wizard/ +~/.claude/skills/hyperlex-scan/ +~/.claude/skills/hyperlex-analyze/ +~/.claude/skills/hyperlex-pending/ +~/.claude/skills/hyperlex-settle/ +``` + +Reload Claude Code so it sees the new skills. + +## Local plugin dir + +```bash +bash install.sh --claude-plugin --dry-run +bash install.sh --claude-plugin +# tree: ~/.claude/plugins/hyperlex +# enable via Claude plugin UI, or: claude plugin add ~/.claude/plugins/hyperlex +``` + +Plugin slash commands live in repo `commands/` (`/hyperlex-demo`, …). The main +`/hyperlex` skill is the root `SKILL.md` (single-skill layout — no duplicate +engine under `skills/hyperlex/`). + +You can also add this git checkout as a plugin path. `CLAUDE.md` at the repo +root is **project** context only; Claude does not load it as plugin context. + +## First success (no Anthropic API) + +```bash +$HLX demo +$HLX wizard --auto +``` + +Expect `ok: true` and `brier: null`. No paid keys. Settlement stays a human +step: `pending` → operator decision → `settle` → `score-series`. + +## Differences from Hermes + +| | Hermes | Claude Code | +|--|--------|-------------| +| Default skill path | `~/.hermes/skills/hyperlex` | `~/.claude/skills/hyperlex` | +| Env name | `HERMES_SKILL_DIR` | `HYPERLEX_SKILL_DIR` (may reuse Hermes) | +| Wrapper | `python3 $HERMES_SKILL_DIR/scripts/hyperlex.py` | `scripts/claude_hlx.sh` | +| Discovery | Hermes scans `SKILL.md` | Personal skills + optional plugin | +| Slash helpers | Hermes procedure in `SKILL.md` | `.claude/skills/` and `commands/` | +| Contract | Same `SKILL.md` | Same `SKILL.md` + Claude section | + +Fail-closed rules do not change: no invented Brier, no auto-settle, no +phenomenology claims. + +## See also + +- [Claude runtime contract](claude-runtime-contract.md) +- [Hermes skill model](hermes-skill.md) +- [Operator loop](operator-loop.md) diff --git a/docs/commands.md b/docs/commands.md index 99aabce..7d684a5 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -4,7 +4,7 @@ Hyperlex has many subcommands. Prefer this map. Full list: `$HLX commands` (JSON or `python3 scripts/hyperlex.py -h`. ```bash -HLX="python3 ${HERMES_SKILL_DIR:-$HOME/.hermes/skills/hyperlex}/scripts/hyperlex.py" +HLX="python3 ${HYPERLEX_SKILL_DIR:-${HERMES_SKILL_DIR:-$HOME/.hermes/skills/hyperlex}}/scripts/hyperlex.py" ``` ## Daily ops (automatic backend) diff --git a/docs/hermes-skill.md b/docs/hermes-skill.md index 61e6e55..4466bf7 100644 --- a/docs/hermes-skill.md +++ b/docs/hermes-skill.md @@ -35,6 +35,9 @@ $HLX check && $HLX doctor && $HLX commands $HLX wizard --auto ``` +Claude Code is an additional host (same CLI, same settlement rule): +[claude-skill.md](claude-skill.md). `--claude` on `install.sh` is additive. + The **wizard** is the preferred first-run Hermes procedure: offline guided path (env → doctor → demo → first pipeline → calibration coach). It never auto-settles; settlement stays a human operator step (`pending` → `settle` → `score-series`). diff --git a/docs/install-package.md b/docs/install-package.md index a54fbe0..4655393 100644 --- a/docs/install-package.md +++ b/docs/install-package.md @@ -23,3 +23,12 @@ It does **not** rsync `src/` or `scripts/hyperlex.py`. The agent runs `hyperlex Reload the agent session after init. Legacy full-tree install remains `bash install.sh` for hosts that still want the fat skill CLI. +Claude Code full tree (SKILL.md + scripts/src, not the thin init wire): + +```bash +bash install.sh --claude --dry-run +bash install.sh --claude # ~/.claude/skills/hyperlex +bash install.sh --claude-plugin # ~/.claude/plugins/hyperlex +``` + +See [claude-skill.md](claude-skill.md). diff --git a/docs/start/quickstart.md b/docs/start/quickstart.md index 20878b2..c448440 100644 --- a/docs/start/quickstart.md +++ b/docs/start/quickstart.md @@ -45,6 +45,18 @@ $HLX demo $HLX wizard --auto ``` +## Claude Code + +```bash +bash install.sh --claude +export HYPERLEX_SKILL_DIR="${HOME}/.claude/skills/hyperlex" +export HLX="python3 $HYPERLEX_SKILL_DIR/scripts/hyperlex.py" +$HLX demo +``` + +Same offline first success. Hermes remains the default `install.sh` target; +`--claude` is additive. Details: [Claude skill](../claude-skill.md). + ## Committed sample output See [`examples/quickstart/`](https://github.com/scrimshawlife-ctrl/Hyperlex/tree/main/examples/quickstart) diff --git a/hyperlex.manifest.yaml b/hyperlex.manifest.yaml index 75a300c..e6f9027 100644 --- a/hyperlex.manifest.yaml +++ b/hyperlex.manifest.yaml @@ -1,5 +1,5 @@ name: hyperlex -version: 0.4.0 +version: 0.4.2 display_name: Hyperlex kind: hermes-openclaw-skill description: > @@ -7,8 +7,8 @@ description: > tracking, virality analysis, lineage matching, settled Brier calibration, and slang mutation-operator detection on attested text. license: LicenseRef-Zero-State-Proprietary-1.0 -homepage: https://github.com/scrimshawlife-ctrl/Hyperlex -repository: https://github.com/scrimshawlife-ctrl/Hyperlex +homepage: https://github.com/Zero-State-LLC/Hyperlex +repository: https://github.com/Zero-State-LLC/Hyperlex skill_entry: SKILL.md cli_entry: scripts/hyperlex.py @@ -19,10 +19,13 @@ package_root: src/hyperlex hosts: - hermes - openclaw + - claude-code install: hermes_path: ~/.hermes/skills/hyperlex openclaw_path: ~/.openclaw/skills/hyperlex + claude_skill_path: ~/.claude/skills/hyperlex + claude_plugin_path: ~/.claude/plugins/hyperlex atomic: true supports_dry_run: true supports_rollback: true diff --git a/install.sh b/install.sh index 0b0b407..af4175e 100755 --- a/install.sh +++ b/install.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash -# Hyperlex — Hermes skill installer +# Hyperlex — Hermes skill installer (Claude Code is an additional host) # Usage: # ./install.sh # ./install.sh --dry-run # ./install.sh --target DIR # ./install.sh --rollback # ./install.sh --openclaw +# ./install.sh --claude +# ./install.sh --claude-plugin # ./install.sh --version set -euo pipefail @@ -13,6 +15,16 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HERMES_ROOT="${HERMES_HOME:-$HOME/.hermes}" DEFAULT_TARGET="${HERMES_ROOT}/skills/hyperlex" OPENCLAW_TARGET="${HOME}/.openclaw/skills/hyperlex" +CLAUDE_SKILL_TARGET="${HOME}/.claude/skills/hyperlex" +CLAUDE_PLUGIN_TARGET="${HOME}/.claude/plugins/hyperlex" +CLAUDE_HELPERS=( + hyperlex-demo + hyperlex-wizard + hyperlex-scan + hyperlex-analyze + hyperlex-pending + hyperlex-settle +) VERSION="$(tr -d '[:space:]' < "${ROOT}/VERSION" 2>/dev/null || echo "0.0.0")" @@ -20,6 +32,8 @@ DRY_RUN=0 ROLLBACK=0 ALLOW_OUTSIDE_HOME=0 INSTALL_OPENCLAW=0 +INSTALL_CLAUDE=0 +INSTALL_CLAUDE_PLUGIN=0 SKIP_SMOKE=0 TARGET="$DEFAULT_TARGET" @@ -29,15 +43,25 @@ Hyperlex Hermes skill installer v${VERSION} Usage: ./install.sh [options] +Hermes remains the default target. Claude flags are additive extra hosts. + Options: --dry-run Show actions without writing --target DIR Install to DIR (default: ${DEFAULT_TARGET}) --rollback Restore most recent target-keyed backup --openclaw Also install to ~/.openclaw/skills/hyperlex + --claude Also install personal skill to ~/.claude/skills/hyperlex + plus slash helpers (hyperlex-demo, …) as sibling skills + --claude-plugin Also install plugin tree to ~/.claude/plugins/hyperlex --skip-smoke Skip staged check/smoke (marks UNVERIFIED) --allow-outside-home Permit --target outside \$HOME --version Print version and exit -h, --help Show this help + +Claude paths: + Personal skill: ~/.claude/skills/hyperlex/ (SKILL.md + scripts/src) + Local plugin: ~/.claude/plugins/hyperlex/ (.claude-plugin/ + commands/) + Project helpers: .claude/skills/ in this repo (when the checkout is the project) EOF } @@ -62,6 +86,8 @@ while [[ $# -gt 0 ]]; do --dry-run) DRY_RUN=1; shift ;; --rollback) ROLLBACK=1; shift ;; --openclaw) INSTALL_OPENCLAW=1; shift ;; + --claude) INSTALL_CLAUDE=1; shift ;; + --claude-plugin) INSTALL_CLAUDE_PLUGIN=1; shift ;; --skip-smoke) SKIP_SMOKE=1; shift ;; --allow-outside-home) ALLOW_OUTSIDE_HOME=1; shift ;; --version) echo "$VERSION"; exit 0 ;; @@ -116,7 +142,9 @@ validate_source() { "VERSION" "scripts/hyperlex.py" "scripts/hlx-mutation" + "scripts/claude_hlx.sh" "scripts/install_transaction.py" + ".claude-plugin/plugin.json" "src/hyperlex/__init__.py" "src/hyperlex/calibration/scoring.py" "src/hyperlex/schemas/result.v1.schema.json" @@ -149,6 +177,40 @@ run_transaction() { python3 "${ROOT}/scripts/install_transaction.py" "$ROOT" "$dest" hyperlex "${check_args[@]}" } +install_extra_host() { + local dest="$1" + local label="$2" + local _saved="$TARGET" + TARGET="$dest" + validate_target + if [[ $DRY_RUN -eq 1 ]]; then + log "DRY RUN: would also install ${label} to ${TARGET}" + TARGET="$_saved" + return 0 + fi + run_transaction "$TARGET" + TARGET="$_saved" +} + +copy_claude_helpers() { + local dest_root="${HOME}/.claude/skills" + local name src dest + for name in "${CLAUDE_HELPERS[@]}"; do + src="${ROOT}/.claude/skills/${name}/SKILL.md" + dest="${dest_root}/${name}/SKILL.md" + if [[ ! -f "$src" ]]; then + warn "Claude helper missing in source: ${src}" + continue + fi + if [[ $DRY_RUN -eq 1 ]]; then + log "DRY RUN: would install Claude helper ${name} → ${dest}" + continue + fi + mkdir -p "$(dirname "$dest")" + cp -f "$src" "$dest" + done +} + do_rollback() { validate_target if [[ $DRY_RUN -eq 1 ]]; then @@ -174,17 +236,25 @@ if [[ $DRY_RUN -eq 1 ]]; then log "DRY RUN: two-rename activation is not crash-atomic; locks are never auto-reclaimed" [[ -d "$TARGET" ]] && log "DRY RUN: would publish a target-keyed backup under ${HERMES_ROOT}/backups/hyperlex/" [[ $INSTALL_OPENCLAW -eq 1 ]] && log "DRY RUN: would also install to ${OPENCLAW_TARGET}" + [[ $INSTALL_CLAUDE -eq 1 ]] && log "DRY RUN: would also install Claude personal skill to ${CLAUDE_SKILL_TARGET}" + [[ $INSTALL_CLAUDE -eq 1 ]] && copy_claude_helpers + [[ $INSTALL_CLAUDE_PLUGIN -eq 1 ]] && log "DRY RUN: would also install Claude plugin dir to ${CLAUDE_PLUGIN_TARGET}" exit 0 fi run_transaction "$TARGET" if [[ $INSTALL_OPENCLAW -eq 1 ]]; then - _saved="$TARGET" - TARGET="$OPENCLAW_TARGET" - validate_target - run_transaction "$TARGET" - TARGET="$_saved" + install_extra_host "$OPENCLAW_TARGET" "OpenClaw" +fi + +if [[ $INSTALL_CLAUDE -eq 1 ]]; then + install_extra_host "$CLAUDE_SKILL_TARGET" "Claude personal skill" + copy_claude_helpers +fi + +if [[ $INSTALL_CLAUDE_PLUGIN -eq 1 ]]; then + install_extra_host "$CLAUDE_PLUGIN_TARGET" "Claude plugin" fi echo "" @@ -195,11 +265,22 @@ else fi echo " Hermes: ${TARGET}" [[ $INSTALL_OPENCLAW -eq 1 ]] && echo " OpenClaw: ${OPENCLAW_TARGET}" +[[ $INSTALL_CLAUDE -eq 1 ]] && echo " Claude: ${CLAUDE_SKILL_TARGET}" +[[ $INSTALL_CLAUDE_PLUGIN -eq 1 ]] && echo " Plugin: ${CLAUDE_PLUGIN_TARGET}" echo "" echo "Next:" echo " export HERMES_SKILL_DIR=\"${TARGET}\"" echo " python3 \"\$HERMES_SKILL_DIR/scripts/hyperlex.py\" check" echo " python3 \"\$HERMES_SKILL_DIR/scripts/hyperlex.py\" pipeline \"rizz\" --route offline" echo " python3 \"\$HERMES_SKILL_DIR/scripts/hlx-mutation\" trace \"it's giving mid rizz\"" +if [[ $INSTALL_CLAUDE -eq 1 ]]; then + echo " export HYPERLEX_SKILL_DIR=\"${CLAUDE_SKILL_TARGET}\"" + echo " export HLX=\"python3 \$HYPERLEX_SKILL_DIR/scripts/hyperlex.py\"" + echo " bash \"\$HYPERLEX_SKILL_DIR/scripts/claude_hlx.sh\" check" + echo " # Reload Claude Code so it sees ~/.claude/skills/hyperlex" +fi +if [[ $INSTALL_CLAUDE_PLUGIN -eq 1 ]]; then + echo " # Enable the local plugin, or: claude plugin add ${CLAUDE_PLUGIN_TARGET}" +fi echo " # Reload Hermes skills if the agent is already running" echo "" diff --git a/mkdocs.yml b/mkdocs.yml index c3dad2a..7269879 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,8 @@ nav: - Cron / live emergence: cron-live-emergence.md - Case studies: case-studies.md - Hermes skill: hermes-skill.md + - Claude Code skill: claude-skill.md + - Claude runtime contract: claude-runtime-contract.md - Core Reference: - Architecture: architecture.md - Spec: spec.md diff --git a/references/claude-runtime-contract.md b/references/claude-runtime-contract.md new file mode 100644 index 0000000..64a917f --- /dev/null +++ b/references/claude-runtime-contract.md @@ -0,0 +1,78 @@ +# Hyperlex — Claude Code runtime contract + +Skill-tree copy. Published docs page: `docs/claude-runtime-contract.md`. + +## Identity + +| Field | Value | +|-------|--------| +| Skill / plugin name | `hyperlex` | +| Personal skill | `~/.claude/skills/hyperlex` | +| Local plugin dir | `~/.claude/plugins/hyperlex` | +| Plugin manifest | `.claude-plugin/plugin.json` | +| Entry contract | root `SKILL.md` (single-skill plugin layout) | +| CLI | `scripts/hyperlex.py` | +| Wrapper | `scripts/claude_hlx.sh` | +| Package | `src/hyperlex/` | + +Claude Code loads a root `SKILL.md` as one skill when the plugin has no +`skills/` directory and no `skills` manifest field. Slash helpers are project +skills under `.claude/skills/` and plugin commands under `commands/`. Do not +copy the engine into `skills/hyperlex/`. + +`CLAUDE.md` is project context when this repo is the working directory. It is +not plugin context. + +## Paths + +```text +Personal skill (~/.claude/skills/hyperlex/): + SKILL.md + scripts/hyperlex.py + scripts/claude_hlx.sh + src/hyperlex/ + +Local plugin (~/.claude/plugins/hyperlex/): + .claude-plugin/plugin.json + SKILL.md + commands/hyperlex-*.md + scripts/ src/ + +Project checkout: + CLAUDE.md + .claude/skills//SKILL.md +``` + +Operator data is unchanged (outside the skill tree): + +```text +~/.hyperlex/receipts/ +~/.hyperlex/score_log.jsonl +``` + +## Environment + +| Variable | Role | +|----------|------| +| `HLX` | CLI invocation string (`python3 …/scripts/hyperlex.py`) | +| `HYPERLEX_SKILL_DIR` | Claude-preferred skill root | +| `CLAUDE_SKILL_DIR` | Optional alias for `HYPERLEX_SKILL_DIR` | +| `HERMES_SKILL_DIR` | Hermes name for the same tree; Claude may reuse it | + +`scripts/claude_hlx.sh` resolves the skill dir in this order: +`HYPERLEX_SKILL_DIR`, `CLAUDE_SKILL_DIR`, `HERMES_SKILL_DIR`, this checkout, +`~/.claude/skills/hyperlex`, `~/.claude/plugins/hyperlex`, +`~/.hermes/skills/hyperlex`. Then it execs `scripts/hyperlex.py`. + +## Execution rules + +1. Prefer Bash CLI: `$HLX …` or `bash scripts/claude_hlx.sh …`. +2. Baseline work uses `--route offline` (no network, no API keys). +3. Never invent numeric Brier. Open analysis keeps `provenance.brier` null. +4. Never auto-settle. Ask for TRUE\|FALSE\|VOID\|CONFLICT, then run `settle`. +5. `doctor` reports `CLAUDE_OK` or `CLAUDE_MISSING`. Missing does not fail Hermes. + +## Authority + +Same classes as [hermes-runtime-contract.md](hermes-runtime-contract.md): +OBSERVED, INFERRED, SPECULATIVE, NOT_COMPUTABLE, operator settlement. diff --git a/scripts/claude_hlx.sh b/scripts/claude_hlx.sh new file mode 100755 index 0000000..40ad6b4 --- /dev/null +++ b/scripts/claude_hlx.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Claude Code operator entry — set skill dir + HLX, then exec hyperlex.py. +# Mirrors the Hermes operator path. Offline. No network. No secrets. +set -euo pipefail + +_here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_root="$(cd "${_here}/.." && pwd)" + +_resolve_skill_dir() { + local candidate + for candidate in \ + "${HYPERLEX_SKILL_DIR:-}" \ + "${CLAUDE_SKILL_DIR:-}" \ + "${HERMES_SKILL_DIR:-}" \ + "${_root}" \ + "${HOME}/.claude/skills/hyperlex" \ + "${HOME}/.claude/plugins/hyperlex" \ + "${HOME}/.hermes/skills/hyperlex" + do + [[ -n "${candidate}" ]] || continue + if [[ -f "${candidate}/scripts/hyperlex.py" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + done + return 1 +} + +SKILL_DIR="$(_resolve_skill_dir)" || { + printf 'error: Hyperlex CLI not found. Run: bash install.sh --claude\n' >&2 + exit 1 +} + +export HYPERLEX_SKILL_DIR="${SKILL_DIR}" +export HERMES_SKILL_DIR="${HERMES_SKILL_DIR:-${SKILL_DIR}}" +export HLX="${HLX:-python3 ${SKILL_DIR}/scripts/hyperlex.py}" + +exec python3 "${SKILL_DIR}/scripts/hyperlex.py" "$@" diff --git a/scripts/hyperlex.py b/scripts/hyperlex.py index a20689c..0a26ad7 100755 --- a/scripts/hyperlex.py +++ b/scripts/hyperlex.py @@ -100,6 +100,34 @@ def _check(condition: bool, name: str, message_ok: str, message_fail: str) -> _C return _Check(name=name, ok=bool(condition), message=message_ok if condition else message_fail) +def _claude_host_check() -> _Check: + """Report Claude Code personal-skill / plugin install. Absence is not a failure.""" + import os + + home = Path.home() + personal = home / ".claude" / "skills" / "hyperlex" + plugin = home / ".claude" / "plugins" / "hyperlex" + found: List[str] = [] + if (personal / "SKILL.md").is_file(): + kind = "personal" if (personal / "scripts" / "hyperlex.py").is_file() else "personal_thin" + found.append(f"{kind}={personal}") + if (plugin / ".claude-plugin" / "plugin.json").is_file() or (plugin / "SKILL.md").is_file(): + found.append(f"plugin={plugin}") + env_raw = os.environ.get("HYPERLEX_SKILL_DIR") or os.environ.get("CLAUDE_SKILL_DIR") or "" + if env_raw: + env_path = Path(env_raw).expanduser() + if (env_path / "scripts" / "hyperlex.py").is_file() or (env_path / "SKILL.md").is_file(): + found.append(f"env={env_path}") + if found: + return _check(True, "claude_host", "CLAUDE_OK: " + "; ".join(found), "") + return _check( + True, + "claude_host", + "CLAUDE_MISSING: ~/.claude/skills/hyperlex not installed (optional host)", + "", + ) + + def _import_hyperlex(): try: import importlib @@ -224,6 +252,8 @@ def cmd_doctor(_args: argparse.Namespace) -> int: "mkdocs.yml", "STATUS.md", "src/hyperlex/receipt/stats.py", + ".claude-plugin/plugin.json", + "scripts/claude_hlx.sh", ] for rel in required_files: p = ROOT / rel @@ -351,6 +381,7 @@ def cmd_doctor(_args: argparse.Namespace) -> int: checks.append( _check(True, "operator_home", f"~/.hyperlex exists={home_hx.is_dir()} path={home_hx}", "") ) + checks.append(_claude_host_check()) ok = all(c.ok for c in checks) _emit({ @@ -2322,7 +2353,9 @@ def _build_parser() -> argparse.ArgumentParser: check_parser = subparsers.add_parser("check", help="Validate package and manifest readiness") check_parser.set_defaults(func=cmd_check) - doctor_parser = subparsers.add_parser("doctor", help="Deep Hermes-skill health check") + doctor_parser = subparsers.add_parser( + "doctor", help="Deep skill health check (Hermes + optional Claude host)" + ) doctor_parser.set_defaults(func=cmd_doctor) sources_parser = subparsers.add_parser( diff --git a/tests/test_claude_host.py b/tests/test_claude_host.py new file mode 100644 index 0000000..2f49039 --- /dev/null +++ b/tests/test_claude_host.py @@ -0,0 +1,179 @@ +"""Claude Code packaging: install dry-run + doctor host detection. + +Offline only. Does not call Anthropic. Hermes dry-run must stay intact. +""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "hyperlex.py" +INSTALL = ROOT / "install.sh" + + +def _run(args: list[str], *, env: dict[str, str] | None = None, cwd: Path | None = None) -> subprocess.CompletedProcess[str]: + merged = os.environ.copy() + merged["HYPERLEX_OFFLINE"] = "1" + merged["HYPERLEX_NO_RATE_LIMIT"] = "1" + if env: + merged.update(env) + return subprocess.run( + args, + capture_output=True, + text=True, + cwd=str(cwd or ROOT), + env=merged, + ) + + +def test_hermes_dry_run_unchanged(tmp_path: Path) -> None: + home = tmp_path / "home" + home.mkdir() + env = { + "HOME": str(home), + "HERMES_HOME": str(home / "profile"), + } + result = _run(["bash", str(INSTALL), "--dry-run"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + assert "would staged-validate then activate Hyperlex" in result.stdout + assert "Claude personal skill" not in result.stdout + assert "Claude plugin dir" not in result.stdout + assert not (home / ".claude").exists() + assert not (home / "profile" / "skills").exists() + + +def test_claude_dry_run_lists_personal_path(tmp_path: Path) -> None: + home = tmp_path / "home" + home.mkdir() + env = { + "HOME": str(home), + "HERMES_HOME": str(home / "profile"), + } + result = _run(["bash", str(INSTALL), "--claude", "--dry-run"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + assert "would staged-validate then activate Hyperlex" in result.stdout + assert "Claude personal skill" in result.stdout + assert str(home / ".claude" / "skills" / "hyperlex") in result.stdout + assert "hyperlex-demo" in result.stdout + assert "hyperlex-settle" in result.stdout + assert not (home / ".claude").exists() + + +def test_claude_plugin_dry_run_lists_plugin_path(tmp_path: Path) -> None: + home = tmp_path / "home" + home.mkdir() + env = { + "HOME": str(home), + "HERMES_HOME": str(home / "profile"), + } + result = _run(["bash", str(INSTALL), "--claude-plugin", "--dry-run"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + assert "Claude plugin dir" in result.stdout + assert str(home / ".claude" / "plugins" / "hyperlex") in result.stdout + assert not (home / ".claude").exists() + + +def test_install_claude_skip_smoke_writes_personal_and_keeps_hermes(tmp_path: Path) -> None: + home = tmp_path / "home" + home.mkdir() + profile = home / "profile" + env = { + "HOME": str(home), + "HERMES_HOME": str(profile), + } + result = _run(["bash", str(INSTALL), "--claude", "--skip-smoke"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + hermes = profile / "skills" / "hyperlex" + claude = home / ".claude" / "skills" / "hyperlex" + assert (hermes / "SKILL.md").is_file() + assert (hermes / "scripts" / "hyperlex.py").is_file() + assert (claude / "SKILL.md").is_file() + assert (claude / "scripts" / "hyperlex.py").is_file() + assert (claude / "scripts" / "claude_hlx.sh").is_file() + assert (home / ".claude" / "skills" / "hyperlex-demo" / "SKILL.md").is_file() + assert (home / ".claude" / "skills" / "hyperlex-settle" / "SKILL.md").is_file() + assert not (home / ".hermes").exists() + + +def test_doctor_reports_claude_missing(tmp_path: Path) -> None: + home = tmp_path / "empty-home" + home.mkdir() + env = { + "HOME": str(home), + "HYPERLEX_SKILL_DIR": "", + "CLAUDE_SKILL_DIR": "", + "HERMES_SKILL_DIR": "", + } + # Drop leftover skill-dir hints from the parent environment. + result = _run([sys.executable, str(SCRIPT), "doctor"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + body = json.loads(result.stdout) + assert body["ok"] is True + assert body["n_failed"] == 0 + claude = next(c for c in body["checks"] if c["name"] == "claude_host") + assert claude["ok"] is True + assert claude["message"].startswith("CLAUDE_MISSING") + + +def test_doctor_reports_claude_ok(tmp_path: Path) -> None: + home = tmp_path / "home" + skill = home / ".claude" / "skills" / "hyperlex" + skill.mkdir(parents=True) + (skill / "SKILL.md").write_text("# hyperlex\n", encoding="utf-8") + (skill / "scripts").mkdir() + (skill / "scripts" / "hyperlex.py").write_text("# stub\n", encoding="utf-8") + env = { + "HOME": str(home), + "HYPERLEX_SKILL_DIR": "", + "CLAUDE_SKILL_DIR": "", + "HERMES_SKILL_DIR": "", + } + result = _run([sys.executable, str(SCRIPT), "doctor"], env=env) + assert result.returncode == 0, result.stderr + result.stdout + body = json.loads(result.stdout) + assert body["ok"] is True + claude = next(c for c in body["checks"] if c["name"] == "claude_host") + assert claude["ok"] is True + assert claude["message"].startswith("CLAUDE_OK") + assert "personal=" in claude["message"] + + +def test_claude_hlx_wrapper_check() -> None: + result = _run(["bash", str(ROOT / "scripts" / "claude_hlx.sh"), "check"]) + assert result.returncode == 0, result.stderr + result.stdout + body = json.loads(result.stdout) + assert body["ok"] is True + + +def test_plugin_manifest_matches_version() -> None: + version = (ROOT / "VERSION").read_text(encoding="utf-8").strip() + manifest = json.loads((ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) + assert manifest["name"] == "hyperlex" + assert manifest["version"] == version + assert "Applied Alchemy Labs" in manifest["author"]["name"] + assert "github.com/Zero-State-LLC/Hyperlex" in manifest["homepage"] + + +def test_helper_skills_are_short_and_fail_closed() -> None: + helpers = ( + "hyperlex-demo", + "hyperlex-wizard", + "hyperlex-scan", + "hyperlex-analyze", + "hyperlex-pending", + "hyperlex-settle", + ) + for name in helpers: + skill = ROOT / ".claude" / "skills" / name / "SKILL.md" + command = ROOT / "commands" / f"{name}.md" + body = skill.read_text(encoding="utf-8") + assert skill.is_file() + assert command.is_file() + assert "Never invent" in body or "Do not invent" in body or "do not invent" in body.lower() + assert "$HLX" in body + assert len(body.splitlines()) < 50 diff --git a/tests/test_doctor.py b/tests/test_doctor.py index aeb4730..983c55b 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -34,3 +34,7 @@ def test_doctor_ok() -> None: assert "brier_null" in names assert "api_v1" in names assert "golden_corpus" in names + assert "claude_host" in names + claude = next(c for c in body["checks"] if c["name"] == "claude_host") + assert claude["ok"] is True + assert claude["message"].startswith("CLAUDE_")