Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,67 @@ All notable changes to `role-x` are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] — 2026-05-13

Hook integration (M2): role-x intake fires automatically on every substantive
user prompt via a Claude Code `UserPromptSubmit` hook. Closes the
reasoning-enforcement gap from v0.1.0 — now machine-checkable.

### Added
- `scripts/role-x-intake-hook.sh` — Claude Code `UserPromptSubmit` hook
wrapper. Reads JSON payload from stdin, calls `role-x.py intake`,
outputs context to stdout (added to the agent's working context).
Graceful-fails (exit 0) if PyYAML missing, `roles/` absent, or workspace
not detected. Never blocks a user turn.
- `role-x intake` subcommand on `scripts/role-x.py`. Reads JSON event from
stdin **or** accepts `--prompt`/`--workspace`/`--session` flags for
testing. Snapshots git signals (branch, touched files), tokenizes prompt
keywords, scores all `roles/*.md` lenses, walks `extends:` chain,
decides mode (augment / rewrite / decompose), emits structured event to
`~/.config/broomva/role/events.jsonl`, prints intake context to stdout.
- 7 new tests covering carve-outs (short prompts, missing `roles/`),
keyword matching, event persistence, multi-domain decompose escalation,
`_meta`-only fallback, stdin JSON protocol.

### Changed
- Test count: 6 → **13** (all green on Python 3.11 + 3.12 CI).
- `references/feedback-loop.md` is now wired up: hook + intake subcommand
produce events the M4 dream cycle will replay.

### Workspace wiring (separate PR in `broomva/workspace`)
This release ships the hook *script*. The workspace's `.claude/settings.json`
must register the hook to fire automatically:

```json
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$HOME/.agents/skills/role-x/scripts/role-x-intake-hook.sh",
"timeout": 5
}
]
}
]
```

The hook is reasoning-supplementing, not blocking — `exit 0` always.

### Manual invocation (test or fallback)

```bash
# Direct CLI test
python3 ~/.agents/skills/role-x/scripts/role-x.py intake \
--prompt "your prompt here" \
--workspace "$PWD" \
--session "manual"

# Via the hook script (simulating Claude Code stdin)
echo '{"prompt": "your prompt", "session_id": "manual"}' \
| ~/.agents/skills/role-x/scripts/role-x-intake-hook.sh
```

## [0.1.0] — 2026-05-13

Initial release. Ships the Markdown lens registry + Python CLI for the bstack
Expand Down
71 changes: 67 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,69 @@ Selection and mode-decision are **reasoning-enforced** (bstack-idiom, same as P1
| `role-x list [--roles-dir roles]` | List all lenses with status + extends + default_mode |
| `role-x validate <path>` | Validate a lens markdown file against the schema (frontmatter shape, required fields, enum values, name-matches-filename) |
| `role-x index [--roles-dir roles]` | Regenerate `roles/_index.md` discovery file |
| `role-x intake [--prompt … --workspace … --session …]` | **v0.2.0+** — `UserPromptSubmit` hook entry point. Scores lenses against current signals (git + prompt content), walks `extends:` chain, decides mode, emits event to `~/.config/broomva/role/events.jsonl`, prints agent-context to stdout. Reads JSON from stdin if `--prompt` omitted (the Claude Code hook protocol). |

## Hook integration (v0.2.0+)

The `intake` subcommand can fire automatically on every substantive user prompt via a Claude Code `UserPromptSubmit` hook. Add to your workspace's `.claude/settings.json`:

```json
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$HOME/.agents/skills/role-x/scripts/role-x-intake-hook.sh",
"timeout": 5
}
]
}
]
}
}
```

The hook:

- Reads the prompt JSON payload from stdin
- Resolves workspace via `$CLAUDE_PROJECT_DIR` (or `$PWD`)
- Scores `roles/*.md` against signals (git branch, touched files, prompt keywords)
- Selects lens(es) with score ≥2, walks `extends:` chain to `_meta`
- Decides mode (`augment` / `rewrite` / `decompose`)
- Appends a structured event to `~/.config/broomva/role/events.jsonl`
- Prints the lens metadata + composed `quality_bar` + suggestions to stdout (added to the agent's working context)

**Always exits 0.** Graceful-fails if PyYAML is missing, the workspace has no `roles/` directory, or the prompt is shorter than 3 words (carve-out for trivial prompts).

### Test the hook locally

```bash
echo '{"prompt": "implement rust cargo tokio async support", "session_id": "manual"}' \
| CLAUDE_PROJECT_DIR=$PWD ~/.agents/skills/role-x/scripts/role-x-intake-hook.sh
```

Expected output: lens selected, mode decided, quality_bar surfaced, event appended to events.jsonl.

### Event schema

```json
{
"ts": "<ISO-8601 UTC>",
"event": "intake",
"session": "<session id>",
"prompt_digest": "sha256:<hex>",
"prompt_word_count": 42,
"lenses_selected": ["rust-systems"],
"lenses_extended": ["rust-systems", "_meta"],
"mode": "augment",
"mode_escalation_reason": null,
"signals_matched": {"paths": 0, "prompt_keywords": 4, "branch_patterns": 0, "linear_labels": 0}
}
```

See [`references/feedback-loop.md`](references/feedback-loop.md) for the full design (M4 dream cycle consumes this telemetry).

## Lens schema (minimal example)

Expand Down Expand Up @@ -158,11 +221,11 @@ Full design lives at [`broomva/workspace`](https://github.com/broomva/workspace)

## Roadmap

- **v0.1.0** (this release) — Markdown lens registry + CLI (`validate`, `list`, `index`) + reference docs
- **v0.2.0 (M2)**Hook integration (`UserPromptSubmit` / `PostToolUse` / `Stop`) + `~/.config/broomva/role/events.jsonl` capture + `~/.config/broomva/role/status.json` cache
- **v0.1.0** — Markdown lens registry + CLI (`validate`, `list`, `index`) + reference docs
- **v0.2.0** (this release)`intake` subcommand + `UserPromptSubmit` hook + `~/.config/broomva/role/events.jsonl` capture
- **v0.3.0 (M3)** — Seed lens corpus expansion (`ts-nextjs`, `api-design`, `security-review`, `infra-deploy`, `docs-research`)
- **v0.4.0 (M4)** — P13 dream cycle: `role-x-replay.py` with replay-against-frozen-substrate
- **v0.5.0 (M5)** — `persona-*` skill referenceability + thin lens wrappers
- **v0.4.0 (M4)** — P13 dream cycle: `role-x-replay.py` with replay-against-frozen-substrate; `status.json` per-lens stats cache
- **v0.5.0 (M5)** — `persona-*` skill referenceability + thin lens wrappers + `PostToolUse` / `Stop` outcome hooks

## License

Expand Down
39 changes: 39 additions & 0 deletions scripts/role-x-intake-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# role-x-intake-hook.sh — Claude Code UserPromptSubmit hook for bstack P17.
#
# Wires the role-x intake reflex (lens selection + mode decision + event
# capture + agent-context output) into every substantive user prompt.
#
# Always exits 0 (never blocks the user's turn). Graceful-fails if PyYAML
# isn't available or the workspace has no `roles/` directory.
#
# Installed by: `npx skills add broomva/role-x`
# Canonical location: ~/.agents/skills/role-x/scripts/role-x-intake-hook.sh
# Referenced from: $WORKSPACE/.claude/settings.json under "UserPromptSubmit"

set -eu

PYTHON_BIN="${ROLE_X_PYTHON:-python3}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROLE_X_PY="$SCRIPT_DIR/role-x.py"

# Graceful-fail if Python or the CLI are not present.
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
exit 0
fi
if [ ! -f "$ROLE_X_PY" ]; then
exit 0
fi

# Graceful-fail if PyYAML isn't importable in the chosen interpreter.
if ! "$PYTHON_BIN" -c "import yaml" >/dev/null 2>&1; then
exit 0
fi

# Resolve workspace: prefer Claude Code's CLAUDE_PROJECT_DIR env, else $PWD.
WORKSPACE="${CLAUDE_PROJECT_DIR:-$PWD}"

# Stream stdin (the hook JSON payload) through to the intake subcommand.
# `intake` always exits 0; we still guard with `|| true` so the hook never
# fails the user's turn for any unexpected reason.
exec "$PYTHON_BIN" "$ROLE_X_PY" intake --workspace "$WORKSPACE" || true
Loading
Loading