From 50400aa1280fec3902c13cacf522644c55a438d2 Mon Sep 17 00:00:00 2001 From: ProfRandom92 Date: Fri, 22 May 2026 10:22:43 +0200 Subject: [PATCH 1/3] feat: add MCP context CLI entrypoint --- docs/mcp_context_layer.md | 14 +++++ scripts/mcp_context_cli.py | 93 +++++++++++++++++++++++++++++++++ tests/test_mcp_context_layer.py | 80 ++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 scripts/mcp_context_cli.py diff --git a/docs/mcp_context_layer.md b/docs/mcp_context_layer.md index d1361d5..8a05a15 100644 --- a/docs/mcp_context_layer.md +++ b/docs/mcp_context_layer.md @@ -86,6 +86,20 @@ example generated from `fixtures/mcp_trace_replay_v1/original`. It contains the compact replay payload, prompt-context rendering, and validation result for the baseline MCP trace replay fixture. +## CLI usage + +Generate compact prompt context with validation from the baseline MCP fixture: + +```bash +python scripts/mcp_context_cli.py \ + --fixture fixtures/mcp_trace_replay_v1/original \ + --render-prompt \ + --validate +``` + +Add `--json` to emit deterministic JSON containing the compact replay payload, +optional prompt context, and optional validation result. + ## Relationship to MCP This layer augments context integrity for MCP-compatible systems. It is not an diff --git a/scripts/mcp_context_cli.py b/scripts/mcp_context_cli.py new file mode 100644 index 0000000..4efb983 --- /dev/null +++ b/scripts/mcp_context_cli.py @@ -0,0 +1,93 @@ +"""Deterministic CLI for compact MCP context-layer outputs.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +REPO_ROOT = Path(__file__).resolve().parents[1] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from src.comptext_v7.mcp import build_replay_payload, render_prompt_context, validate_replay_payload + +DEFAULT_FIXTURE = Path("fixtures/mcp_trace_replay_v1/original") + + +def _load_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise RuntimeError(f"missing required fixture file: {path.as_posix()}") from exc + if not isinstance(payload, dict): + raise RuntimeError(f"fixture file must contain a JSON object: {path.as_posix()}") + return payload + + +def _repo_relative(path: Path) -> str: + resolved = path.resolve() + try: + return resolved.relative_to(REPO_ROOT).as_posix() + except ValueError: + return path.as_posix() + + +def load_fixture_context(fixture: Path) -> dict[str, Any]: + fixture_path = fixture if fixture.is_absolute() else REPO_ROOT / fixture + return { + "task": fixture_path.parent.name, + "trace": _load_json(fixture_path / "trace.json"), + "state": _load_json(fixture_path / "state.json"), + "dependency_graph": _load_json(fixture_path / "dependency_graph.json"), + } + + +def build_cli_output(fixture: Path, *, include_prompt: bool, include_validation: bool) -> dict[str, Any]: + fixture_path = fixture if fixture.is_absolute() else REPO_ROOT / fixture + replay_payload = build_replay_payload(load_fixture_context(fixture_path)) + output: dict[str, Any] = { + "replay_payload": replay_payload, + "source_fixture_path": _repo_relative(fixture_path), + } + + validation = validate_replay_payload(replay_payload) if include_validation else None + if validation is not None: + output["validation"] = validation + if include_prompt: + prompt_payload = {**replay_payload} + if validation is not None: + prompt_payload["validation"] = validation + output["prompt_context"] = render_prompt_context(prompt_payload) + + return output + + +def _parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Generate deterministic MCP context-layer output from a fixture.") + parser.add_argument("--fixture", type=Path, default=DEFAULT_FIXTURE, help="Fixture directory containing trace/state/dependency_graph JSON files.") + parser.add_argument("--json", action="store_true", help="Emit deterministic JSON output.") + parser.add_argument("--render-prompt", action="store_true", help="Include or emit compact prompt context.") + parser.add_argument("--validate", action="store_true", help="Include validation/admissibility result.") + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = _parse_args(sys.argv[1:] if argv is None else argv) + output = build_cli_output(args.fixture, include_prompt=args.render_prompt, include_validation=args.validate) + + if args.json or not args.render_prompt: + sys.stdout.write(json.dumps(output, indent=2, sort_keys=True) + "\n") + return 0 + + prompt_context = output.get("prompt_context") + if not isinstance(prompt_context, str): + prompt_context = render_prompt_context(output["replay_payload"]) + sys.stdout.write(prompt_context + "\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_mcp_context_layer.py b/tests/test_mcp_context_layer.py index 5b2423b..2079e35 100644 --- a/tests/test_mcp_context_layer.py +++ b/tests/test_mcp_context_layer.py @@ -1,6 +1,8 @@ from __future__ import annotations import json +import subprocess +import sys from pathlib import Path import pytest @@ -22,6 +24,7 @@ FIXTURE_ROOT = Path("fixtures/mcp_trace_replay_v1/original") ARTIFACT_PATH = Path("artifacts/mcp_context_layer_example.json") +CLI_PATH = Path("scripts/mcp_context_cli.py") def _load_fixture_context() -> dict[str, object]: @@ -284,3 +287,80 @@ def test_mcp_context_layer_artifact_excludes_raw_trace_state_and_graph() -> None assert '"graph_version"' not in artifact_text assert '"dependency_graph"' not in artifact_text assert '"permission_scopes"' not in artifact_text + + +def _run_cli(*args: str) -> str: + completed = subprocess.run( + [sys.executable, str(CLI_PATH), *args], + check=True, + capture_output=True, + text=True, + ) + return completed.stdout + + +def test_mcp_context_cli_json_output_is_deterministic() -> None: + args = ( + "--fixture", + str(FIXTURE_ROOT), + "--json", + "--render-prompt", + "--validate", + ) + + first = _run_cli(*args) + second = _run_cli(*args) + + assert first == second + assert first.endswith("\n") + payload = json.loads(first) + assert set(payload.keys()) == { + "prompt_context", + "replay_payload", + "source_fixture_path", + "validation", + } + assert payload["source_fixture_path"] == "fixtures/mcp_trace_replay_v1/original" + assert payload["replay_payload"] == build_replay_payload(_load_fixture_context()) + assert payload["validation"] == validate_replay_payload(payload["replay_payload"]) + assert payload["prompt_context"] == render_prompt_context( + { + **payload["replay_payload"], + "validation": payload["validation"], + } + ) + + +def test_mcp_context_cli_prompt_output_is_compact_and_deterministic() -> None: + args = ( + "--fixture", + str(FIXTURE_ROOT), + "--render-prompt", + "--validate", + ) + + first = _run_cli(*args) + second = _run_cli(*args) + + assert first == second + assert first == render_prompt_context( + { + **build_replay_payload(_load_fixture_context()), + "validation": validate_replay_payload(build_replay_payload(_load_fixture_context())), + } + ) + "\n" + assert "events" not in first + assert "dependency_graph" not in first + assert "permission_scopes" not in first + + +def test_mcp_context_cli_payload_json_excludes_raw_trace_state_and_graph() -> None: + output = _run_cli("--fixture", str(FIXTURE_ROOT), "--json") + + assert '"events"' not in output + assert '"state_version"' not in output + assert '"graph_version"' not in output + assert '"dependency_graph"' not in output + assert '"permission_scopes"' not in output + payload = json.loads(output) + assert set(payload.keys()) == {"replay_payload", "source_fixture_path"} From da482dbf15cbd336c6bef3616635b639ca2b29ba Mon Sep 17 00:00:00 2001 From: ProfRandom92 Date: Fri, 22 May 2026 10:25:52 +0200 Subject: [PATCH 2/3] docs: add Codex workflow skill docs --- docs/codex_skills/artifact-validation.md | 47 ++++++++++++++++++++++ docs/codex_skills/docs-positioning.md | 49 +++++++++++++++++++++++ docs/codex_skills/git-pr-workflow.md | 51 ++++++++++++++++++++++++ docs/codex_skills/mcp-context-layer.md | 47 ++++++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 docs/codex_skills/artifact-validation.md create mode 100644 docs/codex_skills/docs-positioning.md create mode 100644 docs/codex_skills/git-pr-workflow.md create mode 100644 docs/codex_skills/mcp-context-layer.md diff --git a/docs/codex_skills/artifact-validation.md b/docs/codex_skills/artifact-validation.md new file mode 100644 index 0000000..eb4c933 --- /dev/null +++ b/docs/codex_skills/artifact-validation.md @@ -0,0 +1,47 @@ +# Artifact Validation Skill + +## Purpose + +Create or update deterministic artifact checks without changing benchmark meaning or unrelated generated outputs. + +## When to use + +Use for artifacts under `artifacts/`, generator scripts under `scripts/`, regeneration parity tests, stable JSON checks, and committed artifact reproducibility. + +## Allowed actions + +- Inspect existing artifact and generator patterns first. +- Use deterministic JSON with stable key ordering and stable list ordering. +- Prefer generator-to-committed-artifact parity tests. +- Compare byte-identical text when formatting and key order matter. +- Use fixture-bound inputs and explicit metadata such as `artifact_id`, `version`, `evaluation_mode`, `llm_judges`, and `external_apis` when consistent with nearby artifacts. + +## Forbidden actions + +- No timestamps, random IDs, environment-dependent fields, or network calls. +- No semantic scoring, embeddings, vector DB, external APIs, or LLM judging. +- No unrelated artifact rewrites. +- No benchmark semantic changes unless explicitly requested. +- No broad refactors. +- No commit or push unless explicitly requested. + +## Required validation + +- Run the focused artifact test. +- Run the exact generator into a temporary path when testing regeneration. +- Confirm `git diff --stat` only shows intended artifact, script, test, or doc files. + +## Stop conditions + +- Stop if generated output differs from the committed artifact and the reason is unclear. +- Stop if deterministic metadata cannot be defined without inventing claims. +- Stop if the task would require regenerating unrelated artifacts. + +## Preferred prompt pattern + +```text +Task: add or tighten one deterministic artifact validation. +Allowed files: one artifact, its generator, focused tests, optional docs. +Validation: generate to tmp, compare to committed artifact, run focused tests. +Done when: output is byte-identical or exact JSON-equivalent as specified. +``` diff --git a/docs/codex_skills/docs-positioning.md b/docs/codex_skills/docs-positioning.md new file mode 100644 index 0000000..1fa2f41 --- /dev/null +++ b/docs/codex_skills/docs-positioning.md @@ -0,0 +1,49 @@ +# Docs Positioning Skill + +## Purpose + +Update CompTextv7 documentation while preserving conservative replay-integrity positioning and scope boundaries. + +## When to use + +Use for README, docs, reports, positioning, benchmark explanations, MCP docs, artifact narratives, and PR-facing summaries. + +## Allowed actions + +- Inspect existing docs and nearby wording before editing. +- Keep claims fixture-bound, deterministic, and artifact-backed. +- Prefer small docs-only patches for positioning changes. +- Separate core CompTextv7 documentation from showcase or brand/demo work. +- Use exact validation or artifact names when referencing evidence. +- State limitations and non-goals clearly. + +## Forbidden actions + +- No production-ready, clinical-grade, universal memory, or solved-memory claims. +- No semantic scoring, embeddings, vector DB, external APIs, or LLM judging claims. +- No autonomous agent framework, workflow orchestrator, runtime tool execution, or policy-engine positioning. +- No invented benchmark results or unrun validation claims. +- No Chilli/Hatch/Pet or showcase asset changes unless explicitly requested. +- No commit or push unless explicitly requested. + +## Required validation + +- Run `git diff --stat`. +- Confirm only intended docs files changed. +- Run no broad checks unless the docs change is tied to generated artifacts or explicitly requested. + +## Stop conditions + +- Stop if documentation would broaden project identity beyond deterministic replay-integrity validation. +- Stop if a metric lacks a committed artifact or exact source. +- Stop if requested copy conflicts with repository non-goals. + +## Preferred prompt pattern + +```text +Task: make one docs-only positioning update. +Allowed files: exact docs paths. +Constraints: fixture-bound claims, no overclaims, no showcase/core mixing. +Validation: git diff --stat and changed-file scope check. +Done when: wording matches existing CompTextv7 positioning and non-goals. +``` diff --git a/docs/codex_skills/git-pr-workflow.md b/docs/codex_skills/git-pr-workflow.md new file mode 100644 index 0000000..538cc63 --- /dev/null +++ b/docs/codex_skills/git-pr-workflow.md @@ -0,0 +1,51 @@ +# Git PR Workflow Skill + +## Purpose + +Handle CompTextv7 branch sync, local commits, pushes, and pull-request setup with staged-file discipline. + +## When to use + +Use for branch setup, syncing from `main`, local commits, staged-file review, pushing current branches, PR creation, and CI follow-up. + +## Allowed actions + +- Inspect `git status --short` before git operations. +- Use non-destructive git commands. +- Stage only explicitly allowed paths. +- Show staged files with `git diff --cached --name-only` before committing. +- Commit only after staged files match the requested scope. +- Push only when explicitly requested and the working tree is clean. +- Create a PR only when explicitly requested and GitHub CLI is available and authenticated. + +## Forbidden actions + +- No force push. +- No merge unless explicitly requested. +- No branch deletion. +- No destructive commands such as `git reset --hard` or `git clean -fd`. +- No changing commits unless explicitly requested. +- No commit or push unless explicitly requested. + +## Required validation + +- Before commit: `git status --short`, `git diff --stat`, and `git diff --cached --name-only`. +- Before push: clean `git status --short` and current branch confirmation. +- For PRs: include validation commands actually run; do not claim CI status unless checked. + +## Stop conditions + +- Stop if working tree contains unrelated changes. +- Stop if staged files exceed the allowed scope. +- Stop if GitHub CLI is missing or unauthenticated; print manual commands. +- Stop if sync or push fails. + +## Preferred prompt pattern + +```text +Task: create a local commit or PR for current scoped changes. +Allowed files: exact paths. +Validation already run: exact commands. +Steps: status, stage exact files, show staged list, commit/push only if scoped. +Done when: local commit or PR is created, no merge performed. +``` diff --git a/docs/codex_skills/mcp-context-layer.md b/docs/codex_skills/mcp-context-layer.md new file mode 100644 index 0000000..15e11f0 --- /dev/null +++ b/docs/codex_skills/mcp-context-layer.md @@ -0,0 +1,47 @@ +# MCP Context Layer Skill + +## Purpose + +Work on the lightweight MCP-compatible replay-aware context layer while preserving CompTextv7 replay-integrity boundaries. + +## When to use + +Use for changes involving `src/comptext_v7/mcp/`, `scripts/mcp_context_cli.py`, compact replay payloads, prompt-context rendering, validation, save/load helpers, or token-light replay-safe context. + +## Allowed actions + +- Inspect repository files before editing. +- Make the smallest safe patch. +- Reuse `build_replay_payload`, `render_prompt_context`, `validate_replay_payload`, `ContextStore`, `save_context`, and `load_context`. +- Keep examples fixture-bound, usually `fixtures/mcp_trace_replay_v1/original`. +- Add focused tests in `tests/test_mcp_context_layer.py`. +- Update `docs/mcp_context_layer.md` when public behavior changes. + +## Forbidden actions + +- No semantic scoring, embeddings, vector DB, external APIs, or LLM judging. +- No autonomous orchestration or runtime tool execution unless explicitly scoped. +- No broad refactors or changes to existing benchmark semantics. +- No ContractValidator behavior changes unless explicitly requested. +- No commit or push unless explicitly requested. + +## Required validation + +- `python -m compileall -q src/comptext_v7/mcp` +- `pytest tests/test_mcp_context_layer.py -q` +- If CLI behavior changes, run the affected `python scripts/mcp_context_cli.py ...` command. + +## Stop conditions + +- Stop if fixture shape is unclear. +- Stop if a change requires non-deterministic behavior. +- Stop if requested behavior would broaden MCP context into orchestration, policy enforcement, or tool execution. + +## Preferred prompt pattern + +```text +Task: make one deterministic MCP context-layer change. +Allowed files: exact MCP files, focused tests, and docs. +Validation: compile MCP package and run tests/test_mcp_context_layer.py. +Done when: replay-safe ordering, validation output, and no raw trace leakage are verified. +``` From d0b6df65688deea9f7c771f5dd7a86b5cc0744ba Mon Sep 17 00:00:00 2001 From: ProfRandom92 Date: Fri, 22 May 2026 10:38:57 +0200 Subject: [PATCH 3/3] fix: harden MCP context CLI JSON loading --- scripts/mcp_context_cli.py | 23 +++++++++++++---------- tests/test_mcp_context_layer.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/scripts/mcp_context_cli.py b/scripts/mcp_context_cli.py index 4efb983..524759b 100644 --- a/scripts/mcp_context_cli.py +++ b/scripts/mcp_context_cli.py @@ -17,16 +17,6 @@ DEFAULT_FIXTURE = Path("fixtures/mcp_trace_replay_v1/original") -def _load_json(path: Path) -> dict[str, Any]: - try: - payload = json.loads(path.read_text(encoding="utf-8")) - except FileNotFoundError as exc: - raise RuntimeError(f"missing required fixture file: {path.as_posix()}") from exc - if not isinstance(payload, dict): - raise RuntimeError(f"fixture file must contain a JSON object: {path.as_posix()}") - return payload - - def _repo_relative(path: Path) -> str: resolved = path.resolve() try: @@ -35,6 +25,19 @@ def _repo_relative(path: Path) -> str: return path.as_posix() +def _load_json(path: Path) -> dict[str, Any]: + display_path = _repo_relative(path) + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise RuntimeError(f"missing required fixture file: {display_path}") from exc + except json.JSONDecodeError as exc: + raise RuntimeError(f"invalid JSON in fixture file: {display_path}: {exc}") from exc + if not isinstance(payload, dict): + raise RuntimeError(f"fixture file must contain a JSON object: {display_path}") + return payload + + def load_fixture_context(fixture: Path) -> dict[str, Any]: fixture_path = fixture if fixture.is_absolute() else REPO_ROOT / fixture return { diff --git a/tests/test_mcp_context_layer.py b/tests/test_mcp_context_layer.py index 2079e35..c400948 100644 --- a/tests/test_mcp_context_layer.py +++ b/tests/test_mcp_context_layer.py @@ -20,6 +20,7 @@ EXAMPLE_FIXTURE_ID, generate_mcp_context_layer_example_artifact, ) +from scripts.mcp_context_cli import REPO_ROOT, _load_json FIXTURE_ROOT = Path("fixtures/mcp_trace_replay_v1/original") @@ -364,3 +365,32 @@ def test_mcp_context_cli_payload_json_excludes_raw_trace_state_and_graph() -> No assert '"permission_scopes"' not in output payload = json.loads(output) assert set(payload.keys()) == {"replay_payload", "source_fixture_path"} + + +def test_mcp_context_cli_load_json_reports_repo_relative_missing_path() -> None: + missing_path = REPO_ROOT / "fixtures" / "mcp_trace_replay_v1" / "original" / "missing.json" + + with pytest.raises(RuntimeError) as excinfo: + _load_json(missing_path) + + assert str(excinfo.value) == "missing required fixture file: fixtures/mcp_trace_replay_v1/original/missing.json" + + +def test_mcp_context_cli_load_json_reports_invalid_json(tmp_path: Path) -> None: + invalid_path = tmp_path / "trace.json" + invalid_path.write_text("{invalid-json", encoding="utf-8") + + with pytest.raises(RuntimeError) as excinfo: + _load_json(invalid_path) + + assert str(excinfo.value).startswith(f"invalid JSON in fixture file: {invalid_path.as_posix()}:") + + +def test_mcp_context_cli_load_json_requires_json_object(tmp_path: Path) -> None: + list_path = tmp_path / "trace.json" + list_path.write_text("[]\n", encoding="utf-8") + + with pytest.raises(RuntimeError) as excinfo: + _load_json(list_path) + + assert str(excinfo.value) == f"fixture file must contain a JSON object: {list_path.as_posix()}"