Add sssf skill, installable via the skills CLI

Port the sssf skill from ~/.agents/skills/sssf into this repo so it can be
distributed and installed with the skills CLI (skills add INDigitalStudio/skills
--skill sssf).

- Copy the skill (SKILL.md, cookbooks, references, scripts, templates, and the
  visualizer app source) into sssf/.
- Gitignore build/runtime artifacts: the visualizer's node_modules/ and dist/,
  Python bytecode, and the machine-specific repos.json.
- Make the skill location-independent: install.py now stamps the skill's real
  path into the stamped justfile's skill_dir (replacing the hardcoded
  ~/.agents/skills/sssf), so 'just obs' finds the visualizer wherever the CLI
  installed the skill.
- Update cookbooks to use <skill>/scripts/... instead of the hardcoded path,
  and document the skills CLI install command.
- Update the repo README with install instructions.
This commit is contained in:
INDigitalStudio 2026-08-09 21:00:28 +00:00
parent a42608f602
commit 2cc766aabe
98 changed files with 12508 additions and 0 deletions

301
sssf/scripts/install.py Executable file
View file

@ -0,0 +1,301 @@
#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""/install — stamp the SSSF factory from the skill into the cwd. Idempotent.
Usage:
uv run <skill>/scripts/install.py [--force] [--harness pi|omp]
Stamps: adws/ (modules + starter ADWs), adws/adw_data/prompt_engineering/
(4 starter agents), adws/adw_sssf_config/sssf.config.yaml, .env.sample,
.gitignore entries.
Prompts for the coding-agent harness (pi or omp) unless --harness is given,
writes it into the stamped config's defaults, and registers the repo in the
visualizer's repos.json so the multi-repo UI can browse it.
Existing files are skipped unless --force.
"""
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
TEMPLATES = Path(__file__).resolve().parent.parent / "templates"
# The skill's own directory — where the visualizer app lives. install.py stamps
# it into the justfile's `skill_dir` so `just obs` finds the app no matter
# where the skill was installed (user scope, a repo, or via the skills CLI).
SKILL_DIR = Path(__file__).resolve().parent.parent
# The visualizer's repo registry lives next to the skill (machine-specific:
# it maps repo slugs to local db paths). install.py keeps it in sync.
REPOS_FILE = SKILL_DIR / "repos.json"
GITIGNORE_ENTRIES = [
"adws/adw_data/sessions/",
"adws/adw_data/sssf.db*",
".env",
# The ADWs are Python, so importing adw_modules writes bytecode next to it.
# Chains that end in a commit phase call `git add -A`, so without this a
# stamped repo commits its own .pyc files — 15 of them showed up in the
# first repo that was ever installed into from scratch.
"__pycache__/",
"*.pyc",
]
HARNESSES = ("pi", "omp")
def stamp(src: Path, dest: Path, force: bool, stamped: list, skipped: list) -> None:
if src.is_dir():
for child in sorted(src.iterdir()):
if child.name == "__pycache__":
continue
stamp(child, dest / child.name, force, stamped, skipped)
return
if dest.exists() and not force:
skipped.append(str(dest))
return
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dest)
stamped.append(str(dest))
def ensure_gitignore(root: Path, stamped: list) -> None:
gitignore = root / ".gitignore"
existing = gitignore.read_text().splitlines() if gitignore.exists() else []
missing = [e for e in GITIGNORE_ENTRIES if e not in existing]
if missing:
with gitignore.open("a") as f:
f.write("\n# sssf runtime\n" + "\n".join(missing) + "\n")
stamped.append(f"{gitignore} (+{len(missing)} entries)")
def stamp_justfile(root: Path, force: bool, stamped: list, skipped: list) -> None:
"""Stamp the justfile, substituting the skill's real path into `skill_dir`.
The `obs` recipe boots the visualizer app, which ships with the skill, so
the stamped justfile has to know where the skill lives. The template keeps
a `@SSSF_SKILL_DIR@` placeholder; this fills it with the actual install
location (user scope, a repo, or wherever the skills CLI put it).
"""
dest = root / "justfile"
if dest.exists() and not force:
skipped.append(str(dest))
return
text = (TEMPLATES / "justfile").read_text()
text = text.replace("@SSSF_SKILL_DIR@", str(SKILL_DIR))
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(text)
stamped.append(str(dest))
def choose_harness(arg: str | None) -> str:
"""Pick the coding-agent harness: --harness wins, else prompt."""
if arg:
if arg not in HARNESSES:
raise SystemExit(f"--harness must be one of {', '.join(HARNESSES)}, got {arg!r}")
return arg
while True:
choice = input(f"Coding-agent harness [{HARNESSES[0]}/{HARNESSES[1]}] (default {HARNESSES[0]}): ").strip().lower()
if not choice:
return HARNESSES[0]
if choice in HARNESSES:
return choice
print(f" {choice!r} is not a harness; pick {', '.join(HARNESSES)}.")
def set_harness_in_config(config_path: Path, harness: str) -> None:
"""Rewrite the stamped config's defaults.coding_agent to the chosen harness."""
text = config_path.read_text()
# Target the `defaults:` block's coding_agent, not the comment above it
# (which also contains the words "coding_agent"). The template ships
# ` coding_agent: pi` as the first line under `defaults:`.
defaults_marker = "defaults:"
d_idx = text.find(defaults_marker)
if d_idx == -1:
return
marker = "coding_agent:"
idx = text.find(marker, d_idx)
if idx == -1:
return
line_end = text.find("\n", idx)
text = text[:idx] + f"coding_agent: {harness}" + text[line_end:]
config_path.write_text(text)
def harness_catalog(harness: str) -> list[str]:
"""List the model selectors the harness can actually run, or [] if unknown.
The template config ships with a roster of models (google/gemini-3.6-flash,
fireworks/..., openai/...) that only resolve if the harness has them
registered. Most machines have a handful of local/cloud models, so the
stamped config usually fails validation until every agent is pointed at a
model that exists. Reading the harness's own catalog lets install offer to
fix that automatically.
"""
try:
if harness == "pi":
out = subprocess.run(
["pi", "--list-models"], capture_output=True, text=True,
timeout=30, check=False,
)
if out.returncode != 0:
return []
# provider model context max-out thinking images
return [f"{row[0]}/{row[1]}" for row in
(line.split() for line in out.stdout.splitlines()[1:])
if len(row) >= 2]
if harness == "omp":
out = subprocess.run(
["omp", "models", "--json"], capture_output=True, text=True,
timeout=30, check=False,
)
if out.returncode != 0:
return []
data = json.loads(out.stdout)
return [m.get("selector") or f"{m['provider']}/{m['id']}"
for m in data.get("models", []) if isinstance(m, dict)]
except (OSError, subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
return []
return []
def fix_models_in_config(config_path: Path, harness: str) -> None:
"""Point every agent at a model the harness can actually run.
The template roster names several providers; if none of them resolve in the
harness's catalog, offer to set `defaults.model` to the first registered
model and drop the per-agent `model:` overrides — the fastest way to a
validating roster on a single model. No-op when the catalog is unknown or
the template models already resolve.
"""
catalog = harness_catalog(harness)
if not catalog:
return
text = config_path.read_text()
# Collect the model patterns the template ships (defaults + per-agent).
import re
patterns = re.findall(r"^\s*model:\s*(\S+)", text, re.M)
if not patterns:
return
# Does any template model resolve? A pattern resolves if it's in the catalog
# or is a bare id that matches exactly one catalog entry.
def resolves(pattern: str) -> bool:
if pattern in catalog:
return True
matches = [c for c in catalog if pattern == c.split("/")[-1]]
return len(matches) == 1
if any(resolves(p) for p in patterns):
return
# None resolve — offer to point the whole roster at the first model.
target = catalog[0]
print(f" none of the template models resolve in {harness}'s catalog "
f"({len(catalog)} model(s) available)")
choice = input(f" point the whole roster at {target}? [Y/n]: ").strip().lower()
if choice in ("n", "no"):
return
# Set defaults.model and drop every per-agent `model:` line.
defaults_marker = "defaults:"
d_idx = text.find(defaults_marker)
if d_idx != -1:
m_idx = text.find("model:", d_idx)
if m_idx != -1:
line_end = text.find("\n", m_idx)
text = text[:m_idx] + f"model: {target}" + text[line_end:]
# Remove per-agent model overrides (indented `model:` lines in agent
# blocks, i.e. after the defaults block ends). The defaults.model line we
# just wrote stays.
lines = text.splitlines(keepends=True)
kept = []
in_defaults = False
for line in lines:
stripped = line.strip()
if stripped.startswith("defaults:"):
in_defaults = True
elif stripped and not stripped.startswith("#") and not line.startswith((" ", "\t")):
in_defaults = False
if not in_defaults and stripped.startswith("model:") and line.startswith((" ", "\t")):
continue
kept.append(line)
config_path.write_text("".join(kept))
print(f" defaults.model = {target}; per-agent model overrides removed")
def register_repo(root: Path) -> None:
"""Add/update this repo in the visualizer's repos.json (idempotent)."""
slug = root.resolve().name
db = str((root / "adws" / "adw_data" / "sssf.db").resolve())
entry = {"slug": slug, "name": slug, "db": db}
repos = []
if REPOS_FILE.exists():
try:
repos = json.loads(REPOS_FILE.read_text())
except (json.JSONDecodeError, OSError):
repos = []
if not isinstance(repos, list):
repos = []
# Replace an existing entry for this slug, else append.
repos = [r for r in repos if not (isinstance(r, dict) and r.get("slug") == slug)]
repos.append(entry)
REPOS_FILE.write_text(json.dumps(repos, indent=2) + "\n")
print(f" registered {slug} in {REPOS_FILE}")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--force", action="store_true", help="overwrite existing files")
parser.add_argument("--harness", choices=HARNESSES, help="coding-agent harness (pi or omp)")
args = parser.parse_args()
harness = choose_harness(args.harness)
root = Path.cwd()
stamped, skipped = [], []
stamp(TEMPLATES / "adws", root / "adws", args.force, stamped, skipped)
stamp(TEMPLATES / "prompt_engineering",
root / "adws" / "adw_data" / "prompt_engineering", args.force, stamped, skipped)
stamp(TEMPLATES / "harness_engineering",
root / "adws" / "adw_data" / "harness_engineering", args.force, stamped, skipped)
stamp(TEMPLATES / "sssf.config.yaml",
root / "adws" / "adw_sssf_config" / "sssf.config.yaml",
args.force, stamped, skipped)
stamp(TEMPLATES / "env.sample", root / ".env.sample", args.force, stamped, skipped)
# The recipes are part of the operating experience, and several cookbooks
# plus the run banner tell you to use them, so a stamped repo has to have
# them. Skipped like any other file if the repo already has a justfile.
stamp_justfile(root, args.force, stamped, skipped)
ensure_gitignore(root, stamped)
config_path = root / "adws" / "adw_sssf_config" / "sssf.config.yaml"
if config_path.exists():
set_harness_in_config(config_path, harness)
print(f" defaults.coding_agent = {harness} in {config_path}")
fix_models_in_config(config_path, harness)
register_repo(root)
print(f"sssf installed into {root}")
print(f" stamped: {len(stamped)} file(s)")
for s in stamped:
print(f" + {s}")
if skipped:
print(f" skipped (already exist, use --force to overwrite): {len(skipped)}")
print("\nnext steps:")
print(" 1. just validate # roster check: names, prompts, models all resolve")
print(" 2. cp .env.sample .env # if no .env yet; else append the keys you need")
print(" 3. just demo # two cheap read-only runs, end to end")
print(" 4. just sessions # what just happened")
print(" 5. just obs # the trace UI, needs bun")
print("\n no just? the raw form of step 3 is:")
print(" uv run adws/adw_prompt.py \"say hello\" --agent scout")
return 0
if __name__ == "__main__":
sys.exit(main())

119
sssf/scripts/make_adw.py Normal file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""make_adw — generate a new one-shot ADW script from agents in the config.
Usage:
uv run <skill>/scripts/make_adw.py --name review_docs --agents scout,builder
Each named agent becomes one agent phase, chained by envelope. Starter agents
map to their concrete output types; unknown agents get GenericOutput (define a
concrete type in adw_modules/data_types.py and swap it in).
"""
import argparse
import sys
from pathlib import Path
OUTPUT_TYPES = {"planner": "PlanOutput", "builder": "BuildOutput",
"scout": "ScoutOutput",
"reviewer": "ReviewOutput", "documenter": "DocumentOutput"}
HEADER = '''#!/usr/bin/env -S uv run
# /// script
# dependencies = ["pydantic", "python-dotenv", "pyyaml"]
# ///
"""ADW {title} — generated by make_adw.
Usage:
uv run adws/adw_{name}.py "<prompt or path/to/prompt.md>" [--config adws/adw_sssf_config/sssf.config.yaml] [--adw-id a1b2c3d4]
Phases: engineer(request) -> {chain}
"""
import argparse
import sys
from adw_modules import agents, gates, session, utils
from adw_modules.data_types import AgentCall, PhaseParams, {imports}
REQUIRED_AGENTS = {agents_list}
def main(prompt: str, config: str = "adws/adw_sssf_config/sssf.config.yaml", adw_id: str | None = None) -> int:
cfg = agents.load_config(config)
agents.validate(cfg, REQUIRED_AGENTS)
run = session.ensure(cfg, adw_id)
with run.phase(PhaseParams(name="request", kind="engineer", owner=run.engineer,
description="Capture the incoming ask")) as ph:
ph.log(input=prompt)
previous = None
{phases}
return 0 if run.succeeded else 1
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("prompt", help="inline text or a path to a prompt file")
parser.add_argument("--config", default="adws/adw_sssf_config/sssf.config.yaml")
parser.add_argument("--adw-id", default=None, help="join or pin an existing session")
args = parser.parse_args()
sys.exit(main(utils.resolve_prompt(args.prompt), args.config, args.adw_id))
'''
PHASE = ''' # TODO: replace this description — say what THIS phase does and why.
with run.phase(PhaseParams(name="{name}", kind="agent", owner="{agent}",
description="Run {agent} over the request and hand its envelope on")) as ph:
previous = ph.call(AgentCall(output_type={output_type}, prompt=prompt,
previous=previous,
gates=[gates.artifacts_exist]))
'''
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--name", required=True, help="snake_case adw name")
parser.add_argument("--agents", required=True, help="comma-separated agent names, in order")
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
agent_names = [a.strip() for a in args.agents.split(",") if a.strip()]
if not agent_names:
print("no agents given")
return 1
types = [OUTPUT_TYPES.get(a, "GenericOutput") for a in agent_names]
seen: dict[str, int] = {}
phases = []
for agent, output_type in zip(agent_names, types):
seen[agent] = seen.get(agent, 0) + 1
phase_name = agent if seen[agent] == 1 else f"{agent}_{seen[agent]}"
phases.append(PHASE.format(name=phase_name, agent=agent, output_type=output_type))
body = HEADER.format(
title=args.name.replace("_", " ").title(),
name=args.name,
chain=" -> ".join(agent_names),
imports=", ".join(sorted(set(types))),
agents_list=repr(sorted(set(agent_names))),
phases="\n".join(phases),
)
dest = Path.cwd() / "adws" / f"adw_{args.name}.py"
if dest.exists() and not args.force:
print(f"{dest} already exists — use --force to overwrite")
return 1
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(body)
print(f"wrote {dest}")
print("next: replace each phase description — a generated one says nothing, "
"and the description is the only intent the trace ever shows")
print(f"run: uv run adws/adw_{args.name}.py \"your request\"")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,35 @@
#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""make_config — generate adws/adw_sssf_config/sssf.config.yaml with great defaults.
Usage:
uv run <skill>/scripts/make_config.py [--force]
"""
import argparse
import shutil
import sys
from pathlib import Path
TEMPLATE = Path(__file__).resolve().parent.parent / "templates" / "sssf.config.yaml"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
dest = Path.cwd() / "adws" / "adw_sssf_config" / "sssf.config.yaml"
if dest.exists() and not args.force:
print(f"{dest} already exists — use --force to overwrite")
return 1
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(TEMPLATE, dest)
print(f"wrote {dest}")
return 0
if __name__ == "__main__":
sys.exit(main())