karmaterminal/learned-skills
v0.4.2MIT
Turns repeated work into reviewable skills. Every few turns a Stop hook starts a background claude -p review of the session transcript (credentials redacted, on your own account), which may create or patch learned-* skills in the project's .claude/skills, with a Learned notice and one-command undo. Off until a project opts in with .claude/learned-skills.json. Optional learning records, a git sink and a disclosure filter for teams.
Changelog
0.4.2 (2026-10-20)
For the plugin directory's checks. No behaviour change.
- An icon,
.claude-plugin/icon.png(1024 px square). - The tests no longer ship. They moved from
scripts/to the repository'stests/learned-skills/, so an installed plugin holds only what runs. - Every environment variable the plugin reads is named literally: the review guard in both
hooks, and the four
LEARNED_SKILLS_SINK_*overrides. Before, those names were built at run time.
0.4.1 (2026-10-19)
- JSON structure can no longer close a private-key guard. Decoded JSON used to be joined
with line breaks between keys, values and array items. So with a key open, a later message such
as
{"note": "-----END RSA PRIVATE KEY-----"}decoded to a whole END line, closed the guard, and showed what followed. Decoded JSON is now joined by a separator that is not a line break (JSON_SEP), at object and array edges too, so only a line break inside one string can bound an END. A JSON string that ends right after its END line, with no trailing\n, now leaves the guard open (fail closed);/learned-skills:learned-reset-guardclears it.
0.4.0 (2026-10-18): first public release
The first release outside the private catalog it grew up in. Changes from the last private build (0.3.2):
- Commits are opt-in.
commitnow defaults tofalse: a review leaves its change in the working tree, and the💾 Learnednotice says how to commit it or undo it. - Undo works without a commit. For an uncommitted change, the review keeps copies of the
skill from before and after it, and
/learned-skills:learned-undorestores from those. It refuses if the skill was edited by hand since. Committed changes are still undone with a revert commit. /learned-skills:learned-review-nowwrites its output to the review log as well, so/learned-skills:learned-logshows it, as the command's text always said.- The reviewer sees only what the README lists. Only
learned-*skills are staged for the review; other skills reach it as names and descriptions, never their bodies. Its reads are scoped to a dedicatedSTATE/reviewer/dir, not the whole state dir. Staging refuses symlinks and special files at every level, so nothing from outside the skills dir is staged, and a learned skill left out that way can't be replaced by the review, in the project or, withlive: false, in the sink. - No sink, no queue. With no sink configured, a review stages nothing in
sink-pending/and logs nothing about a sink. A sink that is configured but incomplete, unsafe or unreachable still keeps its items pending until it can take them. - Each digest block is redacted before any clip or cut to the digest's tail, so a cut can't
strip a private key's header and leave its body. A key split across messages or reviews is
withheld too: everything from a
BEGINto the wholeENDline with the same label, from the same place (the user's text, Claude's, or the very tool call, by its id), is withheld; a nested, mismatched or malformedENDis body, and a guard of unknown source closes at noEND. JSON line breaks are decided by the JSON parser, also for JSON inside prose, and markers and credential shapes are matched on the decoded JSON (string literals included), so\uescapes can't hide a key, and a block that can't be decoded that way is withheld whole (it can open a key, never close one); nothing outside JSON is unescaped. That state is saved per transcript beside its offset. There is no size bound: only the matchingENDline closes a key, so it fails closed. A strayBEGINkeeps the rest of that session out of reviews;learned-statusshows it, and the new/learned-skills:learned-reset-guard SESSIONclears that one session's guard (the full path when two share an id; recorded in the review log). The reviewer's stdout, stderr and summaries are redacted before they are clipped. - Each saved offset is bound to its transcript file (device, inode and a hash of its first 4 KB). A transcript replaced at the same path is read again from the start with a fresh guard, even when the new file is the same size or larger.
- Claude engine only. The optional Copilot CLI engine (records only) is removed, with its
transcript reader and the
engine_commandkey.engineaccepts onlyclaude. - Neutral wording throughout, a README with a data and cost disclosure, and a
licensefield in both manifests.
Private history
0.1.0 to 0.3.2 were built and used in a private plugin catalog on 2026-10-17 and 2026-10-18, with several rounds of review. In brief:
- 0.3.2: the digest is redacted before the review reads it (whole private-key blocks in PEM,
encrypted PEM, PGP and JSON-escaped shapes); the state directory is
0700and the digest0600. From a hostile-transcript red team. - 0.3.1: events and records written in the same second no longer collide; a
hostconfig key; the sink always rehashes and restages what it writes, whatever the sink's.gitignore. - 0.3.0: an optional Copilot CLI engine for records (removed again in 0.4.0) and the root Agent Plugins 1.0.0 manifest.
- 0.2.x: learning records (
kind: record), the git sink with its private clone, and the fail-closedfilter_command; review fixes; nobin/, and no user text in any command's shell line. - 0.1.0: the Stop hook, detached headless review,
💾 Learnednotices and undo, with credential-shape checks, frontmatter limited tonameanddescription, and no symlinks; after OpenClaw's skill workshop.