Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
79d2733
feat(agent-comparison): add autoresearch optimization review flow
notque Mar 29, 2026
9d9601c
merge: resolve conflicts with main (keep autoresearch-clean versions)
notque Mar 29, 2026
1d3b452
feat(autoresearch): migrate SDK to claude -p, add beam search, fix re…
notque Mar 29, 2026
5490a08
fix(review-round-1): address 8 findings from PR review
notque Mar 29, 2026
db510bb
fix(review-round-2): handle JSON parse error in _run_trigger_rate, fi…
notque Mar 29, 2026
bb60b7d
fix(review-round-3): catch TimeoutExpired, move write_text inside cle…
notque Mar 29, 2026
926bedf
style: fix import sort order and formatting
notque Mar 29, 2026
fdf897d
feat(adr-132): add behavioral eval mode and creation compliance task set
notque Mar 29, 2026
0434c2b
feat(adr-133): strengthen Phase 1 creation detection in /do SKILL.md
notque Mar 29, 2026
c25f6a7
feat(adr-133): add creation-protocol-enforcer PreToolUse hook
notque Mar 29, 2026
1d13702
fix(index): register kotlin, php, and swift agent entries in INDEX.json
notque Mar 29, 2026
757063f
fix(behavioral-eval): raise timeout to 240s, check artifacts after Ti…
notque Mar 29, 2026
06a8664
fix(review-round-1): address 4 findings from PR review
notque Mar 29, 2026
4b67bb1
fix(optimize-loop): expand behavioral cleanup scope, add best-by-test…
notque Mar 29, 2026
06292b8
feat(hooks): add UserPromptSubmit creation enforcement hook for early…
notque Mar 29, 2026
43052d4
feat(hooks): add SQL injection detector hook and db performance indexes
notque Mar 29, 2026
50b6a81
feat(perses-plugin): add ExamplePanel plugin scaffold with CUE schema…
notque Mar 29, 2026
f9c18e0
fix(lint): fix ruff I001 import sort in team-config-loader.py
notque Mar 30, 2026
d62659d
fix(lint): resolve ruff check and format errors for CI
notque Mar 30, 2026
85ae9d8
fix(review-round-1): remove duplicate schema, add missing index.ts, r…
notque Mar 30, 2026
f1690e8
fix(review-round-2): fix version check type coercion, add missing tes…
notque Mar 30, 2026
9fdc523
merge: resolve conflicts with origin/main (PR #205 overlap)
notque Mar 30, 2026
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
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
"command": "python3 \"$HOME/.claude/hooks/anti-rationalization-injector.py\"",
"description": "Inject anti-rationalization warnings based on task-type keywords",
"timeout": 1000
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/creation-request-enforcer-userprompt.py\"",
"description": "Early ADR enforcement: detect creation requests before model processing begins",
"timeout": 5000
}
]
}
Expand Down Expand Up @@ -297,6 +303,16 @@
"timeout": 2000
}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/sql-injection-detector.py",
"timeout": 5000
}
]
}
],
"PreCompact": [
Expand Down
7 changes: 2 additions & 5 deletions agents/INDEX.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"agents": {
"agent-creator-engineer": {
"file": "agent-creator-engineer.md",
"short_description": "**DEPRECATED**: Use skill-creator skill instead",
"short_description": "**DEPRECATED**: Use skill-creator agent instead",
"triggers": [
"create agent",
"new agent",
Expand Down Expand Up @@ -107,10 +107,7 @@
"programming rules"
],
"pairs_with": [
"github-profile-rules-repo-analysis",
"github-profile-rules-pr-review",
"github-profile-rules-synthesis",
"github-profile-rules-validation"
"github-profile-rules"
],
"complexity": "Medium",
"category": "meta"
Expand Down
158 changes: 158 additions & 0 deletions hooks/creation-request-enforcer-userprompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
# hook-version: 1.0.0
"""
UserPromptSubmit Hook: Creation Request ADR Enforcer

Fires at UserPromptSubmit time — BEFORE the model begins processing — and checks
whether the user's prompt contains creation keywords. If a creation request is
detected without a recent ADR session, it injects a strong context message
reminding Claude that an ADR is mandatory before any other action.

This hook complements the PreToolUse:Agent creation-protocol-enforcer.py by
catching the requirement earlier in the pipeline, before routing has occurred.

Allow-through conditions:
- No creation keywords found in prompt
- .adr-session.json exists and was modified within the last 900 seconds
- ADR_PROTOCOL_BYPASS=1 env var is set
"""

import json
import os
import sys
import time
import traceback
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent / "lib"))
from hook_utils import context_output, empty_output
from stdin_timeout import read_stdin

_BYPASS_ENV = "ADR_PROTOCOL_BYPASS"
_ADR_SESSION_FILE = ".adr-session.json"
_STALENESS_THRESHOLD_SECONDS = 900
_EVENT_NAME = "UserPromptSubmit"

_CREATION_KEYWORDS = [
"create",
"scaffold",
"build a new",
"build a ",
"add a new",
"add new",
"new agent",
"new skill",
"new pipeline",
"new hook",
"new feature",
"new workflow",
"new plugin",
"implement new",
"i need a ",
"i need an ",
"we need a ",
"we need an ",
]

_WARNING_TEXT = """\
[creation-enforcer] CREATION REQUEST DETECTED — ADR IS MANDATORY BEFORE ANY OTHER ACTION

You MUST complete these steps BEFORE dispatching any agent or writing any files:
1. Write ADR at adr/{name}.md (use kebab-case name describing what you're creating)
2. Register: python3 scripts/adr-query.py register --adr adr/{name}.md
3. Only THEN proceed to routing and agent dispatch.

Skipping this step will be blocked by the pretool-adr-creation-gate hook.\
"""


def _has_creation_keywords(prompt: str) -> bool:
"""Return True if the prompt contains any creation keyword (case-insensitive)."""
lower = prompt.lower()
return any(kw in lower for kw in _CREATION_KEYWORDS)


def _adr_session_is_recent(base_dir: Path) -> bool:
"""Return True if .adr-session.json exists and was modified within the threshold."""
adr_session_path = base_dir / _ADR_SESSION_FILE
if not adr_session_path.exists():
return False
try:
mtime = os.path.getmtime(adr_session_path)
age = time.time() - mtime
return age <= _STALENESS_THRESHOLD_SECONDS
except OSError:
return False


def main() -> None:
"""Run the UserPromptSubmit creation enforcement check."""
debug = os.environ.get("CLAUDE_HOOKS_DEBUG")

raw = read_stdin(timeout=2)
try:
event = json.loads(raw)
except (json.JSONDecodeError, ValueError):
empty_output(_EVENT_NAME).print_and_exit()

# Bypass env var.
if os.environ.get(_BYPASS_ENV) == "1":
if debug:
print(
f"[creation-enforcer] Bypassed via {_BYPASS_ENV}=1",
file=sys.stderr,
)
empty_output(_EVENT_NAME).print_and_exit()

# UserPromptSubmit event uses the "prompt" field for the user message.
prompt = event.get("prompt", "") if isinstance(event, dict) else ""
if not prompt:
empty_output(_EVENT_NAME).print_and_exit()

# Check for creation keywords.
if not _has_creation_keywords(prompt):
if debug:
print(
"[creation-enforcer] No creation keywords found — allowing through",
file=sys.stderr,
)
empty_output(_EVENT_NAME).print_and_exit()

# Resolve project root.
cwd_str = event.get("cwd") or os.environ.get("CLAUDE_PROJECT_DIR", ".")
base_dir = Path(cwd_str).resolve()

# Check whether a recent ADR session exists.
if _adr_session_is_recent(base_dir):
if debug:
print(
"[creation-enforcer] Recent .adr-session.json found — allowing through",
file=sys.stderr,
)
empty_output(_EVENT_NAME).print_and_exit()

if debug:
print(
"[creation-enforcer] Creation keywords found, no recent ADR session — injecting warning",
file=sys.stderr,
)

# No recent ADR session — inject strong advisory context.
context_output(_EVENT_NAME, _WARNING_TEXT).print_and_exit()


if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception as e:
if os.environ.get("CLAUDE_HOOKS_DEBUG"):
traceback.print_exc(file=sys.stderr)
else:
print(
f"[creation-enforcer] Error: {type(e).__name__}: {e}",
file=sys.stderr,
)
# Fail open — never exit non-zero on unexpected errors.
sys.exit(0)
28 changes: 27 additions & 1 deletion hooks/lib/learning_db_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

_DEFAULT_DB_DIR = Path.home() / ".claude" / "learning"

_CURRENT_SCHEMA_VERSION = 2
_CURRENT_SCHEMA_VERSION = 3

CATEGORY_DEFAULTS = {
"error": 0.55,
Expand Down Expand Up @@ -132,6 +132,26 @@ def _run_migrations(conn: sqlite3.Connection) -> None:
"VALUES (2, 'add graduation_proposed_at column to learnings')"
)

if current < 3:
# v2 -> v3: Add performance indexes for timestamp range queries and ROI cohort scans
for ddl in (
"CREATE INDEX IF NOT EXISTS idx_learnings_last_seen ON learnings(last_seen)",
"CREATE INDEX IF NOT EXISTS idx_learnings_first_seen ON learnings(first_seen)",
"CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time)",
"CREATE INDEX IF NOT EXISTS idx_activations_timestamp ON activations(timestamp)",
"CREATE INDEX IF NOT EXISTS idx_session_stats_had_retro ON session_stats(had_retro_knowledge)",
"CREATE INDEX IF NOT EXISTS idx_session_stats_created_at ON session_stats(created_at)",
):
try:
conn.execute(ddl)
except sqlite3.OperationalError:
pass # Index already exists
conn.execute("PRAGMA user_version = 3")
conn.execute(
"INSERT OR IGNORE INTO schema_migrations (version, description) "
"VALUES (3, 'add timestamp and cohort indexes for query performance')"
)

conn.commit()


Expand Down Expand Up @@ -235,7 +255,10 @@ def _migrate_fts(pre_migration_version: int = 0) -> None:
CREATE INDEX IF NOT EXISTS idx_learnings_project ON learnings(project_path);
CREATE INDEX IF NOT EXISTS idx_learnings_graduated ON learnings(graduated_to);
CREATE INDEX IF NOT EXISTS idx_learnings_error_sig ON learnings(error_signature);
CREATE INDEX IF NOT EXISTS idx_learnings_last_seen ON learnings(last_seen);
CREATE INDEX IF NOT EXISTS idx_learnings_first_seen ON learnings(first_seen);
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);

CREATE VIRTUAL TABLE IF NOT EXISTS learnings_fts USING fts5(
topic,
Expand Down Expand Up @@ -267,7 +290,10 @@ def _migrate_fts(pre_migration_version: int = 0) -> None:

CREATE INDEX IF NOT EXISTS idx_activations_topic_key ON activations(topic, key);
CREATE INDEX IF NOT EXISTS idx_activations_session ON activations(session_id);
CREATE INDEX IF NOT EXISTS idx_activations_timestamp ON activations(timestamp);
CREATE INDEX IF NOT EXISTS idx_session_stats_session ON session_stats(session_id);
CREATE INDEX IF NOT EXISTS idx_session_stats_had_retro ON session_stats(had_retro_knowledge);
CREATE INDEX IF NOT EXISTS idx_session_stats_created_at ON session_stats(created_at);

CREATE TRIGGER IF NOT EXISTS learnings_ai AFTER INSERT ON learnings BEGIN
INSERT INTO learnings_fts(rowid, topic, key, value, tags)
Expand Down
2 changes: 2 additions & 0 deletions hooks/lib/usage_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ def init_db():
CREATE INDEX IF NOT EXISTS idx_agent_type ON agent_invocations(agent_type);
CREATE INDEX IF NOT EXISTS idx_skill_ts ON skill_invocations(timestamp);
CREATE INDEX IF NOT EXISTS idx_agent_ts ON agent_invocations(timestamp);
CREATE INDEX IF NOT EXISTS idx_skill_name_ts ON skill_invocations(skill_name, timestamp);
CREATE INDEX IF NOT EXISTS idx_agent_type_ts ON agent_invocations(agent_type, timestamp);
""")
conn.commit()

Expand Down
Loading
Loading