agentsclimarketplace

Michat dev frozen toolset parity

Skill filmicgaze/MiChat-desktop/.codex/skills/michat-dev-frozen-toolset-parity

Prevent dev vs frozen tool behavior drift in MiChat. Use whenever changing toolset code, tool schemas/metadata, toolset configs, loader behavior, or build/spec wiring that affects tool loading.From its SKILL.md

Install
npx -y skills add filmicgaze/MiChat-desktop --skill michat-dev-frozen-toolset-parity

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.9 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it

MiChat Dev/Frozen Toolset Parity

Internal toolsets are canonical. Frozen behavior must match for the same profile/toolsets/config.

Canonical rule

  • Treat michat/toolsets/<id>/__init__.py as canonical behavior.
  • Keep toolsets/<id>/toolset.py behavior-equivalent for frozen runtime.
  • Treat external config files (for example toolsets/<id>/config.json) as runtime contract files.

Common failure modes

  1. Internal/external code drift
  • Internal toolset changed; external toolset.py not updated.
  1. External config drift
  • Runtime config in toolsets/<id>/ no longer matches intended behavior.
  1. Loader path mismatch
  • Dev path tested (michat.toolsets.*) but frozen path (_load_external_toolset) not tested.
  1. Build/staging mismatch
  • Dist contains stale/missing external toolset code or config.

Required workflow when touching toolsets

1) Auto-detect touched toolset IDs

Do not hand-maintain toolset lists.

Use changed files from current working tree vs HEAD, then derive IDs from both trees:

  • internal pattern: michat/toolsets/<id>/...
  • external pattern: toolsets/<id>/...

If nothing is detected, ask for explicit IDs before proceeding.

@'
import re, subprocess

changed = subprocess.run(
    ["git", "diff", "--name-only", "HEAD"],
    capture_output=True, text=True, check=True
).stdout.splitlines()

ids = set()
for p in changed:
    m = re.match(r"michat/toolsets/([^/]+)/", p.replace("\\\\", "/"))
    if m:
        ids.add(m.group(1))
    m = re.match(r"toolsets/([^/]+)/", p.replace("\\\\", "/"))
    if m:
        ids.add(m.group(1))

print(" ".join(sorted(ids)))
'@ | python -

2) Implement with mirror discipline

  • Apply behavior change in internal canonical module.
  • Mirror behavior-critical changes into external module.
  • Mirror required external config changes (toolsets/<id>/*.json etc.).
  • Update toolsets/<id>/TOOLSET.md if contract text changed.

3) Run contract parity checks (required)

Use frozen-style module loading and canonicalized schema comparison.

Parity checks must compare, for each touched toolset:

  • get_tools(...) contract shape
  • get_handlers(...) tool name set
  • tool metadata key set (get_tool_metadata or TOOL_METADATA)

Canonicalize before comparing:

  • sort dict keys recursively
  • preserve list order by default
  • sort only known order-insensitive scalar lists (required, enum)
@'
import importlib
import json
import re
import subprocess
import sys
from pathlib import Path

import michat.toolsets as registry
from michat.profile_loader import load_profile
from michat.runtime_paths import get_profiles_dir

ORDER_INSENSITIVE_LIST_KEYS = {"required", "enum"}

def norm(v, key=None):
    if isinstance(v, dict):
        return {k: norm(v[k], k) for k in sorted(v.keys())}
    if isinstance(v, list):
        out = [norm(x, key) for x in v]
        if key in ORDER_INSENSITIVE_LIST_KEYS and all(isinstance(x, (str, int, float, bool, type(None))) for x in out):
            return sorted(out, key=lambda x: json.dumps(x, sort_keys=True))
        return out
    return v

def get_tool_sig(mod, profile, context):
    rows = {}
    for t in (mod.get_tools(profile=profile, context=context) or []):
        if isinstance(t, dict) and isinstance(t.get("name"), str):
            rows[t["name"]] = {
                "type": t.get("type"),
                "parameters": norm(t.get("parameters")),
            }
    return norm(rows)

def get_handler_sig(mod, profile, context):
    h = mod.get_handlers(profile=profile, context=context) or {}
    return sorted([k for k in h.keys() if isinstance(k, str)])

def get_metadata_sig(mod, profile, context):
    meta = None
    gm = getattr(mod, "get_tool_metadata", None)
    if callable(gm):
        try:
            meta = gm(profile=profile, context=context)
        except Exception:
            meta = None
    if meta is None:
        meta = getattr(mod, "TOOL_METADATA", None)
    if not isinstance(meta, dict):
        return []
    return sorted([k for k in meta.keys() if isinstance(k, str)])

def touched_ids():
    changed = subprocess.run(
        ["git", "diff", "--name-only", "HEAD"],
        capture_output=True, text=True, check=True
    ).stdout.splitlines()
    ids = set()
    for p in changed:
        p = p.replace("\\\\", "/")
        m = re.match(r"michat/toolsets/([^/]+)/", p)
        if m:
            ids.add(m.group(1))
        m = re.match(r"toolsets/([^/]+)/", p)
        if m:
            ids.add(m.group(1))
    return sorted(ids)

ids = touched_ids()
if not ids:
    raise SystemExit("No touched toolset IDs detected from git diff --name-only HEAD.")

profile = None
context = {}
assumption = None

try:
    profile = load_profile("assistant")
    profile_dir = Path(profile["profile_dir"])
    workspace_root = profile.get("workspace_root")
    context = {
        "profile_dir": str(profile_dir),
        "skills_root": str(profile_dir / "skills"),
        "global_skills_root": str(get_profiles_dir() / "_global" / "skills"),
        "workspace_root": workspace_root,
        "allow_skill_editing": bool(profile.get("allow_skill_editing", False)),
        "allow_web_browsing": bool(profile.get("allow_web_browsing", False)),
    }
except Exception as exc:
    assumption = f"profile-aware context unavailable; using neutral context only ({exc})"
    profile = None
    context = {}

if assumption:
    print(f"ASSUMPTION: {assumption}")

for name in ids:
    internal = importlib.import_module(f"michat.toolsets.{name}")
    sys.modules.pop(f"michat.toolsets.{name}", None)
    external = registry._load_external_toolset(name)

    if "toolsets" not in str(external.__file__).replace("\\\\", "/"):
        raise AssertionError(f"{name}: external module path unexpected: {external.__file__}")

    assert get_tool_sig(internal, profile, context) == get_tool_sig(external, profile, context), f"{name}: get_tools mismatch"
    assert get_handler_sig(internal, profile, context) == get_handler_sig(external, profile, context), f"{name}: get_handlers mismatch"
    assert get_metadata_sig(internal, profile, context) == get_metadata_sig(external, profile, context), f"{name}: metadata mismatch"
    print(f"parity-ok: {name}")
'@ | python -

4) Run minimal behavior smoke tests (required)

For each touched toolset, run:

  • 1 happy-path call
  • 1 expected-rejection call
  • in both internal and frozen-style external modules when practical

Use temp directories and patch OS side effects where needed.

5) Build and verify dist staging (required for touched toolsets)

Build the target edition:

powershell -ExecutionPolicy Bypass -File .\scripts\build_windows.ps1 -Edition public

Then verify source vs dist hashes for touched external files:

  • always: toolsets/<id>/toolset.py
  • include external config files under toolsets/<id>/ (*.json, *.yaml, *.yml)
@'
import hashlib
import re
import subprocess
from pathlib import Path

repo = Path(".").resolve()
dist = repo / "dist" / "MiChat" / "toolsets"

changed = subprocess.run(
    ["git", "diff", "--name-only", "HEAD"],
    capture_output=True, text=True, check=True
).stdout.splitlines()

ids = set()
for p in changed:
    p = p.replace("\\\\", "/")
    m = re.match(r"michat/toolsets/([^/]+)/", p)
    if m:
        ids.add(m.group(1))
    m = re.match(r"toolsets/([^/]+)/", p)
    if m:
        ids.add(m.group(1))

def sha256(path: Path) -> str:
    h = hashlib.sha256()
    h.update(path.read_bytes())
    return h.hexdigest()

for tid in sorted(ids):
    src_root = repo / "toolsets" / tid
    dst_root = dist / tid
    if not src_root.is_dir():
        continue
    for src in [src_root / "toolset.py"] + sorted(src_root.glob("*.json")) + sorted(src_root.glob("*.yaml")) + sorted(src_root.glob("*.yml")):
        if not src.is_file():
            continue
        dst = dst_root / src.name
        if not dst.is_file():
            raise AssertionError(f"missing dist file: {dst}")
        if sha256(src) != sha256(dst):
            raise AssertionError(f"hash mismatch: {src} vs {dst}")
        print(f"staged-ok: {tid}/{src.name}")
'@ | python -

Done gate

Do not declare completion until all are true:

  • touched toolset IDs were auto-detected (or explicitly documented fallback)
  • internal/external contract parity passed with canonicalized comparison
  • profile-aware context was used, or fallback assumption was explicitly logged
  • minimal behavior smoke passed
  • dist staging/hash verification passed for toolset.py plus external config files

Anti-patterns

  • Manually curating touched toolset IDs.
  • Testing only dev imports and assuming frozen parity.
  • Comparing raw schema dicts without canonicalization.
  • Skipping context assumptions in parity runs.
  • Verifying only code files and ignoring runtime config files.

What ships with it: 1 file

270 B alongside SKILL.md

agents/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.