diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 906f016..f84fe7b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -401,8 +401,8 @@ { "name": "domain", "source": "./skills/domain", - "description": "0.11.0 (2026-06-07) ML subverbs unify the retired `skillopt` + `skillopt-hook` plugins under `/domain`: `/domain doctor` (skill-train readiness) · `/domain train [--bg]` (NATIVE pip-free SkillOpt loop — rollout → reflect → edit → held-out gate via `claude -p` = your Claude Code subscription, NOT a metered API; only a SEPARATE skill.md is optimized — the domain's own DOMAIN.md / verdicts / log are NEVER touched) · `consume ` · `activate`/`deactivate` (write ~/.sidecar/domain-skill/active-skill.md) · `status`/`log`. Bundled `examples/toyqa` (6 format-sensitive QA items) proves the loop end-to-end with NO external data/pip. ML verbs route to `bin/_domain_ml.sh` (+ `bin/_domain_ml.py` engine); ALL existing domain verbs route to `_domain.hexa` UNCHANGED. 0.10.1 (2026-06-03) `_is_name` now accepts a DIGIT-LEADING NAME — first char may be an UPPERCASE letter OR a digit 0-9, so `/domain init 8VERB` works (previously rejected: digit-leading start); subsequent chars unchanged (UPPERCASE/digit/`-`/`+`). 0.10.0 — bare `/domain` + `/domain list` now LINT for scattered report-like `.md` (a basename token audit|report|verdict|gap|finding|impl OR an ISO date like `2026-06-02`) sitting loose at the repo root or under `docs/` WHILE a domain is active, and emit a `⚠ 분리보관 감지` advisory naming each stray with its `/domain absorb ` runbook (excludes the domain's own pair / CHANGELOG / README / `.log.md` / already-absorbed pointer files; read-only). 0.9.0 — new `absorb [--state]` verb enforces combined-storage (합산보관) over scattered docs/*.md: folds an existing separate report/finding `.md` into the ACTIVE domain's single doc pair (default → .log.md as a dated `## — absorbed ` entry embedding the body; `--state` → .md snapshot), then replaces the source with a one-line `` pointer (idempotent — an already-absorbed pointer file is skipped; refuses to absorb the domain's own pair). Resolves the active domain via the same _get_active / _snap_path / _log_path logic the other verbs use. Maintain UPPERCASE .md (snapshot = final-goal milestone checkboxes) + sister .log.md (append-only step log) at project root — auto-scaffolds both, defaults NAME to uppercase basename of git root. Verbs: /domain (SELECT the session's active domain → later verbs default to it + show a final-goal progress bar `▓▓▓░░ NN% · done/total` from the snapshot's checkboxes) · /domain (show active) · /domain list (alias ls — repo-wide index; with a DOMAINS.tape roster it is AUTHORITATIVE: tables each registered domain ★ = active · @goal · progress · location, progress/@goal stay DERIVED so the checked-in roster holds only NAME→path and never churns; flags ghosts + unregistered; no roster → legacy disk-scan) · /domain list --sync (reconcile DOMAINS.tape with disk — bootstraps the roster) · /domain (append `- [x]` to log) · /domain todo (`- [ ]`) · /domain done (flip [ ]→[x]) · /domain title (alias subtitle — OPTIONAL `@title:` display header: icon · name · alias, e.g. `🧠 IIT4 — 의식 측정자(尺)`, rendered in place of plain `active domain: NAME`; absence keeps current output, no lint warning) · /domain new
. Progress is final-goal-based (snapshot completion), not log-based — it doesn't drift as the log grows. Path resolution: a DOMAINS.tape roster row (`@domain NAME := \"path\"`) wins FIRST, so a domain can live at ANY path (e.g. `domains/RUNTIME/RUNTIME.md`); absent a roster entry it falls back to root → folder-nested → root-default-scaffold (log follows the snapshot's dir), so folder-nested meta-domains no longer regenerate an empty root scaffold each call. The folder-nested fallback only matches when the nested file's first line is the domain header `# ` (guards against false-matching an unrelated same-named file like a digest on case-insensitive filesystems). 0.8.8: /domain set now SELF-HEALS the DOMAINS.tape roster (auto-registers an on-disk-but-unregistered domain — the gap that ghosted anima's LIFE) and warns on a stale SSOT shadow (the working-tree .md is UNTRACKED or BEHIND origin/main → progress/closure may read a stale local copy; reference_domain_init_untracked_ssot); a perpetual `@goal` (no-termination marker like `종료 조건 없음`/`perpetual`/`open horizon`) renders a ♾️ perpetual badge so the progress bar reads as frontier-depletion, NOT a 100% completion countdown (per feedback-closure-is-physical-limit).", - "version": "0.11.0" + "description": "0.12.0 (2026-06-07) AUTO-USE half restored — the retired `skillopt-hook`'s SessionStart auto-inject now lives INSIDE the domain plugin: `hooks/hooks.json` fires `bin/_domain_skill_inject.sh` at every SessionStart, injecting the activated skill (`~/.sidecar/domain-skill/active-skill.md`, written by `/domain activate`) as additionalContext so a trained skill is auto-applied with NO command (closes the 0.11.0 gap where `activate` wrote a file nothing read). New `/domain agent-active on|off` toggles the optional `~/.sidecar/domain-skill/agent-active` nudge marker (one-line 'PROPOSE /domain train for repeatable scored tasks — never auto-run'). OPT-IN + safe: the hook is silent when nothing is activated, NEVER trains, always exits 0 (fail-open). This restores full parity with the two retired plugins (TRAIN + USE) entirely under `/domain`. 0.11.0 (2026-06-07) ML subverbs unify the retired `skillopt` + `skillopt-hook` plugins under `/domain`: `/domain doctor` (skill-train readiness) · `/domain train [--bg]` (NATIVE pip-free SkillOpt loop — rollout → reflect → edit → held-out gate via `claude -p` = your Claude Code subscription, NOT a metered API; only a SEPARATE skill.md is optimized — the domain's own DOMAIN.md / verdicts / log are NEVER touched) · `consume ` · `activate`/`deactivate` (write ~/.sidecar/domain-skill/active-skill.md) · `status`/`log`. Bundled `examples/toyqa` (6 format-sensitive QA items) proves the loop end-to-end with NO external data/pip. ML verbs route to `bin/_domain_ml.sh` (+ `bin/_domain_ml.py` engine); ALL existing domain verbs route to `_domain.hexa` UNCHANGED. 0.10.1 (2026-06-03) `_is_name` now accepts a DIGIT-LEADING NAME — first char may be an UPPERCASE letter OR a digit 0-9, so `/domain init 8VERB` works (previously rejected: digit-leading start); subsequent chars unchanged (UPPERCASE/digit/`-`/`+`). 0.10.0 — bare `/domain` + `/domain list` now LINT for scattered report-like `.md` (a basename token audit|report|verdict|gap|finding|impl OR an ISO date like `2026-06-02`) sitting loose at the repo root or under `docs/` WHILE a domain is active, and emit a `⚠ 분리보관 감지` advisory naming each stray with its `/domain absorb ` runbook (excludes the domain's own pair / CHANGELOG / README / `.log.md` / already-absorbed pointer files; read-only). 0.9.0 — new `absorb [--state]` verb enforces combined-storage (합산보관) over scattered docs/*.md: folds an existing separate report/finding `.md` into the ACTIVE domain's single doc pair (default → .log.md as a dated `## — absorbed ` entry embedding the body; `--state` → .md snapshot), then replaces the source with a one-line `` pointer (idempotent — an already-absorbed pointer file is skipped; refuses to absorb the domain's own pair). Resolves the active domain via the same _get_active / _snap_path / _log_path logic the other verbs use. Maintain UPPERCASE .md (snapshot = final-goal milestone checkboxes) + sister .log.md (append-only step log) at project root — auto-scaffolds both, defaults NAME to uppercase basename of git root. Verbs: /domain (SELECT the session's active domain → later verbs default to it + show a final-goal progress bar `▓▓▓░░ NN% · done/total` from the snapshot's checkboxes) · /domain (show active) · /domain list (alias ls — repo-wide index; with a DOMAINS.tape roster it is AUTHORITATIVE: tables each registered domain ★ = active · @goal · progress · location, progress/@goal stay DERIVED so the checked-in roster holds only NAME→path and never churns; flags ghosts + unregistered; no roster → legacy disk-scan) · /domain list --sync (reconcile DOMAINS.tape with disk — bootstraps the roster) · /domain (append `- [x]` to log) · /domain todo (`- [ ]`) · /domain done (flip [ ]→[x]) · /domain title (alias subtitle — OPTIONAL `@title:` display header: icon · name · alias, e.g. `🧠 IIT4 — 의식 측정자(尺)`, rendered in place of plain `active domain: NAME`; absence keeps current output, no lint warning) · /domain new
. Progress is final-goal-based (snapshot completion), not log-based — it doesn't drift as the log grows. Path resolution: a DOMAINS.tape roster row (`@domain NAME := \"path\"`) wins FIRST, so a domain can live at ANY path (e.g. `domains/RUNTIME/RUNTIME.md`); absent a roster entry it falls back to root → folder-nested → root-default-scaffold (log follows the snapshot's dir), so folder-nested meta-domains no longer regenerate an empty root scaffold each call. The folder-nested fallback only matches when the nested file's first line is the domain header `# ` (guards against false-matching an unrelated same-named file like a digest on case-insensitive filesystems). 0.8.8: /domain set now SELF-HEALS the DOMAINS.tape roster (auto-registers an on-disk-but-unregistered domain — the gap that ghosted anima's LIFE) and warns on a stale SSOT shadow (the working-tree .md is UNTRACKED or BEHIND origin/main → progress/closure may read a stale local copy; reference_domain_init_untracked_ssot); a perpetual `@goal` (no-termination marker like `종료 조건 없음`/`perpetual`/`open horizon`) renders a ♾️ perpetual badge so the progress bar reads as frontier-depletion, NOT a 100% completion countdown (per feedback-closure-is-physical-limit).", + "version": "0.12.0" }, { "name": "domain-doc-guard", diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b5a86..bb00518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ For the full audit trail, see `git log`. --- +## 2026-06-07 — 🎓 domain 0.12.0 — 자동사용(USE) 절반 복원 · skillopt-hook 의 SessionStart 주입을 domain 안으로 + +0.11.0 통합에서 빠졌던 **자동사용 훅**을 domain 플러그인 내부에 포팅 — `activate` 가 +파일만 쓰고 아무도 안 읽던 갭을 닫음. 폐기한 2개 플러그인과 **완전 동등(TRAIN+USE)** 회복. + +- 🪝 **SessionStart 자동주입** — `hooks/hooks.json` 가 `bin/_domain_skill_inject.sh` 를 + 세션 시작마다 발화. `/domain activate` 가 쓴 `~/.sidecar/domain-skill/active-skill.md` + 를 additionalContext 로 주입 → 학습된 스킬이 **명령 없이** 자동 적용. +- 🔔 **`/domain agent-active on|off`** — 선택 마커 `~/.sidecar/domain-skill/agent-active` + 토글. 켜면 "반복+채점가능 작업엔 `/domain train` 을 제안(자동실행 금지)" 한 줄 nudge. +- 🔒 OPT-IN + 안전 — 활성 스킬 없으면 침묵 · 절대 학습 안 함(비용) · 항상 exit 0(fail-open). +- 검증: activate→훅 JSON(hookEventName=SessionStart + 본문) · agent-active nudge 추가 · + deactivate 후 침묵 · sh/json 문법 · g22 lockstep(plugin 0.12.0 ↔ marketplace 0.12.0). + ## 2026-06-07 — 🎓 domain 0.11.0 — `/domain train` 으로 통일 · skillopt + skillopt-hook 폐기 스킬 학습 능력을 별도 플러그인 대신 **`/domain` 하나로 통일**. 네이티브 · pip 무의존. diff --git a/skills/domain/.claude-plugin/plugin.json b/skills/domain/.claude-plugin/plugin.json index b2f3928..1e6e127 100644 --- a/skills/domain/.claude-plugin/plugin.json +++ b/skills/domain/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "domain", - "description": "0.11.0 (2026-06-07) ML subverbs unify the retired `skillopt` + `skillopt-hook` plugins under `/domain`: `/domain doctor` (skill-train readiness) · `/domain train [--bg]` (NATIVE pip-free SkillOpt loop — rollout → reflect → edit → held-out gate via `claude -p` = your Claude Code subscription, NOT a metered API; only a SEPARATE skill.md is optimized — the domain's own DOMAIN.md / verdicts / log are NEVER touched) · `consume ` · `activate`/`deactivate` (write ~/.sidecar/domain-skill/active-skill.md) · `status`/`log`. Bundled `examples/toyqa` (6 format-sensitive QA items) proves the loop end-to-end with NO external data/pip. ML verbs route to `bin/_domain_ml.sh` (+ `bin/_domain_ml.py` engine); ALL existing domain verbs route to `_domain.hexa` UNCHANGED. 0.10.1 (2026-06-03) `_is_name` now accepts a DIGIT-LEADING NAME — first char may be an UPPERCASE letter OR a digit 0-9, so `/domain init 8VERB` works (previously rejected: digit-leading start); subsequent chars unchanged (UPPERCASE/digit/`-`/`+`). 0.10.0 — bare `/domain` + `/domain list` now LINT for scattered report-like `.md` (a basename token audit|report|verdict|gap|finding|impl OR an ISO date like `2026-06-02`) sitting loose at the repo root or under `docs/` WHILE a domain is active, and emit a `⚠ 분리보관 감지` advisory naming each stray with its `/domain absorb ` runbook (excludes the domain's own pair / CHANGELOG / README / `.log.md` / already-absorbed pointer files; read-only). 0.9.0 — new `absorb [--state]` verb enforces combined-storage (합산보관) over scattered docs/*.md: folds an existing separate report/finding `.md` into the ACTIVE domain's single doc pair (default → .log.md as a dated `## — absorbed ` entry embedding the body; `--state` → .md snapshot), then replaces the source with a one-line `` pointer (idempotent — an already-absorbed pointer file is skipped; refuses to absorb the domain's own pair). Resolves the active domain via the same _get_active / _snap_path / _log_path logic the other verbs use. Maintain UPPERCASE .md (snapshot = `@goal:` final-goal declaration + `- [ ]` progress milestones) + sister .log.md (append-only step log) at project root. NAME = UPPERCASE-or-digit-start + UPPERCASE/digits/`-`/`+` (e.g. `TTR-LM`, `RTSC+HTS`); `_` is rejected (use `-`); `+` enables meta-domain composition (`RTSC+HTS` → RTSC+HTS.md + RTSC+HTS.log.md). Subcommands: /domain init [] (scaffold files + register a DOMAINS.tape roster row; optional places the pair anywhere, e.g. domains/) · /domain set (select session active — bare /domain shortcut) · /domain list (alias ls — repo-wide index: with a DOMAINS.tape roster present it is AUTHORITATIVE — tables each registered domain with ★ = active · @goal · progress bar · location; progress/@goal stay DERIVED (read live from each snapshot) so the checked-in roster holds only stable NAME→path data and never churns on a milestone flip; flags ghosts + unregistered disk domains; no roster → legacy disk-scan of root + one folder level) · /domain list --sync (reconcile DOMAINS.tape with disk — append unregistered domains, bootstraps the roster from an existing repo) · /domain goal (declare the FINAL goal — sets the snapshot's `@goal:` line) · /domain milestone (alias ms — add `- [ ]` progress milestone) · /domain title (alias subtitle — set an OPTIONAL `@title:` display header: icon · name · alias, e.g. `🧠 IIT4 — 의식 측정자(尺)`; rendered by bare /domain + set in place of the plain `active domain: NAME`; absence keeps current output, no lint warning) · /domain done (flip a milestone [ ]→[x], else flip a log task) · /domain (show active + @title-or-name + @goal + progress bar `▓▓▓░░ NN% · done/total` + lint) · /domain todo / / new
(log). Lint: warns if snapshot has no `@goal:` OR no milestones (NOT `@title:` — it is optional). Progress = milestone completion (snapshot), not log-based. Path resolution: a DOMAINS.tape roster row (`@domain := \"\"`) wins FIRST — letting a domain live at ANY path (e.g. `domains/RUNTIME/RUNTIME.md`, `sub/deep/X/X.md`), lifting the old root+one-folder-level limit; absent a roster entry, every verb falls back to (1) existing root `.md` → (2) existing folder-nested `/.md` → (3) root default (fresh scaffold), with the log following the snapshot's directory, so folder-nested meta-domains no longer regenerate an empty root scaffold on each call. The folder-nested fallback only matches when the nested file's first non-empty line is the domain header `# ` (guards against false-matching an unrelated same-named file — e.g. a digest `/.md` — on case-insensitive filesystems).", - "version": "0.11.0", + "description": "0.12.0 (2026-06-07) AUTO-USE half restored — the retired `skillopt-hook`'s SessionStart auto-inject now lives INSIDE the domain plugin: `hooks/hooks.json` fires `bin/_domain_skill_inject.sh` at every SessionStart, injecting the activated skill (`~/.sidecar/domain-skill/active-skill.md`, written by `/domain activate`) as additionalContext so a trained skill is auto-applied with NO command (closes the 0.11.0 gap where `activate` wrote a file nothing read). New `/domain agent-active on|off` toggles the optional `~/.sidecar/domain-skill/agent-active` nudge marker (one-line 'PROPOSE /domain train for repeatable scored tasks — never auto-run'). OPT-IN + safe: the hook is silent when nothing is activated, NEVER trains, always exits 0 (fail-open). This restores full parity with the two retired plugins (TRAIN + USE) entirely under `/domain`. 0.11.0 (2026-06-07) ML subverbs unify the retired `skillopt` + `skillopt-hook` plugins under `/domain`: `/domain doctor` (skill-train readiness) · `/domain train [--bg]` (NATIVE pip-free SkillOpt loop — rollout → reflect → edit → held-out gate via `claude -p` = your Claude Code subscription, NOT a metered API; only a SEPARATE skill.md is optimized — the domain's own DOMAIN.md / verdicts / log are NEVER touched) · `consume ` · `activate`/`deactivate` (write ~/.sidecar/domain-skill/active-skill.md) · `status`/`log`. Bundled `examples/toyqa` (6 format-sensitive QA items) proves the loop end-to-end with NO external data/pip. ML verbs route to `bin/_domain_ml.sh` (+ `bin/_domain_ml.py` engine); ALL existing domain verbs route to `_domain.hexa` UNCHANGED. 0.10.1 (2026-06-03) `_is_name` now accepts a DIGIT-LEADING NAME — first char may be an UPPERCASE letter OR a digit 0-9, so `/domain init 8VERB` works (previously rejected: digit-leading start); subsequent chars unchanged (UPPERCASE/digit/`-`/`+`). 0.10.0 — bare `/domain` + `/domain list` now LINT for scattered report-like `.md` (a basename token audit|report|verdict|gap|finding|impl OR an ISO date like `2026-06-02`) sitting loose at the repo root or under `docs/` WHILE a domain is active, and emit a `⚠ 분리보관 감지` advisory naming each stray with its `/domain absorb ` runbook (excludes the domain's own pair / CHANGELOG / README / `.log.md` / already-absorbed pointer files; read-only). 0.9.0 — new `absorb [--state]` verb enforces combined-storage (합산보관) over scattered docs/*.md: folds an existing separate report/finding `.md` into the ACTIVE domain's single doc pair (default → .log.md as a dated `## — absorbed ` entry embedding the body; `--state` → .md snapshot), then replaces the source with a one-line `` pointer (idempotent — an already-absorbed pointer file is skipped; refuses to absorb the domain's own pair). Resolves the active domain via the same _get_active / _snap_path / _log_path logic the other verbs use. Maintain UPPERCASE .md (snapshot = `@goal:` final-goal declaration + `- [ ]` progress milestones) + sister .log.md (append-only step log) at project root. NAME = UPPERCASE-or-digit-start + UPPERCASE/digits/`-`/`+` (e.g. `TTR-LM`, `RTSC+HTS`); `_` is rejected (use `-`); `+` enables meta-domain composition (`RTSC+HTS` → RTSC+HTS.md + RTSC+HTS.log.md). Subcommands: /domain init [] (scaffold files + register a DOMAINS.tape roster row; optional places the pair anywhere, e.g. domains/) · /domain set (select session active — bare /domain shortcut) · /domain list (alias ls — repo-wide index: with a DOMAINS.tape roster present it is AUTHORITATIVE — tables each registered domain with ★ = active · @goal · progress bar · location; progress/@goal stay DERIVED (read live from each snapshot) so the checked-in roster holds only stable NAME→path data and never churns on a milestone flip; flags ghosts + unregistered disk domains; no roster → legacy disk-scan of root + one folder level) · /domain list --sync (reconcile DOMAINS.tape with disk — append unregistered domains, bootstraps the roster from an existing repo) · /domain goal (declare the FINAL goal — sets the snapshot's `@goal:` line) · /domain milestone (alias ms — add `- [ ]` progress milestone) · /domain title (alias subtitle — set an OPTIONAL `@title:` display header: icon · name · alias, e.g. `🧠 IIT4 — 의식 측정자(尺)`; rendered by bare /domain + set in place of the plain `active domain: NAME`; absence keeps current output, no lint warning) · /domain done (flip a milestone [ ]→[x], else flip a log task) · /domain (show active + @title-or-name + @goal + progress bar `▓▓▓░░ NN% · done/total` + lint) · /domain todo / / new
(log). Lint: warns if snapshot has no `@goal:` OR no milestones (NOT `@title:` — it is optional). Progress = milestone completion (snapshot), not log-based. Path resolution: a DOMAINS.tape roster row (`@domain := \"\"`) wins FIRST — letting a domain live at ANY path (e.g. `domains/RUNTIME/RUNTIME.md`, `sub/deep/X/X.md`), lifting the old root+one-folder-level limit; absent a roster entry, every verb falls back to (1) existing root `.md` → (2) existing folder-nested `/.md` → (3) root default (fresh scaffold), with the log following the snapshot's directory, so folder-nested meta-domains no longer regenerate an empty root scaffold on each call. The folder-nested fallback only matches when the nested file's first non-empty line is the domain header `# ` (guards against false-matching an unrelated same-named file — e.g. a digest `/.md` — on case-insensitive filesystems).", + "version": "0.12.0", "author": { "name": "dancinlab" }, diff --git a/skills/domain/bin/_domain_ml.sh b/skills/domain/bin/_domain_ml.sh index 65dc546..8660d19 100755 --- a/skills/domain/bin/_domain_ml.sh +++ b/skills/domain/bin/_domain_ml.sh @@ -60,9 +60,16 @@ consume() { activate() { local f="${1:-}"; [ -n "$f" ] && [ -f "$f" ] || { echo "usage: /domain activate "; return 1; } mkdir -p "$HOME_DIR"; cp "$f" "$HOME_DIR/active-skill.md" - echo "✅ activated → $HOME_DIR/active-skill.md (a SessionStart hook can auto-inject it) · off: /domain deactivate" + echo "✅ activated → $HOME_DIR/active-skill.md (the /domain SessionStart hook auto-injects it next session) · off: /domain deactivate" } deactivate() { rm -f "$HOME_DIR/active-skill.md"; echo "✅ deactivated — no skill auto-injected."; } +agent_active() { + case "${1:-}" in + on) mkdir -p "$HOME_DIR"; : > "$HOME_DIR/agent-active"; echo "✅ agent-active ON — sessions nudge the agent to PROPOSE /domain train for scored tasks." ;; + off) rm -f "$HOME_DIR/agent-active"; echo "✅ agent-active OFF — no train-proposal nudge." ;; + *) [ -f "$HOME_DIR/agent-active" ] && echo "agent-active: ON" || echo "agent-active: OFF"; echo "usage: /domain agent-active on|off" ;; + esac +} case "${1:-doctor}" in doctor) doctor ;; @@ -72,5 +79,6 @@ case "${1:-doctor}" in consume) shift 2>/dev/null || true; consume "$@" ;; activate) shift 2>/dev/null || true; activate "$@" ;; deactivate) deactivate ;; + agent-active) shift 2>/dev/null || true; agent_active "$@" ;; *) echo "unknown ML subverb: ${1:-}"; exit 2 ;; esac diff --git a/skills/domain/bin/_domain_skill_inject.sh b/skills/domain/bin/_domain_skill_inject.sh new file mode 100644 index 0000000..4f08f6c --- /dev/null +++ b/skills/domain/bin/_domain_skill_inject.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# _domain_skill_inject — SessionStart hook for /domain. Emits the ACTIVE learned +# skill (if the user activated one via `/domain activate `) as +# additionalContext, so a trained skill is auto-used WITHOUT typing a command. +# NEVER trains (cost) and NEVER fails the session (always exit 0). Silent when +# nothing is activated. Hook half of the /domain USE-vs-TRAIN split; the `/domain` +# command verbs (activate/train) are the TRAIN/activate half. +set -u +cat >/dev/null 2>&1 # drain the hook payload on stdin + +DIR="${DOMAIN_ML_HOME:-$HOME/.sidecar/domain-skill}" +SKILL="$DIR/active-skill.md" +NUDGE="$DIR/agent-active" # opt-in marker for the train-proposal nudge + +ctx="" +if [ -f "$SKILL" ]; then + body=$(cat "$SKILL" 2>/dev/null) + ctx="# 🎓 Active learned skill (/domain) — apply this as task guidance when it fits: + +$body" +fi +if [ -f "$NUDGE" ]; then + line="🎓 /domain active-use: when you see a repeatable, auto-scorable task with no learned skill yet, PROPOSE \`/domain train\` (never auto-run it)." + if [ -n "$ctx" ]; then ctx="$ctx + +$line"; else ctx="$line"; fi +fi + +[ -n "$ctx" ] || exit 0 # nothing activated → stay silent + +# Emit additionalContext as JSON (hookEventName must match the firing event). +if command -v python3 >/dev/null 2>&1; then + python3 - "$ctx" <<'PY' +import json, sys +print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart", + "additionalContext": sys.argv[1]}})) +PY +fi +exit 0 diff --git a/skills/domain/commands/domain.md b/skills/domain/commands/domain.md index 4dcbcc7..0261149 100644 --- a/skills/domain/commands/domain.md +++ b/skills/domain/commands/domain.md @@ -11,7 +11,7 @@ if [ ! -d "$ROOT/bin" ]; then fi set -- $ARGUMENTS case "${1:-}" in - doctor|train|consume|activate|deactivate|status|log) + doctor|train|consume|activate|deactivate|agent-active|status|log) M="$ROOT/bin/_domain_ml.sh" [ -f "$M" ] && bash "$M" "$@" || echo "✗ _domain_ml.sh not found — /reload-plugins" ;; *) diff --git a/skills/domain/hooks/hooks.json b/skills/domain/hooks/hooks.json new file mode 100644 index 0000000..bab9a4c --- /dev/null +++ b/skills/domain/hooks/hooks.json @@ -0,0 +1,7 @@ +{ + "hooks": { + "SessionStart": [ + { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/bin/_domain_skill_inject.sh\"" } ] } + ] + } +}