Compare commits
2 commits
2ffb3a8b1e
...
518d95df80
| Author | SHA1 | Date | |
|---|---|---|---|
| 518d95df80 | |||
| abae256c1d |
26 changed files with 443 additions and 34 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
|
@ -1,4 +1,4 @@
|
|||
[submodule "_blocks/registries"]
|
||||
path = _blocks/registries
|
||||
url = https://keigit.com/keisei/kei-registries.git
|
||||
url = https://github.com/KeiSeiLab/kei-registries.git
|
||||
shallow = true
|
||||
|
|
|
|||
39
DECISIONS.md
39
DECISIONS.md
|
|
@ -6,6 +6,45 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-05-25 — Opt-in hook packs + stack profiles (public-prep posture)
|
||||
|
||||
### Context
|
||||
|
||||
The kit force-activated every hook via `settings-snippet.json`, including the
|
||||
author's personal research discipline (numeric-claims evidence markers,
|
||||
no-downgrade, citation-verify, rust-first / no-python). For a public,
|
||||
general-audience kit that is presumptuous — users bring their own stack and do
|
||||
not need a Rust-only policy or evidence-marker enforcement by default.
|
||||
|
||||
### Decision
|
||||
|
||||
- Posture: **safety hooks on by default; all discipline packs opt-in.** Packs:
|
||||
`safety` (always), `evidence`, `observability`, `epistemic`, `orchestration`,
|
||||
`git-guard`, `stack-rust`. SSoT = `_primitives/hook-packs.toml`.
|
||||
- **Stack profiles** (minimal / web / ml / systems / mobile) pull a set of
|
||||
discipline packs AND an agent set. `rust-first` / `no-python` live only in
|
||||
`stack-rust`, which only the `systems` stack enables. `git-guard`
|
||||
(no-github-push) is opt-in only and pulled by NO stack — a general kit must
|
||||
not block a user's normal `git push` to github.
|
||||
- Mechanism: install-time **filter** of the snippet by selected packs
|
||||
(`filter_snippet_by_packs`) + **prune** of kit-owned hooks on reconfigure
|
||||
(`prune_kit_hooks`, foreign hooks preserved). Selection persists to
|
||||
`~/.claude/config/onboarding.toml`; re-runnable via `kei configure`.
|
||||
- Non-interactive / `--yes` / CI default = minimal (safety + cosmetic only),
|
||||
all agents (back-compat for power users).
|
||||
|
||||
### Consequences
|
||||
|
||||
- Gate wiring (`_lib/gate.sh`) added to the 8 highest-friction discipline hooks
|
||||
for runtime toggling via the `hooks-control` skill; remaining cosmetic/event
|
||||
hooks deferred (install-time filtering already gives "off by default", so the
|
||||
runtime gate is a convenience, not a correctness requirement).
|
||||
- Agent-set changes via `kei configure` apply on the next `./install.sh`
|
||||
(reconfigure re-applies hooks fully but does not remove already-installed
|
||||
agent manifests — they are harmless extra `.md` files).
|
||||
- `_toml_array` extracted from `lib-profile.sh:profile_members` as the shared
|
||||
one-line-array TOML reader (no new dependency).
|
||||
|
||||
## 2026-04-28 — Three scheduling abstractions in workspace
|
||||
|
||||
### Context
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ curl -fsSL https://install.keisei.app | bash -s -- --profile=dev --yes # CI
|
|||
/plugin install keisei@keisei-marketplace
|
||||
|
||||
# Any MCP-compatible client (Cursor / Continue / Zed / Aider / etc)
|
||||
git clone https://keigit.com/keisei/KeiSeiKit-1.0.git
|
||||
git clone https://github.com/KeiSeiLab/KeiSeiKit-1.0.git
|
||||
cd KeiSeiKit-1.0
|
||||
./bootstrap.sh # interactive profile picker
|
||||
# or: ./install.sh --profile=minimal # direct
|
||||
|
|
|
|||
55
_primitives/hook-packs.toml
Normal file
55
_primitives/hook-packs.toml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# KeiSeiKit hook-pack + stack-profile map — single source of truth for the
|
||||
# opt-in install posture. Parsed by install/lib-packs.sh, which reuses the
|
||||
# generic TOML array reader `_toml_array` extracted from lib-profile.sh
|
||||
# (python-tomllib preferred, awk fallback). No new dependency.
|
||||
#
|
||||
# Values are HOOK BASENAMES WITHOUT `.sh`, matched against the command
|
||||
# basenames in settings-snippet.json. Every hook wired in the snippet MUST
|
||||
# appear in exactly one [pack] entry or in [pack-always]; anything missing
|
||||
# would be silently filtered out of a fresh install.
|
||||
#
|
||||
# Posture: only `safety` + `pack-always` are active on a fresh/non-interactive
|
||||
# install. All other packs are opt-in (via onboarding or `kei configure`).
|
||||
# `git-guard` (no-github-push) is opt-in ONLY and is pulled by NO stack — a
|
||||
# general kit must never block a user's normal `git push` to github by default.
|
||||
|
||||
[pack]
|
||||
safety = ["block-dangerous", "safety-guard", "destructive-guard", "disk-headroom-check", "secrets-pre-guard", "no-hand-edit-agents", "assemble-validate", "assemble-agents"]
|
||||
evidence = ["numeric-claims-guard", "citation-verify", "chat-numeric-prewarn", "chat-numeric-postflag"]
|
||||
observability = ["task-timer", "session-end-dump", "extract-task-durations", "error-spike-detector", "agent-event-spawn", "agent-event-done", "agent-heartbeat-tick", "stop-verify"]
|
||||
epistemic = ["alignment-check", "no-downgrade", "recurrence-suggest"]
|
||||
orchestration = ["agent-fork-logger", "agent-fork-done", "orchestrator-dirty-check", "orchestrator-branch-check", "agent-capability-check", "agent-stub-scan", "milestone-commit-hook", "post-commit-audit", "post-write-check"]
|
||||
git-guard = ["no-github-push"]
|
||||
stack-rust = ["rust-first", "no-python-without-approval"]
|
||||
|
||||
# Always wired, never filtered (cosmetic / infra). The keisei-pet*.sh status
|
||||
# updater + the inline pet hook are kept by the filter directly (name match),
|
||||
# so they are NOT listed here.
|
||||
[pack-always]
|
||||
base = ["first-run-onboard", "mailbox-inject", "tomd-preread", "site-wysiwyd-check"]
|
||||
|
||||
# Stack profile -> discipline packs auto-enabled (safety is always implicit).
|
||||
# git-guard intentionally absent from every stack (opt-in only).
|
||||
[stack-packs]
|
||||
minimal = []
|
||||
web = ["evidence", "observability"]
|
||||
ml = ["evidence", "observability", "epistemic"]
|
||||
systems = ["evidence", "observability", "stack-rust"]
|
||||
mobile = ["evidence", "observability"]
|
||||
|
||||
# Stack profile -> agent groups installed (the `base` group is always added).
|
||||
[stack-agents]
|
||||
minimal = ["base"]
|
||||
web = ["base", "web"]
|
||||
ml = ["base", "ml"]
|
||||
systems = ["base", "systems"]
|
||||
mobile = ["base", "mobile"]
|
||||
|
||||
# Agent group -> manifest basenames (without `.toml`). When no stack is chosen
|
||||
# (power user / --profile=full / non-interactive), ALL manifests install.
|
||||
[agent-set]
|
||||
base = ["architect", "critic", "validator", "researcher", "code-implementer", "security-auditor"]
|
||||
web = ["code-implementer-typescript", "frontend-validator", "validator-api", "validator-doc", "researcher-web", "researcher-code"]
|
||||
ml = ["ml-implementer", "ml-researcher", "modal-runner", "cost-guardian", "fal-ai-runner", "code-implementer-python", "validator-benchmark"]
|
||||
systems = ["code-implementer-rust", "code-implementer-go", "infra-implementer", "infra-implementer-cicd", "infra-implementer-container", "infra-implementer-iac", "infra-implementer-secrets", "validator-version"]
|
||||
mobile = ["code-implementer-swift", "code-implementer-flutter", "frontend-validator"]
|
||||
7
bin/kei
7
bin/kei
|
|
@ -19,12 +19,17 @@
|
|||
set -e
|
||||
|
||||
# --- subcommand dispatch (before splash) ---------------------------------
|
||||
# `kei message ...` → the mailbox CLI; everything else falls through to launch.
|
||||
# `kei message ...` → mailbox CLI; `kei configure` → hook/stack re-picker;
|
||||
# everything else falls through to launch.
|
||||
case "${1:-}" in
|
||||
message|msg|m)
|
||||
shift
|
||||
exec "$HOME/.claude/scripts/kei-message.sh" "$@"
|
||||
;;
|
||||
configure|config|reconfigure)
|
||||
shift
|
||||
exec "$HOME/.claude/scripts/kei-configure.sh" "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- args ----------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ All hooks live under `hooks/` directory. Format: `| Hook Name | Event | Severity
|
|||
- **remind (exit 0 + stderr on trigger)** — passive reminder
|
||||
- **advisory** — informational, never blocks
|
||||
|
||||
### Hook packs (opt-in posture)
|
||||
|
||||
A fresh install activates **only the `safety` pack** (plus cosmetic/infra hooks).
|
||||
Discipline packs are opt-in, chosen during onboarding (step 6) or later via
|
||||
`kei configure`. SSoT for pack membership + stack profiles is
|
||||
`_primitives/hook-packs.toml`. Packs: `safety` (always on), `evidence`,
|
||||
`observability`, `epistemic`, `orchestration`, `git-guard` (opt-in only),
|
||||
`stack-rust` (only under the `systems` stack profile). Discipline hooks also
|
||||
respect runtime toggling via `KEI_DISABLED_HOOKS` / `KEI_HOOK_PROFILE` (see the
|
||||
`hooks-control` skill).
|
||||
|
||||
### Core Safety Hooks
|
||||
|
||||
| Hook | Event | Severity | Purpose | Bypass Env |
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "alignment-check" || exit 0; fi
|
||||
# ALIGNMENT CHECK HOOK
|
||||
# Fires on UserPromptSubmit when comparison/experiment keywords detected.
|
||||
# THREE-TIME REPEAT BUG: exp6, exp24-28, basecaller — all forgot alignment.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "chat-numeric-postflag" || exit 0; fi
|
||||
# chat-numeric-postflag.sh — Stop warn (RULE 0.18 chat-output)
|
||||
#
|
||||
# Reads the session transcript, extracts the last assistant message,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "chat-numeric-prewarn" || exit 0; fi
|
||||
# chat-numeric-prewarn.sh — UserPromptSubmit remind (RULE 0.18 chat-output)
|
||||
#
|
||||
# Detects time/cost/effort keywords in the user's prompt and injects an
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "citation-verify" || exit 0; fi
|
||||
# PreToolUse(Edit|Write) — block unverified academic citations
|
||||
#
|
||||
# Rule 0.5 NO HALLUCINATION enforcer.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "no-downgrade" || exit 0; fi
|
||||
# RULE -1 NO DOWNGRADE / CONSTRUCTIVE ONLY (2026-04-15 LOCK) enforcement.
|
||||
#
|
||||
# Detects downgrade-style phrases in Write/Edit content without accompanying
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "no-python-without-approval" || exit 0; fi
|
||||
# Hard block on python/python3/python2 invocations in Bash tool.
|
||||
# RULE 0.2 (Rust First) — Python requires explicit architectural reason.
|
||||
# Claude кroнически нарушает RULE 0.2 inline-вызовами python3 для мелких расчётов.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "numeric-claims-guard" || exit 0; fi
|
||||
# RULE 0.18 — Numeric claim enforcement — block Edit/Write of numeric claims
|
||||
# without evidence marker. Bypass: RULE_017_BYPASS=1 prefix (kept for compat).
|
||||
#
|
||||
|
|
@ -26,7 +29,7 @@ fi
|
|||
# - "N MB/GB/LOC/tests/crates/atomars"
|
||||
# - "~$N", "$N/mo"
|
||||
# - "Nm Ns", "займёт N", "should take N"
|
||||
NUMERIC_PATTERN='(~\s*[0-9]+(\.[0-9]+)?\s*(min|minute|hour|hr|day|week|month|sec|second|MB|GB|KB|LOC|line|test|crate|atomar|%|µs|ms|ns|TPS|req/s)|[0-9]+m\s*[0-9]+s|\$[0-9]+(\.[0-9]+)?(/(mo|hr|day|run))?|~\s*\$[0-9]+|should take|will take|takes about|займёт|за ~|estimated at|ETA[: ]|approximately\s+[0-9])'
|
||||
NUMERIC_PATTERN='(~\s*[0-9]+(\.[0-9]+)?\s*(min|minute|hour|hr|day|week|month|sec|second|MB|GB|KB|LOC|line|test|crate|atomar|%|µs|ms|ns|TPS|req/s)|[0-9]+m\s*[0-9]+s|\$[0-9]+\.[0-9]+|\$[0-9]+/(mo|hr|day|run)|\$[0-9]{2,}|~\s*\$[0-9]+|should take|will take|takes about|займёт|за ~|estimated at|ETA[: ]|approximately\s+[0-9])'
|
||||
|
||||
# Markers that satisfy the rule
|
||||
EVIDENCE_PATTERN='\[(REAL|FROM-JOURNAL|ESTIMATE-HTC)[: ]'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
|
||||
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "rust-first" || exit 0; fi
|
||||
# RULE 0.2 — RUST FIRST reminder hook.
|
||||
#
|
||||
# Fires on UserPromptSubmit. Detects keywords indicating language choice
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ source "$LIB_DIR/lib-log.sh"
|
|||
source "$LIB_DIR/lib-backup.sh"
|
||||
# shellcheck source=install/lib-profile.sh
|
||||
source "$LIB_DIR/lib-profile.sh"
|
||||
# shellcheck source=install/lib-packs.sh
|
||||
source "$LIB_DIR/lib-packs.sh"
|
||||
# shellcheck source=install/lib-args.sh
|
||||
source "$LIB_DIR/lib-args.sh"
|
||||
# shellcheck source=install/lib-menu.sh
|
||||
|
|
@ -150,6 +152,8 @@ say "profile: $PROFILE"
|
|||
# Stamp the chosen profile so `kei` splash + tools can show it (bin/kei reads this).
|
||||
mkdir -p "$HOME_DIR/.claude" 2>/dev/null || true
|
||||
printf '%s\n' "$PROFILE" > "$HOME_DIR/.claude/.kei-profile" 2>/dev/null || true
|
||||
# Stamp the kit checkout dir so `kei configure` can re-source the libs later.
|
||||
printf '%s\n' "$KIT_DIR" > "$HOME_DIR/.claude/.kei-kit-dir" 2>/dev/null || true
|
||||
|
||||
# --- resolve profile -> primitive list (UNCONDITIONAL, SSoT) -------------
|
||||
# Must run BEFORE any reader of PROFILE_PRIMS: the --no-execute plan block
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ STR_WELCOME_TITLE="KeiSeiKit · Exobrain installer"
|
|||
STR_WELCOME_TAGLINE="Portable Rust agent substrate for AI coding tools"
|
||||
|
||||
# Onboarding wizard steps
|
||||
STR_ONBOARDING_INTRO="Onboarding wizard (5 steps)"
|
||||
STR_ONBOARDING_INTRO="Onboarding wizard (6 steps)"
|
||||
STR_PICK_LANGUAGE="Choose interface language:"
|
||||
STR_PICK_TRANSPORT="Choose connection transport:"
|
||||
STR_PICK_PROVIDER="Choose provider within"
|
||||
|
|
@ -46,3 +46,19 @@ STR_PICK_INVALID="please type one of the numbers shown"
|
|||
STR_EXPLAIN_TRANSPORT="How the agents reach the AI. subscription = log in with your plan, no API key (Claude Code is option 1); direct-api = your own API key. Press Enter for the default."
|
||||
STR_EXPLAIN_PROVIDER="Which AI service. Option 1 is the recommended default — press Enter."
|
||||
STR_EXPLAIN_MODEL="Default model the agents use. Option 1 is the recommended default — press Enter."
|
||||
|
||||
# Stack profile + hook-pack picker (step 6)
|
||||
STR_PICK_STACK="Pick your stack profile (selects which hooks + agents install):"
|
||||
STR_PICK_STACK_PROMPT="[1-5, default 1=minimal]: "
|
||||
STR_STACK_MINIMAL="safety hooks + core agents only"
|
||||
STR_STACK_WEB="TS/frontend agents + evidence, observability"
|
||||
STR_STACK_ML="ML/data agents + evidence, observability, epistemic"
|
||||
STR_STACK_SYSTEMS="Rust/Go agents + Rust-first + evidence, observability"
|
||||
STR_STACK_MOBILE="Swift/Flutter agents + evidence, observability"
|
||||
STR_PACK_INTRO="Optional discipline packs (safety is always on):"
|
||||
STR_PACK_EVIDENCE="force evidence markers on numeric/cost claims"
|
||||
STR_PACK_OBS="task timing, session dumps, agent telemetry"
|
||||
STR_PACK_EPI="no-downgrade + alignment + recurrence reminders"
|
||||
STR_PACK_ORCH="multi-agent fork logging + orchestrator git checks"
|
||||
STR_PACK_GIT="block git push to github (for private-remote teams)"
|
||||
STR_PACK_ENABLE="enable? [y/N]: "
|
||||
|
|
|
|||
|
|
@ -14,9 +14,18 @@
|
|||
# when present.
|
||||
install_manifests() {
|
||||
say "copying generic manifests -> $AGENTS_DIR/_manifests/ (skip if exists)"
|
||||
local copied=0 skipped=0 f name t has_templates=0
|
||||
# Stack filter: when a stack profile is chosen, install only its agent set.
|
||||
# Empty allowlist (no stack / non-interactive) => install ALL (back-compat).
|
||||
local allow=""
|
||||
if command -v resolve_selected_agent_manifests >/dev/null 2>&1; then
|
||||
allow="$(resolve_selected_agent_manifests)"
|
||||
fi
|
||||
local copied=0 skipped=0 filtered=0 f name t has_templates=0
|
||||
for f in "$KIT_DIR/_manifests/"*.toml; do
|
||||
name="$(basename "$f")"
|
||||
if [ -n "$allow" ] && ! printf '%s\n' "$allow" | grep -qx "${name%.toml}"; then
|
||||
filtered=$((filtered+1)); continue
|
||||
fi
|
||||
if [[ -f "$AGENTS_DIR/_manifests/$name" ]]; then
|
||||
skipped=$((skipped+1))
|
||||
else
|
||||
|
|
@ -24,7 +33,11 @@ install_manifests() {
|
|||
copied=$((copied+1))
|
||||
fi
|
||||
done
|
||||
say " copied $copied, skipped $skipped (already present)"
|
||||
if [ -n "$allow" ]; then
|
||||
say " copied $copied, skipped $skipped, stack-filtered $filtered"
|
||||
else
|
||||
say " copied $copied, skipped $skipped (already present)"
|
||||
fi
|
||||
|
||||
for t in "$KIT_DIR/_templates/"*.template; do
|
||||
[ -f "$t" ] && { has_templates=1; break; }
|
||||
|
|
|
|||
|
|
@ -102,20 +102,64 @@ _jq_merge_hooks() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Write a filtered copy of the snippet keeping only hook entries whose command
|
||||
# basename is in the newline allowlist (plus the cosmetic pet hooks, always
|
||||
# kept). Drops emptied matcher groups. Echoes the temp path. Arg: $1 = allowlist.
|
||||
filter_snippet_by_packs() {
|
||||
local allow="$1" snippet="$KIT_DIR/settings-snippet.json" tmp
|
||||
tmp="$(mktemp -t kei-snippet.XXXXXX)"
|
||||
jq --arg allow "$allow" '
|
||||
def b: sub("^.*/"; "") | sub("\\.sh$"; "");
|
||||
def keep($ok; $c): (($c | b) as $x | ($ok | index($x)) != null)
|
||||
or ($c | test("keisei-pet")) or ($c | test("^CMD="));
|
||||
($allow | split("\n") | map(select(length > 0))) as $ok
|
||||
| .hooks |= with_entries(
|
||||
.value |= ( map(.hooks |= map(select(keep($ok; .command))))
|
||||
| map(select((.hooks | length) > 0)) )
|
||||
)
|
||||
' "$snippet" > "$tmp" || { err "snippet filter failed"; rm -f "$tmp"; return 1; }
|
||||
printf '%s' "$tmp"
|
||||
}
|
||||
|
||||
# Remove every kit-owned hook entry from an existing settings.json (ownership =
|
||||
# basename in the full pack universe, plus pet hooks). Foreign hooks survive.
|
||||
# Lets reconfigure REMOVE deselected hooks (the merge alone is additive-only).
|
||||
# Args: $1 = target settings.json, $2 = newline list of all kit hook basenames.
|
||||
prune_kit_hooks() {
|
||||
local target="$1" universe="$2" tmp
|
||||
tmp="$(mktemp "$target.XXXXXX")"
|
||||
jq --arg universe "$universe" '
|
||||
def b: sub("^.*/"; "") | sub("\\.sh$"; "");
|
||||
def owned($kit; $c): (($c | b) as $x | ($kit | index($x)) != null)
|
||||
or ($c | test("keisei-pet")) or ($c | test("^CMD="));
|
||||
($universe | split("\n") | map(select(length > 0))) as $kit
|
||||
| .hooks |= with_entries(
|
||||
.value |= ( map(.hooks |= map(select(owned($kit; .command) | not)))
|
||||
| map(select((.hooks | length) > 0)) )
|
||||
)
|
||||
' "$target" > "$tmp" && mv "$tmp" "$target" || { err "prune failed"; rm -f "$tmp"; return 1; }
|
||||
}
|
||||
|
||||
activate_hooks() {
|
||||
local snippet="$KIT_DIR/settings-snippet.json"
|
||||
local target="$HOME_DIR/.claude/settings.json"
|
||||
[ -f "$snippet" ] || { warn "no snippet at $snippet"; return 0; }
|
||||
local allow filtered
|
||||
allow="$(resolve_selected_hook_basenames)"
|
||||
filtered="$(filter_snippet_by_packs "$allow")" || return 1
|
||||
if [ ! -f "$target" ]; then
|
||||
local tmp
|
||||
tmp="$(mktemp "$target.XXXXXX")"
|
||||
jq 'del(._comment)' "$snippet" > "$tmp"
|
||||
jq 'del(._comment)' "$filtered" > "$tmp"
|
||||
mv "$tmp" "$target"
|
||||
say "created $target from snippet (no prior settings.json)"
|
||||
rm -f "$filtered"
|
||||
say "created $target from filtered snippet"
|
||||
return 0
|
||||
fi
|
||||
backup_file "$target"
|
||||
_jq_merge_hooks "$snippet" "$target"
|
||||
prune_kit_hooks "$target" "$(all_pack_basenames)"
|
||||
_jq_merge_hooks "$filtered" "$target"
|
||||
rm -f "$filtered"
|
||||
}
|
||||
|
||||
# Flag-or-prompt dispatcher, mirroring the v0.15 behavior:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ language = "$ONBOARDING_LANG"
|
|||
transport = "$ONBOARDING_TRANSPORT"
|
||||
provider = "$ONBOARDING_PROVIDER"
|
||||
default_model = "$ONBOARDING_MODEL"
|
||||
stack_profile = "$ONBOARDING_STACK"
|
||||
enabled_packs = "$ONBOARDING_PACKS"
|
||||
EOF
|
||||
|
||||
# Override для kei-model-router (HIGH аудит-1).
|
||||
|
|
|
|||
|
|
@ -29,6 +29,53 @@ _onb_read_choice() {
|
|||
done
|
||||
}
|
||||
|
||||
# Step 6 — pick a stack profile (selects which discipline hooks + agents
|
||||
# install) then optionally toggle discipline packs the stack does not pull.
|
||||
# Sets ONBOARDING_STACK + ONBOARDING_PACKS. Reuses _onb_read_choice + stack_packs
|
||||
# (lib-packs.sh). Default = minimal (safety hooks + core agents only).
|
||||
onboarding_pick_stack() {
|
||||
echo "" >&2
|
||||
printf '%s\n' "${STR_PICK_STACK:-Pick your stack profile (selects hooks + agents):}" >&2
|
||||
local opts="minimal web ml systems mobile" i=1 o d ans
|
||||
for o in $opts; do
|
||||
case "$o" in
|
||||
minimal) d="${STR_STACK_MINIMAL:-safety hooks + core agents only}" ;;
|
||||
web) d="${STR_STACK_WEB:-TS/frontend agents + evidence, observability}" ;;
|
||||
ml) d="${STR_STACK_ML:-ML/data agents + evidence, observability, epistemic}" ;;
|
||||
systems) d="${STR_STACK_SYSTEMS:-Rust/Go agents + Rust-first + evidence, observability}" ;;
|
||||
mobile) d="${STR_STACK_MOBILE:-Swift/Flutter agents + evidence, observability}" ;;
|
||||
esac
|
||||
printf ' %d) %-8s — %s\n' "$i" "$o" "$d" >&2
|
||||
i=$((i+1))
|
||||
done
|
||||
ans="$(_onb_read_choice 5 "${STR_PICK_STACK_PROMPT:-[1-5, default 1=minimal]: }")"
|
||||
ONBOARDING_STACK="$(echo "$opts" | cut -d' ' -f"$ans")"
|
||||
[ -n "$ONBOARDING_STACK" ] || ONBOARDING_STACK="minimal"
|
||||
|
||||
# Offer discipline packs the chosen stack does not already enable.
|
||||
local stackpacks p pd reply
|
||||
stackpacks=" $(command -v stack_packs >/dev/null 2>&1 && stack_packs "$ONBOARDING_STACK") "
|
||||
ONBOARDING_PACKS=""
|
||||
printf '%s\n' "${STR_PACK_INTRO:-Optional discipline packs (safety is always on):}" >&2
|
||||
for p in evidence observability epistemic orchestration git-guard; do
|
||||
case "$stackpacks" in *" $p "*) continue ;; esac
|
||||
case "$p" in
|
||||
evidence) pd="${STR_PACK_EVIDENCE:-force evidence markers on numeric/cost claims}" ;;
|
||||
observability) pd="${STR_PACK_OBS:-task timing, session dumps, agent telemetry}" ;;
|
||||
epistemic) pd="${STR_PACK_EPI:-no-downgrade + alignment + recurrence reminders}" ;;
|
||||
orchestration) pd="${STR_PACK_ORCH:-multi-agent fork logging + orchestrator git checks}" ;;
|
||||
git-guard) pd="${STR_PACK_GIT:-block git push to github (for private-remote teams)}" ;;
|
||||
esac
|
||||
printf ' + %-13s — %s\n' "$p" "$pd" >&2
|
||||
read -r -p " ${STR_PACK_ENABLE:-enable? [y/N]: }" reply
|
||||
case "$reply" in y|Y|yes|YES) ONBOARDING_PACKS="$ONBOARDING_PACKS $p" ;; esac
|
||||
done
|
||||
ONBOARDING_PACKS="$(echo "$ONBOARDING_PACKS" | sed 's/^ *//;s/ *$//')"
|
||||
if command -v say >/dev/null 2>&1; then
|
||||
say "stack: $ONBOARDING_STACK packs: ${ONBOARDING_PACKS:-(stack defaults only)}"
|
||||
fi
|
||||
}
|
||||
|
||||
onboarding_pick_language() {
|
||||
local langs
|
||||
langs="$(i18n_available_languages 2>/dev/null)"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ ONBOARDING_LANG=""
|
|||
ONBOARDING_TRANSPORT=""
|
||||
ONBOARDING_PROVIDER=""
|
||||
ONBOARDING_MODEL=""
|
||||
ONBOARDING_STACK=""
|
||||
ONBOARDING_PACKS=""
|
||||
declare -a ONBOARDING_AUTH_ENV_KEYS=()
|
||||
declare -a ONBOARDING_AUTH_ENV_VALUES=()
|
||||
|
||||
|
|
@ -49,20 +51,21 @@ onboarding_should_run() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Оркестратор: 5 шагов + preflight + запись.
|
||||
# Оркестратор: 6 шагов + preflight + запись.
|
||||
onboarding_run() {
|
||||
onboarding_should_run || return 0
|
||||
|
||||
if command -v say >/dev/null 2>&1; then
|
||||
say "${STR_ONBOARDING_INTRO:-Onboarding wizard (5 steps)}"
|
||||
say "${STR_ONBOARDING_INTRO:-Onboarding wizard (6 steps)}"
|
||||
else
|
||||
echo "── KeiSei: ${STR_ONBOARDING_INTRO:-onboarding (5 steps)} ──" >&2
|
||||
echo "── KeiSei: ${STR_ONBOARDING_INTRO:-onboarding (6 steps)} ──" >&2
|
||||
fi
|
||||
|
||||
onboarding_pick_language
|
||||
onboarding_pick_transport
|
||||
onboarding_pick_provider
|
||||
onboarding_pick_model
|
||||
onboarding_pick_stack
|
||||
|
||||
# Preflight — провайдер-специфичная проверка CLI/daemon до сбора ключей.
|
||||
if command -v preflight_run >/dev/null 2>&1; then
|
||||
|
|
|
|||
74
install/lib-packs.sh
Normal file
74
install/lib-packs.sh
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# shellcheck shell=bash
|
||||
# lib-packs.sh — hook-pack + stack-profile resolver. Reads _primitives/hook-packs.toml
|
||||
# via the generic _toml_array reader (from lib-profile.sh). Decides which hooks get
|
||||
# wired into settings.json and which agent manifests install, based on the user's
|
||||
# onboarding selection (or the safe minimal default when none was made).
|
||||
#
|
||||
# Requires: _toml_array from lib-profile.sh.
|
||||
# Reads globals: $KIT_DIR (kit checkout), optional $ONBOARDING_STACK / $ONBOARDING_PACKS
|
||||
# (live onboarding), optional $ONBOARDING_CONFIG (persisted selection).
|
||||
|
||||
PACKS_TOML="${PACKS_TOML:-$KIT_DIR/_primitives/hook-packs.toml}"
|
||||
|
||||
# --- thin table readers ---------------------------------------------------
|
||||
pack_hooks() { _toml_array "$PACKS_TOML" "pack" "$1"; }
|
||||
stack_packs() { _toml_array "$PACKS_TOML" "stack-packs" "$1"; }
|
||||
stack_agent_groups() { _toml_array "$PACKS_TOML" "stack-agents" "$1"; }
|
||||
agent_set_members() { _toml_array "$PACKS_TOML" "agent-set" "$1"; }
|
||||
_packs_always() { _toml_array "$PACKS_TOML" "pack-always" "base"; }
|
||||
|
||||
# --- selection (live onboarding globals > persisted toml > none) ----------
|
||||
# Echo the chosen stack, or empty if the user never chose one.
|
||||
_packs_chosen_stack() {
|
||||
if [ -n "${ONBOARDING_STACK:-}" ]; then printf '%s' "$ONBOARDING_STACK"; return; fi
|
||||
local cfg="${ONBOARDING_CONFIG:-$HOME/.claude/config/onboarding.toml}"
|
||||
[ -f "$cfg" ] && grep -E '^stack_profile[[:space:]]*=' "$cfg" \
|
||||
| sed -E 's/.*=[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/' | head -1
|
||||
}
|
||||
# Echo the explicitly enabled packs (space-separated), or empty.
|
||||
_packs_chosen_packs() {
|
||||
if [ -n "${ONBOARDING_PACKS:-}" ]; then printf '%s' "$ONBOARDING_PACKS"; return; fi
|
||||
local cfg="${ONBOARDING_CONFIG:-$HOME/.claude/config/onboarding.toml}"
|
||||
[ -f "$cfg" ] && grep -E '^enabled_packs[[:space:]]*=' "$cfg" \
|
||||
| sed -E 's/.*=[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/' | head -1
|
||||
}
|
||||
|
||||
# --- resolution -----------------------------------------------------------
|
||||
# Newline list of hook basenames to wire on install: safety + always + every
|
||||
# pack pulled by the chosen stack + every explicitly enabled pack. Default
|
||||
# (no choice) = safety + always only.
|
||||
resolve_selected_hook_basenames() {
|
||||
local stack packs p out
|
||||
stack="$(_packs_chosen_stack)"; stack="${stack:-minimal}"
|
||||
packs="$(_packs_chosen_packs)"
|
||||
out="$(pack_hooks safety) $(_packs_always)"
|
||||
for p in $(stack_packs "$stack") $packs; do
|
||||
out="$out $(pack_hooks "$p")"
|
||||
done
|
||||
echo "$out" | tr ' ' '\n' | grep -v '^$' | sort -u
|
||||
}
|
||||
|
||||
# Newline list of agent manifest basenames to install for the chosen stack
|
||||
# (base group always included). EMPTY when no stack was chosen → caller
|
||||
# installs ALL manifests (power-user / non-interactive default).
|
||||
resolve_selected_agent_manifests() {
|
||||
local stack g out
|
||||
stack="$(_packs_chosen_stack)"
|
||||
[ -n "$stack" ] || return 0
|
||||
out=""
|
||||
for g in $(stack_agent_groups "$stack"); do
|
||||
out="$out $(agent_set_members "$g")"
|
||||
done
|
||||
echo "$out" | tr ' ' '\n' | grep -v '^$' | sort -u
|
||||
}
|
||||
|
||||
# Newline list of EVERY kit-owned hook basename (all packs + always). Used by
|
||||
# prune_kit_hooks to identify which settings.json entries the kit owns.
|
||||
all_pack_basenames() {
|
||||
local p out=""
|
||||
for p in safety evidence observability epistemic orchestration git-guard stack-rust; do
|
||||
out="$out $(pack_hooks "$p")"
|
||||
done
|
||||
out="$out $(_packs_always)"
|
||||
echo "$out" | tr ' ' '\n' | grep -v '^$' | sort -u
|
||||
}
|
||||
|
|
@ -19,13 +19,16 @@ have_python_toml() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Echo space-separated primitive names for a given profile.
|
||||
# Usage: profile_members <profile-name>
|
||||
profile_members() {
|
||||
local profile="$1"
|
||||
[ -f "$MANIFEST" ] || { err "MANIFEST.toml not found at $MANIFEST"; return 1; }
|
||||
# Generic one-line-array TOML reader. Echoes space-separated values of
|
||||
# [<table>]
|
||||
# <key> = ["a", "b", ...]
|
||||
# python-tomllib preferred; awk fallback handles one-line arrays only.
|
||||
# Usage: _toml_array <file> <table> <key>
|
||||
_toml_array() {
|
||||
local file="$1" table="$2" key="$3"
|
||||
[ -f "$file" ] || return 1
|
||||
if have_python_toml; then
|
||||
python3 - "$MANIFEST" "$profile" <<'PY' 2>/dev/null || return 1
|
||||
python3 - "$file" "$table" "$key" <<'PY' 2>/dev/null || return 1
|
||||
import sys
|
||||
try:
|
||||
import tomllib
|
||||
|
|
@ -33,20 +36,19 @@ try:
|
|||
except ImportError:
|
||||
import toml as tomllib
|
||||
mode = "r"
|
||||
path, prof = sys.argv[1], sys.argv[2]
|
||||
path, table, key = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
with open(path, mode) as f:
|
||||
data = tomllib.load(f) if mode == "rb" else tomllib.load(f)
|
||||
members = data.get("profile", {}).get(prof)
|
||||
if members is None:
|
||||
data = tomllib.load(f)
|
||||
vals = data.get(table, {}).get(key)
|
||||
if vals is None:
|
||||
sys.exit(2)
|
||||
print(" ".join(members))
|
||||
print(" ".join(vals))
|
||||
PY
|
||||
else
|
||||
# awk fallback — only handles `profile.<name> = [...]` on one line
|
||||
awk -v prof="$profile" '
|
||||
/^\[profile\]/ { in_profile=1; next }
|
||||
/^\[/ && !/^\[profile\]/ { in_profile=0 }
|
||||
in_profile && $0 ~ "^[[:space:]]*" prof "[[:space:]]*=" {
|
||||
awk -v table="$table" -v key="$key" '
|
||||
$0 ~ "^\\[" table "\\]" { in_t=1; next }
|
||||
/^\[/ { in_t=0 }
|
||||
in_t && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" {
|
||||
line = $0
|
||||
sub(/^[^\[]*\[/, "", line)
|
||||
sub(/\].*$/, "", line)
|
||||
|
|
@ -55,10 +57,18 @@ PY
|
|||
print line
|
||||
exit
|
||||
}
|
||||
' "$MANIFEST"
|
||||
' "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Echo space-separated primitive names for a given profile.
|
||||
# Usage: profile_members <profile-name>
|
||||
profile_members() {
|
||||
local profile="$1"
|
||||
[ -f "$MANIFEST" ] || { err "MANIFEST.toml not found at $MANIFEST"; return 1; }
|
||||
_toml_array "$MANIFEST" "profile" "$profile"
|
||||
}
|
||||
|
||||
# Echo a field of a primitive. Usage: primitive_field <name> <field>
|
||||
# field ∈ { kind, file, crate, desc, deps }
|
||||
primitive_field() {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
"displayName": "KeiSei",
|
||||
"description": "Constructor Pattern agent substrate — 59 agents, 67 skills, 39 hooks, 86 blocks. Rust primitives via classic ./install.sh.",
|
||||
"version": "0.38.0",
|
||||
"homepage": "https://keigit.com/keisei/KeiSeiKit-1.0",
|
||||
"repository": "https://keigit.com/keisei/KeiSeiKit-1.0.git",
|
||||
"homepage": "https://keisei.app",
|
||||
"repository": "https://github.com/KeiSeiLab/KeiSeiKit-1.0.git",
|
||||
"author": {
|
||||
"name": "Denis Parfionovich",
|
||||
"email": "parfionovich@keilab.io"
|
||||
|
|
|
|||
62
scripts/kei-configure.sh
Normal file
62
scripts/kei-configure.sh
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
# kei-configure — re-pick hook packs + stack profile after install, without a
|
||||
# full reinstall. Updates ~/.claude/config/onboarding.toml and re-applies the
|
||||
# hook selection to settings.json (adds newly selected hooks, removes deselected
|
||||
# kit hooks, leaves your own hooks untouched). Agent-set changes apply on the
|
||||
# next `./install.sh`.
|
||||
#
|
||||
# Invoked via `kei configure`. Interactive (needs a terminal).
|
||||
set -u
|
||||
set -o pipefail 2>/dev/null || true
|
||||
|
||||
HOME_DIR="${HOME:?HOME not set}"
|
||||
KIT_DIR="$(cat "$HOME_DIR/.claude/.kei-kit-dir" 2>/dev/null || true)"
|
||||
if [ -z "$KIT_DIR" ] || [ ! -d "$KIT_DIR/install" ]; then
|
||||
echo "kei configure: KeiSeiKit checkout not found." >&2
|
||||
echo " (expected its path in ~/.claude/.kei-kit-dir; re-run ./install.sh from your checkout)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -t 0 ]; then
|
||||
echo "kei configure: interactive only — run it from a terminal." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIB_DIR="$KIT_DIR/install"
|
||||
MANIFEST="$KIT_DIR/_primitives/MANIFEST.toml"
|
||||
PACKS_TOML="$KIT_DIR/_primitives/hook-packs.toml"
|
||||
ONBOARDING_CONFIG="$HOME_DIR/.claude/config/onboarding.toml"
|
||||
export HOME_DIR KIT_DIR LIB_DIR MANIFEST PACKS_TOML ONBOARDING_CONFIG
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-log.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-backup.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-profile.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-packs.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-hooks.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB_DIR/lib-onboarding-ui.sh"
|
||||
|
||||
ONBOARDING_STACK=""
|
||||
ONBOARDING_PACKS=""
|
||||
onboarding_pick_stack
|
||||
|
||||
# Update only stack_profile/enabled_packs in onboarding.toml; preserve the rest.
|
||||
mkdir -p "$(dirname "$ONBOARDING_CONFIG")"
|
||||
touch "$ONBOARDING_CONFIG"
|
||||
_tmp="$(mktemp)"
|
||||
grep -vE '^(stack_profile|enabled_packs)[[:space:]]*=' "$ONBOARDING_CONFIG" > "$_tmp" 2>/dev/null || true
|
||||
{
|
||||
printf 'stack_profile = "%s"\n' "$ONBOARDING_STACK"
|
||||
printf 'enabled_packs = "%s"\n' "$ONBOARDING_PACKS"
|
||||
} >> "$_tmp"
|
||||
mv "$_tmp" "$ONBOARDING_CONFIG"
|
||||
|
||||
# Re-apply hooks: prune kit-owned entries, merge the newly selected set.
|
||||
activate_hooks
|
||||
|
||||
say "reconfigured: stack=$ONBOARDING_STACK packs=${ONBOARDING_PACKS:-none}"
|
||||
say " settings.json hooks updated. Agent-set changes apply on the next ./install.sh."
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
#
|
||||
# Env / args:
|
||||
# KEISEI_ROOT install dir (default: $HOME/.local/share/keisei)
|
||||
# KEISEI_REPO git URL (default: https://keigit.com/keisei/KeiSeiKit-1.0.git)
|
||||
# KEISEI_REPO git URL (default: https://github.com/KeiSeiLab/KeiSeiKit-1.0.git)
|
||||
# KEISEI_REF branch/tag/sha (default: main)
|
||||
# --profile=NAME passed through to ./bootstrap.sh
|
||||
# --yes passed through to ./bootstrap.sh
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
set -euo pipefail
|
||||
|
||||
KEISEI_ROOT="${KEISEI_ROOT:-$HOME/.local/share/keisei}"
|
||||
KEISEI_REPO="${KEISEI_REPO:-https://keigit.com/keisei/KeiSeiKit-1.0.git}"
|
||||
KEISEI_REPO="${KEISEI_REPO:-https://github.com/KeiSeiLab/KeiSeiKit-1.0.git}"
|
||||
KEISEI_REF="${KEISEI_REF:-main}"
|
||||
|
||||
PASS_THROUGH=()
|
||||
|
|
|
|||
Loading…
Reference in a new issue