smriti/cli/smriti_cli/skill_pack/targets.py
Himanshu Dongre ec0139f707 Add Smriti agent skill pack source and renderer
The skill pack is an instruction file installed into an agent host's
project directory so Smriti's workflow lives in the agent's system
context instead of documentation nobody reads. A single versioned
template.md renders for both Claude Code (MCP-primary) and Codex
(CLI-primary) via a pure-function substituter, keeping content in
sync mechanically across targets.

template.md contains 15 sections. The load-bearing one is Section 5,
When NOT to checkpoint, with equal weight to Section 4. Agents are
told explicitly not to checkpoint after every small step, not to
produce end-of-session blobs, not to treat commits as a save button,
not to stack commits on inconsistent state, not to restate existing
state, and not to checkpoint just because the user asked when there
is no real inflection point. A frequency target (2-4 checkpoints per
4-hour session) and a three-question signal test give agents concrete
criteria for every call.

Other sections cover the read-state-first reflex, when to fork, when
to review, when to compare, when to restore, drift detection,
explicit anti-patterns (HANDOFF.md, silent state reads, inconsistent
author_agent, /chat/send), and the phrases the agent should say out
loud so the human watching has an audit trail.

Renderer API (all pure functions): load_template, get_version, render,
install. install is version-aware: refuses to overwrite a destination
whose installed version is >= the template version unless force=True.
Dry-run mode returns the rendered content without writing.

Content-integrity tests parametrized over both targets assert that
every anti-pattern rule, the signal test, the frequency target, and
the drift-detection guidance appear in the rendered output. If a
future template edit drops any of them, tests fail loudly.

22 skill pack tests, all green.
2026-04-12 02:07:47 +05:30

76 lines
2.4 KiB
Python

"""Skill pack target configurations.
A "target" is the agent host the skill pack is being installed into.
Two targets ship in v1.0:
- claude-code: installs to `./.claude/skills/smriti/SKILL.md`. Claude
Code reads skills out of `.claude/skills/<name>/SKILL.md` as
project-level instructions. Primary tool notation is MCP
(`smriti_state(space="x")`) because Claude Code speaks MCP natively.
- codex: installs to `./AGENTS.md`. Codex reads `AGENTS.md` at the
project root as its primary instruction file. Primary tool notation
is CLI (`smriti state x`) because the Codex CLI runs commands in a
shell loop.
Both targets render from the SAME template source — only the primary
tool notation and display name vary. Workflow heuristics, anti-patterns,
and when-not-to-checkpoint rules are identical for both targets by
design.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Literal
PrimaryMode = Literal["mcp", "cli"]
@dataclass(frozen=True)
class SkillTarget:
"""Configuration for one agent skill pack target."""
key: str
display_name: str
default_destination: Path
primary_mode: PrimaryMode
# One-line summary used by `smriti skills list`.
description: str
TARGETS: dict[str, SkillTarget] = {
"claude-code": SkillTarget(
key="claude-code",
display_name="Claude Code",
default_destination=Path(".claude/skills/smriti/SKILL.md"),
primary_mode="mcp",
description="Claude Code (MCP-native host; installs to .claude/skills/smriti/SKILL.md)",
),
"codex": SkillTarget(
key="codex",
display_name="Codex",
default_destination=Path("AGENTS.md"),
primary_mode="cli",
description="Codex (shell-based host; installs to AGENTS.md)",
),
}
def list_targets() -> list[SkillTarget]:
"""Return all known skill pack targets in a deterministic order."""
return [TARGETS[k] for k in sorted(TARGETS.keys())]
def get_target(target_key: str) -> SkillTarget:
"""Resolve a target by key. Raises ValueError for unknown keys.
The error message lists the known targets so CLI users and agents
get actionable feedback without having to consult the docs.
"""
if target_key not in TARGETS:
known = ", ".join(sorted(TARGETS.keys()))
raise ValueError(
f"Unknown skill pack target: '{target_key}'. Known targets: {known}"
)
return TARGETS[target_key]