feat(skills)!: kvb-dream processes everything, with kvb-optimize, cleanups and one reminder
kvb-skill-dream becomes the uncategorized collector kvb-dream. It reads the last 24 hours of every repository and session the agent touched, writes each finding into the memory of its own project, hands its findings to the new kvb-optimize (skill-cleanup, transcend, skill-auto, skill-tune, skill-compile, skill-essence, code-refactor, code-tune, think-gaps, one round of questions) and then runs kvb-cleanup and kvb-git-branch-gc. The separate kvb-skill-tune reminder is gone: one reminder reads the session id from the hook payload and hints each agent at most every twelve hours, and the global install enables kvb-dream-notify.timer, a systemd user timer that recommends /kvb-dream in a GNOME notification daily at 17:00 local time and catches up once after a missed run. enable-plugins.js retires the tune reminder hooks and repoints every earlier dream hook path. Operator order 2026-11-03; three descriptions trimmed for the listing budget. BREAKING CHANGE: /kvb-skill-dream is now /kvb-dream; kvb-skill-tune no longer has its own reminder hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c47393a73b
commit
5c30103b43
18 files changed
+327
-293
No files matched your search
@@ -9,7 +9,7 @@ Compatible with Claude Code, Codex, Gemini CLI, Cursor, Copilot, Windsurf, Cline
|
||||
|
||||
## Naming 🏷️
|
||||
|
||||
Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `check`, `org`, `skill`. Only `kvb-cleanup`, `kvb-help` and the collector `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category.
|
||||
Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `check`, `org`, `skill`. Only `kvb-cleanup`, `kvb-help` and the collectors `kvb-dream`, `kvb-optimize` and `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category.
|
||||
|
||||
## Organigram 🏛️
|
||||
|
||||
@@ -65,7 +65,6 @@ flowchart LR
|
||||
org --> org_wsjf[kvb-org-wsjf]
|
||||
org --> org_robot[kvb-org-robot]
|
||||
org --> org_operator[kvb-org-operator]
|
||||
skill --> skill_dream[kvb-skill-dream]
|
||||
skill --> skill_tune[kvb-skill-tune]
|
||||
skill --> skill_auto[kvb-skill-auto]
|
||||
skill --> skill_essence[kvb-skill-essence]
|
||||
@@ -75,6 +74,8 @@ flowchart LR
|
||||
none --> cleanup[kvb-cleanup]
|
||||
none --> help[kvb-help]
|
||||
none --> transcend_all[kvb-transcend]
|
||||
none --> dream_all[kvb-dream]
|
||||
none --> optimize_all[kvb-optimize]
|
||||
```
|
||||
|
||||
## Skill map 🗺️
|
||||
@@ -87,7 +88,7 @@ flowchart LR
|
||||
commit_req([commit request])
|
||||
stage_req([staging or fix ready])
|
||||
edit_hook([edit in a git worktree])
|
||||
reminders([reminder hooks])
|
||||
reminders([12 h agent hint, 17:00 GNOME popup])
|
||||
code_task([code task])
|
||||
spawn([subagent spawn])
|
||||
end
|
||||
@@ -144,7 +145,6 @@ flowchart LR
|
||||
operator[kvb-org-operator]
|
||||
end
|
||||
subgraph cat_skill [skill]
|
||||
dream[kvb-skill-dream]
|
||||
autotune[kvb-skill-tune]
|
||||
autoskill[kvb-skill-auto]
|
||||
essence[kvb-skill-essence]
|
||||
@@ -156,6 +156,8 @@ flowchart LR
|
||||
cleanup[kvb-cleanup]
|
||||
help_skills[kvb-help]
|
||||
transcend[kvb-transcend]
|
||||
dream[kvb-dream]
|
||||
optimize[kvb-optimize]
|
||||
end
|
||||
|
||||
commit_req -.-> commit_next
|
||||
@@ -163,7 +165,6 @@ flowchart LR
|
||||
stage_req -.-> confidence
|
||||
edit_hook -. council gate .-> democracy
|
||||
reminders -.-> dream
|
||||
reminders -.-> autotune
|
||||
code_task -.-> no_defaults
|
||||
code_task -.-> scope_guard
|
||||
spawn -.-> subagent_model
|
||||
@@ -227,18 +228,26 @@ flowchart LR
|
||||
robot --> dialectic
|
||||
|
||||
dream --> dialectic
|
||||
dream --> autotune
|
||||
dream --> autoskill
|
||||
dream --> optimize
|
||||
dream --> cleanup
|
||||
dream --> branch_gc
|
||||
optimize --> skill_cleanup
|
||||
optimize --> transcend
|
||||
optimize --> autoskill
|
||||
optimize --> autotune
|
||||
optimize --> compile
|
||||
optimize --> essence
|
||||
optimize --> refactor
|
||||
optimize --> tune
|
||||
optimize --> gaps
|
||||
autoskill --> autotune
|
||||
autotune --> essence
|
||||
dream --> transcend
|
||||
transcend --> skill_transcend
|
||||
transcend --> code_transcend
|
||||
skill_transcend --> active_listening
|
||||
skill_transcend --> autotune
|
||||
code_transcend --> active_listening
|
||||
autotune -. own scripts .-> skill_transcend
|
||||
dream --> compile
|
||||
autotune -. deterministic prose .-> compile
|
||||
autoskill -. deterministic steps .-> compile
|
||||
skill_transcend -. deterministic moves .-> compile
|
||||
@@ -274,7 +283,7 @@ Copy the skills into a specific project (`<repo>/.agents/skills` and `<repo>/.cl
|
||||
make project TARGET=/path/to/repo
|
||||
```
|
||||
|
||||
Both commands also patch the target's `.claude/settings.local.json` — the machine-local, untracked file, so a project's reviewed `.claude/settings.json` is never rewritten by an install: the caveman and ponytail plugins are enabled so their modes auto-activate on session start, and two reminder hooks are registered — `kvb-skill-tune` points you at `/kvb-skill-tune` at most once an hour, `kvb-skill-dream` points you at `/kvb-skill-dream` at most once every twelve hours per repository (stamped in that repository's git dir). A PreToolUse gate from the `kvb-org-organize` skill blocks edits to files in a git worktree until the session holds a claim (`skills/kvb-org-organize/scripts/council.py`), and its resource watch (`skills/kvb-org-organize/scripts/watch.py`) measures disk, Docker and caches, cleans up low-risk data and blocks new heavy jobs before the disk runs out; it stays inactive until `~/.config/agents-council/config.json` exists. Two hooks of the `kvb-org-perform` skill (`skills/kvb-org-perform/scripts/log.py`) log every Agent spawn with its task class, model and effort, and store the quality feedback a sandboxed session prints, in `~/.local/state/agents-council/subagent-model.jsonl`. Existing settings are preserved.
|
||||
Both commands also patch the target's `.claude/settings.local.json` — the machine-local, untracked file, so a project's reviewed `.claude/settings.json` is never rewritten by an install: the caveman and ponytail plugins are enabled so their modes auto-activate on session start, and one reminder hook is registered — `kvb-dream` points each agent at `/kvb-dream` at most once every twelve hours (stamped per session in `~/.claude/.dream-stamps/`); the global install also enables the `kvb-dream-notify.timer` systemd user timer, which recommends `/kvb-dream` in a GNOME notification daily at 17:00 local time and catches up once after a missed run. A PreToolUse gate from the `kvb-org-organize` skill blocks edits to files in a git worktree until the session holds a claim (`skills/kvb-org-organize/scripts/council.py`), and its resource watch (`skills/kvb-org-organize/scripts/watch.py`) measures disk, Docker and caches, cleans up low-risk data and blocks new heavy jobs before the disk runs out; it stays inactive until `~/.config/agents-council/config.json` exists. Two hooks of the `kvb-org-perform` skill (`skills/kvb-org-perform/scripts/log.py`) log every Agent spawn with its task class, model and effort, and store the quality feedback a sandboxed session prints, in `~/.local/state/agents-council/subagent-model.jsonl`. Existing settings are preserved.
|
||||
|
||||
Restart your agent afterwards so it loads the new skills.
|
||||
|
||||
|
||||
@@ -15,12 +15,16 @@ const MARKETPLACES = {
|
||||
ponytail: { source: { source: "github", repo: "DietrichGebert/ponytail" } },
|
||||
};
|
||||
const PLUGINS = { "caveman@caveman": true, "ponytail@ponytail": true };
|
||||
const REMINDERS = ["kvb-skill-tune", "kvb-skill-dream"];
|
||||
const REMINDERS = ["kvb-dream"];
|
||||
const RETIRED = [
|
||||
"autotune/hooks/reminder.sh",
|
||||
"kvb-skills-tune/hooks/reminder.sh",
|
||||
"kvb-skill-tune/hooks/reminder.sh",
|
||||
];
|
||||
const RENAMED = {
|
||||
"autotune/hooks/reminder.sh": "kvb-skill-tune/hooks/reminder.sh",
|
||||
"dream/hooks/reminder.sh": "kvb-skill-dream/hooks/reminder.sh",
|
||||
"kvb-skills-tune/hooks/reminder.sh": "kvb-skill-tune/hooks/reminder.sh",
|
||||
"kvb-skills-dream/hooks/reminder.sh": "kvb-skill-dream/hooks/reminder.sh",
|
||||
"dream/hooks/reminder.sh": "kvb-dream/hooks/reminder.sh",
|
||||
"kvb-skills-dream/hooks/reminder.sh": "kvb-dream/hooks/reminder.sh",
|
||||
"kvb-skill-dream/hooks/reminder.sh": "kvb-dream/hooks/reminder.sh",
|
||||
"democracy/scripts/council.py": "kvb-org-organize/scripts/council.py",
|
||||
"democracy/scripts/watch.py": "kvb-org-organize/scripts/watch.py",
|
||||
"subagent-model/scripts/log.py": "kvb-org-perform/scripts/log.py",
|
||||
@@ -46,6 +50,15 @@ for (const [name, entry] of Object.entries(MARKETPLACES)) {
|
||||
data.enabledPlugins = Object.assign(data.enabledPlugins || {}, PLUGINS);
|
||||
|
||||
data.hooks = data.hooks || {};
|
||||
const retired = (hook) =>
|
||||
typeof hook.command === "string" && RETIRED.some((r) => hook.command.includes(`/skills/${r}`));
|
||||
for (const [event, entries] of Object.entries(data.hooks)) {
|
||||
if (!Array.isArray(entries)) continue;
|
||||
data.hooks[event] = entries
|
||||
.map((entry) =>
|
||||
Array.isArray(entry.hooks) ? { ...entry, hooks: entry.hooks.filter((h) => !retired(h)) } : entry)
|
||||
.filter((entry) => !Array.isArray(entry.hooks) || entry.hooks.length > 0);
|
||||
}
|
||||
for (const entries of Object.values(data.hooks)) {
|
||||
if (!Array.isArray(entries)) continue;
|
||||
for (const entry of entries) {
|
||||
|
||||
@@ -53,4 +53,17 @@ else
|
||||
warn "skills: node not found; skipped auto-enabling caveman/ponytail plugins."
|
||||
fi
|
||||
|
||||
if [[ "${TARGET}" == "${HOME}" ]]; then
|
||||
if command -v systemctl &>/dev/null && systemctl --user show-environment &>/dev/null; then
|
||||
log "skills: enabling the daily kvb-dream notification timer"
|
||||
units="${HOME}/.config/systemd/user"
|
||||
mkdir -p "${units}"
|
||||
cp -f "${REPO_ROOT}/skills/kvb-dream/systemd/kvb-dream-notify.service" "${REPO_ROOT}/skills/kvb-dream/systemd/kvb-dream-notify.timer" "${units}/"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now kvb-dream-notify.timer
|
||||
else
|
||||
warn "skills: no systemd user session; skipped the kvb-dream notification timer."
|
||||
fi
|
||||
fi
|
||||
|
||||
log "skills: done. Restart your agent to load the skills."
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: kvb-check-legal
|
||||
description: >
|
||||
Check a commit for legal soundness: licence compatibility of added code and
|
||||
dependencies, attribution, copied snippets, GDPR, trademarks. Run
|
||||
AUTOMATICALLY before every commit the operator requests; also on /kvb-check-legal
|
||||
[ref|range], "rechtlich prüfen", "lizenz prüfen".
|
||||
Check a commit's licences, attribution, copied snippets, GDPR and
|
||||
trademarks. Run AUTOMATICALLY before every commit the operator requests;
|
||||
also on /kvb-check-legal [ref|range], "rechtlich prüfen", "lizenz prüfen".
|
||||
---
|
||||
|
||||
The stance is the project's counsel: find what would make the change unlawful
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
name: kvb-skill-dream
|
||||
name: kvb-dream
|
||||
description: >
|
||||
Consolidate the last 24 hours of this repository (what held, what broke)
|
||||
into memory and proposals. Trigger on /kvb-skill-dream or a request to
|
||||
learn from recent work.
|
||||
Process the last 24 hours of every repository and session: memory,
|
||||
improvements, kvb-optimize, cleanups. Trigger on /kvb-dream or a request
|
||||
to learn from recent work.
|
||||
---
|
||||
|
||||
Sleep on the work: replay what happened in this repository, separate what held
|
||||
from what broke, and keep only the conclusions that survive an attack. A dream
|
||||
that flatters the dreamer is worthless — an unverified success counts as
|
||||
unknown, not as a win.
|
||||
Sleep on the work: replay what happened, separate what held from what broke,
|
||||
keep only the conclusions that survive an attack, then optimize and clean up.
|
||||
Dreaming processes everything. A dream that flatters the dreamer is worthless —
|
||||
an unverified success counts as unknown, not as a win.
|
||||
|
||||
## Objective
|
||||
|
||||
The dream exists to raise the confidence of every future run in this repository
|
||||
until the mistakes it found cannot happen again. Judge every finding, memory
|
||||
The dream exists to raise the confidence of every future run until the
|
||||
mistakes it found cannot happen again. Judge every finding, memory
|
||||
entry, and proposal by one question: does it make the next run measurably less
|
||||
likely to repeat this failure? What changes nothing about future behaviour is
|
||||
not worth recording.
|
||||
@@ -27,18 +27,23 @@ confidence the next run can now hold on each closed class, calibrated as the
|
||||
|
||||
## Scope
|
||||
|
||||
Everything is read from the repository the skill runs in (`git rev-parse
|
||||
--show-toplevel`); never mix in another project's history or memory. The default
|
||||
window is the last 24 hours — the operator may widen it (`/kvb-skill-dream 7d`). State the
|
||||
repository and the window in one line before you start.
|
||||
Every repository and session this agent touched in the window: the
|
||||
repositories named by its session transcripts (working directories and
|
||||
worktrees), by the workdirs of its `kvb-org-rasci` register tasks, and the
|
||||
current one. The default window is the last 24 hours — the operator may widen
|
||||
it (`/kvb-dream 7d`). State the repositories and the window in one table before
|
||||
you start. Each repository is recalled and scored on its own; a finding goes
|
||||
into the memory of the project it concerns, never mixed with another's.
|
||||
|
||||
Run only when the operator asks. The 12-hour reminder hook
|
||||
(`hooks/reminder.sh`, stamped per repository in its git dir) is a hint for the
|
||||
operator, not a trigger: print it and continue with their actual request.
|
||||
Run only when the operator asks. Two hints point at it, neither is a trigger:
|
||||
`hooks/reminder.sh` tells each agent at most once every twelve hours (stamped
|
||||
per session in `~/.claude/.dream-stamps/`), and the `kvb-dream-notify.timer`
|
||||
user timer shows a GNOME notification daily at 17:00 local time, catching up
|
||||
once after a missed run. Print the hint and continue with the actual request.
|
||||
|
||||
## Step 1: recall
|
||||
|
||||
Gather the window's actual record, not your recollection of it:
|
||||
Gather the window's actual record per repository, not your recollection of it:
|
||||
|
||||
- **History**: `git log --since` across all branches plus `--reflog`, with
|
||||
`--stat`; the reflog carries the resets, rebases, and amends the log hides.
|
||||
@@ -50,7 +55,7 @@ Gather the window's actual record, not your recollection of it:
|
||||
- **Register**: the `kvb-org-rasci` register: `rasci.py tools` ranks the
|
||||
scripts and tools the window's tasks ran, the refactoring candidates;
|
||||
`get <task>` shows a task's objective, roles, workdir and artifacts.
|
||||
- **Memory**: this project's memory directory and its `MEMORY.md` index — the
|
||||
- **Memory**: each project's memory directory and its `MEMORY.md` index — the
|
||||
findings already recorded, so the dream extends them instead of repeating them.
|
||||
- **Instructions**: `CLAUDE.md`, `AGENTS.md`, and the project's own skills, to
|
||||
see which rule a mistake actually violated.
|
||||
@@ -86,7 +91,7 @@ the next dream does not re-derive it.
|
||||
|
||||
## Step 5: write memory
|
||||
|
||||
Persist the findings in this project's memory directory: one file per fact, one
|
||||
Persist the findings in the memory directory of the project they concern: one file per fact, one
|
||||
pointer line in `MEMORY.md`. Update the file that already covers a fact instead
|
||||
of adding a duplicate, and delete the memories this run disproved. Absolute
|
||||
dates, never "yesterday". Nothing that the repository already records — code
|
||||
@@ -96,7 +101,7 @@ structure, git history, `CLAUDE.md` — belongs in memory.
|
||||
|
||||
Two kinds come out of the findings:
|
||||
|
||||
- **Code** — the change to this repository's source, tests, config, or CI that
|
||||
- **Code** — the change to the affected repository's source, tests, config, or CI that
|
||||
the finding calls for.
|
||||
- **Agent instructions** — the missing or wrong rule in `CLAUDE.md`,
|
||||
`AGENTS.md`, the project's own skills, or the contributor docs. Every mistake
|
||||
@@ -108,18 +113,24 @@ 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-skill-tune, kvb-skill-auto, kvb-transcend, kvb-skill-compile
|
||||
## Step 7: optimize
|
||||
|
||||
With the dialectic done and memory written, run `kvb-skill-tune` — hand it this run's
|
||||
findings as its evidence so it does not re-collect them — then `kvb-skill-auto`
|
||||
for the operator corrections the window revealed, `kvb-transcend` for the
|
||||
scripts and tests the window's sessions wrote outside a repository, and
|
||||
`kvb-skill-compile` for the skill steps the window's sessions worked through
|
||||
by hand although they are deterministic. All keep
|
||||
their own confirmation steps; the dream never writes a skill on their behalf.
|
||||
With the dialectic done and memory written, run the `kvb-optimize` skill and
|
||||
hand it this run's findings as its evidence, so none of its skills
|
||||
re-collects them.
|
||||
|
||||
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"`.
|
||||
## Step 8: clean up
|
||||
|
||||
Run the `kvb-cleanup` skill for the disk, `/tmp`, Docker and agent
|
||||
directories the window filled, with the artifacts of the window's finished
|
||||
register tasks (`rasci.py artifacts --ended`) as its candidates, then the
|
||||
`kvb-git-branch-gc` skill for the merged branches and worktrees of every
|
||||
repository in scope.
|
||||
|
||||
Every skill in steps 7 and 8 keeps its own confirmation steps; the dream never
|
||||
writes, deletes or commits on their behalf. Close the run by refreshing this
|
||||
agent's reminder stamp, so its next hint is twelve hours out:
|
||||
`date +%s > ~/.claude/.dream-stamps/"$CLAUDE_CODE_SESSION_ID"`.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -129,8 +140,8 @@ is twelve hours out: `date +%s > "$(git rev-parse --path-format=absolute --git-c
|
||||
attempt even when the final state is green.
|
||||
- Read-only until step 6: the dream inspects, and writes memory; repository code
|
||||
changes only on an explicit radio answer.
|
||||
- An empty window is a valid result — report "nothing in the last 24 hours in
|
||||
this repository" and stop. Never invent activity to have something to dream
|
||||
- An empty window is a valid result — report "nothing in the last 24 hours" per
|
||||
repository and skip it. Never invent activity to have something to dream
|
||||
about.
|
||||
- Prefer the structural fix over the remembered one: a test, check, hook, or
|
||||
written rule that makes the error impossible beats a memory entry that only
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
[[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] || exit 0
|
||||
command -v notify-send >/dev/null || exit 0
|
||||
|
||||
notify-send --app-name=kvb-dream "kvb-dream" \
|
||||
"Time to dream: run /kvb-dream in your agent sessions to process the day."
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
interval="${DREAM_INTERVAL_SECONDS:-43200}"
|
||||
stamps="${DREAM_STAMP_DIR:-${HOME}/.claude/.dream-stamps}"
|
||||
|
||||
session="$(python3 -c 'import json, sys; print(json.load(sys.stdin).get("session_id", ""))' 2>/dev/null)" || exit 0
|
||||
[[ "${session}" =~ ^[A-Za-z0-9_-]+$ ]] || exit 0
|
||||
stamp="${stamps}/${session}"
|
||||
|
||||
now="$(date +%s)"
|
||||
last=0
|
||||
if [[ -f "${stamp}" ]]; then
|
||||
read -r last <"${stamp}" || true
|
||||
[[ "${last}" =~ ^[0-9]+$ ]] || last=0
|
||||
fi
|
||||
|
||||
if ((now - last < interval)); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "${stamps}"
|
||||
printf '%s\n' "${now}" >"${stamp}"
|
||||
|
||||
cat <<'HINT'
|
||||
DREAM HINT: over twelve hours since this agent's last hint. Show the operator
|
||||
this notice verbatim as a short block, then continue with their request. Do NOT
|
||||
run kvb-dream, do not start replaying the history, do not ask questions about
|
||||
it.
|
||||
|
||||
💤 /kvb-dream - process the last 24h of every repository and session this
|
||||
agent touched: memory, improvements, kvb-optimize and the cleanups.
|
||||
Runs only when you trigger it.
|
||||
HINT
|
||||
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=Recommend /kvb-dream in a GNOME notification
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/env bash %h/.claude/skills/kvb-dream/hooks/notify.sh
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Daily /kvb-dream recommendation at 17:00 local time
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 17:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: kvb-optimize
|
||||
description: >
|
||||
Run every optimization skill in one pass with one round of questions.
|
||||
Trigger on /kvb-optimize; kvb-dream runs it.
|
||||
---
|
||||
|
||||
Collector for the optimization skills, so one pass needs one evidence
|
||||
collection and one round of questions. Evidence handed over by the
|
||||
`kvb-dream` skill is used as is; standalone, collect it once from the
|
||||
current session and repository and hand the same set to every skill.
|
||||
|
||||
## Order
|
||||
|
||||
Each runs its own inventory and keeps its own confirmation step; an empty
|
||||
inventory is skipped with one line.
|
||||
|
||||
1. `kvb-skill-cleanup`: delete the legacy skills first, so nothing below
|
||||
optimizes a dead copy.
|
||||
2. `kvb-transcend`: carry scripts, tests and skills out of scratchpads and
|
||||
agent homes into the skills repository or the current repository.
|
||||
3. `kvb-skill-auto`: turn the operator's repetitions into skills.
|
||||
4. `kvb-skill-tune`: turn frictions into new or sharper skills.
|
||||
5. `kvb-skill-compile`: turn deterministic skill steps into scripts.
|
||||
6. `kvb-skill-essence`: compact skills and memory.
|
||||
7. `kvb-code-refactor`: bring the uncommitted changes up to the project's
|
||||
standards.
|
||||
8. `kvb-code-tune`: optimize the hot paths the evidence measured.
|
||||
9. `kvb-think-gaps`: find the missing work and hand each pick to
|
||||
`kvb-create-requirement`.
|
||||
|
||||
## Questions
|
||||
|
||||
Collect the candidates of all skills first, then run one `kvb-think-listen`
|
||||
round and put every skill's own confirmation questions into one batch,
|
||||
grouped by skill. Each skill writes only what was picked for it.
|
||||
|
||||
## Output
|
||||
|
||||
| Skill | Candidates | Picked | Written | Skipped with reason |
|
||||
|
||||
Tests and lints of the written changes run once at the end, on the resource
|
||||
chair's grant (the `kvb-org-organize` skill); nothing is committed.
|
||||
@@ -53,7 +53,7 @@ published). `done` is refused while one is open; a task ended short of them is
|
||||
executing the task right now.
|
||||
- Artifacts share this table and follow the `kvb-org-artefacts` skill:
|
||||
record each one when it appears, drop it once deleted. Add every script or
|
||||
tool the task ran (`--tool`): `tools` ranks them for `kvb-skill-dream`.
|
||||
tool the task ran (`--tool`): `tools` ranks them for `kvb-dream`.
|
||||
- Priority is the rank of the `kvb-org-wsjf` skill: an operator's pin
|
||||
(`--prio`) first, then WSJF from the estimates the chair collects; `show`
|
||||
prints the rank as Prio and the score as WSJF.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
interval="${DREAM_INTERVAL_SECONDS:-43200}"
|
||||
if [[ -n "${DREAM_STAMP:-}" ]]; then
|
||||
stamp="${DREAM_STAMP}"
|
||||
else
|
||||
git_dir="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || exit 0
|
||||
stamp="${git_dir}/.dream-stamp"
|
||||
fi
|
||||
|
||||
now="$(date +%s)"
|
||||
last=0
|
||||
if [[ -f "${stamp}" ]]; then
|
||||
read -r last <"${stamp}" || true
|
||||
[[ "${last}" =~ ^[0-9]+$ ]] || last=0
|
||||
fi
|
||||
|
||||
if ((now - last < interval)); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${stamp}")"
|
||||
printf '%s\n' "${now}" >"${stamp}"
|
||||
|
||||
cat <<'HINT'
|
||||
DREAM HINT: over twelve hours since the last hint for this repository. Show the
|
||||
operator this notice verbatim as a short block, then continue with their
|
||||
request. Do NOT run kvb-skill-dream, do not start replaying the history, do
|
||||
not ask questions about it.
|
||||
|
||||
💤 /kvb-skill-dream - consolidate the last 24h in this repository: what
|
||||
held, what broke, which hypotheses follow, into memory and improvement
|
||||
proposals.
|
||||
Runs only when you trigger it.
|
||||
HINT
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
name: kvb-skill-essence
|
||||
description: >
|
||||
Compact skills and memory to their essence after a health pass that finds
|
||||
dead weight. Trigger on /kvb-skill-essence or when skills or MEMORY.md grow too long,
|
||||
duplicate, or eat too much context.
|
||||
Compact skills and memory after a health pass for dead weight. Trigger on
|
||||
/kvb-skill-essence or when skills or MEMORY.md grow too long or duplicate.
|
||||
---
|
||||
|
||||
Shorter, not thinner. Every fact that still earns its place survives; what goes
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: kvb-skill-tune
|
||||
description: >
|
||||
Generate new skills or optimize existing ones (fewer tokens and errors,
|
||||
persistent learnings), each proposal confirmed. Trigger ONLY on an explicit
|
||||
request: /kvb-skill-tune, "autotune", "tune the skills".
|
||||
Generate or sharpen skills from observed friction, each proposal confirmed.
|
||||
Trigger only on /kvb-skill-tune, "autotune", "tune the skills" or through
|
||||
kvb-optimize.
|
||||
---
|
||||
|
||||
Autotune turns observed friction into durable skills. It runs on demand only,
|
||||
@@ -43,9 +43,9 @@ it moves is not an optimization, it is a preference.
|
||||
## Trigger discipline
|
||||
|
||||
- Run **only** when the operator asks: `/kvb-skill-tune`, "autotune", "tune the
|
||||
skills", or an equivalent instruction.
|
||||
- The 60-minute reminder hook is a hint for the operator, not a trigger. When it
|
||||
fires, print the hint and continue the operator's actual request.
|
||||
skills", or an equivalent instruction, or as part of the `kvb-optimize`
|
||||
skill the operator started (directly or through `kvb-dream`).
|
||||
- It has no reminder of its own; the `kvb-dream` hint covers it.
|
||||
- Never propose, write, or edit a skill as a side effect of unrelated work.
|
||||
|
||||
## Step 1: collect evidence
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
interval="${AUTOTUNE_INTERVAL_SECONDS:-3600}"
|
||||
stamp="${AUTOTUNE_STAMP:-${HOME}/.claude/.autotune-stamp}"
|
||||
|
||||
now="$(date +%s)"
|
||||
last=0
|
||||
if [[ -f "${stamp}" ]]; then
|
||||
read -r last <"${stamp}" || true
|
||||
[[ "${last}" =~ ^[0-9]+$ ]] || last=0
|
||||
fi
|
||||
|
||||
if ((now - last < interval)); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${stamp}")"
|
||||
printf '%s\n' "${now}" >"${stamp}"
|
||||
|
||||
cat <<'HINT'
|
||||
AUTOTUNE HINT: over an hour since the last hint. Show the operator this notice
|
||||
verbatim as a short block, then continue with their request. Do NOT run
|
||||
kvb-skill-tune, do not propose skills, do not ask questions about it.
|
||||
|
||||
💡 /kvb-skill-tune - generate a new skill or optimize an existing one
|
||||
(fewer tokens, fewer errors, more focus, learnings that persist).
|
||||
Runs only when you trigger it.
|
||||
HINT
|
||||
+4
-83
@@ -1,33 +1,12 @@
|
||||
"""Validate the kvb-skill-tune reminder hook and its settings registration."""
|
||||
"""Validate the kvb-skill-tune skill text and its persistence targets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILL = REPO_ROOT / "skills" / "kvb-skill-tune" / "SKILL.md"
|
||||
HOOK = REPO_ROOT / "skills" / "kvb-skill-tune" / "hooks" / "reminder.sh"
|
||||
PATCHER = REPO_ROOT / "scripts" / "enable-plugins.js"
|
||||
|
||||
|
||||
def _run_hook(stamp: Path, interval: str = "3600") -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
["bash", str(HOOK)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
env={
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": str(stamp.parent),
|
||||
"AUTOTUNE_STAMP": str(stamp),
|
||||
"AUTOTUNE_INTERVAL_SECONDS": interval,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestAutotuneSkill(unittest.TestCase):
|
||||
@@ -36,6 +15,9 @@ class TestAutotuneSkill(unittest.TestCase):
|
||||
self.assertIn("name: kvb-skill-tune", text)
|
||||
self.assertIn("multiSelect: false", text)
|
||||
|
||||
def test_skill_has_no_own_reminder_hook(self):
|
||||
self.assertFalse((SKILL.parent / "hooks").exists())
|
||||
|
||||
|
||||
class TestSkillPersistence(unittest.TestCase):
|
||||
"""Both skill writers must persist to the skills repository AND the live dirs."""
|
||||
@@ -58,66 +40,5 @@ class TestSkillPersistence(unittest.TestCase):
|
||||
self.assertIn(target, text)
|
||||
|
||||
|
||||
class TestAutotuneHook(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.stamp = Path(self.tmp.name) / ".claude" / ".autotune-stamp"
|
||||
|
||||
def test_first_run_hints_and_stamps(self):
|
||||
result = _run_hook(self.stamp)
|
||||
self.assertIn("/kvb-skill-tune", result.stdout)
|
||||
self.assertTrue(self.stamp.is_file())
|
||||
|
||||
def test_second_run_is_silent_within_interval(self):
|
||||
_run_hook(self.stamp)
|
||||
self.assertEqual(_run_hook(self.stamp).stdout, "")
|
||||
|
||||
def test_hint_returns_after_the_interval(self):
|
||||
_run_hook(self.stamp)
|
||||
self.assertIn("/kvb-skill-tune", _run_hook(self.stamp, interval="0").stdout)
|
||||
|
||||
def test_corrupt_stamp_does_not_crash(self):
|
||||
self.stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.stamp.write_text("not-a-timestamp\n", encoding="utf-8")
|
||||
self.assertIn("/kvb-skill-tune", _run_hook(self.stamp).stdout)
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("node"), "node not installed")
|
||||
class TestSettingsRegistration(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.settings = Path(self.tmp.name) / "settings.json"
|
||||
|
||||
def _patch(self) -> dict:
|
||||
subprocess.run(
|
||||
["node", str(PATCHER), str(self.settings)], check=True, capture_output=True
|
||||
)
|
||||
return json.loads(self.settings.read_text(encoding="utf-8"))
|
||||
|
||||
def test_hook_registered_once(self):
|
||||
self._patch()
|
||||
data = self._patch()
|
||||
entries = json.dumps(data["hooks"]["UserPromptSubmit"])
|
||||
self.assertEqual(entries.count("kvb-skill-tune/hooks/reminder.sh"), 1)
|
||||
|
||||
def test_existing_hooks_preserved(self):
|
||||
self.settings.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{"hooks": [{"type": "command", "command": "true"}]}
|
||||
]
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
data = self._patch()
|
||||
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+113
-82
@@ -1,4 +1,4 @@
|
||||
"""Validate the dream reminder hook and its settings registration."""
|
||||
"""Validate the kvb-dream reminder, its GNOME notification, timer and settings registration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -10,101 +10,124 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILL = REPO_ROOT / "skills" / "kvb-skill-dream" / "SKILL.md"
|
||||
HOOK = REPO_ROOT / "skills" / "kvb-skill-dream" / "hooks" / "reminder.sh"
|
||||
DREAM = REPO_ROOT / "skills" / "kvb-dream"
|
||||
SKILL = DREAM / "SKILL.md"
|
||||
HOOK = DREAM / "hooks" / "reminder.sh"
|
||||
NOTIFY = DREAM / "hooks" / "notify.sh"
|
||||
TIMER = DREAM / "systemd" / "kvb-dream-notify.timer"
|
||||
SERVICE = DREAM / "systemd" / "kvb-dream-notify.service"
|
||||
PATCHER = REPO_ROOT / "scripts" / "enable-plugins.js"
|
||||
|
||||
|
||||
def _run_hook(
|
||||
cwd: Path, stamp: Path | None = None, interval: str = "43200"
|
||||
) -> subprocess.CompletedProcess:
|
||||
env = {
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": str(cwd),
|
||||
"DREAM_INTERVAL_SECONDS": interval,
|
||||
}
|
||||
if stamp is not None:
|
||||
env["DREAM_STAMP"] = str(stamp)
|
||||
return subprocess.run(
|
||||
["bash", str(HOOK)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
cwd=str(cwd),
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
class TestDreamSkill(unittest.TestCase):
|
||||
def test_skill_is_trigger_only(self):
|
||||
def test_skill_runs_optimize_and_the_cleanups(self):
|
||||
text = SKILL.read_text(encoding="utf-8")
|
||||
self.assertIn("name: kvb-skill-dream", text)
|
||||
self.assertIn("name: kvb-dream", text)
|
||||
self.assertIn("multiSelect: false", text)
|
||||
for skill in ("`kvb-optimize`", "`kvb-cleanup`", "`kvb-git-branch-gc`"):
|
||||
self.assertIn(skill, text)
|
||||
|
||||
def test_optimize_covers_every_optimization_skill(self):
|
||||
text = (REPO_ROOT / "skills" / "kvb-optimize" / "SKILL.md").read_text(encoding="utf-8")
|
||||
for skill in (
|
||||
"kvb-skill-cleanup",
|
||||
"kvb-transcend",
|
||||
"kvb-skill-auto",
|
||||
"kvb-skill-tune",
|
||||
"kvb-skill-compile",
|
||||
"kvb-skill-essence",
|
||||
"kvb-code-refactor",
|
||||
"kvb-code-tune",
|
||||
"kvb-think-gaps",
|
||||
):
|
||||
self.assertIn(f"`{skill}`", text)
|
||||
|
||||
|
||||
class TestDreamHook(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.cwd = Path(self.tmp.name)
|
||||
self.stamp = self.cwd / ".claude" / ".dream-stamp"
|
||||
self.stamps = Path(self.tmp.name) / "stamps"
|
||||
|
||||
def test_first_run_hints_and_stamps(self):
|
||||
result = _run_hook(self.cwd, self.stamp)
|
||||
self.assertIn("/kvb-skill-dream", result.stdout)
|
||||
self.assertTrue(self.stamp.is_file())
|
||||
def _run(self, payload: str, interval: str = "43200") -> str:
|
||||
return subprocess.run(
|
||||
["bash", str(HOOK)],
|
||||
input=payload,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
env={
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": self.tmp.name,
|
||||
"DREAM_STAMP_DIR": str(self.stamps),
|
||||
"DREAM_INTERVAL_SECONDS": interval,
|
||||
},
|
||||
).stdout
|
||||
|
||||
def test_second_run_is_silent_within_interval(self):
|
||||
_run_hook(self.cwd, self.stamp)
|
||||
self.assertEqual(_run_hook(self.cwd, self.stamp).stdout, "")
|
||||
@staticmethod
|
||||
def _session(sid: str) -> str:
|
||||
return json.dumps({"session_id": sid, "prompt": "hi"})
|
||||
|
||||
def test_first_run_hints_and_stamps_the_session(self):
|
||||
self.assertIn("/kvb-dream", self._run(self._session("a1")))
|
||||
self.assertTrue((self.stamps / "a1").is_file())
|
||||
|
||||
def test_second_run_of_the_same_agent_is_silent(self):
|
||||
self._run(self._session("a1"))
|
||||
self.assertEqual(self._run(self._session("a1")), "")
|
||||
|
||||
def test_every_agent_gets_its_own_hint(self):
|
||||
self._run(self._session("a1"))
|
||||
self.assertIn("/kvb-dream", self._run(self._session("b2")))
|
||||
|
||||
def test_hint_returns_after_the_interval(self):
|
||||
_run_hook(self.cwd, self.stamp)
|
||||
self.assertIn("/kvb-skill-dream", _run_hook(self.cwd, self.stamp, interval="0").stdout)
|
||||
self._run(self._session("a1"))
|
||||
self.assertIn("/kvb-dream", self._run(self._session("a1"), interval="0"))
|
||||
|
||||
def test_corrupt_stamp_does_not_crash(self):
|
||||
self.stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.stamp.write_text("not-a-timestamp\n", encoding="utf-8")
|
||||
self.assertIn("/kvb-skill-dream", _run_hook(self.cwd, self.stamp).stdout)
|
||||
self.stamps.mkdir()
|
||||
(self.stamps / "a1").write_text("not-a-timestamp\n", encoding="utf-8")
|
||||
self.assertIn("/kvb-dream", self._run(self._session("a1")))
|
||||
|
||||
def test_outside_a_repository_it_stays_silent(self):
|
||||
self.assertEqual(_run_hook(self.cwd).stdout, "")
|
||||
def test_without_a_valid_session_it_stays_silent(self):
|
||||
for payload in ("", "not json", self._session(""), self._session("../escape")):
|
||||
with self.subTest(payload=payload):
|
||||
self.assertEqual(self._run(payload), "")
|
||||
self.assertFalse(self.stamps.exists())
|
||||
|
||||
@unittest.skipUnless(shutil.which("git"), "git not installed")
|
||||
def test_stamp_is_per_repository(self):
|
||||
for name in ("one", "two"):
|
||||
repo = self.cwd / name
|
||||
repo.mkdir()
|
||||
subprocess.run(
|
||||
["git", "init", "-q", str(repo)], check=True, capture_output=True
|
||||
)
|
||||
self.assertIn("/kvb-skill-dream", _run_hook(repo).stdout)
|
||||
self.assertTrue((repo / ".git" / ".dream-stamp").is_file())
|
||||
self.assertEqual(_run_hook(repo).stdout, "")
|
||||
|
||||
@unittest.skipUnless(shutil.which("git"), "git not installed")
|
||||
def test_linked_worktrees_share_the_stamp(self):
|
||||
repo = self.cwd / "main"
|
||||
tree = self.cwd / "tree"
|
||||
env = {
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": str(self.cwd),
|
||||
"GIT_AUTHOR_NAME": "t",
|
||||
"GIT_AUTHOR_EMAIL": "t@t",
|
||||
"GIT_COMMITTER_NAME": "t",
|
||||
"GIT_COMMITTER_EMAIL": "t@t",
|
||||
}
|
||||
repo.mkdir()
|
||||
for args in (
|
||||
["init", "-q"],
|
||||
["commit", "-q", "--allow-empty", "-m", "init"],
|
||||
["worktree", "add", "-q", str(tree)],
|
||||
):
|
||||
subprocess.run(
|
||||
["git", *args], check=True, capture_output=True, env=env, cwd=str(repo)
|
||||
)
|
||||
self.assertIn("/kvb-skill-dream", _run_hook(repo).stdout)
|
||||
self.assertEqual(_run_hook(tree).stdout, "")
|
||||
class TestDreamNotification(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.bin = Path(self.tmp.name) / "bin"
|
||||
self.bin.mkdir()
|
||||
self.calls = Path(self.tmp.name) / "calls"
|
||||
fake = self.bin / "notify-send"
|
||||
fake.write_text(f'#!/bin/sh\nprintf "%s\\n" "$@" >> "{self.calls}"\n', encoding="utf-8")
|
||||
fake.chmod(0o755)
|
||||
|
||||
def _run(self, desktop: str) -> None:
|
||||
subprocess.run(
|
||||
["bash", str(NOTIFY)],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
env={"PATH": f"{self.bin}:/usr/bin:/bin", "XDG_CURRENT_DESKTOP": desktop},
|
||||
)
|
||||
|
||||
def test_gnome_session_gets_the_popup(self):
|
||||
self._run("ubuntu:GNOME")
|
||||
self.assertIn("/kvb-dream", self.calls.read_text(encoding="utf-8"))
|
||||
|
||||
def test_other_desktops_stay_silent(self):
|
||||
self._run("KDE")
|
||||
self.assertFalse(self.calls.exists())
|
||||
|
||||
def test_timer_fires_daily_at_five_pm_and_catches_up(self):
|
||||
timer = TIMER.read_text(encoding="utf-8")
|
||||
self.assertIn("OnCalendar=*-*-* 17:00:00", timer)
|
||||
self.assertIn("Persistent=true", timer)
|
||||
self.assertIn("kvb-dream/hooks/notify.sh", SERVICE.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("node"), "node not installed")
|
||||
@@ -124,8 +147,8 @@ class TestSettingsRegistration(unittest.TestCase):
|
||||
self._patch()
|
||||
data = self._patch()
|
||||
entries = json.dumps(data["hooks"]["UserPromptSubmit"])
|
||||
self.assertEqual(entries.count("kvb-skill-dream/hooks/reminder.sh"), 1)
|
||||
self.assertEqual(entries.count("kvb-skill-tune/hooks/reminder.sh"), 1)
|
||||
self.assertEqual(entries.count("kvb-dream/hooks/reminder.sh"), 1)
|
||||
self.assertNotIn("tune/hooks/reminder.sh", entries)
|
||||
gate = json.dumps(data["hooks"]["PreToolUse"])
|
||||
self.assertEqual(gate.count("kvb-org-organize/scripts/council.py"), 1)
|
||||
self.assertEqual(gate.count("kvb-org-organize/scripts/watch.py"), 1)
|
||||
@@ -135,7 +158,15 @@ class TestSettingsRegistration(unittest.TestCase):
|
||||
self.assertEqual(gate.count("kvb-org-perform/scripts/log.py"), 1)
|
||||
self.assertEqual(post.count("kvb-org-perform/scripts/log.py"), 1)
|
||||
|
||||
def test_hooks_of_renamed_skills_are_repointed(self):
|
||||
def test_existing_hooks_preserved(self):
|
||||
self.settings.write_text(
|
||||
json.dumps({"hooks": {"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "true"}]}]}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
data = self._patch()
|
||||
self.assertIn('"true"', json.dumps(data["hooks"]["UserPromptSubmit"]))
|
||||
|
||||
def test_tune_reminders_retire_and_dream_reminders_are_repointed(self):
|
||||
base = Path(self.tmp.name) / "skills"
|
||||
|
||||
def hook(command):
|
||||
@@ -147,7 +178,8 @@ class TestSettingsRegistration(unittest.TestCase):
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
hook(f'bash "{base}/autotune/hooks/reminder.sh"'),
|
||||
hook(f'bash "{base}/kvb-skills-dream/hooks/reminder.sh"'),
|
||||
hook(f'bash "{base}/kvb-skill-tune/hooks/reminder.sh"'),
|
||||
hook(f'bash "{base}/kvb-skill-dream/hooks/reminder.sh"'),
|
||||
hook(f'python3 "{base}/democracy/scripts/watch.py" watch'),
|
||||
hook("echo keep"),
|
||||
],
|
||||
@@ -167,12 +199,11 @@ class TestSettingsRegistration(unittest.TestCase):
|
||||
)
|
||||
data = self._patch()
|
||||
text = json.dumps(data["hooks"])
|
||||
for old in ("/autotune/", "/kvb-skills-dream/", "/democracy/", "/subagent-model/"):
|
||||
for old in ("/autotune/", "/kvb-skill-tune/", "/kvb-skill-dream/", "/democracy/", "/subagent-model/"):
|
||||
self.assertNotIn(f"skills{old}", text)
|
||||
self.assertIn("echo keep", text)
|
||||
entries = json.dumps(data["hooks"]["UserPromptSubmit"])
|
||||
self.assertEqual(entries.count("kvb-skill-dream/hooks/reminder.sh"), 1)
|
||||
self.assertEqual(entries.count("kvb-skill-tune/hooks/reminder.sh"), 1)
|
||||
self.assertEqual(entries.count("kvb-dream/hooks/reminder.sh"), 1)
|
||||
self.assertEqual(entries.count("kvb-org-organize/scripts/watch.py"), 1)
|
||||
gate = json.dumps(data["hooks"]["PreToolUse"])
|
||||
self.assertEqual(gate.count("kvb-org-organize/scripts/council.py"), 1)
|
||||
|
||||
@@ -10,7 +10,9 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILLS = REPO_ROOT / "skills"
|
||||
README = REPO_ROOT / "README.md"
|
||||
CATEGORIES = ("think", "create", "code", "ci", "git", "check", "org", "skill")
|
||||
UNCATEGORIZED = frozenset({"kvb-cleanup", "kvb-help", "kvb-transcend"})
|
||||
UNCATEGORIZED = frozenset(
|
||||
{"kvb-cleanup", "kvb-dream", "kvb-help", "kvb-optimize", "kvb-transcend"}
|
||||
)
|
||||
SPEC = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
||||
SCHEMA = re.compile(rf"^kvb-(?:{'|'.join(CATEGORIES)})-[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
NAME_LINE = re.compile(r"^name:\s*(\S+)\s*$", re.MULTILINE)
|
||||
|
||||
Reference in new issue
Block a user