feat(transcend): carry session scripts, tests and skills into the repos
Adds kvb-transcend (collector), kvb-skills-transcend (into the skills repo) and kvb-code-transcend (into the current repo). transcend.py scans the own scratchpad, peer scratchpads and the agent homes for scripts, tests and skills the skills repo does not ship, skips secret-named and oversized files, and remembers carried/dropped decisions by content digest in the gitignored .state/transcend.json, so a rescan offers only new or changed candidates. Wired into kvb-skills-tune (own scripts), kvb-code-tune step 6 (measurements) and kvb-skills-dream step 7. Seven descriptions are tightened to stay inside the 1300-word listing budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
42b66e3ddf
commit
2fbbd7386c
16 files changed
+506
-25
No files matched your search
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: kvb-code-transcend
|
||||
description: >
|
||||
Carry the session's ad-hoc tests and scripts into the current repo.
|
||||
Trigger on /kvb-code-transcend.
|
||||
---
|
||||
|
||||
The test the agent ran from its scratchpad, the probe that proved a cause, the
|
||||
benchmark behind an optimization: they protected this session's work and
|
||||
nothing after it. This skill moves them into the repository the agent works
|
||||
in (`git rev-parse --show-toplevel`), in that repository's own form. It
|
||||
changes nothing before the operator confirms.
|
||||
|
||||
## Step 1: inventory
|
||||
|
||||
Read-only:
|
||||
|
||||
- **Tests and scripts outside the repo**: `transcend.py scan` of the
|
||||
`kvb-transcend` skill lists them in the own and peers' scratchpads and
|
||||
skips what was already decided and has not changed; peer candidates follow
|
||||
that skill's consent step. Add the `python -c` checks and negative controls
|
||||
this session ran inline.
|
||||
- **Probes and reproductions** that proved a root cause or a fix.
|
||||
- **Measurements**: prototypes and before/after harnesses of the
|
||||
`kvb-code-tune` skill.
|
||||
- **Repeated commands** a make target or script of the repository could own.
|
||||
- **Untracked helpers** the agent left inside the repository.
|
||||
|
||||
One line each: `candidate -> source -> what it guards`.
|
||||
|
||||
## Step 2: classify
|
||||
|
||||
Read the repository's rules first (AGENTS.md, CONTRIBUTING, the Makefile, the
|
||||
test layout). Per candidate one target in that repository's form: a test in
|
||||
its test tree and framework, a make target, a script under its scripts
|
||||
directory, a CI step, or drop. No new framework, dependency or language
|
||||
without the operator. A kept optimization keeps its measurement as a
|
||||
benchmark or regression test.
|
||||
|
||||
## Step 3: listen and confirm
|
||||
|
||||
Run the `kvb-think-listen` skill on the open points: target path, framework,
|
||||
which repository line owns the file. Then `AskUserQuestion`: one
|
||||
`multiSelect` with the candidates, nothing preselected, and per pick a
|
||||
single-select `Write it` / `Revise it` / `Discard` with the drafted diff.
|
||||
|
||||
## Step 4: write
|
||||
|
||||
- Write only the confirmed files, path-scoped; a peer's line goes to its
|
||||
owner through the `kvb-org-coordinate` skill.
|
||||
- Every carried test fails without the code it guards: show the negative
|
||||
control once.
|
||||
- Run the repository's tests only on the resource chair's grant (the
|
||||
`kvb-org-organize` skill); leave the result uncommitted for the
|
||||
`kvb-git-commit-next` skill.
|
||||
- `transcend.py mark` every offered candidate with its verdict.
|
||||
|
||||
## Output
|
||||
|
||||
| Candidate | Source | Target in the repo | Verdict |
|
||||
|
||||
Dropped candidates stay with their reason.
|
||||
@@ -2,9 +2,8 @@
|
||||
name: kvb-code-tune
|
||||
description: >
|
||||
Find and apply performance optimizations (CI, deploy, build, gate, hot
|
||||
paths) without reducing logic, coverage or correctness. Trigger on
|
||||
/kvb-code-tune, a job dying on its time budget, a slower pipeline or deploy,
|
||||
or asked where time goes.
|
||||
paths) without losing logic or coverage. Trigger on /kvb-code-tune, a job
|
||||
hitting its time budget, a slower pipeline, or asked where time goes.
|
||||
---
|
||||
|
||||
# Tune
|
||||
@@ -205,6 +204,9 @@ failure, or only moves the cost somewhere the measurement does not look.
|
||||
proposals. Re-measure from the same source under the same conditions and
|
||||
report a before and an after number.
|
||||
5. Repeat until the next candidate is worth less than the run that measures it.
|
||||
6. Hand the prototypes and before/after harnesses of every kept candidate to
|
||||
the `kvb-code-transcend` skill, so the measurement stays in the repository
|
||||
as a benchmark or regression test.
|
||||
|
||||
## Output
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: kvb-git-changelog
|
||||
description: >
|
||||
Write the release message since the last release for `pkgmgr release`;
|
||||
prints it only, never edits CHANGELOG.md, tags or releases. Trigger on
|
||||
Write the release message for `pkgmgr release`;
|
||||
prints only, never edits, tags or releases. Trigger on
|
||||
/kvb-git-changelog, "release message", "changelog entry", before cutting a release,
|
||||
or on pkgmgr's markdown-lint rejection.
|
||||
---
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: kvb-git-merge-fix
|
||||
description: >
|
||||
Hunt down the commits on any branch that carry a described fix and merge it
|
||||
into the current branch. Trigger on /kvb-git-merge-fix <description> or when the
|
||||
operator asks to bring over a fix from another branch.
|
||||
Find the commits on any branch that carry a described fix and merge them
|
||||
here. Trigger on /kvb-git-merge-fix <description> or a request to bring over
|
||||
a fix from another branch.
|
||||
---
|
||||
|
||||
The free text after `/kvb-git-merge-fix` is the fix description, exactly like `/loop`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: kvb-org-organize
|
||||
description: >
|
||||
Keep parallel agents from duplicating work: the first to claim it owns it;
|
||||
line representatives and a resource chair coordinate. Run AUTOMATICALLY
|
||||
Keep parallel agents from duplicating work: the first claim owns it; a
|
||||
resource chair allocates. Run AUTOMATICALLY
|
||||
before work another agent might also do (failure, cluster, feature, gate,
|
||||
deploy, cleanup), and on /kvb-org-organize.
|
||||
---
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: kvb-org-split
|
||||
description: >
|
||||
Keep agent work in its own git worktree until it reaches 90% fix confidence
|
||||
and under 5% break risk; below that bar it becomes a proposal. Trigger when
|
||||
delegating work to agents, when agents work in parallel, or on /kvb-org-split.
|
||||
Keep agent work in its own worktree until 90% fix confidence and under 5%
|
||||
break risk; below that it becomes a proposal. Trigger when delegating to or
|
||||
parallelizing agents, or on /kvb-org-split.
|
||||
---
|
||||
|
||||
# Funnel
|
||||
|
||||
@@ -109,12 +109,13 @@ as radio buttons: the improvement, the evidence behind it, and the options
|
||||
`Apply now`, `Record as a task`, `Skip`. Ranked by evidence, one question per
|
||||
improvement. No repository file changes before an answer comes back.
|
||||
|
||||
## Step 7: kvb-skills-tune, then kvb-skills-auto
|
||||
## Step 7: kvb-skills-tune, kvb-skills-auto, kvb-transcend
|
||||
|
||||
With the dialectic done and memory written, run `kvb-skills-tune` — hand it this run's
|
||||
findings as its evidence so it does not re-collect them — and then `kvb-skills-auto`
|
||||
for the operator corrections the window revealed. Both keep their own
|
||||
confirmation steps; the dream never writes a skill on their behalf.
|
||||
findings as its evidence so it does not re-collect them — then `kvb-skills-auto`
|
||||
for the operator corrections the window revealed, and `kvb-transcend` for the
|
||||
scripts and tests the window's sessions wrote outside a repository. All keep
|
||||
their own confirmation steps; the dream never writes a skill on their behalf.
|
||||
|
||||
Close the run by refreshing this repository's reminder stamp, so the next hint
|
||||
is twelve hours out: `date +%s > "$(git rev-parse --path-format=absolute --git-common-dir)/.dream-stamp"`.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: kvb-skills-transcend
|
||||
description: >
|
||||
Carry the session's skill use and own scripts into the skills repo.
|
||||
Trigger on /kvb-skills-transcend.
|
||||
---
|
||||
|
||||
What an agent improvised in one session dies with it: the helper script in its
|
||||
scratchpad, the command sequence it ran five times, the skill it had to work
|
||||
around. This skill carries that into the operator's skills repository, so the
|
||||
next agent starts from it. It changes nothing before the operator confirms.
|
||||
|
||||
## Step 1: inventory
|
||||
|
||||
Read-only:
|
||||
|
||||
- **Scripts and skills**: `transcend.py scan` of the `kvb-transcend` skill
|
||||
lists the own scratchpad's scripts, peers' scripts, and skills, commands,
|
||||
hooks and rules in `~/.claude`, `~/.agents` and `~/.codex` that the skills
|
||||
repository does not ship; it skips what was already decided and has not
|
||||
changed. Peer and home candidates follow that skill's consent step.
|
||||
- **Skills used**: every skill this session invoked, how often, and where it
|
||||
fell short (a workaround, a step skipped, an operator correction).
|
||||
- **Recurring moves without a skill**: command sequences, peer exchanges or
|
||||
lookups run three times or more. `rasci.py tools` of the `kvb-org-rasci`
|
||||
register lists the scripts the session's tasks recorded.
|
||||
|
||||
One line each: `candidate -> source -> evidence`.
|
||||
|
||||
## Step 2: classify
|
||||
|
||||
Per candidate one target: a script under an existing skill's `scripts/`, a
|
||||
new skill, a sharper step in an existing skill, a memory entry instead, or
|
||||
drop. Recurring moves without a script go to the `kvb-skills-tune` skill as
|
||||
evidence. A script is portable only once its host paths, names and values
|
||||
became explicit parameters (the `kvb-code-no-defaults` skill); one that needs
|
||||
a secret, a credential or a single host is dropped or stays a memory entry.
|
||||
|
||||
## Step 3: listen and confirm
|
||||
|
||||
Run the `kvb-think-listen` skill on the open points: scope, target skill,
|
||||
generality. Then `AskUserQuestion`: one `multiSelect` with the candidates
|
||||
(target and evidence in each description, nothing preselected), and per pick a
|
||||
single-select `Write it` / `Revise it` / `Discard` showing the drafted file or
|
||||
diff.
|
||||
|
||||
## Step 4: write
|
||||
|
||||
- Write where the `kvb-skills-tune` skill's **Persistence** section says:
|
||||
the skills repository as source of truth, then the live mirrors.
|
||||
- Read every script whole before carrying it; strip comments the way the
|
||||
`kvb-code-no-comments` skill does.
|
||||
- A carried script gets a unit test in the repository's `tests/` with a
|
||||
negative control. Tests run only on the resource chair's grant (the
|
||||
`kvb-org-organize` skill).
|
||||
- `transcend.py mark` every offered candidate with its verdict.
|
||||
- Leave the result uncommitted; committing is the `kvb-git-commit-next`
|
||||
skill's job.
|
||||
|
||||
## Output
|
||||
|
||||
| Candidate | Source | Target | Verdict |
|
||||
|
||||
Dropped candidates stay with their reason.
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: kvb-skills-tune
|
||||
description: >
|
||||
Generate new skills or optimize existing ones (fewer tokens, fewer errors,
|
||||
persistent learnings), each proposal confirmed by single-select question.
|
||||
Trigger ONLY on an explicit request: /kvb-skills-tune, "autotune", "tune the
|
||||
skills"; never on your own initiative.
|
||||
Generate new skills or optimize existing ones (fewer tokens and errors,
|
||||
persistent learnings), each proposal confirmed. Trigger ONLY on an explicit
|
||||
request: /kvb-skills-tune, "autotune", "tune the skills".
|
||||
---
|
||||
|
||||
Autotune turns observed friction into durable skills. It runs on demand only,
|
||||
@@ -63,6 +62,9 @@ where readable, from the project's memory and agent instruction files:
|
||||
deploys, questions that stalled an autonomous run.
|
||||
- **Lost learnings**: facts re-derived this session that a memory file or a
|
||||
skill should already have carried.
|
||||
- **Own scripts**: helpers the agent wrote outside any repository belong to
|
||||
the `kvb-skills-transcend` skill, which carries them over; this skill
|
||||
takes the frictions they reveal.
|
||||
|
||||
List each finding as one line: `symptom -> cost -> candidate fix`. Skip
|
||||
anything that happened once and is not a pattern.
|
||||
|
||||
@@ -3,8 +3,7 @@ name: kvb-think-dialectic
|
||||
description: >
|
||||
Reach a defensible conclusion by thesis, independent skeptic antithesis and
|
||||
synthesis, iterated to ~99% confidence. Trigger for any consequential root
|
||||
cause, design or decision where being wrong is expensive and the answer is
|
||||
uncertain.
|
||||
cause, design or decision that is uncertain and expensive to get wrong.
|
||||
---
|
||||
|
||||
Reach the truth by dialectic, not by first guess. Do not stop at a plausible
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: kvb-transcend
|
||||
description: >
|
||||
Run both transcend skills in one pass. Trigger on /kvb-transcend.
|
||||
---
|
||||
|
||||
Collector for the `kvb-skills-transcend` and `kvb-code-transcend` skills, so
|
||||
one harvest needs one round of questions. Its script finds the candidates and
|
||||
remembers every decision, so a rescan offers only what is new or changed:
|
||||
|
||||
```bash
|
||||
transcend.py scan --state <skills repo>/.state/transcend.json \
|
||||
--skills-repo <skills repo> --session-dir <own scratchpad> \
|
||||
--peers-root /tmp/claude-<uid> --home ~ [--json]
|
||||
transcend.py mark --state <same> <path> carried|dropped|later
|
||||
```
|
||||
|
||||
`${CLAUDE_SKILL_DIR}/scripts/transcend.py` scans three sources: the own
|
||||
scratchpad, every peer session's scratchpad, and the agent homes
|
||||
(`~/.claude`, `~/.agents`, `~/.codex`) for skills, commands, agents, hooks
|
||||
and rules the skills repository does not ship. It keeps scripts, tests and
|
||||
skills, and skips files named like secrets and files over 1 MB. Carried or
|
||||
dropped candidates return only when their content changes; `later` returns
|
||||
every time.
|
||||
|
||||
1. Scan, then classify each candidate by both skills' classify steps.
|
||||
2. Consent: a peer's candidates go to that peer, one message listing them,
|
||||
and only what it releases stays; home candidates need the operator's yes.
|
||||
3. One `kvb-think-listen` round and one confirmation for all candidates,
|
||||
grouped by target: the skills repository or the current repository. In
|
||||
the skills repository itself, scripts and skills go to
|
||||
`kvb-skills-transcend`, tests to `kvb-code-transcend`.
|
||||
4. Hand each confirmed candidate to its skill's write step, then `mark` every
|
||||
offered candidate with its verdict.
|
||||
5. One table: `| Candidate | Source | Target | Verdict |`.
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find scripts, tests and skills worth carrying over, and remember every decision.
|
||||
|
||||
Exit codes:
|
||||
0 done, 2 usage error.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_SUFFIXES = {".py", ".sh", ".bash", ".js", ".mjs", ".ts", ".rb", ".pl"}
|
||||
TEST_NAME = re.compile(r"(^test_.+|.+_test|.+\.(spec|test))\.[a-z]+$")
|
||||
SECRET_NAME = re.compile(
|
||||
r"(secret|token|credential|passw|auth|\.env$|id_[a-z0-9]+$|\.pem$|\.key$)", re.IGNORECASE
|
||||
)
|
||||
MAX_BYTES = 1_000_000
|
||||
HOME_SOURCES = {
|
||||
".claude/skills": "skill",
|
||||
".agents/skills": "skill",
|
||||
".codex/skills": "skill",
|
||||
".claude/commands": "command",
|
||||
".claude/agents": "agent",
|
||||
".claude/hooks": "script",
|
||||
".claude/scripts": "script",
|
||||
".codex/rules": "rule",
|
||||
}
|
||||
SETTLED = ("carried", "dropped")
|
||||
VERDICTS = (*SETTLED, "later")
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
print(f"transcend: {message}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def digest(path: Path) -> str:
|
||||
sha = hashlib.sha256()
|
||||
files = sorted(p for p in path.rglob("*") if p.is_file()) if path.is_dir() else [path]
|
||||
for file in files:
|
||||
sha.update(str(file.relative_to(path) if path.is_dir() else file.name).encode())
|
||||
sha.update(file.read_bytes())
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def file_kind(path: Path) -> str | None:
|
||||
if SECRET_NAME.search(path.name) or path.stat().st_size > MAX_BYTES:
|
||||
return None
|
||||
if TEST_NAME.match(path.name):
|
||||
return "test"
|
||||
if path.suffix in SCRIPT_SUFFIXES or os.access(path, os.X_OK):
|
||||
return "script"
|
||||
return None
|
||||
|
||||
|
||||
def session_names(home: Path) -> dict[str, str]:
|
||||
names = {}
|
||||
for path in (home / ".claude" / "sessions").glob("*.json"):
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
if data.get("sessionId"):
|
||||
names[data["sessionId"]] = data.get("name") or data["sessionId"]
|
||||
return names
|
||||
|
||||
|
||||
def scratch_candidates(root: Path, source: str, owner: str) -> list[dict]:
|
||||
found = []
|
||||
for path in sorted(p for p in root.rglob("*") if p.is_file()):
|
||||
kind = file_kind(path)
|
||||
if kind:
|
||||
found.append({"source": source, "owner": owner, "kind": kind, "path": str(path)})
|
||||
return found
|
||||
|
||||
|
||||
def shipped(skills_repo: Path) -> set[str]:
|
||||
names = {p.name for p in (skills_repo / "skills").iterdir() if p.is_dir()}
|
||||
lock = json.loads((skills_repo / "skills-lock.json").read_text(encoding="utf-8"))
|
||||
return names | set(lock.get("skills", {}))
|
||||
|
||||
|
||||
def home_candidates(home: Path, skills_repo: Path) -> list[dict]:
|
||||
known = shipped(skills_repo)
|
||||
found = []
|
||||
for sub, kind in HOME_SOURCES.items():
|
||||
base = home / sub
|
||||
if not base.is_dir():
|
||||
continue
|
||||
for entry in sorted(base.iterdir()):
|
||||
if entry.name.startswith(".") or SECRET_NAME.search(entry.name):
|
||||
continue
|
||||
if kind == "skill" and (entry.name in known or not (entry / "SKILL.md").is_file()):
|
||||
continue
|
||||
if kind != "skill" and not entry.is_file():
|
||||
continue
|
||||
found.append({"source": "home", "owner": "operator", "kind": kind, "path": str(entry)})
|
||||
return found
|
||||
|
||||
|
||||
def load_state(path: Path) -> dict[str, dict]:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
except ValueError:
|
||||
fail(f"{path} is not valid JSON")
|
||||
|
||||
|
||||
def cmd_scan(args: argparse.Namespace) -> int:
|
||||
session = Path(args.session_dir).resolve()
|
||||
home = Path(args.home)
|
||||
names = session_names(home)
|
||||
candidates = scratch_candidates(session, "own", "self")
|
||||
for scratch in sorted(Path(args.peers_root).glob("*/*/scratchpad")):
|
||||
if scratch.resolve() == session:
|
||||
continue
|
||||
sid = scratch.parent.name
|
||||
candidates += scratch_candidates(scratch, "peer", names.get(sid, sid))
|
||||
candidates += home_candidates(home, Path(args.skills_repo))
|
||||
|
||||
state = load_state(Path(args.state))
|
||||
offered = []
|
||||
for item in candidates:
|
||||
seen = state.get(item["path"])
|
||||
item["digest"] = digest(Path(item["path"]))
|
||||
if seen and seen["verdict"] in SETTLED and seen["digest"] == item["digest"]:
|
||||
continue
|
||||
item["previous"] = seen["verdict"] if seen else None
|
||||
offered.append(item)
|
||||
if args.json:
|
||||
print(json.dumps(offered, indent=1))
|
||||
else:
|
||||
for item in offered:
|
||||
note = f"\t(was {item['previous']})" if item["previous"] else ""
|
||||
print(f"{item['source']}\t{item['owner']}\t{item['kind']}\t{item['path']}{note}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_mark(args: argparse.Namespace) -> int:
|
||||
target = Path(args.path)
|
||||
if not target.exists():
|
||||
fail(f"{target} does not exist")
|
||||
state_path = Path(args.state)
|
||||
state = load_state(state_path)
|
||||
state[str(target)] = {
|
||||
"verdict": args.verdict,
|
||||
"digest": digest(target),
|
||||
"at": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds"),
|
||||
}
|
||||
state_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = state_path.with_suffix(".tmp")
|
||||
tmp.write_text(json.dumps(state, indent=1, sort_keys=True), encoding="utf-8")
|
||||
tmp.replace(state_path)
|
||||
print(f"{args.verdict} {target}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("scan")
|
||||
p.add_argument("--state", required=True)
|
||||
p.add_argument("--skills-repo", required=True)
|
||||
p.add_argument("--session-dir", required=True)
|
||||
p.add_argument("--peers-root", required=True)
|
||||
p.add_argument("--home", required=True)
|
||||
p.add_argument("--json", action="store_true")
|
||||
p.set_defaults(func=cmd_scan)
|
||||
p = sub.add_parser("mark")
|
||||
p.add_argument("--state", required=True)
|
||||
p.add_argument("path")
|
||||
p.add_argument("verdict", choices=VERDICTS)
|
||||
p.set_defaults(func=cmd_mark)
|
||||
args = parser.parse_args()
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user