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
103
sssf/templates/adws/adw_modules/changes.py
Normal file
103
sssf/templates/adws/adw_modules/changes.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
"""Deterministic change capture: what was built, straight from git.
|
||||
|
||||
"What changed since main" is not a judgement call — it is two git commands and
|
||||
a subtraction. So it is code, and an agent is only handed the result. The
|
||||
capture writes the full diff into `context_handoff/` and returns a ChangeSet;
|
||||
`as_envelope` adapts that into the one door every agent handoff uses.
|
||||
|
||||
The base is resolved, not assumed. Off the base branch the diff covers the
|
||||
whole branch plus the working tree; on it, the uncommitted tree; and on a clean
|
||||
tree, the last commit — because "document the work that was just done" still
|
||||
has an answer right after a chain committed. Whichever it picked rides along in
|
||||
`BaseRef.reason`, so the trace never leaves you guessing what a diff was
|
||||
measured against.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from . import git_helper
|
||||
from .data_types import BaseRef, ChangeCapture, ChangeSet, ChangesOutput
|
||||
|
||||
DIFF_FILENAME = "changes.diff"
|
||||
|
||||
|
||||
def resolve_base(ref: str) -> BaseRef:
|
||||
"""Pick the commit the work is measured from, and record why that one."""
|
||||
if not git_helper.is_repo():
|
||||
raise RuntimeError(
|
||||
"not a git repository — change capture needs one. Run `git init` in "
|
||||
"the repo root before running an ADW that documents a change.")
|
||||
if not git_helper.ref_exists(ref):
|
||||
raise RuntimeError(
|
||||
f"base ref {ref!r} does not exist in this repository — pass --base "
|
||||
f"with a ref that does (e.g. --base master, --base HEAD~1).")
|
||||
|
||||
# Built first, then given its reason: BaseRef.label knows how to print a
|
||||
# pinned sha, and the reason is the line a human reads in the trace.
|
||||
base = BaseRef(ref=ref, commit=git_helper.merge_base(ref, "HEAD"))
|
||||
if git_helper.short_sha(base.commit) != git_helper.short_sha("HEAD"):
|
||||
base.reason = (f"HEAD is ahead of {base.label} — diffing every commit since, "
|
||||
f"plus the working tree")
|
||||
elif git_helper.is_dirty():
|
||||
base.reason = f"HEAD is on {base.label} — diffing the uncommitted working tree"
|
||||
elif git_helper.ref_exists("HEAD~1"):
|
||||
base.commit = git_helper.rev("HEAD~1")
|
||||
base.reason = (f"HEAD is on {base.label} with a clean tree — falling back to "
|
||||
f"the last commit")
|
||||
else:
|
||||
base.reason = f"HEAD is on {base.label} with a clean tree and no parent commit"
|
||||
return base
|
||||
|
||||
|
||||
def capture(run, params: ChangeCapture) -> ChangeSet:
|
||||
"""Diff the working tree against the resolved base and persist the evidence."""
|
||||
base = resolve_base(params.base)
|
||||
files = git_helper.diff_files(base.commit)
|
||||
untracked = git_helper.untracked_files() if params.include_untracked else []
|
||||
insertions, deletions = git_helper.diff_counts(base.commit)
|
||||
stat = git_helper.diff_stat(base.commit)
|
||||
|
||||
text = git_helper.diff_text(base.commit)
|
||||
lines = text.splitlines()
|
||||
truncated = len(lines) > params.max_diff_lines
|
||||
if truncated:
|
||||
text = "\n".join(lines[:params.max_diff_lines])
|
||||
text += (f"\n\n[truncated at {params.max_diff_lines} lines of "
|
||||
f"{len(lines)} — run `git diff {base.commit}` for the rest]")
|
||||
|
||||
# Untracked files are absent from `git diff` by construction, so they are
|
||||
# named here rather than silently missing from the record. The reader has
|
||||
# `read` and can open any of them.
|
||||
untracked_block = ("\n".join(f" {f}" for f in untracked) if untracked
|
||||
else " (none)")
|
||||
diff_path = run.context_handoff_dir / DIFF_FILENAME
|
||||
diff_path.write_text(
|
||||
f"# changes since {base.label} @ {git_helper.short_sha(base.commit)}\n"
|
||||
f"# {base.reason}\n"
|
||||
f"# +{insertions} -{deletions} across {len(files)} tracked file(s)\n\n"
|
||||
f"## stat\n{stat or ' (no tracked changes)'}\n\n"
|
||||
f"## untracked files\n{untracked_block}\n\n"
|
||||
f"## diff\n{text}\n")
|
||||
|
||||
return ChangeSet(base=base, files=files, untracked=untracked,
|
||||
insertions=insertions, deletions=deletions, stat=stat,
|
||||
diff_path=str(diff_path), truncated=truncated)
|
||||
|
||||
|
||||
def as_envelope(changes: ChangeSet, notes: str = "") -> ChangesOutput:
|
||||
"""Wrap a captured change so an agent can be handed it directly."""
|
||||
total = len(changes.files) + len(changes.untracked)
|
||||
return ChangesOutput(
|
||||
status="success",
|
||||
summary=(f"{total} file(s) changed since {changes.base.label} "
|
||||
f"(+{changes.insertions} -{changes.deletions})"),
|
||||
artifacts=[changes.diff_path],
|
||||
notes_for_next_agent=notes,
|
||||
base=f"{changes.base.label} @ {git_helper.short_sha(changes.base.commit)} "
|
||||
f"— {changes.base.reason}",
|
||||
changed_files=changes.files + changes.untracked,
|
||||
insertions=changes.insertions,
|
||||
deletions=changes.deletions,
|
||||
stat=changes.stat,
|
||||
diff_path=changes.diff_path,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue