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:
parent
a42608f602
commit
2cc766aabe
98 changed files with 12508 additions and 0 deletions
120
sssf/templates/adws/adw_modules/git_helper.py
Normal file
120
sssf/templates/adws/adw_modules/git_helper.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"""Low-level git operations for code phases. All low-level logic lives in adw_modules."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _git(*args: str) -> str:
|
||||
result = subprocess.run(["git", *args], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()}")
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def current_branch() -> str:
|
||||
return _git("rev-parse", "--abbrev-ref", "HEAD")
|
||||
|
||||
|
||||
def create_branch(name: str) -> str:
|
||||
_git("checkout", "-b", name)
|
||||
return name
|
||||
|
||||
|
||||
def is_repo() -> bool:
|
||||
result = subprocess.run(["git", "rev-parse", "--git-dir"],
|
||||
capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
"""Absolute root of the codebase — where agents are spawned to work.
|
||||
|
||||
The git toplevel when there is one, else the process cwd (ADWs run fine in a
|
||||
non-git dir; only a commit phase requires a repo). Always absolute, so it is
|
||||
safe to hand to a subprocess regardless of where the ADW was launched from.
|
||||
"""
|
||||
if is_repo():
|
||||
return Path(_git("rev-parse", "--show-toplevel")).resolve()
|
||||
return Path.cwd().resolve()
|
||||
|
||||
|
||||
def commit_all(message: str) -> str:
|
||||
"""Stage the working tree and commit it. Returns the new short sha."""
|
||||
if not is_repo():
|
||||
raise RuntimeError(
|
||||
"not a git repository — a commit phase needs one. Run `git init` in the "
|
||||
"repo root (and make a first commit) before running an ADW that commits.")
|
||||
_git("add", "-A")
|
||||
if not _git("status", "--porcelain"):
|
||||
raise RuntimeError("nothing to commit — the preceding phases changed no files")
|
||||
_git("commit", "-m", message)
|
||||
return _git("rev-parse", "--short", "HEAD")
|
||||
|
||||
|
||||
def changed_files() -> list[str]:
|
||||
out = _git("status", "--porcelain")
|
||||
return [line[3:] for line in out.splitlines() if line]
|
||||
|
||||
|
||||
# ── diff plumbing (composed into a ChangeSet by documentation.py) ────────────
|
||||
|
||||
def ref_exists(ref: str) -> bool:
|
||||
"""True when `ref` resolves to a commit. Never raises — this is a question."""
|
||||
result = subprocess.run(["git", "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"],
|
||||
capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def rev(ref: str = "HEAD") -> str:
|
||||
return _git("rev-parse", ref)
|
||||
|
||||
|
||||
def short_sha(ref: str = "HEAD") -> str:
|
||||
return _git("rev-parse", "--short", ref)
|
||||
|
||||
|
||||
def merge_base(ref: str, other: str = "HEAD") -> str:
|
||||
"""The commit where `ref` and `other` diverged — the honest base of a branch.
|
||||
|
||||
On the base branch itself this returns HEAD, which makes the diff exactly
|
||||
"what is not committed yet". Off it, the diff is the whole branch plus the
|
||||
working tree. One command covers both cases, so no ADW has to branch on it.
|
||||
"""
|
||||
return _git("merge-base", ref, other)
|
||||
|
||||
|
||||
def is_dirty() -> bool:
|
||||
return bool(_git("status", "--porcelain"))
|
||||
|
||||
|
||||
def untracked_files() -> list[str]:
|
||||
out = _git("ls-files", "--others", "--exclude-standard")
|
||||
return [line for line in out.splitlines() if line]
|
||||
|
||||
|
||||
def diff_files(base: str) -> list[str]:
|
||||
"""Tracked files that differ between `base` and the working tree."""
|
||||
out = _git("diff", "--name-only", base)
|
||||
return [line for line in out.splitlines() if line]
|
||||
|
||||
|
||||
def diff_stat(base: str) -> str:
|
||||
return _git("diff", "--stat", base)
|
||||
|
||||
|
||||
def diff_counts(base: str) -> tuple[int, int]:
|
||||
"""(insertions, deletions) across the diff. Binary files count as neither."""
|
||||
insertions = deletions = 0
|
||||
for line in _git("diff", "--numstat", base).splitlines():
|
||||
added, removed, *_ = line.split("\t")
|
||||
if added.isdigit():
|
||||
insertions += int(added)
|
||||
if removed.isdigit():
|
||||
deletions += int(removed)
|
||||
return insertions, deletions
|
||||
|
||||
|
||||
def diff_text(base: str) -> str:
|
||||
return _git("diff", base)
|
||||
Loading…
Add table
Add a link
Reference in a new issue