Skip to content

Releases: AndrewDongminYoo/cc-agents-kit

v0.3.9 — read-only git passes the staged-secret guard

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 03 Sep 08:52
4a8947c

The guard that scans a commit for credentials used to refuse git commands it could not parse, whether or not they committed anything. This release makes it wait until it knows the subcommand, and closes six ways a real commit slipped past it unseen.

What changed

guard-hooks 0.2.6 → 0.2.7staged-secret-guard no longer blocks read-only git. A global option the parser could not resolve, such as a shell variable in -C or a --no-pager, used to be refused before the subcommand was known, so for d in */; do git -C "$d" log; done came back as an unparsable commit. The verdict now waits for the subcommand and fires only on a real git commit. In the other direction, six shapes that ran a commit the hook never scanned are now caught: a long option carrying its value without = (git --git-dir /repo/.git commit), "$@" and "${name[@]}" expanding to several words, time git commit, a commit option value that can smuggle -a, a git command behind if or in a for/while body, and a subcommand that is itself an expansion (git "$cmd" -m x). -c and --config-env in front of a subcommand the hook cannot identify are still refused, now with a message that says so instead of reporting an unparsable commit, because command-scoped config can alias commit under another name.

The repository also carries docs/notes/staged-secret-guard-approach-review-2026-09-03.md: thirteen ways to reach a commit were run against the guard with a credential staged, and it recognises two. A function wrapper, sh -c, eval, a script file, xargs, make, and npm run all pass it. That is the ceiling of deciding from a command string what it will execute, and the note is there so the next attempt starts from that number.

repo-gate 0.1.5 → 0.1.6 — no code change. The bump exists because two skill changes from 1 September had shipped without one, and the client caches a plugin under its version string, so no installed copy had received them. ci-babysit now names the zero-steps failure shape: a run that fails in seconds with an empty steps array is a billing or runner block upstream of the workflow, read from the run's annotation rather than from a diff. cspell-triage warns that a local dictionary registered under a bundled dictionary's name silently masks the bundled one, and says to check a candidate name against cspell dictionaries before registering it. That last correction is #13 by @webdevsamran, the first outside contribution to this repository.

context-handoff is unchanged (0.1.4).

Why it matters

If you run guard-hooks 0.2.6, any git invocation with a variable in a global option is refused today, including loops over repositories and anything with --no-pager; 0.2.7 lets those through and scans strictly more of the commits that matter. If you run repo-gate 0.1.5, the two skill notes above are not in your copy at all, whatever the changelog says.

Upgrade

claude plugin update guard-hooks@cc-agents-kit
claude plugin update repo-gate@cc-agents-kit

v0.3.8 — mask credential-shaped values out of Bash output

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 31 Aug 07:15
8f94007

The guards already refused to read a secrets file and to commit a credential. This release closes the third path: a command that prints one.

What changed

guard-hooks 0.2.5 → 0.2.6 — new output-secret-mask.sh, a PostToolUse hook on Bash. After a command finishes it runs gitleaks' default rules over stdout and stderr and rewrites every credential-shaped value to [REDACTED] before the model sees the result (hookSpecificOutput.updatedToolOutput), with a one-line note so the agent treats the marker as deliberate. It never blocks and it fails open: no gitleaks, more than 2 MB of output, or an unexpected result shape means the output passes through untouched. Measured at about 30 ms per Bash call.

context-handoff and repo-gate are unchanged (0.1.4 / 0.1.5).

Why it matters

A cat of a config file, a printenv, or a verbose CLI that echoes its token used to leave the live value in the transcript for the rest of the session and in the session log on disk. Now only the masked copy reaches the model. Your terminal scrollback and any file the command wrote still hold the value — this masks the agent's copy, nothing else.

Upgrade

brew install gitleaks   # optional; without it the new hook is a silent no-op
claude plugin update guard-hooks@cc-agents-kit

Turn the hook off with CC_GUARD_DISABLE_OUTPUT_SECRET_MASK=1.

v0.3.7 — harden marketplace and workflow safeguards

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 28 Aug 11:22
d7c2613

v0.3.7

This release hardens plugin installation, safety hooks, and repository workflow tools.

Why update

  • Marketplace validation now rejects sources that can escape the checked repository.
  • Guard hooks recognize more valid command wrappers and more unsafe rewriter forms.
  • Repository workflow tools now report invalid input and GitHub API failures more clearly.

Plugin updates

  • context-handoff 0.1.30.1.4: session-to-md validates options and refuses to overwrite an existing output file.
  • guard-hooks 0.2.40.2.5: command parsing covers additional wrapper and rewriter forms without bypassing safety checks.
  • repo-gate 0.1.40.1.5: marketplace and CI checks are stricter, and workflow skills require explicit authority for mutations.

Upgrade

claude plugin update context-handoff@cc-agents-kit
claude plugin update guard-hooks@cc-agents-kit
claude plugin update repo-gate@cc-agents-kit

Restart any open Claude Code session after the update.

v0.3.6 — audit cuts

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 11:11
3fe6b7e

An over-engineering audit of the whole tree, and what it turned up. −234 lines of shipped code. Nothing you invoke changes except one flag-parsing fix.

claude plugin update guard-hooks@cc-agents-kit
claude plugin update context-handoff@cc-agents-kit

Then restart open sessions.

Fixed — session-to-md swallowed mistyped flags

Its hand-rolled argument loop treated anything it did not recognise as the session id. So --tools none typed as --tool none did not complain — it silently exported a different session.

The flags are parsed with node:util's parseArgs now, which is strict: a flag it does not know is an error naming the flag, on one line rather than as a stack trace. The usage line also still named session-to-md.mjs, the path this script had before 0.3.1 moved it into bin/.

Honest note: this one is not a cut. With the error handling it is a line or two longer than the loop it replaces. It shipped because the audit's "use the stdlib" finding turned out to be hiding a real defect.

The opt-out contract was copied seven times

Every guard hook checks its CC_GUARD_DISABLE_* variable after draining stdin, never before — otherwise a disabled hook leaves the harness writing to a closed pipe and the tool call reports an error at exactly the moment you asked the hook to be off. The case that pins this feeds the hook a 200KB payload through a real shell pipe and reads the writer's PIPESTATUS, because Python's subprocess swallows BrokenPipeError and an ordinary test cannot see the bug.

That machinery was byte-identical in all seven suites, and the three blocking cases after it were byte-identical in four. It now lives in hooks/_optout.py. 324 lines of copies became 29 lines of calls.

staged-secret-guard's diff selection

The -C <path> expansion was spelled out at twelve call sites, and the diff selection was three branches by two sub-branches of one command differing only in ref and pathspec — now one condition and one call.

One call stays spelled out, and the suite is why: routed through the new wrapper, $! became the subshell's pid instead of git's, so the kill and wait around check-attr no longer reached it and a terminated hook would have orphaned the child. The case caught it on the first run.

Smaller

  • CI listed the shell scripts to check twice, in two byte-identical heredocs. One step writes the list now, and asserts it is non-empty — a discovery that quietly matched nothing would otherwise leave both consumers passing on empty stdin, which is a gate that reads as green while checking nothing.
  • marketplace.json drops version. Verified dead rather than assumed: the client records a version per plugin in installed_plugins.json, and only source, install path, and timestamp in known_marketplaces.json. Nothing reads a marketplace-level version, and it had sat at 0.1.0 through five releases.
  • CLAUDE.md claimed a component thin enough to be effectively vendored upstream code does not belong here, while context-budget ships at 87% verbatim and CREDITS.md says so. The rule now matches the practice: mostly-upstream is fine as a declared redistribution — measured overlap, copyright holder named, licence permitting it — and what is barred is upstream work presented as this repository's own.

Verification

Equivalence is the whole claim of a refactor, so it was measured rather than inferred from the suites going green:

  • Ten fixtures covering every branch of the old diff-selection tree — staged, worktree, pathspec, -a, with and without HEAD — return identical verdicts under both versions.
  • Moving each hook's opt-out check above its stdin read fails the drain case in all seven suites, with writer=141. Deleting the check outright fails the first blocking case. No mutation was skipped for matching nothing.

Versions

guard-hooks 0.2.3 → 0.2.4, context-handoff 0.1.2 → 0.1.3. repo-gate is unchanged at 0.1.4.

v0.3.5 — a credential leak in staged-secret-guard

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 10:19
caa07a9

staged-secret-guard could let a staged credential through. If you use git commit -u, update.

claude plugin update guard-hooks@cc-agents-kit

Then restart open sessions. Affected releases are 0.3.2, 0.3.3, and 0.3.4; earlier ones do not carry the defect.

The leak

The guard's git commit flag table put -u / --untracked-files among the options that consume the next token as their value. git defines them as -u[<mode>] — the value is attached and optional, never a separate token.

So on git commit -u -m "fix: x" the guard read -m as -u's value and fix: x as a pathspec. It built a commit candidate for a path that does not exist, scanned nothing, and exited 0. Real git meanwhile parsed both flags correctly and committed the staged content. Confirmed end to end in a throwaway repository: exit 0, and the token sitting in HEAD.

-S / --gpg-sign carries the same optional-attached shape and is now handled with it. Both the separate-token forms and the attached forms (-uall, -S<key-id>) are covered by regression cases that exit 0 against the previous table.

The false block

git commit -q was refused as unparseable. -q / --quiet was simply absent from the no-value flag list, so it fell to the catch-all that blocks an unrecognised flag — a benign and extremely common flag failing closed. It is added along with the other no-value flags the table had omitted, each read off git commit's own option list rather than recalled: -z / --null, --verify, --no-signoff, --reset-author, --allow-empty, --allow-empty-message, --status / --no-status, --no-gpg-sign.

What stays refused, on purpose

  • Flags that change which content is committed: -i, -p, --interactive, --pathspec-from-file.
  • -e / --edit. It parses fine, but it opens $EDITOR against a shell with no TTY, so admitting it would trade a millisecond refusal for a hung tool call.
  • Bundled short flags (-sq) and anything unrecognised. The table is an allowlist because an unknown flag might take a value, and mis-parsing one is exactly how the leak above happened. The README's known-limits section now says so, rather than implying the list is exhaustive.

Verification

Every new case distinguishes "parsed, scanned, found the credential" from "refused to parse" — both exit 2, so the exit code alone proves nothing.

Coverage was checked by mutation, with none skipped. Four mutations, four caught: restoring -u to the value-consuming list, dropping -q, dropping the attached-value arm, and widening the list far enough to admit -p.

Versions

guard-hooks 0.2.3. context-handoff 0.1.2 and repo-gate 0.1.4 are unchanged.

v0.3.4 — cspell-dict-report

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 09:52
65245a8

cspell-triage gets the bulk form it was missing.

Upgrade with claude plugin update repo-gate@cc-agents-kit, then restart open sessions.

Added — cspell-dict-report

The skill had a one-word verify (cspell trace <word>) and nothing for a whole lint run, so step 2 was guesswork over a log and step 4 required editing a scratch copy of the config.

cspell lint . --words-only --no-progress --no-summary | cspell-dict-report

It traces every unique word in one process and separates four things: words a dictionary knows as misspellings (with the correction it hands over), words an enabled dictionary already covers, unenabled dictionaries worth adding, and words nothing has.

The ranking is a greedy set cover, not a per-dictionary count. Each row is scored against the words the rows above it leave behind, so NEW is what enabling that dictionary actually buys. Overlap is the norm — a common word sits in twenty dictionaries — and a plain count credits the second dictionary for words the first already took. It is also what lets the report state that a dictionary is redundant and be right about it.

Run backwards, it prunes. cspell-dict-report --exclude custom-dictionary < .cspell/custom-dictionary.txt answers "if this dictionary did not exist, what would still be covered?" without editing anything: the covered section is what to delete, and the in-no-dictionary section is what the file is carrying its weight for. That is not cspell trace --no-dictionary, which leaves the row in place and only re-marks it.

Three ways cspell trace output misleads a parser

Each is handled, and each is pinned by a case in the suite:

  • The name column truncates at 20 characters and marks the truncation with a trailing * — the same character that marks a dictionary as enabled. Eight bundled names are long enough to collide, so coding-compound-ter* is unreadable either way. cspell dictionaries prints both columns in full and its default path format is byte-identical, so the join runs on the on-disk location and the name column is never parsed.
  • A compound hit renders as look•ahead. The dictionary holds the pieces, not the word, so it clears only under allowCompoundWords — which the configs this skill writes turn off. Counting it would overstate what enabling that dictionary buys.
  • A word carrying a separator is decomposed, each part traced under its own heading with the whole word's verdict on the first line. cspell's own --words-only output never contains a separator, so this surfaces only on a hand-written list.

There is deliberately no --config passthrough: cspell -c merges with the config the repo already has instead of replacing it, so a report built on one would describe a configuration that does not exist.

Changed

CI runs every plugins/*/bin/*.test.py rather than naming one suite — the new suite would otherwise have shipped outside the gate, which is the same shape 0.3.1 and 0.3.2 both corrected — and pins actions/checkout at v7.

Provenance

CREDITS.md gains rows for cspell-dict-report and for find-trunk-repos, which shipped in 0.3.1 with no entry.

Versions

repo-gate 0.1.4. guard-hooks 0.2.2 and context-handoff 0.1.2 are unchanged.

v0.3.3 — hunk-level commit splitting

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 09:49
fdbf804

A file that genuinely spans two concerns no longer has to land wholesale in one commit.

Upgrade with claude plugin update repo-gate@cc-agents-kit, then restart open sessions.

Added — semantic-commit splits at hunk level

The skill used to assign a mixed file to whichever concern dominated it. It now splits the file when the split is real, and documents the mechanics that make it work unattended:

Driving git add -p over a pipe. It reads answers from stdin, so no TTY is needed. The git diff --cached confirmation is mandatory rather than optional, because on EOF git add -p quits: an answer count that does not match the hunk count under-stages, silently.

The patch-editing fallback, for two concerns interleaved inside one hunk. s splits on line counts and rarely lands on the concern boundary, so the hunk is edited directly and applied with --recount — deleting lines from inside a hunk leaves the @@ counts stale and git apply then rejects the whole file.

git stash push --keep-index --include-untracked before verifying. Verification then runs against the commit candidate instead of a working tree that no longer matches it. Without --include-untracked, a new file belonging to a later group stays in the tree and test discovery still finds it.

Provenance

CREDITS.md gains rows for pathless-rewriter-guard.sh and staged-secret-guard.sh. Both are original work, but they shipped in 0.2.0 with no entries, so the file that claims to cover everything published did not.

Versions

repo-gate 0.1.3. guard-hooks 0.2.2 and context-handoff 0.1.2 are unchanged.

v0.3.2 — sk- boundary fix, hardened guards, honest tool failures

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 25 Aug 09:49

A guard that blocked a legitimate commit, several guards that read less of a command than they claimed, and two tools that reported success on failure.

Upgrade with claude plugin update <plugin>@cc-agents-kit, then restart open sessions.

Fixed

staged-secret-guard blocked a real commit over a kebab-case word. Its two sk- key patterns had no left boundary, so any word whose tail cleared the 20-character floor read as an OpenAI key — live-task-status-transitioning contains sk-status-transitioning. Both patterns now require a non-word character or line start before sk-, with regression cases for the slug and for a key at the start of a line.

The guards read less of a command than they claimed to. Git global options, separated long-option globs, dangerous segments after the first, absolute pipeline interpreters, multi-suffix and mixed live dotenv paths, effective commit -a, pathspec candidates, and value-taking rewriter options were all outside what was actually parsed. Each is now covered, and large ShellCheck findings no longer fail on an advisory SIGPIPE.

find-trunk-repos reported an empty success when it had failed. GitHub authentication and repository-list failures now propagate instead of looking like "no repositories found" — the worst shape a discovery tool can take, because the caller cannot tell it apart from a real empty result.

config-gc guessed at which config root was live, and now derives one from CLAUDE_CONFIG_DIR or ~/.claude. setup-trunk selects linters from evidence in the repository before proposing a scoped change, rather than proposing a set and asking.

Changed

CI lints and parses extensionless Bash entries under plugins/*/bin/ as well as *.sh. The 0.3.1 claim that every shipped shell artifact was gated was broader than the gate that had been implemented.

Versions

All three plugins move: guard-hooks 0.2.1 → 0.2.2, context-handoff 0.1.1 → 0.1.2, repo-gate 0.1.1 → 0.1.2.

v0.3.1 — a skill that did not run, and the checks that missed it

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 19 Aug 00:07

A repository review, not new features. One skill did not work at all; two more shipped things nobody could reach; and the checks that were supposed to catch this had gaps.

Upgrade with claude plugin update <plugin>@cc-agents-kit, then restart open sessions.

Fixed

session-export could not run once installed. Its skill told the agent to execute ~/.claude/skills/session-export/scripts/session-to-md.mjs — the path it had while it was a personal skill, and a path that exists for nobody who installs the plugin.

Substituting ${CLAUDE_PLUGIN_ROOT} does not fix it. That substitution is for hook commands; the variable is unset in the Bash tool the skill actually uses. The mechanism intended for this is a plugin bin/ directory, whose contents join PATH while the plugin is enabled. The exporter now ships as context-handoff/bin/session-to-md and the skill calls it by bare name.

find-trunk-repos.sh shipped with nothing referencing it — a file users downloaded and could never be told to run. It now lives in repo-gate/bin/find-trunk-repos, where PATH reaches it, and setup-trunk documents what it is good at: reading a trunk config you already wrote instead of inventing one.

setup-trunk deferred day-to-day work to an agent that is not part of this repository. The paragraph now states the scope boundary rather than pointing at something the reader has no way to obtain.

Changed — the gate covers what it claimed to

Shellcheck and the bash 3.2 parse ran over plugins/*/hooks/*.sh only, so two scripts that ship to users sat outside them, one of them inside a skill. Both were clean, but a repository arguing that shell quality is gated should not exempt the shell it hands out. Both steps now walk every .sh in the tree. The hook-wiring check no longer hardcodes guard-hooks either.

Two new checks, each confirmed to fail before being trusted:

  • bin/ entries are executable and carry a shebang. On PATH and unrunnable is exactly the failure a skill calling it by bare name cannot observe — the shape of the bug above.
  • The demo is compared against what the guard actually prints, by running it. Comparing against the source would miss the part of the message assembled at runtime. It found a real mismatch on its first run: the recording had been shortening the message's final line, so the GIF showed text the tool does not print. Corrected, and the recording regenerated.

Versions

context-handoff and repo-gate move to 0.1.1. guard-hooks is unchanged at 0.2.1.

v0.3.0 — context-handoff and repo-gate

Choose a tag to compare

@AndrewDongminYoo AndrewDongminYoo released this 18 Aug 10:50

Two new plugins. The marketplace now covers the three moments where an agent session tends to lose something: a command that should not run, a session that ends, and a push that should not go out yet.

/plugin marketplace add AndrewDongminYoo/cc-agents-kit
/plugin install context-handoff@cc-agents-kit
/plugin install repo-gate@cc-agents-kit

context-handoff — work that outlives one session

Skill Use it when
handoff Context is long or degrading and the next session needs a self-contained brief.
session-export You want the transcript as readable markdown, tool calls collapsed.
log-it A session found something non-obvious. Routes each fact by who has to read it.
wayfinder The effort is too big for one session — charts it as decision tickets, one resolved per session.
context-budget You want to know what is eating the context window, ranked by what you would get back.
config-gc ~/.claude has accumulated, and stale items want removing one confirmation at a time.

repo-gate — between "the code works" and "it is pushed"

Skill Use it when
semantic-commit Unrelated changes are staged together and want splitting into conventional commits.
setup-trunk Adding trunk.io to a repo, or configuring it over an existing violation backlog.
ci-babysit A branch is pushed and CI has to go green — self-heals mechanical failures, then stops honestly.
fix-osv-vulnerabilities GHSA advisories need triage rather than a blanket bump.
cspell-triage cspell is reporting unknown words, or its dictionaries need consolidating.

On provenance

Three of these carry MIT-licensed work by other people. Rather than describing the adaptation, the overlap is measured — unique non-blank lines present in both files, over unique lines in the shipped one:

Skill Upstream Verbatim
wayfinder mattpocock/skills 14%
config-gc affaan-m/ecc 54%
context-budget affaan-m/ecc 87%

context-budget is a redistribution more than an adaptation, and is labelled as one rather than presented as original work. Each names its source in a metadata.origin key, and CREDITS.md carries the command to re-derive the percentages.

One candidate was dropped: a skill for whitelisting files into a dotfiles repo hardcoded a single machine's home as the git root, so there was nothing general to publish.

Also

CI now parses every skill's frontmatter and asserts its name matches its directory — either failing makes a skill install silently and never trigger — and rejects absolute home paths in published plugins. That second check exists because the review that cleared the first plugin grepped for path shapes and missed content: a keychain naming scheme and two pointers to a private CLAUDE.md shipped anyway.