diff --git a/.agents/skills/README.md b/.agents/skills/README.md index e6857f1..708296d 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -10,6 +10,7 @@ Installed skills: - `msdmd/` — Module Self-Declared Metadata Markdown - `test-build/` — test contract metadata blocks - `meta-module-build/` — metadata-first module scaffolding +- `new-retain-old/` — safe replacement workflow that preserves the old implementation while creating a new active path - `manifest/` — living-spec generator for `CLAUDE.md` (vendored from `The-Interdependency/skill-lib@d6e4d78`). Generates the mechanical facts block in `CLAUDE.md` from `backend/pyproject.toml` + the tree; the diff --git a/.agents/skills/new-retain-old/SKILL.md b/.agents/skills/new-retain-old/SKILL.md new file mode 100644 index 0000000..ec1f928 --- /dev/null +++ b/.agents/skills/new-retain-old/SKILL.md @@ -0,0 +1,96 @@ +--- +name: new-retain-old +description: Preserve the prior implementation while creating a replacement implementation with explicit rollback, archive, metadata, tests, docs, and hmmm boundaries. Use when a user asks to replace, rewrite, rebuild, redesign, rename current to *_old, archive old backend/frontend/module/service, create new while retaining old, or when friction shows the old behavior must remain available during a new implementation. +--- + +# new-retain-old — create new while retaining old + +## Purpose + +Use this skill when the change is not a simple edit: it creates a new active +surface while keeping the prior surface available as an explicit rollback and +reference object. + +The goal is to prevent three failures: + +1. deleting useful old behavior before the replacement is proven; +2. hiding uncertainty about what moved, changed, or stayed unresolved; +3. making the new path technically correct but hard to run, test, or review. + +## Workflow + +1. **Name the active and retained surfaces.** + - Active path: the new implementation users should use now. + - Retained path: the old implementation, usually `_old`, `legacy/`, or + `archive/-`. + - If the user named the retained path, use that exact name. + +2. **Inventory before moving.** + - Inspect package metadata, imports, tests, docs, examples, data files, and + entry points. + - Identify what must remain runnable from the retained path. + - Record unknowns as `hmmm`; do not invent certainty. + +3. **Move old first, then build new.** + - Preserve the old tree with history-friendly renames where possible. + - Do not leave the active path half-old and half-new unless the user asked + for an incremental migration. + - Keep licenses and data files with the retained implementation unless there + is a clear reason not to. + +4. **Declare the new module beside the code.** + - Add `MODULE_BUILD` for the new active module. + - Add `CONTRACTS` for tests the module promises. + - Add `DEPENDENCIES` when imports/calls matter. + - Add `BOUNDARIES` when runtime effects, data, permissions, storage, + network, or admin behavior matter. + - Add `DOCS` when public usage guidance exists or should exist. + +5. **Make the new path few-click usable.** + - Provide a copy-paste install/run path. + - Include a smoke example or minimal demo. + - Test the new path from outside the repo root when packaging matters. + - Avoid relying on ambient `PYTHONPATH` unless explicitly documented as a + temporary hmmm boundary. + +6. **Test both the promise and the transition.** + - Test core new behavior. + - Test import/dependency boundaries that motivated the rewrite. + - Test that the retained path exists. + - Test docs/examples when they are the user entry point. + +7. **Report with a boundary object.** + - Delivered: what is now active, retained, tested, and documented. + - hmmm: unresolved constraints, compatibility gaps, migration risks, or next + continuation steps. + +## Required checks before commit + +- `git status --short` shows only intentional active/new/retained changes. +- New active path has usage guidance. +- Retained path is named clearly and mentioned in docs or PR text. +- Tests cover at least one behavior of the new path and one transition invariant + such as import hygiene, package self-containment, or retained-path presence. +- Any unresolved behavior is written as `hmmm`, not omitted. + +## PR body checklist + +Include: + +```text +active_path: +retained_path: +rollback: restore retained_path or switch import/entry point back +few_click_path: +tests: +hmmm: +``` + +## Anti-patterns + +- Calling the old path “backup” without documenting how to restore it. +- Replacing package metadata but not testing installation. +- Adding examples that only work because the agent shell has special paths. +- Flattening `hmmm` into prose that cannot be tested or carried forward. +- Treating archived old code as dead if users still need it for comparison, + rollback, or migration. diff --git a/.agents/skills/new-retain-old/agents/openai.yaml b/.agents/skills/new-retain-old/agents/openai.yaml new file mode 100644 index 0000000..b7b5eeb --- /dev/null +++ b/.agents/skills/new-retain-old/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "New While Retaining Old" + short_description: "Safe replacement with retained old path" + default_prompt: "Use this skill to replace an implementation while preserving the old one, documenting rollback, and testing the transition." diff --git a/backend/README.md b/backend/README.md index 71f8455..8d02b90 100644 --- a/backend/README.md +++ b/backend/README.md @@ -23,14 +23,6 @@ fields. If that prints, the backend is installed and usable. After installation, use the backend directly: ```python -## Usage Guidance - -Install or run from this repository with both the repository root and backend -source path on `PYTHONPATH` so the backend can import the UCNS compatibility -module: - -```bash -PYTHONPATH="$PWD:$PWD/backend/src" python - <<'PY' import edcmbone_backend as backend boundary = backend.make_boundary( @@ -38,7 +30,6 @@ boundary = backend.make_boundary( "remaining constraint to carry forward", ) print(backend.serialize_boundary(boundary)) -PY ``` ### Boundary lifecycle @@ -59,7 +50,6 @@ PY - Runtime dependency boundary: active backend source imports only `ucns`. - Packaging boundary: the backend wheel includes a local `ucns` package so users do not need to put the repository root on `PYTHONPATH`. -- Runtime dependency boundary: backend source imports only `ucns`. - Risk boundary: in-memory only; no auth, storage, network, admin, or secret side effects. - Documentation and runtime boundaries are declared in msdmd blocks beside the diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7fab17c..a8dfe5e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -13,7 +13,6 @@ dependencies = [] [tool.hatch.build.targets.wheel] packages = ["src/edcmbone_backend", "src/ucns"] -packages = ["src/edcmbone_backend"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/backend/src/edcmbone_backend/__init__.py b/backend/src/edcmbone_backend/__init__.py index 75bad0e..6963ce7 100644 --- a/backend/src/edcmbone_backend/__init__.py +++ b/backend/src/edcmbone_backend/__init__.py @@ -23,7 +23,6 @@ # rollback: restore backend_old as backend # requires: ucns # since: 2026-06-25 -# unresolved: The requested The-Interdependency/skill-lib path was not present in this checkout; local .agents/skills was used as the msdmd/test-build source. # unresolved: none; canonical https://github.com/The-Interdependency/skill-lib guidance verified on 2026-06-25 # === END MODULE_BUILD === @@ -175,7 +174,6 @@ def make_boundary(delivered, unresolved=None): """Create a UCNS-backed boundary object from delivered and unresolved text.""" unresolved_text = _coerce_text(unresolved) return BoundaryObject(delivered, hmmm(unresolved_text), _anchor(0 if unresolved_text else 1)) - return BoundaryObject(delivered, hmmm(unresolved), _anchor(0 if unresolved else 1)) def merge_boundaries(left, right): @@ -184,9 +182,6 @@ def merge_boundaries(left, right): return BoundaryObject( "\n".join(part for part in (left.delivered, right.delivered) if part), hmmm(unresolved), - return BoundaryObject( - "\n".join(part for part in (left.delivered, right.delivered) if part), - "\n".join(str(part) for part in (left.hmmm, right.hmmm) if part), ucns.multiply(left.ucns_object, right.ucns_object), ) diff --git a/backend/src/ucns/__init__.py b/backend/src/ucns/__init__.py index 22348b9..8614068 100644 --- a/backend/src/ucns/__init__.py +++ b/backend/src/ucns/__init__.py @@ -1,5 +1,6 @@ """ ucns_v04 — UCNS Engine (turn-fraction angle convention) +======================================================== Angles are stored as Fraction objects representing fractions of a full turn: 0 = 0 deg, 1/4 = 90 deg, 1/2 = 180 deg, 2 = 720 deg = 0 on doubled cover. @@ -203,8 +204,3 @@ def multiply(A: UCNSObject, B: UCNSObject) -> UCNSObject: anchors_pos=tuple(new_anchors), faces_pos=tuple(new_faces), ) -"""Compatibility name for the UCNS v0.4 engine.""" - -from .ucns_v04 import AnchorPayload, UCNSObject, is_unit_payload, multiply, unit_obj - -__all__ = ["AnchorPayload", "UCNSObject", "is_unit_payload", "multiply", "unit_obj"] diff --git a/backend/tests/test_backend_contracts.py b/backend/tests/test_backend_contracts.py index 31af8c9..ccc2740 100644 --- a/backend/tests/test_backend_contracts.py +++ b/backend/tests/test_backend_contracts.py @@ -77,6 +77,7 @@ def test_boundaries_record_no_hidden_side_effects(): def test_backend_src_path_is_self_contained(tmp_path): + import os import subprocess import sys @@ -86,10 +87,13 @@ def test_backend_src_path_is_self_contained(tmp_path): assert boundary.ucns_object.n_min == 1 assert backend.serialize_boundary(boundary)['hmmm']['text'] == 'unresolved' """ + env = os.environ.copy() + env["PYTHONPATH"] = str(Path(__file__).resolve().parents[1] / "src") result = subprocess.run( [sys.executable, "-c", code], cwd=tmp_path, - env={"PYTHONPATH": str(Path(__file__).resolve().parents[1] / "src")}, + env={**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parents[1] / "src")}, + env=env, text=True, capture_output=True, check=False,