diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index a947829..02fe946 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -1,4 +1,4 @@ - + # episteme — command reference A one-page map of every `episteme` subcommand, grouped by lifecycle phase. @@ -59,6 +59,19 @@ Scope key: | `episteme evolve {run,report,promote,rollback,friction}` | framework | Run and manage gated self-evolution episodes. | | `episteme bridge {am,substrate}` | framework | Bridge external runtime event logs into memory-contract envelopes. | | `episteme private-skill` | framework | Enable or disable a private experimental skill. | +| `episteme surface {author,list,show,chain}` | practice | Author / sign / manage Signed Reasoning Surfaces (operator UX). | +| `episteme evidence {view,packet}` | practice | Auditor-facing viewer + Regulator Evidence Packet exporter. | +| `episteme verify` | practice | Standalone signed-surface verifier (independent of the episteme runtime). | +| `episteme practice {walk,retro,demo}` | practice | Make the practice tangible — walk / retro / demo without authoring a surface. | +| `episteme report` | practice | Quantified value report — surface authoring, failure modes, Tier-1 soak, calibration trend. | +| `episteme status` | runtime | Runtime-state snapshot: surface freshness, branch, rigor, framework counts, profile drift. | +| `episteme deferred {list,resolve}` | framework | List OPEN deferred discoveries (project-scoped; `--all-projects`, `--expired`) or chain an operator verdict. | +| `episteme docs {lint,index}` | docs | Doc-lifecycle marker lint + generated docs index (`--check` gates CI). | +| `episteme check {list,scaffold}` | governance | Manage user-authored PreToolUse checks. | +| `episteme bench {scaffold,run,grade,report}` | evaluation | Empirical-lift benchmark suite: paired-comparison runner + blind LLM grader. | +| `episteme dev watch` | development | Source-to-plugin-cache file watcher for plugin development. | +| `episteme new-project` | scaffold | Interactive wrapper over `bootstrap` for starting a fresh governed project. | +| `episteme verify-examples` | docs | Structural-parity guard: `core/memory/global/examples/*.example.md` stay at v2. | --- diff --git a/docs/COMPLIANCE_CROSSWALK.md b/docs/COMPLIANCE_CROSSWALK.md index 94c413b..69ce5d3 100644 --- a/docs/COMPLIANCE_CROSSWALK.md +++ b/docs/COMPLIANCE_CROSSWALK.md @@ -143,7 +143,7 @@ Published 2023-12; first publicly certified deployers in 2025 (SAP, Cornerstone, | **6.1.3 — AI risk treatment** | Documented mitigation per identified risk | `surface.disconfirmation_conditions[]` + `surface.decision.stop_rollback_path` | **Direct** | Per-decision mitigation (falsifier + rollback) pre-committed | | **7.4 — Communication** | Internal + external communication channels for AI matters | Operator-signed surface + Regulator Evidence Packet ZIP | **Supporting** | Substrate; channel policy is deployer-defined | | **8.1 — Operational planning and control** | Documented AI lifecycle processes | [`kernel/CONSTITUTION.md`](../kernel/CONSTITUTION.md) + [`docs/THE_WAY_TO_THINK.md`](THE_WAY_TO_THINK.md) + project-level `workflow_policy.md` | **Direct** | Five-stage cognitive practice IS the operational process; constitution is the invariant policy | -| **9.1 — Performance evaluation** | Monitoring, measurement, analysis | [`kernel/CALIBRATION_TELEMETRY.md`](../kernel/CALIBRATION_TELEMETRY.md) (Brier · calibration curve · base-rate slicing · coverage first-class) | **Direct** | Falsifiable measurement spec; refuses to emit Brier below coverage threshold | +| **9.1 — Performance evaluation** | Monitoring, measurement, analysis | [`kernel/CALIBRATION_TELEMETRY.md`](../kernel/CALIBRATION_TELEMETRY.md) (Brier · calibration curve · base-rate slicing · coverage first-class) | **Direct** | Falsifiable measurement spec; PLANNED, not implemented: no Brier/coverage computation exists in code yet (Event 172 correction); the implemented report is a per-day success-rate trend (src/episteme/_report.py) | | **9.2 — Internal audit** | Independent audit programme | `episteme verify` standalone CLI + `episteme review` spot-check CLI | **Direct** | Reproducible audit substrate; auditor independence is structural (signing key out of agent reach) | | **10.1 — Continual improvement** | Continual improvement of the AIMS | Phase 12 audit + [`kernel/ACTIVE_GUIDANCE_RANKING.md`](../kernel/ACTIVE_GUIDANCE_RANKING.md) | **Direct** | Friction-weighted axis rescoring drives improvement loop on the same evidentiary substrate as the audit | @@ -171,7 +171,7 @@ Anthropic's Agent Skills open spec (agentskills.io; emerging 2025-Q4 / 2026-Q1) | `version` (frontmatter, optional) | Repo-level via `pyproject.toml` + release-please-managed `.release-please-manifest.json`; per-skill version not declared | **Conditional** | Versioning is repo-level, not per-skill; a future per-skill version field would be additive | | `model` (frontmatter, optional) | Not declared per-skill | **Conditional** | episteme is BYOS (Bring Your Own Substrate); skills do not pin a model | | Markdown body (skill content) | Body of `skills/**/SKILL.md` | **Direct** | Identical convention | -| Frontmatter validation | `core/manifest.py` validators + `episteme doctor` | **Direct** | Per-skill schema validation runs at session start (`episteme doctor`) | +| Frontmatter validation | `src/episteme/cli.py (validate) + src/episteme/doc_lifecycle.py` validators + `episteme doctor` | **Direct** | Per-skill schema validation runs at session start (`episteme doctor`) | ### 5.1 episteme-specific extensions (not in the open spec) @@ -179,7 +179,7 @@ episteme skills carry additional structure the open spec does not (yet) cover; t | Extension | Purpose | Path | |---|---|---| -| Vendor classification (`[vendor]` · `[custom]` · `[agent]`) | Three-bucket pre-commit validation distinguishes upstream skills, episteme-custom skills, and agent-defined skills | `core/manifest.py` | +| Vendor classification (`[vendor]` · `[custom]` · `[agent]`) | Three-bucket pre-commit validation distinguishes upstream skills, episteme-custom skills, and agent-defined skills | `src/episteme/cli.py (validate) + src/episteme/doc_lifecycle.py` | | Episodic skill provenance | Skill artifacts can be hash-chained into `~/.episteme/framework/protocols.jsonl` when a skill synthesis fires at runtime | `core/hooks/_chain.py` | | Skill-level Reasoning Surface enforcement | When a skill is invoked on a high-impact op, the Reasoning Surface gate fires before skill execution — the skill cannot bypass the precondition | `core/hooks/reasoning_surface_guard.py` | diff --git a/docs/CUSTOMIZATION.md b/docs/CUSTOMIZATION.md index 1c01006..66ab472 100644 --- a/docs/CUSTOMIZATION.md +++ b/docs/CUSTOMIZATION.md @@ -5,7 +5,7 @@ Three places you can shape episteme to your own working life: personal memory, s ## Personal memory -Edit `core/memory/global/*.md` — these are gitignored and never leave your machine. The `*.example.md` files in the same directory are committed templates that show what belongs in each file. +Edit `core/memory/global/*.md` — these are gitignored and never leave your machine. The `*.example.md` files under `core/memory/global/examples/` are committed templates that show what belongs in each file. Recommended additions: - `core/memory/global/build_story.md` from `build_story.example.md` — a short, stable builder narrative (not project-specific details). diff --git a/docs/LAYER_MODEL.md b/docs/LAYER_MODEL.md index 1ee09d2..2deb3ea 100644 --- a/docs/LAYER_MODEL.md +++ b/docs/LAYER_MODEL.md @@ -117,7 +117,7 @@ Detection: `episteme detect [path]` scores signals in the repo (dependency files ### 3. Project Memory -Every project keeps its definitive truth in repo files. The episteme reference pattern uses `AGENTS.md` plus a staged-execution doc set (in this repo: `docs/PLAN.md`, `docs/EVENTS.md`, `docs/NEXT_STEPS.md`, and `docs/COGNITIVE_SYSTEM_PLAYBOOK.md`; planning-state docs are private) — these names are convention, not contract. Adopt or rename to fit your project. +Every project keeps its definitive truth in repo files. The episteme reference pattern uses `AGENTS.md` plus a staged-execution doc set (in this repo: `docs/EVENTS.md` and `docs/NEXT_STEPS.md`; PLAN retired E168 and the operating PLAYBOOK is operator-private) — these names are convention, not contract. Adopt or rename to fit your project. This layer must remain tool-agnostic. diff --git a/kernel/ARTIFACT_TAXONOMY.md b/kernel/ARTIFACT_TAXONOMY.md index 3259c94..58ee208 100644 --- a/kernel/ARTIFACT_TAXONOMY.md +++ b/kernel/ARTIFACT_TAXONOMY.md @@ -42,7 +42,7 @@ State that is **actively maintained** but **carries authority** — losing or ov Examples in this repo: -- `docs/PLAN.md`, `docs/PROGRESS.md`, `docs/NEXT_STEPS.md` — operational record +- `docs/NEXT_STEPS.md`, `docs/EVENTS.md` — operational record (PLAN/PROGRESS retired E168/E170) - `kernel/CHANGELOG.md` — versioned kernel history - `kernel/REFERENCES.md` — attribution contract - `core/memory/global/*.md` — the operator's lived profile (gitignored, but authoritative) @@ -83,7 +83,7 @@ Examples in this repo: - `.git/` — version-control internals - `node_modules/`, `__pycache__/`, `*.pyc`, `web/.next/` - `.venv/` -- Operator-private symlinked content (e.g. `docs/PLAN.md` → `~/episteme-private/docs/PLAN.md`) +- Operator-private symlinked content (e.g. `docs/NEXT_STEPS.md` → `~/episteme-private/docs/NEXT_STEPS.md`) The failure this tier blocks: the agent treats ephemeral state as authoritative and reasons from it ("the cache says X, therefore X"). Ephemeral state is the *consequence* of authoritative state, never the *source*. diff --git a/kernel/FALSIFIABILITY_CONDITIONS.md b/kernel/FALSIFIABILITY_CONDITIONS.md index c552656..ad98629 100644 --- a/kernel/FALSIFIABILITY_CONDITIONS.md +++ b/kernel/FALSIFIABILITY_CONDITIONS.md @@ -51,7 +51,7 @@ A row whose action-on-disconfirmation is empty or generic is ceremonial. Either - **Falsification condition.** A surface with disconfirmation field set to a lazy token (any item from the rejection list) passes the validator and the op proceeds. - **Measurement method.** Unit tests in `tests/test_*surface*.py`; manual: submit lazy disconfirmation under strict mode, expect refusal. - **Status.** **RUNNING.** Validator implemented; tests cover the canonical lazy-token list across English + Korean. Bypass-vector expansion would require fuzzing. -- **Action on disconfirmation.** Expand the lazy-token list + tighten regex in `core/hooks/_reasoning_surface_validator.py`; lock down the lazy-list as a versioned artifact; add fuzz tests. +- **Action on disconfirmation.** Expand the lazy-token list + tighten regex in `core/hooks/reasoning_surface_guard.py`; lock down the lazy-list as a versioned artifact; add fuzz tests. ### A3 · PreToolUse hook refuses ops when surface is missing/invalid @@ -59,7 +59,7 @@ A row whose action-on-disconfirmation is empty or generic is ceremonial. Either - **Falsification condition.** Operator runs a high-impact op (`git push` to main, irreversible DB migration, etc.) under strict mode in a project with NO `.episteme/reasoning-surface.json` (or a stale one past TTL), and the op proceeds without a refusal event in `~/.episteme/state/hooks.log`. - **Measurement method.** Adversarial test in fresh repo; hook log inspection; Gate 28 dogfood (kernel-on-itself). - **Status.** **RUNNING.** Verified extensively across Events 36-50 (soak window) + the current session. Gate 28 baseline shows 564 cascade:architectural firings with strict-mode enforcement; 3,675 blocked actions across audit stream. -- **Action on disconfirmation.** Audit `core/hooks/_reasoning_surface_validator.py` + `block_dangerous.py`; identify the bypass vector; emergency CP-FENCE-class patch; hard regression bar in CI. +- **Action on disconfirmation.** Audit `core/hooks/reasoning_surface_guard.py` + `block_dangerous.py`; identify the bypass vector; emergency CP-FENCE-class patch; hard regression bar in CI. ### A4 · Stateful interceptor catches bypass shapes @@ -67,7 +67,7 @@ A row whose action-on-disconfirmation is empty or generic is ceremonial. Either - **Falsification condition.** Agent writes `attack.sh` containing `git push`, runs `bash attack.sh`, and the push succeeds with NO RS challenge or refusal event recorded. - **Measurement method.** Adversarial test fixtures (need verification — list of bypass-vectors should live in `tests/test_bypass_*.py` if it doesn't already); systematic fuzzing across the bypass-vector taxonomy. - **Status.** **PARTIAL.** Normalized-command scanner + state-tracker exist; coverage of known bypass shapes verified. Coverage of unknown / future bypass shapes is structurally limited (see Ashby variety-mismatch / FAILURE_MODE 9). The interceptor is escalate-by-default for shapes outside its declared coverage; that is the kernel's response to the variety gap, not a claim of complete coverage. -- **Action on disconfirmation.** When a new bypass surfaces, expand the bypass-vector taxonomy in `system_prompts_leaks` adopt-from-adjacent-ecosystems list (CP-NORM-EXPAND-01, post-soak); patch `core/hooks/_normalized_command_scanner.py`; add regression test; record the prior-undetected vector as a Phase-12 audit data point. +- **Action on disconfirmation.** When a new bypass surfaces, expand the bypass-vector taxonomy in `system_prompts_leaks` adopt-from-adjacent-ecosystems list (CP-NORM-EXPAND-01, post-soak); patch `core/hooks/block_dangerous.py`; add regression test; record the prior-undetected vector as a Phase-12 audit data point. ### A5 · Pillar 3 protocols are context-scoped (only fire on matching context_signature) diff --git a/kernel/HOOKS_MAP.md b/kernel/HOOKS_MAP.md index 140a4e6..2b38fd5 100644 --- a/kernel/HOOKS_MAP.md +++ b/kernel/HOOKS_MAP.md @@ -9,7 +9,7 @@ Which runtime hook enforces which kernel invariant. Adapters register these hook | Constraint regime — forbidden actions | `block_dangerous.py` | PreToolUse/Bash | hard block on rm -rf, force push, sudo, mkfs, etc. | | Reasoning Surface — presence before high-impact op | `reasoning_surface_guard.py` | PreToolUse/Bash\|Write\|Edit\|MultiEdit | **blocks by default** (exit 2) when `.episteme/reasoning-surface.json` is missing, stale, incomplete, or contains lazy placeholders (none, n/a, tbd, 해당 없음, ...). Command text is normalized to catch `subprocess.run(['git','push'])` / `os.system('git push')` bypass shapes. Opt-out per-project: `touch .episteme/advisory-surface`. | | Frame stage — session boot context | `session_context.py` | SessionStart | prints git state, NEXT_STEPS, and Reasoning Surface status | -| Execute stage — docs alignment advisory | `workflow_guard.py` | PreToolUse/Write\|Edit\|MultiEdit | nudges agent to keep docs/PLAN.md, docs/EVENTS.md, and docs/NEXT_STEPS.md aligned | +| Execute stage — docs alignment advisory | `workflow_guard.py` | PreToolUse/Write\|Edit\|MultiEdit | nudges agent to keep docs/EVENTS.md and docs/NEXT_STEPS.md aligned | | Execute stage — prompt-injection defense | `prompt_guard.py` | PreToolUse/Write\|Edit\|MultiEdit | flags suspicious patterns in docs/, AGENTS.md, CLAUDE.md | | Execute stage — context pressure awareness | `context_guard.py` | PostToolUse | warns when compaction is near | | Verify stage — tests | `test_runner.py` | PostToolUse | runs affected tests after edits | diff --git a/kernel/REASONING_SURFACE.md b/kernel/REASONING_SURFACE.md index c379dce..605f525 100644 --- a/kernel/REASONING_SURFACE.md +++ b/kernel/REASONING_SURFACE.md @@ -3,7 +3,7 @@ **Operational summary:** - **Four fallback fields:** **Knowns** (verifiable), **Unknowns** (sharp), **Assumptions** (with falsification conditions), **Disconfirmation** (specific observable outcome). This is the fallback shape — it applies when no named blueprint scenario fires. - **Scenario-polymorphic blueprints (v1.0 RC+):** when a named scenario fires, the surface mutates to a **Cognitive Blueprint** whose required fields are the causal decomposition specific to that scenario's known failure class. Four named blueprints at v1.0 RC — see *Blueprint-polymorphic surface* below. -- Two required markers: **domain** (Clear/Complicated/Complex/Chaotic — posture changes accordingly) and **tacit_call** (true when the decision rests on calibrated expert intuition rather than articulable evidence). +- Two optional, logged markers: **domain** (Clear/Complicated/Complex/Chaotic — posture changes accordingly) and **tacit_call** (true when the decision rests on calibrated expert intuition rather than articulable evidence). - Fill always before irreversible or blast-radius actions; usually before non-trivial design choices. - Blank Unknowns = refusal signal. Knowns-as-assumptions = most common failure. Unfalsifiable plan = story, not plan. - **Update mechanic:** evidence updates plausibility; it does not flip booleans. An Assumption moves to Knowns only when the evidence is decisive. Otherwise it carries an updated plausibility and a sharpened falsification condition. diff --git a/kernel/REFERENCES.md b/kernel/REFERENCES.md index 7d6b723..2352496 100644 --- a/kernel/REFERENCES.md +++ b/kernel/REFERENCES.md @@ -831,7 +831,7 @@ AI RMF (NIST AI 100-1) covering CBRN and cyber-misuse risk. https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.800-1.ipd2.pdf** Episteme's Pillar 3 active-guidance suppression of vapor-verdict -synthesized protocols (`core/hooks/_guidance.query` verdict filter) is +synthesized protocols (`core/hooks/_guidance.py` verdict filter) is adjacent to AI 800-1 misuse-risk *measurement* discipline — guidance quality is the measurement signal. diff --git a/src/episteme/evidence/_packet.py b/src/episteme/evidence/_packet.py index 22fe43e..b469335 100644 --- a/src/episteme/evidence/_packet.py +++ b/src/episteme/evidence/_packet.py @@ -32,6 +32,17 @@ from pathlib import Path from typing import Any, Dict, Iterable, List, Optional +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.ptsp.canonical import jcs_canonical, sha256_hex from core.signing.ed25519_compat import sign_message, signature_mode from episteme.evidence._index import IndexEntry diff --git a/src/episteme/practice/_retro.py b/src/episteme/practice/_retro.py index d63933a..7ea7de7 100644 --- a/src/episteme/practice/_retro.py +++ b/src/episteme/practice/_retro.py @@ -15,6 +15,17 @@ import json from typing import Dict, List, Optional, cast +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.practice.cognitive_moves import get_move from core.practice.quality import observe_surfaces, PracticeRetrospective from episteme import _ui diff --git a/src/episteme/practice/_trace.py b/src/episteme/practice/_trace.py index 2dddad1..7cd7b59 100644 --- a/src/episteme/practice/_trace.py +++ b/src/episteme/practice/_trace.py @@ -43,6 +43,17 @@ from pathlib import Path from typing import Any, Dict, List, Optional +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.ptsp.types import ( Assumption, Fact, diff --git a/src/episteme/practice/_walk.py b/src/episteme/practice/_walk.py index 96e5a58..0abc89e 100644 --- a/src/episteme/practice/_walk.py +++ b/src/episteme/practice/_walk.py @@ -17,6 +17,17 @@ # pyright: reportMissingImports=false from typing import List +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.practice.cognitive_moves import ( moves_by_stage, ordered_stages, diff --git a/src/episteme/surface/_cli.py b/src/episteme/surface/_cli.py index accc357..1247363 100644 --- a/src/episteme/surface/_cli.py +++ b/src/episteme/surface/_cli.py @@ -21,6 +21,17 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.ptsp.canonical import sha256_hex, sha256_of_jcs from core.signing import ( SurfaceVerificationError, diff --git a/src/episteme/verify/_cli.py b/src/episteme/verify/_cli.py index 8dc4788..16beb84 100644 --- a/src/episteme/verify/_cli.py +++ b/src/episteme/verify/_cli.py @@ -30,6 +30,17 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple +# Event 172 — the installed entry point could not import `core.*` +# (repo-root package outside the installed tree), so this command +# CRASHED from the shipped CLI while the docs called it the operator's +# practice UX. Resolve the repo root from this file and put it on +# sys.path before the core imports; a no-op when already importable. +import sys as _sys +from pathlib import Path as _Path +_REPO_ROOT = _Path(__file__).resolve().parents[3] +if str(_REPO_ROOT) not in _sys.path: + _sys.path.insert(0, str(_REPO_ROOT)) + from core.signing.canonical_surface import ( SurfaceVerificationError, verify_surface, diff --git a/tests/doc_references_baseline.txt b/tests/doc_references_baseline.txt index a2483a4..e557b8a 100644 --- a/tests/doc_references_baseline.txt +++ b/tests/doc_references_baseline.txt @@ -18,7 +18,6 @@ docs/ADAPTER_PORTABILITY.md adapters/cursor docs/ADAPTER_PORTABILITY.md core/schemas/canonical-event.json docs/ANTHROPIC_MANAGED_AGENTS_BRIDGE.md core/memory/bridges/anthropic-managed -docs/COMPLIANCE_CROSSWALK.md core/manifest.py docs/CONTRACT_GATE.md contracts/orders.openapi.yaml docs/CUSTOMIZATION.md core/memory/global/build_story.md docs/HOW_TO_AUTHOR_SIGNED_SURFACE.md skills/compliance-evidence-layer @@ -34,8 +33,6 @@ kernel/REFERENCES.md core/patterns docs/SPEC_REASONING_SURFACE_BRANCHING.md core/hooks/_branch.py docs/SPEC_RIGOR_KNOB.md core/hooks/_rigor.py kernel/FALSIFIABILITY_CONDITIONS.md benchmarks/falsifiability -kernel/FALSIFIABILITY_CONDITIONS.md core/hooks/_normalized_command_scanner.py -kernel/FALSIFIABILITY_CONDITIONS.md core/hooks/_reasoning_surface_validator.py # # ── contract placeholders: per-project memory-contract docs, never instantiated ─ docs/HARNESSES.md docs/RUN_CONTEXT.md diff --git a/tests/test_commands_doc_drift.py b/tests/test_commands_doc_drift.py new file mode 100644 index 0000000..a193cc5 --- /dev/null +++ b/tests/test_commands_doc_drift.py @@ -0,0 +1,44 @@ +"""Event 172 — docs/COMMANDS.md claims to map EVERY subcommand; it had +silently dropped 12 (including `deferred`, central to the ledger, and +the four practice commands that were ALSO crashing from the installed +entry point). This diffs the argparse reality against the doc so the +promise is CI-enforced, not aspirational. +""" +from __future__ import annotations + +import re +from pathlib import Path + +from episteme.cli import build_parser # pyright: ignore[reportMissingImports] + +REPO = Path(__file__).resolve().parents[1] + + +def _cli_subcommands() -> set[str]: + parser = build_parser() + for action in parser._actions: # noqa: SLF001 + if hasattr(action, "choices") and action.choices: + return set(action.choices.keys()) + raise AssertionError("no subparsers found on the CLI parser") + + +def _documented() -> set[str]: + text = (REPO / "docs" / "COMMANDS.md").read_text(encoding="utf-8") + return set(re.findall(r"`episteme ([a-z0-9-]+)", text)) + + +def test_every_cli_subcommand_is_documented(): + missing = _cli_subcommands() - _documented() + assert not missing, ( + f"subcommands missing from docs/COMMANDS.md: {sorted(missing)} — " + f"the doc promises a map of EVERY subcommand; keep the promise " + f"or change the promise" + ) + + +def test_no_phantom_commands_documented(): + phantom = _documented() - _cli_subcommands() + assert not phantom, ( + f"docs/COMMANDS.md documents commands the CLI does not expose: " + f"{sorted(phantom)}" + )