preprompt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- preprompt-0.1.0/.claude/hooks/pre_prompt.py +166 -0
- preprompt-0.1.0/.claude/settings.json +24 -0
- preprompt-0.1.0/.env.example +17 -0
- preprompt-0.1.0/.github/workflows/ci.yml +24 -0
- preprompt-0.1.0/.github/workflows/publish.yml +42 -0
- preprompt-0.1.0/.gitignore +32 -0
- preprompt-0.1.0/CLAUDE.md +121 -0
- preprompt-0.1.0/CONTEXT.md +117 -0
- preprompt-0.1.0/LICENSE +22 -0
- preprompt-0.1.0/PKG-INFO +192 -0
- preprompt-0.1.0/README.md +163 -0
- preprompt-0.1.0/cli/__init__.py +0 -0
- preprompt-0.1.0/cli/commands.py +310 -0
- preprompt-0.1.0/docs/CNAME +1 -0
- preprompt-0.1.0/docs/index.html +664 -0
- preprompt-0.1.0/mcp_server/__init__.py +0 -0
- preprompt-0.1.0/mcp_server/classifier.py +98 -0
- preprompt-0.1.0/mcp_server/config.py +13 -0
- preprompt-0.1.0/mcp_server/extractor.py +108 -0
- preprompt-0.1.0/mcp_server/optimizer.py +88 -0
- preprompt-0.1.0/mcp_server/server.py +20 -0
- preprompt-0.1.0/mcp_server/tools.py +82 -0
- preprompt-0.1.0/preprompt.skill.md +64 -0
- preprompt-0.1.0/pyproject.toml +57 -0
- preprompt-0.1.0/scripts/init_github.py +68 -0
- preprompt-0.1.0/scripts/install.sh +48 -0
- preprompt-0.1.0/scripts/install_cursor.py +64 -0
- preprompt-0.1.0/scripts/install_windsurf.py +44 -0
- preprompt-0.1.0/scripts/install_zed.py +45 -0
- preprompt-0.1.0/scripts/setup_global_hook.py +118 -0
- preprompt-0.1.0/storage/__init__.py +0 -0
- preprompt-0.1.0/storage/db.py +322 -0
- preprompt-0.1.0/tests/__init__.py +0 -0
- preprompt-0.1.0/tests/test_classifier.py +94 -0
- preprompt-0.1.0/tests/test_integration.py +241 -0
- preprompt-0.1.0/tests/test_optimizer.py +79 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Code UserPromptSubmit hook for PrePrompt.
|
|
4
|
+
|
|
5
|
+
Receives JSON on stdin, writes JSON to stdout.
|
|
6
|
+
stdin: {"prompt": "...", "conversation_history": [...], "turn_number": N}
|
|
7
|
+
stdout: {"prompt": "..."} ← optimized or original, never blocked
|
|
8
|
+
|
|
9
|
+
DB writes are done via JSON sidecar files in ~/.preprompt/pending/
|
|
10
|
+
so the hook never touches the SQLite file directly (avoids lock conflict
|
|
11
|
+
with the running MCP server). The MCP server flushes sidecars on the
|
|
12
|
+
next optimize_prompt call.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
import os
|
|
17
|
+
import json
|
|
18
|
+
import uuid
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Box annotation constants ──────────────────────────────────────────────────
|
|
24
|
+
_WIDTH = 62 # total box width
|
|
25
|
+
_INNER = _WIDTH - 4 # content between "║ " and " ║" (58 chars)
|
|
26
|
+
_TEXT_W = 48 # prompt text width after label (58 - 10)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _box_line(content: str) -> str:
|
|
30
|
+
return f"║ {content:<{_INNER}} ║"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _render_annotation(score: int, reason: str, original: str, optimized: str) -> str:
|
|
34
|
+
import textwrap
|
|
35
|
+
|
|
36
|
+
# Header: ╔═ PrePrompt +{score} ══...══╗
|
|
37
|
+
prefix = f"╔═ PrePrompt +{score} "
|
|
38
|
+
header = prefix + "═" * (_WIDTH - len(prefix) - 1) + "╗"
|
|
39
|
+
|
|
40
|
+
# Reason (wrapped to inner width)
|
|
41
|
+
reason_lines = [_box_line(l) for l in (textwrap.wrap(reason, _INNER) or [""])]
|
|
42
|
+
|
|
43
|
+
# Separator
|
|
44
|
+
sep = "╠" + "═" * (_WIDTH - 2) + "╣"
|
|
45
|
+
|
|
46
|
+
# ORIGINAL (truncated to TEXT_W)
|
|
47
|
+
orig_text = original if len(original) <= _TEXT_W else original[:_TEXT_W - 3] + "..."
|
|
48
|
+
orig_line = _box_line(f"ORIGINAL {orig_text}")
|
|
49
|
+
|
|
50
|
+
# OPTIMIZED (wrapped, continuation indented 10 spaces to align with text)
|
|
51
|
+
opt_wrapped = textwrap.wrap(optimized, _TEXT_W) or [optimized[:_TEXT_W]]
|
|
52
|
+
opt_lines = [_box_line(f"OPTIMIZED {opt_wrapped[0]}")] + [
|
|
53
|
+
_box_line(f" {line}") for line in opt_wrapped[1:]
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Footer
|
|
57
|
+
footer = "╚" + "═" * (_WIDTH - 2) + "╝"
|
|
58
|
+
|
|
59
|
+
return "\n".join([header] + reason_lines + [sep, orig_line] + opt_lines + [footer])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _write_sidecar(
|
|
63
|
+
prompt: str,
|
|
64
|
+
optimized: str,
|
|
65
|
+
score: int,
|
|
66
|
+
was_intercepted: bool,
|
|
67
|
+
turn_number: int,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Write event to ~/.preprompt/pending/<uuid>.json for async DB flush."""
|
|
70
|
+
sidecar = {
|
|
71
|
+
"original_prompt": prompt,
|
|
72
|
+
"optimized_prompt": optimized,
|
|
73
|
+
"classifier_score": score,
|
|
74
|
+
"was_intercepted": was_intercepted,
|
|
75
|
+
"turn_number": turn_number,
|
|
76
|
+
"timestamp": time.time(),
|
|
77
|
+
}
|
|
78
|
+
sidecar_dir = Path.home() / ".preprompt" / "pending"
|
|
79
|
+
sidecar_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
sidecar_path = sidecar_dir / f"{uuid.uuid4()}.json"
|
|
81
|
+
sidecar_path.write_text(json.dumps(sidecar))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> None:
|
|
85
|
+
raw = sys.stdin.read()
|
|
86
|
+
|
|
87
|
+
# Parse stdin — on failure, echo back whatever we got and exit cleanly
|
|
88
|
+
try:
|
|
89
|
+
data = json.loads(raw)
|
|
90
|
+
except Exception:
|
|
91
|
+
print(raw, end="")
|
|
92
|
+
sys.exit(0)
|
|
93
|
+
|
|
94
|
+
prompt: str = data.get("prompt", "")
|
|
95
|
+
history: list = data.get("conversation_history", [])
|
|
96
|
+
turn: int = data.get("turn_number", 1)
|
|
97
|
+
|
|
98
|
+
def passthrough() -> None:
|
|
99
|
+
print(json.dumps({"prompt": prompt}))
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Always resolve paths relative to this file's location,
|
|
103
|
+
# not the caller's working directory
|
|
104
|
+
_HOOK_FILE = os.path.abspath(__file__)
|
|
105
|
+
_HOOK_DIR = os.path.dirname(_HOOK_FILE) # .claude/hooks/
|
|
106
|
+
_CLAUDE_DIR = os.path.dirname(_HOOK_DIR) # .claude/
|
|
107
|
+
_PROJECT_ROOT = os.path.dirname(_CLAUDE_DIR) # project root
|
|
108
|
+
|
|
109
|
+
# Add project root to path so mcp_server imports work
|
|
110
|
+
if _PROJECT_ROOT not in sys.path:
|
|
111
|
+
sys.path.insert(0, _PROJECT_ROOT)
|
|
112
|
+
|
|
113
|
+
# Load .env from project root
|
|
114
|
+
from dotenv import load_dotenv
|
|
115
|
+
load_dotenv(os.path.join(_PROJECT_ROOT, ".env"))
|
|
116
|
+
|
|
117
|
+
# ── Classify (no API call — always fast) ──────────────────────────────
|
|
118
|
+
from mcp_server.classifier import classify_prompt, OPTIMIZATION_THRESHOLD
|
|
119
|
+
|
|
120
|
+
score = classify_prompt(prompt, history, turn)
|
|
121
|
+
|
|
122
|
+
if score < OPTIMIZATION_THRESHOLD:
|
|
123
|
+
passthrough()
|
|
124
|
+
sys.exit(0)
|
|
125
|
+
|
|
126
|
+
# ── Check API key before importing optimizer (which triggers config) ──
|
|
127
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
|
|
128
|
+
if not api_key:
|
|
129
|
+
print(
|
|
130
|
+
"[PrePrompt WARNING] ANTHROPIC_API_KEY not set — skipping optimization",
|
|
131
|
+
file=sys.stderr,
|
|
132
|
+
)
|
|
133
|
+
passthrough()
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
|
|
136
|
+
# ── Optimize via Haiku API ────────────────────────────────────────────
|
|
137
|
+
from mcp_server.optimizer import optimize
|
|
138
|
+
|
|
139
|
+
result = optimize(prompt, history)
|
|
140
|
+
optimized: str = result["optimized_prompt"]
|
|
141
|
+
reason: str = result["reason"]
|
|
142
|
+
was_intercepted: bool = optimized != prompt
|
|
143
|
+
|
|
144
|
+
# ── Write sidecar (no DB lock needed) ────────────────────────────────
|
|
145
|
+
try:
|
|
146
|
+
_write_sidecar(prompt, optimized, score, was_intercepted, turn)
|
|
147
|
+
except Exception as sidecar_err:
|
|
148
|
+
print(f"[PrePrompt] Sidecar write failed: {sidecar_err}", file=sys.stderr)
|
|
149
|
+
|
|
150
|
+
# ── Rich annotation to stderr ─────────────────────────────────────────
|
|
151
|
+
if was_intercepted:
|
|
152
|
+
print(_render_annotation(score, reason, prompt, optimized), file=sys.stderr)
|
|
153
|
+
else:
|
|
154
|
+
print(f"[PrePrompt +{score}] {reason}", file=sys.stderr)
|
|
155
|
+
|
|
156
|
+
print(json.dumps({"prompt": optimized}))
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(f"[PrePrompt ERROR] {e}", file=sys.stderr)
|
|
160
|
+
passthrough()
|
|
161
|
+
|
|
162
|
+
sys.exit(0)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"preprompt": {
|
|
4
|
+
"command": "python",
|
|
5
|
+
"args": ["-m", "mcp_server.server"],
|
|
6
|
+
"cwd": "${workspaceFolder}/promptforge",
|
|
7
|
+
"env": {
|
|
8
|
+
"PYTHONPATH": "${workspaceFolder}/promptforge"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"hooks": {
|
|
13
|
+
"UserPromptSubmit": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "python ${workspaceFolder}/promptforge/.claude/hooks/pre_prompt.py"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Anthropic API key — required for classifier and optimizer agents
|
|
2
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
3
|
+
|
|
4
|
+
# Model used for classification (cheap + fast)
|
|
5
|
+
CLASSIFIER_MODEL=claude-haiku-4-5-20251001
|
|
6
|
+
|
|
7
|
+
# Model used for prompt optimization (more capable)
|
|
8
|
+
OPTIMIZER_MODEL=claude-sonnet-4-6
|
|
9
|
+
|
|
10
|
+
# DuckDB database file path (use :memory: for in-memory only)
|
|
11
|
+
DB_PATH=./promptforge.duckdb
|
|
12
|
+
|
|
13
|
+
# Minimum confidence score (0.0–1.0) to trigger optimization
|
|
14
|
+
OPTIMIZATION_THRESHOLD=0.7
|
|
15
|
+
|
|
16
|
+
# MCP server transport: stdio or sse
|
|
17
|
+
MCP_TRANSPORT=stdio
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
|
|
18
|
+
- name: Install dev deps
|
|
19
|
+
run: pip install -e ".[dev]"
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
env:
|
|
23
|
+
ANTHROPIC_API_KEY: sk-ant-test-dummy-key-for-ci
|
|
24
|
+
run: python -m pytest -v
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
|
|
18
|
+
- name: Install build
|
|
19
|
+
run: pip install build
|
|
20
|
+
|
|
21
|
+
- name: Build distribution
|
|
22
|
+
run: python -m build
|
|
23
|
+
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: release
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
- name: Publish to PyPI
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
.env
|
|
3
|
+
*.env
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# DuckDB (local dev databases)
|
|
15
|
+
*.duckdb
|
|
16
|
+
*.duckdb.wal
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
|
|
22
|
+
# macOS
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# Tests
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# PrePrompt runtime DB lives outside the repo:
|
|
31
|
+
# ~/.preprompt/history.db ← not tracked
|
|
32
|
+
.venv/
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Activate venv first
|
|
9
|
+
source .venv/bin/activate
|
|
10
|
+
|
|
11
|
+
# Run all tests
|
|
12
|
+
python -m pytest
|
|
13
|
+
|
|
14
|
+
# Run a single test file
|
|
15
|
+
python -m pytest tests/test_classifier.py -v
|
|
16
|
+
|
|
17
|
+
# Install in editable mode (includes dev deps)
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
|
|
20
|
+
# Start the MCP server
|
|
21
|
+
python -m mcp_server.server
|
|
22
|
+
|
|
23
|
+
# CLI tools (after pip install)
|
|
24
|
+
preprompt-history [--limit N] [--intercepted-only]
|
|
25
|
+
preprompt-stats
|
|
26
|
+
preprompt-test-classifier
|
|
27
|
+
preprompt-memory
|
|
28
|
+
preprompt-optimize "your prompt here" # or pipe: echo "..." | preprompt-optimize
|
|
29
|
+
preprompt-optimize --raw "prompt" # prints optimized text only
|
|
30
|
+
preprompt-update-context
|
|
31
|
+
|
|
32
|
+
# One-command install + global hook registration (auto-detects Claude Code, Cursor, Windsurf, Zed)
|
|
33
|
+
bash scripts/install.sh
|
|
34
|
+
|
|
35
|
+
# Per-IDE registration
|
|
36
|
+
python scripts/install_cursor.py
|
|
37
|
+
python scripts/install_windsurf.py
|
|
38
|
+
python scripts/install_zed.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
PrePrompt is an MCP server that intercepts prompts before they reach the LLM, scores them with a local heuristic classifier, optionally rewrites vague/complex ones via Claude Haiku, and logs everything to a local SQLite DB.
|
|
44
|
+
|
|
45
|
+
### Request flow
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
User types prompt
|
|
49
|
+
→ .claude/hooks/pre_prompt.py (UserPromptSubmit hook, subprocess)
|
|
50
|
+
→ classify_prompt() (score 0–100, no API call)
|
|
51
|
+
→ if score < 38: passthrough
|
|
52
|
+
→ else: optimize() via Haiku API
|
|
53
|
+
→ write JSON sidecar → ~/.preprompt/pending/<uuid>.json
|
|
54
|
+
→ print optimized prompt to stdout
|
|
55
|
+
→ Claude Code sends optimized prompt to LLM
|
|
56
|
+
→ (next MCP tool call)
|
|
57
|
+
→ flush_pending_hook_events() reads sidecars → SQLite
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The hook **never touches the DB directly** — this avoids SQLite lock contention with the running MCP server. The sidecar files are the IPC mechanism.
|
|
61
|
+
|
|
62
|
+
### Key modules
|
|
63
|
+
|
|
64
|
+
| File | Responsibility |
|
|
65
|
+
|------|---------------|
|
|
66
|
+
| `mcp_server/classifier.py` | Heuristic scorer. `OPTIMIZATION_THRESHOLD = 38`. No API calls. |
|
|
67
|
+
| `mcp_server/optimizer.py` | Calls Claude Haiku to rewrite prompt. |
|
|
68
|
+
| `mcp_server/tools.py` | MCP tool `optimize_prompt()` — entry point from Claude Code. Calls `flush_pending_hook_events()` first. |
|
|
69
|
+
| `mcp_server/extractor.py` | Extracts stack facts (language, framework, etc.) from prompts → `stack_memory`. |
|
|
70
|
+
| `storage/db.py` | SQLite (WAL mode). Tables: `prompt_history`, `stack_memory`, `sessions`. `_get_connection()` is the long-lived write conn; `get_read_connection()` opens fresh read conns for CLI/history. |
|
|
71
|
+
| `cli/commands.py` | CLI entry points. Always calls `flush_pending_hook_events()` before reading history. Includes `optimize_cmd` for standalone CLI optimization. |
|
|
72
|
+
| `.claude/hooks/pre_prompt.py` | Hook subprocess. Resolves project root via `__file__` (not cwd). Writes sidecars, never imports `storage.db`. |
|
|
73
|
+
| `scripts/install_windsurf.py` | Registers MCP in `~/.codeium/windsurf/mcp_config.json`. |
|
|
74
|
+
| `scripts/install_zed.py` | Registers MCP in `~/.config/zed/settings.json`. |
|
|
75
|
+
| `.github/workflows/publish.yml` | Publishes to PyPI on `git tag v*` via OIDC trusted publisher. |
|
|
76
|
+
| `preprompt.skill.md` | Claude Skill file — manual PrePrompt scoring/optimization for tools without MCP. |
|
|
77
|
+
|
|
78
|
+
### Data directory
|
|
79
|
+
|
|
80
|
+
All runtime data lives in `~/.preprompt/`:
|
|
81
|
+
- `history.db` — SQLite database
|
|
82
|
+
- `pending/*.json` — sidecar files written by hook, flushed by MCP server
|
|
83
|
+
|
|
84
|
+
### Classifier scoring
|
|
85
|
+
|
|
86
|
+
Positive: ambiguity verbs (+25 max), multi-requirement density (12 pts/hit, max 30), turn depth (+5/turn after turn 2, max 15), code task without format hint (+15).
|
|
87
|
+
Negative: short prompt (−20), "what is/are/does" opener (−15), already structured (−15), conversational opener (−25).
|
|
88
|
+
|
|
89
|
+
### SQLite type conventions
|
|
90
|
+
|
|
91
|
+
- Timestamps stored as ISO 8601 TEXT; parsed back to `datetime` via `_coerce_row()` on read.
|
|
92
|
+
- Booleans stored as INTEGER (0/1); coerced back to `bool` on read.
|
|
93
|
+
- All writes followed by `conn.commit()`.
|
|
94
|
+
|
|
95
|
+
### Session identity
|
|
96
|
+
|
|
97
|
+
One session per `hostname + calendar day`. Session key format: `{hostname}-{YYYY-MM-DD}`.
|
|
98
|
+
|
|
99
|
+
### Landing page
|
|
100
|
+
|
|
101
|
+
`docs/index.html` — GitHub Pages site (Tailwind CDN, Space Grotesk + JetBrains Mono, dark theme). Sections: hero terminal, interactive classifier demo, how it works, stack memory CLI mockup, session replay, stats bar, open source CTA, beta signup, FAQ. All interactivity is vanilla JS — no build step.
|
|
102
|
+
|
|
103
|
+
## Mandatory after every task
|
|
104
|
+
|
|
105
|
+
After completing any task in this repository, you MUST always:
|
|
106
|
+
|
|
107
|
+
1. Run `pytest -v` and confirm all tests still pass
|
|
108
|
+
|
|
109
|
+
2. Run `preprompt-update-context` to update CONTEXT.md
|
|
110
|
+
|
|
111
|
+
3. Update CLAUDE.md if any architecture, file map, or interface changed
|
|
112
|
+
|
|
113
|
+
4. Commit with a clear descriptive message authored by Yashdeep Tehlan:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
git -c user.name="yashdeeptehlan" -c user.email="0yashdeeptehlan@gmail.com" commit -m "your message here"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
5. Push to main
|
|
120
|
+
|
|
121
|
+
Never skip these steps. Never use co-authored-by or mention AI tools in commit messages.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<!-- Last updated: 2026-04-27 22:40 -->
|
|
2
|
+
# PrePrompt — CONTEXT.md
|
|
3
|
+
# This file is auto-maintained. Read it fully at the start of every chat.
|
|
4
|
+
|
|
5
|
+
## Build status
|
|
6
|
+
Phase 7c complete. 28/28 tests passing.
|
|
7
|
+
|
|
8
|
+
## What PrePrompt does
|
|
9
|
+
MCP server that intercepts prompts in Claude Code and Cursor, scores them
|
|
10
|
+
with a heuristic classifier (no API), optimizes complex ones using Claude
|
|
11
|
+
Haiku, logs everything to SQLite (WAL mode), and learns the user's stack
|
|
12
|
+
over time via a memory layer. Runs entirely locally.
|
|
13
|
+
|
|
14
|
+
## Tech stack
|
|
15
|
+
- Python 3.11+, FastMCP, Anthropic SDK, SQLite (WAL mode), pydantic-settings
|
|
16
|
+
- Haiku model: claude-haiku-4-5-20251001
|
|
17
|
+
- DB: SQLite WAL at ~/.preprompt/history.db
|
|
18
|
+
|
|
19
|
+
## File map
|
|
20
|
+
mcp_server/
|
|
21
|
+
server.py — entry point, mcp.run(transport=settings.mcp_transport)
|
|
22
|
+
tools.py — MCP tools: optimize_prompt, get_prompt_history
|
|
23
|
+
uses get_or_create_session() for stable daily session identity
|
|
24
|
+
classifier.py — pure heuristic scorer, threshold=38, multi-req weight=12/hit, no API calls
|
|
25
|
+
optimizer.py — Haiku API call + memory context injection
|
|
26
|
+
strips markdown fences from model JSON response
|
|
27
|
+
extractor.py — heuristic stack signal extractor
|
|
28
|
+
config.py — pydantic-settings, reads .env
|
|
29
|
+
|
|
30
|
+
storage/
|
|
31
|
+
db.py — SQLite WAL: prompt_history + stack_memory + sessions tables
|
|
32
|
+
Sidecar pattern: hook writes JSON to ~/.preprompt/pending/,
|
|
33
|
+
flushed by MCP server or CLI commands via flush_pending_hook_events()
|
|
34
|
+
get_or_create_session(): stable {hostname}-{date} session key
|
|
35
|
+
get_all_history(): cross-session history query
|
|
36
|
+
upsert_stack_memory(): compounding confidence (+0.02/hit, reset on value change)
|
|
37
|
+
|
|
38
|
+
cli/
|
|
39
|
+
commands.py — preprompt-history (all sessions), stats, memory,
|
|
40
|
+
test-classifier, optimize, update-context
|
|
41
|
+
|
|
42
|
+
.claude/
|
|
43
|
+
settings.json — MCP server + UserPromptSubmit hook config
|
|
44
|
+
hooks/pre_prompt.py — interception hook with rich box annotation on stderr
|
|
45
|
+
path resolved via __file__ (CWD-independent)
|
|
46
|
+
writes JSON sidecar to ~/.preprompt/pending/ — never touches DB directly
|
|
47
|
+
|
|
48
|
+
scripts/
|
|
49
|
+
install.sh — one-command installer (auto-detects Claude Code, Cursor, Windsurf, Zed)
|
|
50
|
+
setup_global_hook.py — global Claude Code MCP + UserPromptSubmit registration
|
|
51
|
+
install_cursor.py — registers MCP in ~/.cursor/mcp.json
|
|
52
|
+
install_windsurf.py — registers MCP in ~/.codeium/windsurf/mcp_config.json
|
|
53
|
+
install_zed.py — registers MCP in ~/.config/zed/settings.json
|
|
54
|
+
init_github.py — git init + first commit + push instructions
|
|
55
|
+
|
|
56
|
+
.github/
|
|
57
|
+
workflows/ci.yml — runs pytest on every push/PR
|
|
58
|
+
workflows/publish.yml — builds + publishes to PyPI on git tag v*
|
|
59
|
+
|
|
60
|
+
preprompt.skill.md — Claude Skill file for tools without MCP hook support
|
|
61
|
+
|
|
62
|
+
LICENSE — MIT
|
|
63
|
+
|
|
64
|
+
tests/
|
|
65
|
+
test_classifier.py — 12 tests
|
|
66
|
+
test_optimizer.py — 4 tests
|
|
67
|
+
test_integration.py — 12 tests
|
|
68
|
+
|
|
69
|
+
## Key interfaces — never change these signatures
|
|
70
|
+
- classify_prompt(prompt: str, history: list, turn: int) -> int
|
|
71
|
+
- optimize(prompt: str, history: list) -> dict
|
|
72
|
+
- optimize_prompt(user_prompt, conversation_history, turn_number) -> dict
|
|
73
|
+
- save_prompt_event(...) in storage/db.py
|
|
74
|
+
- get_recent_history(session_id, limit) in storage/db.py
|
|
75
|
+
- upsert_stack_memory(key, value, confidence) in storage/db.py
|
|
76
|
+
- get_stack_memory() -> dict[str, str] in storage/db.py
|
|
77
|
+
- extract_stack_signals(prompt, history) -> dict
|
|
78
|
+
- update_memory_from_prompt(prompt, history) -> None
|
|
79
|
+
- flush_pending_hook_events() -> int in storage/db.py
|
|
80
|
+
|
|
81
|
+
## Completed phases
|
|
82
|
+
- Phase 1: scaffold, classifier, optimizer, SQLite, MCP server
|
|
83
|
+
- Phase 2: hook, Cursor install, CLI commands
|
|
84
|
+
- Phase 3: stack memory, extractor, memory-aware optimizer
|
|
85
|
+
- Phase 4: GitHub, live test, CONTEXT.md
|
|
86
|
+
- Phase 5: session identity, memory consolidation, rich annotations, cross-session history
|
|
87
|
+
- Phase 6: packaging, SQLite WAL migration, sidecar concurrency pattern, classifier tuned (38/12), absolute hook path, one-command install, MIT license, distribution README
|
|
88
|
+
- Phase 6b: global rename PromptForge → PrePrompt
|
|
89
|
+
- Phase 7: GitHub Actions CI + publish workflow, PyPI trusted publisher setup
|
|
90
|
+
- Phase 7b: preprompt-optimize CLI command, Windsurf + Zed MCP installers
|
|
91
|
+
- Phase 7c: preprompt.skill.md — Claude Skill for tools without MCP support
|
|
92
|
+
|
|
93
|
+
## PyPI publish instructions
|
|
94
|
+
1. Create account at https://pypi.org
|
|
95
|
+
2. Go to https://pypi.org/manage/account/publishing/
|
|
96
|
+
3. Add trusted publisher: owner=yashdeeptehlan, repo=preprompt, workflow=publish.yml, env=release
|
|
97
|
+
4. Create GitHub environment "release" at https://github.com/yashdeeptehlan/preprompt/settings/environments
|
|
98
|
+
5. Tag a release: git tag v0.1.0 && git push origin v0.1.0
|
|
99
|
+
|
|
100
|
+
## Next phases
|
|
101
|
+
- Phase 8: web dashboard (local FastAPI + HTMX) to browse history and replay sessions
|
|
102
|
+
- Phase 8b: prompt diff view (original vs optimized, side by side)
|
|
103
|
+
|
|
104
|
+
## GitHub Pages
|
|
105
|
+
Landing page: https://yashdeeptehlan.github.io/preprompt/
|
|
106
|
+
Source: docs/index.html (Tailwind CDN, JetBrains Mono, interactive demo, session replay, FAQ)
|
|
107
|
+
|
|
108
|
+
## How new chats should start
|
|
109
|
+
User will say "continuing from last chat" or paste this file.
|
|
110
|
+
Ask for: cat preprompt/CONTEXT.md
|
|
111
|
+
Confirm phase + what comes next, then proceed.
|
|
112
|
+
|
|
113
|
+
## Environment
|
|
114
|
+
- Dev machine: macOS
|
|
115
|
+
- API key: in .env as ANTHROPIC_API_KEY
|
|
116
|
+
- Claude Code workspace: /Users/user/Documents/Promptforge/promptforge
|
|
117
|
+
- GitHub: https://github.com/yashdeeptehlan/preprompt
|
preprompt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2026 Yashdeep Tehlan
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
a copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
19
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
20
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
21
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|