mapify-cli 3.3.0__tar.gz → 3.4.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.
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/PKG-INFO +1 -1
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/pyproject.toml +1 -1
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/__init__.py +1 -1
- mapify_cli-3.4.0/src/mapify_cli/templates/hooks/post-compact-context.py +110 -0
- mapify_cli-3.4.0/src/mapify_cli/templates/hooks/pre-compact-save-transcript.py +182 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/ralph-context-pruner.py +4 -7
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/settings.json +31 -5
- mapify_cli-3.3.0/src/mapify_cli/templates/hooks/block-dangerous.sh +0 -173
- mapify_cli-3.3.0/src/mapify_cli/templates/hooks/block-secrets.py +0 -158
- mapify_cli-3.3.0/src/mapify_cli/templates/hooks/improve-prompt.py +0 -74
- mapify_cli-3.3.0/src/mapify_cli/templates/hooks/post-edit-reminder.py +0 -87
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/.claude/skills/README.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/.gitignore +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/README.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/dependency_graph.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/intent_detector.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/ralph_state.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/repo_insight.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/schemas.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/CLAUDE.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/actor.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/debate-arbiter.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/documentation-reviewer.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/evaluator.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/final-verifier.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/monitor.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/predictor.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/reflector.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/research-agent.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/synthesizer.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/task-decomposer.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-check.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-debate.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-debug.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-efficient.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-fast.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-learn.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-plan.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-release.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-resume.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/commands/map-review.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/end-of-turn.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/ralph-iteration-logger.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/safety-guardrails.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/workflow-context-injector.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/workflow-gate.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/scripts/diagnostics.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/scripts/map_orchestrator.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/scripts/map_step_runner.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/analyze.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/common.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/go.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/python.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/rust.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/typescript.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/ralph-loop-config.json +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/bash-guidelines.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/decomposition-examples.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/escalation-matrix.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/mcp-usage-examples.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/step-state-schema.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/workflow-state-schema.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/README.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-cli-reference/SKILL.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-cli-reference/scripts/check-command.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/SKILL.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/scripts/check-complete.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/scripts/get-plan-path.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/scripts/init-session.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/scripts/show-focus.sh +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/templates/findings.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/templates/iteration_history.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/templates/progress.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-planning/templates/task_plan.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/SKILL.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/agent-architecture.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/map-debug-deep-dive.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/map-efficient-deep-dive.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/map-fast-deep-dive.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/map-feature-deep-dive.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/resources/map-refactor-deep-dive.md +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/scripts/validate-workflow-choice.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/skill-rules.json +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/workflow-rules.json +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/tools/__init__.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/tools/validate_dependencies.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/verification_recorder.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/workflow_finalizer.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/workflow_logger.py +0 -0
- {mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/workflow_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapify-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: MAP Framework installer - Modular Agentic Planner for Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/azalio/map-framework
|
|
6
6
|
Project-URL: Repository, https://github.com/azalio/map-framework.git
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Post-Compact Context Injector - SessionStart Hook (matcher: compact).
|
|
4
|
+
|
|
5
|
+
After context compaction, injects a pointer to the saved transcript
|
|
6
|
+
so Claude knows where to find the full pre-compaction conversation.
|
|
7
|
+
|
|
8
|
+
Also reads restore_point.json if available (from ralph-context-pruner).
|
|
9
|
+
|
|
10
|
+
Exit codes:
|
|
11
|
+
0 - Always
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
PROJECT_DIR = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
21
|
+
MAP_DIR = PROJECT_DIR / ".map"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sanitize_branch_name(branch: str) -> str:
|
|
25
|
+
"""Sanitize branch name for safe filesystem paths."""
|
|
26
|
+
sanitized = branch.replace("/", "-")
|
|
27
|
+
sanitized = re.sub(r"[^a-zA-Z0-9_.-]", "-", sanitized)
|
|
28
|
+
sanitized = re.sub(r"-+", "-", sanitized).strip("-")
|
|
29
|
+
if ".." in sanitized or sanitized.startswith("."):
|
|
30
|
+
return "default"
|
|
31
|
+
return sanitized or "default"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_branch_name() -> str:
|
|
35
|
+
"""Get current git branch name."""
|
|
36
|
+
try:
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
cwd=PROJECT_DIR,
|
|
42
|
+
timeout=2,
|
|
43
|
+
)
|
|
44
|
+
if result.returncode == 0:
|
|
45
|
+
return sanitize_branch_name(result.stdout.strip())
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
return "default"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main() -> None:
|
|
52
|
+
try:
|
|
53
|
+
json.load(sys.stdin)
|
|
54
|
+
except json.JSONDecodeError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
branch = get_branch_name()
|
|
58
|
+
branch_dir = MAP_DIR / branch
|
|
59
|
+
|
|
60
|
+
parts = []
|
|
61
|
+
|
|
62
|
+
# Check for saved transcript pointer
|
|
63
|
+
pointer = branch_dir / "last-transcript.txt"
|
|
64
|
+
if pointer.exists():
|
|
65
|
+
try:
|
|
66
|
+
transcript_path = pointer.read_text(encoding="utf-8").strip()
|
|
67
|
+
if transcript_path:
|
|
68
|
+
parts.append(
|
|
69
|
+
f"The full transcript of the previous conversation "
|
|
70
|
+
f"(before compaction) was saved to {transcript_path}. "
|
|
71
|
+
f"Read that file if you need details from before compaction."
|
|
72
|
+
)
|
|
73
|
+
except (IOError, OSError):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# Check for workflow restore point
|
|
77
|
+
restore = branch_dir / "restore_point.json"
|
|
78
|
+
if restore.exists():
|
|
79
|
+
try:
|
|
80
|
+
data = json.loads(restore.read_text(encoding="utf-8"))
|
|
81
|
+
state = data.get("workflow_state", {})
|
|
82
|
+
workflow = state.get("workflow", "")
|
|
83
|
+
phase = state.get("current_step", {}).get("phase", "") or state.get(
|
|
84
|
+
"current_state", ""
|
|
85
|
+
)
|
|
86
|
+
if workflow or phase:
|
|
87
|
+
parts.append(
|
|
88
|
+
f"MAP workflow state before compaction: "
|
|
89
|
+
f"workflow={workflow}, phase={phase}. "
|
|
90
|
+
f"Full state: .map/{branch}/workflow_state.json"
|
|
91
|
+
)
|
|
92
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
if not parts:
|
|
96
|
+
print("{}")
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
|
|
99
|
+
result = {
|
|
100
|
+
"hookSpecificOutput": {
|
|
101
|
+
"hookEventName": "SessionStart",
|
|
102
|
+
"additionalContext": "\n".join(parts),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
print(json.dumps(result))
|
|
106
|
+
sys.exit(0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Pre-Compact Transcript Saver - PreCompact Hook.
|
|
4
|
+
|
|
5
|
+
Before context compaction, saves the full conversation transcript
|
|
6
|
+
to .map/<branch>/transcript-YYYY-MM-DD-HH-MM-SS.md as readable markdown.
|
|
7
|
+
|
|
8
|
+
This preserves the full context for later review.
|
|
9
|
+
|
|
10
|
+
Exit codes:
|
|
11
|
+
0 - Always (PreCompact hooks don't block)
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
PROJECT_DIR = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
23
|
+
MAP_DIR = PROJECT_DIR / ".map"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def sanitize_branch_name(branch: str) -> str:
|
|
27
|
+
"""Sanitize branch name for safe filesystem paths."""
|
|
28
|
+
sanitized = branch.replace("/", "-")
|
|
29
|
+
sanitized = re.sub(r"[^a-zA-Z0-9_.-]", "-", sanitized)
|
|
30
|
+
sanitized = re.sub(r"-+", "-", sanitized).strip("-")
|
|
31
|
+
if ".." in sanitized or sanitized.startswith("."):
|
|
32
|
+
return "default"
|
|
33
|
+
return sanitized or "default"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_branch_name() -> str:
|
|
37
|
+
"""Get current git branch name."""
|
|
38
|
+
try:
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
cwd=PROJECT_DIR,
|
|
44
|
+
timeout=2,
|
|
45
|
+
)
|
|
46
|
+
if result.returncode == 0:
|
|
47
|
+
return sanitize_branch_name(result.stdout.strip())
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
return "default"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def extract_text_from_content(content):
|
|
54
|
+
"""Extract readable text from message content (string or list)."""
|
|
55
|
+
if isinstance(content, str):
|
|
56
|
+
return content
|
|
57
|
+
if not isinstance(content, list):
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
parts = []
|
|
61
|
+
for item in content:
|
|
62
|
+
if isinstance(item, str):
|
|
63
|
+
parts.append(item)
|
|
64
|
+
elif isinstance(item, dict):
|
|
65
|
+
item_type = item.get("type", "")
|
|
66
|
+
if item_type == "text":
|
|
67
|
+
parts.append(item.get("text", ""))
|
|
68
|
+
elif item_type == "tool_use":
|
|
69
|
+
name = item.get("name", "unknown")
|
|
70
|
+
tool_input = item.get("input", {})
|
|
71
|
+
input_str = json.dumps(tool_input, ensure_ascii=False)
|
|
72
|
+
# Truncate long tool inputs
|
|
73
|
+
if len(input_str) > 500:
|
|
74
|
+
input_str = input_str[:500] + "..."
|
|
75
|
+
parts.append(f"**Tool:** `{name}`\n```json\n{input_str}\n```")
|
|
76
|
+
elif item_type == "tool_result":
|
|
77
|
+
result_content = item.get("content", "")
|
|
78
|
+
if isinstance(result_content, list):
|
|
79
|
+
for rc in result_content:
|
|
80
|
+
if isinstance(rc, dict) and rc.get("type") == "text":
|
|
81
|
+
text = rc.get("text", "")
|
|
82
|
+
if len(text) > 1000:
|
|
83
|
+
text = text[:1000] + "...[truncated]"
|
|
84
|
+
parts.append(text)
|
|
85
|
+
elif isinstance(result_content, str):
|
|
86
|
+
if len(result_content) > 1000:
|
|
87
|
+
result_content = result_content[:1000] + "...[truncated]"
|
|
88
|
+
parts.append(result_content)
|
|
89
|
+
return "\n".join(parts)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def parse_transcript(transcript_path: Path) -> str:
|
|
93
|
+
"""Parse JSONL transcript into readable markdown."""
|
|
94
|
+
lines = []
|
|
95
|
+
try:
|
|
96
|
+
with open(transcript_path, encoding="utf-8") as f:
|
|
97
|
+
for raw_line in f:
|
|
98
|
+
raw_line = raw_line.strip()
|
|
99
|
+
if not raw_line:
|
|
100
|
+
continue
|
|
101
|
+
try:
|
|
102
|
+
entry = json.loads(raw_line)
|
|
103
|
+
except json.JSONDecodeError:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
entry_type = entry.get("type", "")
|
|
107
|
+
message = entry.get("message", {})
|
|
108
|
+
role = message.get("role", "")
|
|
109
|
+
content = message.get("content", "")
|
|
110
|
+
|
|
111
|
+
if entry_type == "human" or role == "user":
|
|
112
|
+
text = extract_text_from_content(content)
|
|
113
|
+
if text.strip():
|
|
114
|
+
lines.append(f"## User\n\n{text}\n")
|
|
115
|
+
elif entry_type == "assistant" or role == "assistant":
|
|
116
|
+
text = extract_text_from_content(content)
|
|
117
|
+
if text.strip():
|
|
118
|
+
lines.append(f"## Assistant\n\n{text}\n")
|
|
119
|
+
elif entry_type == "tool_result":
|
|
120
|
+
text = extract_text_from_content(content)
|
|
121
|
+
if text.strip():
|
|
122
|
+
lines.append(
|
|
123
|
+
f"<details><summary>Tool result</summary>\n\n"
|
|
124
|
+
f"```\n{text}\n```\n</details>\n"
|
|
125
|
+
)
|
|
126
|
+
except (IOError, OSError) as e:
|
|
127
|
+
lines.append(f"Error reading transcript: {e}\n")
|
|
128
|
+
|
|
129
|
+
return "\n".join(lines)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
try:
|
|
134
|
+
input_data = json.load(sys.stdin)
|
|
135
|
+
except json.JSONDecodeError:
|
|
136
|
+
input_data = {}
|
|
137
|
+
|
|
138
|
+
transcript_path = input_data.get("transcript_path", "")
|
|
139
|
+
session_id = input_data.get("session_id", "unknown")
|
|
140
|
+
|
|
141
|
+
if not transcript_path or not Path(transcript_path).is_file():
|
|
142
|
+
print("{}")
|
|
143
|
+
sys.exit(0)
|
|
144
|
+
|
|
145
|
+
branch = get_branch_name()
|
|
146
|
+
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
147
|
+
|
|
148
|
+
branch_dir = MAP_DIR / branch
|
|
149
|
+
branch_dir.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
outfile = branch_dir / f"transcript-{timestamp}.md"
|
|
151
|
+
|
|
152
|
+
header = (
|
|
153
|
+
f"# Conversation snapshot before compact\n\n"
|
|
154
|
+
f"- **Branch:** {branch}\n"
|
|
155
|
+
f"- **Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
156
|
+
f"- **Session:** {session_id}\n\n"
|
|
157
|
+
f"---\n\n"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
body = parse_transcript(Path(transcript_path))
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
outfile.write_text(header + body, encoding="utf-8")
|
|
164
|
+
print(f"[pre-compact-save] Saved transcript to {outfile}", file=sys.stderr)
|
|
165
|
+
except (IOError, OSError) as e:
|
|
166
|
+
print(f"[pre-compact-save] Failed to save: {e}", file=sys.stderr)
|
|
167
|
+
print("{}")
|
|
168
|
+
sys.exit(0)
|
|
169
|
+
|
|
170
|
+
# Write a pointer file so the context-pruner (or compact summary) can reference it
|
|
171
|
+
pointer = branch_dir / "last-transcript.txt"
|
|
172
|
+
try:
|
|
173
|
+
pointer.write_text(str(outfile.relative_to(PROJECT_DIR)), encoding="utf-8")
|
|
174
|
+
except (IOError, OSError):
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
print("{}")
|
|
178
|
+
sys.exit(0)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
main()
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/ralph-context-pruner.py
RENAMED
|
@@ -13,7 +13,7 @@ Exit codes:
|
|
|
13
13
|
0 - Always (PreCompact hooks don't block)
|
|
14
14
|
|
|
15
15
|
Output:
|
|
16
|
-
|
|
16
|
+
Side effects only (PreCompact has no decision control per docs)
|
|
17
17
|
"""
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
@@ -239,12 +239,9 @@ def main() -> None:
|
|
|
239
239
|
file=sys.stderr,
|
|
240
240
|
)
|
|
241
241
|
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"hookEventName": "PreCompact",
|
|
246
|
-
"additionalContext": recovery_msg,
|
|
247
|
-
}
|
|
242
|
+
# Note: PreCompact has no decision control per docs — additionalContext
|
|
243
|
+
# is not supported. Recovery context is injected via SessionStart(compact)
|
|
244
|
+
# hook (post-compact-context.py) which reads restore_point.json.
|
|
248
245
|
|
|
249
246
|
# Prune log files in ALL branch directories
|
|
250
247
|
actions = []
|
|
@@ -35,6 +35,20 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"hooks": {
|
|
38
|
+
"SessionStart": [
|
|
39
|
+
{
|
|
40
|
+
"matcher": "compact",
|
|
41
|
+
"description": "Post-Compact Context - inject transcript path and workflow state after compaction",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-compact-context.py",
|
|
46
|
+
"timeout": 5,
|
|
47
|
+
"description": "Tells Claude where to find the pre-compaction transcript and workflow state"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
38
52
|
"PreToolUse": [
|
|
39
53
|
{
|
|
40
54
|
"matcher": "Edit|Write|Read|MultiEdit|Bash",
|
|
@@ -75,14 +89,14 @@
|
|
|
75
89
|
],
|
|
76
90
|
"PostToolUse": [
|
|
77
91
|
{
|
|
78
|
-
"matcher": "Edit|Write|MultiEdit",
|
|
79
|
-
"description": "
|
|
92
|
+
"matcher": "Edit|Write|MultiEdit|Bash",
|
|
93
|
+
"description": "Iteration Logger - logs tool calls, detects thrashing patterns",
|
|
80
94
|
"hooks": [
|
|
81
95
|
{
|
|
82
96
|
"type": "command",
|
|
83
|
-
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/
|
|
84
|
-
"timeout":
|
|
85
|
-
"description": "
|
|
97
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-iteration-logger.py",
|
|
98
|
+
"timeout": 5,
|
|
99
|
+
"description": "Logs iterations to .map/<branch>/iteration_log.jsonl, alerts on file thrashing"
|
|
86
100
|
}
|
|
87
101
|
]
|
|
88
102
|
}
|
|
@@ -99,6 +113,18 @@
|
|
|
99
113
|
"description": "Saves workflow restore point and prunes old logs"
|
|
100
114
|
}
|
|
101
115
|
]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"matcher": "*",
|
|
119
|
+
"description": "Transcript Saver - save full conversation to .map/ before compaction",
|
|
120
|
+
"hooks": [
|
|
121
|
+
{
|
|
122
|
+
"type": "command",
|
|
123
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-compact-save-transcript.py",
|
|
124
|
+
"timeout": 30,
|
|
125
|
+
"description": "Saves full transcript as readable markdown to .map/<branch>/transcript-YYYY-MM-DD-HH-MM.md"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
102
128
|
}
|
|
103
129
|
],
|
|
104
130
|
"Stop": [
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# =============================================================================
|
|
3
|
-
# Claude Code PreToolUse Hook: Block Dangerous Commands
|
|
4
|
-
# =============================================================================
|
|
5
|
-
#
|
|
6
|
-
# Intercepts Bash tool calls and blocks destructive commands like:
|
|
7
|
-
# - rm -rf (recursive force delete)
|
|
8
|
-
# - git push --force to main/master branches
|
|
9
|
-
# - git reset --hard
|
|
10
|
-
#
|
|
11
|
-
# USAGE:
|
|
12
|
-
# This hook runs automatically before Bash tool calls.
|
|
13
|
-
# Claude Code passes JSON via stdin with tool_name and tool_input.
|
|
14
|
-
#
|
|
15
|
-
# EXIT CODES:
|
|
16
|
-
# 0 - Allow command execution
|
|
17
|
-
# 0 + permissionDecision=deny - Block command execution (preferred)
|
|
18
|
-
#
|
|
19
|
-
# TESTING:
|
|
20
|
-
# echo '{"tool_name": "Bash", "tool_input": {"command": "rm -rf /"}}' | bash block-dangerous.sh
|
|
21
|
-
# # Expected: Exit code 0 with permissionDecision=deny in JSON output
|
|
22
|
-
#
|
|
23
|
-
# =============================================================================
|
|
24
|
-
|
|
25
|
-
set -euo pipefail
|
|
26
|
-
|
|
27
|
-
# Read JSON from stdin
|
|
28
|
-
INPUT=$(cat)
|
|
29
|
-
|
|
30
|
-
deny() {
|
|
31
|
-
local reason="$1"
|
|
32
|
-
|
|
33
|
-
if command -v jq >/dev/null 2>&1; then
|
|
34
|
-
jq -n --arg reason "$reason" '{
|
|
35
|
-
hookSpecificOutput: {
|
|
36
|
-
hookEventName: "PreToolUse",
|
|
37
|
-
permissionDecision: "deny",
|
|
38
|
-
permissionDecisionReason: $reason
|
|
39
|
-
}
|
|
40
|
-
}'
|
|
41
|
-
return 0
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
if command -v python3 >/dev/null 2>&1; then
|
|
45
|
-
python3 - "$reason" <<'PY'
|
|
46
|
-
import json
|
|
47
|
-
import sys
|
|
48
|
-
|
|
49
|
-
reason = sys.argv[1]
|
|
50
|
-
print(
|
|
51
|
-
json.dumps(
|
|
52
|
-
{
|
|
53
|
-
"hookSpecificOutput": {
|
|
54
|
-
"hookEventName": "PreToolUse",
|
|
55
|
-
"permissionDecision": "deny",
|
|
56
|
-
"permissionDecisionReason": reason,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
)
|
|
60
|
-
)
|
|
61
|
-
PY
|
|
62
|
-
return 0
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
local escaped=${reason//\\/\\\\}
|
|
66
|
-
escaped=${escaped//\"/\\\"}
|
|
67
|
-
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$escaped"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if command -v jq >/dev/null 2>&1; then
|
|
71
|
-
TOOL_NAME=$(jq -r '.tool_name // empty' <<<"$INPUT" 2>/dev/null || true)
|
|
72
|
-
COMMAND=$(jq -r '.tool_input.command // empty' <<<"$INPUT" 2>/dev/null || true)
|
|
73
|
-
elif command -v python3 >/dev/null 2>&1; then
|
|
74
|
-
TOOL_NAME=$(
|
|
75
|
-
python3 -c 'import json,sys; d=json.loads(sys.stdin.read() or "{}"); print(d.get("tool_name",""))' <<<"$INPUT" 2>/dev/null || true
|
|
76
|
-
)
|
|
77
|
-
COMMAND=$(
|
|
78
|
-
python3 -c 'import json,sys; d=json.loads(sys.stdin.read() or "{}"); ti=d.get("tool_input") or {}; print(ti.get("command",""))' <<<"$INPUT" 2>/dev/null || true
|
|
79
|
-
)
|
|
80
|
-
else
|
|
81
|
-
TOOL_NAME=""
|
|
82
|
-
COMMAND=""
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
# Only intercept Bash tool
|
|
86
|
-
if [[ "$TOOL_NAME" != "Bash" ]]; then
|
|
87
|
-
echo '{}'
|
|
88
|
-
exit 0
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
# If no command, allow
|
|
92
|
-
if [[ -z "$COMMAND" ]]; then
|
|
93
|
-
echo '{}'
|
|
94
|
-
exit 0
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Normalize command for pattern matching (lowercase for case-insensitive)
|
|
98
|
-
COMMAND_LOWER=$(echo "$COMMAND" | tr '[:upper:]' '[:lower:]')
|
|
99
|
-
|
|
100
|
-
# =============================================================================
|
|
101
|
-
# Dangerous Pattern Checks
|
|
102
|
-
# =============================================================================
|
|
103
|
-
|
|
104
|
-
# Check for rm -rf (recursive force delete)
|
|
105
|
-
# Matches: rm -rf, rm -fr, rm -r -f, rm -f -r, rm --recursive --force, and edge
|
|
106
|
-
# cases where flags touch the path (e.g., rm -rf0dir).
|
|
107
|
-
if echo "$COMMAND_LOWER" | grep -qE '(^|[[:space:];|&()])rm[[:space:]]'; then
|
|
108
|
-
# Long flags: --recursive and --force
|
|
109
|
-
if echo "$COMMAND_LOWER" | grep -qE -- '--recursive' && echo "$COMMAND_LOWER" | grep -qE -- '--force'; then
|
|
110
|
-
deny "Blocked: rm -rf is prohibited (recursive force delete can cause irreversible data loss)"
|
|
111
|
-
exit 0
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
# Combined short flags: -rf / -fr (including edge cases where the path touches flags)
|
|
115
|
-
if echo "$COMMAND_LOWER" | grep -qE '(^|[[:space:];|&()])rm[[:space:]].*-[^[:space:]]*r[^[:space:]]*f'; then
|
|
116
|
-
deny "Blocked: rm -rf is prohibited (recursive force delete can cause irreversible data loss)"
|
|
117
|
-
exit 0
|
|
118
|
-
fi
|
|
119
|
-
if echo "$COMMAND_LOWER" | grep -qE '(^|[[:space:];|&()])rm[[:space:]].*-[^[:space:]]*f[^[:space:]]*r'; then
|
|
120
|
-
deny "Blocked: rm -rf is prohibited (recursive force delete can cause irreversible data loss)"
|
|
121
|
-
exit 0
|
|
122
|
-
fi
|
|
123
|
-
|
|
124
|
-
# Separate short flags: -r -f / -f -r
|
|
125
|
-
if echo "$COMMAND_LOWER" | grep -qE '(^|[[:space:]])-r([[:space:]]|$)' && echo "$COMMAND_LOWER" | grep -qE '(^|[[:space:]])-f([[:space:]]|$)'; then
|
|
126
|
-
deny "Blocked: rm -rf is prohibited (recursive force delete can cause irreversible data loss)"
|
|
127
|
-
exit 0
|
|
128
|
-
fi
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
# Check for git push --force to main/master
|
|
132
|
-
# Matches: git push --force origin main, git push -f origin master
|
|
133
|
-
if echo "$COMMAND" | grep -qE 'git\s+push\s+.*(-f|--force).*\s+(origin|upstream)\s+(main|master)(\s|$)'; then
|
|
134
|
-
deny "Blocked: Force push to main/master is prohibited (can overwrite team work)"
|
|
135
|
-
exit 0
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
# Also check reverse order: git push origin main --force
|
|
139
|
-
if echo "$COMMAND" | grep -qE 'git\s+push\s+(origin|upstream)\s+(main|master)\s+(-f|--force)'; then
|
|
140
|
-
deny "Blocked: Force push to main/master is prohibited (can overwrite team work)"
|
|
141
|
-
exit 0
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
# Check for git reset --hard (without specific commit, or dangerous patterns)
|
|
145
|
-
# Block: git reset --hard, git reset --hard HEAD~, git reset --hard origin/
|
|
146
|
-
if echo "$COMMAND" | grep -qE 'git\s+reset\s+--hard(\s|$)'; then
|
|
147
|
-
deny "Blocked: git reset --hard is prohibited (can cause irreversible loss of uncommitted changes)"
|
|
148
|
-
exit 0
|
|
149
|
-
fi
|
|
150
|
-
|
|
151
|
-
# Check for dangerous chmod/chown on system directories
|
|
152
|
-
if echo "$COMMAND" | grep -qE '(chmod|chown)\s+(-R|--recursive)\s+.*\s+/($|\s)'; then
|
|
153
|
-
deny "Blocked: Recursive chmod/chown on / is prohibited (can break system permissions)"
|
|
154
|
-
exit 0
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
# Check for dd with of=/dev/
|
|
158
|
-
if echo "$COMMAND" | grep -qE 'dd\s+.*of=/dev/'; then
|
|
159
|
-
deny "Blocked: dd with of=/dev/* is prohibited (writing to raw devices can destroy data)"
|
|
160
|
-
exit 0
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
# Check for mkfs (format filesystem)
|
|
164
|
-
if echo "$COMMAND" | grep -qE 'mkfs'; then
|
|
165
|
-
deny "Blocked: mkfs is prohibited (formatting filesystems can destroy data)"
|
|
166
|
-
exit 0
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
# =============================================================================
|
|
170
|
-
# All checks passed - allow command
|
|
171
|
-
# =============================================================================
|
|
172
|
-
echo '{}'
|
|
173
|
-
exit 0
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Claude Code PreToolUse Hook: Block Sensitive File Access
|
|
4
|
-
Intercepts Read/Edit/Write tool calls and prevents access to sensitive files
|
|
5
|
-
like .env files, credentials, private keys, and secrets.
|
|
6
|
-
|
|
7
|
-
USAGE:
|
|
8
|
-
This hook runs automatically before Read/Edit/Write tool calls.
|
|
9
|
-
No manual invocation needed - Claude Code handles hook execution.
|
|
10
|
-
|
|
11
|
-
BLOCKED FILE PATTERNS:
|
|
12
|
-
- .env, .env.local, .env.production (environment variables)
|
|
13
|
-
- *credentials*, *secret* (credential/secret files)
|
|
14
|
-
- *.pem, *_(private|secret|rsa|dsa|ecdsa).key (private keys)
|
|
15
|
-
- *_rsa, *_dsa, *_ecdsa, *_ed25519 (SSH keys without extension)
|
|
16
|
-
- *.p12, *.pfx, *.keystore, *.jks (certificates/keystores)
|
|
17
|
-
- *.ppk (PuTTY keys)
|
|
18
|
-
|
|
19
|
-
ALLOWED FILE PATTERNS:
|
|
20
|
-
- *.pub (public keys - safe to read)
|
|
21
|
-
- license.key, api.key (generic .key files without private key indicators)
|
|
22
|
-
|
|
23
|
-
HOOK BEHAVIOR:
|
|
24
|
-
- Exit code 0: Allow tool execution (non-sensitive file)
|
|
25
|
-
- Exit code 0 + permissionDecision=deny: Block tool execution (sensitive file detected)
|
|
26
|
-
|
|
27
|
-
TESTING:
|
|
28
|
-
echo '{"tool_name": "Read", "tool_input": {"file_path": ".env"}}' | python3 block-secrets.py
|
|
29
|
-
# Expected: Exit code 0, JSON on stdout with permissionDecision=deny
|
|
30
|
-
|
|
31
|
-
PERFORMANCE:
|
|
32
|
-
Target: <100ms per invocation
|
|
33
|
-
Actual: ~27ms average (measured with 100 iterations)
|
|
34
|
-
"""
|
|
35
|
-
import json
|
|
36
|
-
import sys
|
|
37
|
-
import re
|
|
38
|
-
from pathlib import Path
|
|
39
|
-
|
|
40
|
-
# Sensitive file patterns (PRE-COMPILED for performance)
|
|
41
|
-
SENSITIVE_PATTERNS = [
|
|
42
|
-
re.compile(r"\.env.*", re.IGNORECASE), # .env, .env.local, .env.production, etc.
|
|
43
|
-
re.compile(
|
|
44
|
-
r".*credentials.*", re.IGNORECASE
|
|
45
|
-
), # credentials.json, aws-credentials, etc.
|
|
46
|
-
re.compile(r".*secret.*", re.IGNORECASE), # secrets.yaml, secret-key.txt, etc.
|
|
47
|
-
re.compile(r".*\.pem$", re.IGNORECASE), # Private key files
|
|
48
|
-
re.compile(
|
|
49
|
-
r".*_(private|secret|rsa|dsa|ecdsa)\.key$", re.IGNORECASE
|
|
50
|
-
), # Specific private key files only
|
|
51
|
-
re.compile(r".*_rsa$", re.IGNORECASE), # SSH keys without extension
|
|
52
|
-
re.compile(r".*_dsa$", re.IGNORECASE), # DSA keys
|
|
53
|
-
re.compile(r".*_ecdsa$", re.IGNORECASE), # ECDSA keys
|
|
54
|
-
re.compile(r".*_ed25519$", re.IGNORECASE), # Ed25519 keys
|
|
55
|
-
re.compile(r".*\.p12$", re.IGNORECASE), # PKCS#12 certificate files
|
|
56
|
-
re.compile(r".*\.pfx$", re.IGNORECASE), # PKCS#12 certificate files (Windows)
|
|
57
|
-
re.compile(r".*\.keystore$", re.IGNORECASE), # Java keystores
|
|
58
|
-
re.compile(r".*\.jks$", re.IGNORECASE), # Java keystores
|
|
59
|
-
re.compile(r".*\.ppk$", re.IGNORECASE), # PuTTY private keys
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
SAFE_PATH_PREFIXES = [
|
|
64
|
-
".claude/hooks/",
|
|
65
|
-
".claude/agents/",
|
|
66
|
-
".claude/commands/",
|
|
67
|
-
".claude/references/",
|
|
68
|
-
".claude/skills/",
|
|
69
|
-
"src/",
|
|
70
|
-
"tests/",
|
|
71
|
-
"docs/",
|
|
72
|
-
"scripts/",
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def is_sensitive_file(file_path: str) -> bool:
|
|
77
|
-
"""Check if file path matches any sensitive file pattern.
|
|
78
|
-
|
|
79
|
-
Checks ALL path components (not just filename) to catch patterns
|
|
80
|
-
in directory names or parent paths. Skips files in known safe
|
|
81
|
-
directories (hooks, agents, source code, tests, etc.)
|
|
82
|
-
"""
|
|
83
|
-
path_obj = Path(file_path)
|
|
84
|
-
|
|
85
|
-
# Normalize to relative path for prefix matching
|
|
86
|
-
try:
|
|
87
|
-
rel = str(path_obj.relative_to(Path.cwd()))
|
|
88
|
-
except ValueError:
|
|
89
|
-
rel = str(path_obj)
|
|
90
|
-
|
|
91
|
-
# Allow known safe directories
|
|
92
|
-
for prefix in SAFE_PATH_PREFIXES:
|
|
93
|
-
if rel.startswith(prefix):
|
|
94
|
-
return False
|
|
95
|
-
|
|
96
|
-
# Check each path component against all patterns
|
|
97
|
-
for part in path_obj.parts:
|
|
98
|
-
for pattern in SENSITIVE_PATTERNS:
|
|
99
|
-
if pattern.match(part):
|
|
100
|
-
return True
|
|
101
|
-
return False
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def block_access(file_path: str, tool_name: str):
|
|
105
|
-
"""Block tool execution and output error message."""
|
|
106
|
-
print(
|
|
107
|
-
json.dumps(
|
|
108
|
-
{
|
|
109
|
-
"hookSpecificOutput": {
|
|
110
|
-
"hookEventName": "PreToolUse",
|
|
111
|
-
"permissionDecision": "deny",
|
|
112
|
-
"permissionDecisionReason": (
|
|
113
|
-
f"Blocked: Access to sensitive file '{file_path}' is prohibited "
|
|
114
|
-
f"(tool={tool_name})."
|
|
115
|
-
),
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
)
|
|
119
|
-
)
|
|
120
|
-
sys.exit(0)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def main():
|
|
124
|
-
"""Main hook execution logic."""
|
|
125
|
-
# Load input from stdin
|
|
126
|
-
try:
|
|
127
|
-
input_data = json.load(sys.stdin)
|
|
128
|
-
except json.JSONDecodeError as e:
|
|
129
|
-
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
|
130
|
-
sys.exit(1)
|
|
131
|
-
|
|
132
|
-
tool_name = input_data.get("tool_name", "")
|
|
133
|
-
tool_input = input_data.get("tool_input", {})
|
|
134
|
-
|
|
135
|
-
# Only intercept Read, Edit, Write tools
|
|
136
|
-
if tool_name not in ["Read", "Edit", "Write"]:
|
|
137
|
-
print("{}")
|
|
138
|
-
sys.exit(0) # Allow other tools
|
|
139
|
-
|
|
140
|
-
# Extract file_path from tool_input
|
|
141
|
-
file_path = tool_input.get("file_path", "")
|
|
142
|
-
|
|
143
|
-
# If no file_path, allow (shouldn't happen for Read/Edit/Write, but be safe)
|
|
144
|
-
if not file_path:
|
|
145
|
-
print("{}")
|
|
146
|
-
sys.exit(0)
|
|
147
|
-
|
|
148
|
-
# Check if file is sensitive
|
|
149
|
-
if is_sensitive_file(file_path):
|
|
150
|
-
block_access(file_path, tool_name)
|
|
151
|
-
|
|
152
|
-
# Allow non-sensitive files
|
|
153
|
-
print("{}")
|
|
154
|
-
sys.exit(0)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if __name__ == "__main__":
|
|
158
|
-
main()
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Claude Code Prompt Improver Hook
|
|
4
|
-
Intercepts user prompts and evaluates if they need enrichment before execution.
|
|
5
|
-
Uses main session context for intelligent, non-pedantic evaluation.
|
|
6
|
-
"""
|
|
7
|
-
import json
|
|
8
|
-
import sys
|
|
9
|
-
|
|
10
|
-
# Load input from stdin
|
|
11
|
-
try:
|
|
12
|
-
input_data = json.load(sys.stdin)
|
|
13
|
-
except json.JSONDecodeError as e:
|
|
14
|
-
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
|
15
|
-
sys.exit(1)
|
|
16
|
-
|
|
17
|
-
prompt = input_data.get("prompt", "")
|
|
18
|
-
|
|
19
|
-
# Escape quotes in prompt for safe embedding in wrapped instructions
|
|
20
|
-
escaped_prompt = prompt.replace("\\", "\\\\").replace('"', '\\"')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def output_json(text):
|
|
24
|
-
"""Output text in UserPromptSubmit JSON format"""
|
|
25
|
-
output = {
|
|
26
|
-
"hookSpecificOutput": {
|
|
27
|
-
"hookEventName": "UserPromptSubmit",
|
|
28
|
-
"additionalContext": text,
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
print(json.dumps(output))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Check for bypass conditions
|
|
35
|
-
# 1. Explicit bypass with * prefix
|
|
36
|
-
# 2. Slash commands (built-in or custom)
|
|
37
|
-
# 3. Memorize feature (# prefix)
|
|
38
|
-
if prompt.startswith("*") or prompt.startswith("/") or prompt.startswith("#"):
|
|
39
|
-
# User bypassed improvement - don't add evaluation wrapper
|
|
40
|
-
# Output empty JSON to signal success without modification
|
|
41
|
-
print("{}")
|
|
42
|
-
sys.exit(0)
|
|
43
|
-
|
|
44
|
-
# Build the improvement wrapper
|
|
45
|
-
wrapped_prompt = f"""PROMPT EVALUATION
|
|
46
|
-
|
|
47
|
-
Original user request: "{escaped_prompt}"
|
|
48
|
-
|
|
49
|
-
EVALUATE: Is this prompt clear enough to execute, or does it need enrichment?
|
|
50
|
-
|
|
51
|
-
PROCEED IMMEDIATELY if:
|
|
52
|
-
- Detailed/specific OR you have sufficient context OR can infer intent
|
|
53
|
-
|
|
54
|
-
ONLY ASK if genuinely vague (e.g., "fix the bug" with no context):
|
|
55
|
-
- CRITICAL (NON-NEGOTIABLE) RULES:
|
|
56
|
-
- Trust user intent by default. Check conversation history before doing research.
|
|
57
|
-
- Do not rely on base knowledge.
|
|
58
|
-
- Never skip Phase 1. Research before asking.
|
|
59
|
-
- Don't announce evaluation - just proceed or ask.
|
|
60
|
-
|
|
61
|
-
- PHASE 1 - RESEARCH (DO NOT SKIP):
|
|
62
|
-
1. Preface with brief note: "Prompt Improver Hook is seeking clarification because [specific reason: ambiguous scope/missing context/unclear requirements/etc]"
|
|
63
|
-
2. Create research plan with TodoWrite: Ask yourself "What do I need to research to clarify this vague request?" Research WHAT NEEDS CLARIFICATION, not just the project. Use available tools: Task/Explore for codebase, WebSearch for online research (current info, common approaches, best practices, typical architectures), Read/Grep as needed
|
|
64
|
-
3. Execute research
|
|
65
|
-
4. Use research findings (not your training) to formulate grounded questions with specific options
|
|
66
|
-
5. Mark completed
|
|
67
|
-
|
|
68
|
-
- PHASE 2 - ASK (ONLY AFTER PHASE 1):
|
|
69
|
-
1. Use AskUserQuestion tool with max 1-6 questions offering specific options from your research
|
|
70
|
-
2. Use the answers to execute the original user request
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
output_json(wrapped_prompt)
|
|
74
|
-
sys.exit(0)
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Post-Edit Reminder - PostToolUse Hook
|
|
4
|
-
|
|
5
|
-
Lightweight reminder after Edit/Write operations to run tests.
|
|
6
|
-
Only triggers when there's an active MAP workflow.
|
|
7
|
-
|
|
8
|
-
Trigger: Edit|Write
|
|
9
|
-
Exit codes: Always 0 (non-blocking)
|
|
10
|
-
Output: ~80 char reminder via hookSpecificOutput.additionalContext
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import json
|
|
14
|
-
import os
|
|
15
|
-
import re
|
|
16
|
-
import subprocess
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def sanitize_branch_name(branch: str) -> str:
|
|
22
|
-
"""Sanitize branch name for safe filesystem paths."""
|
|
23
|
-
sanitized = branch.replace("/", "-")
|
|
24
|
-
sanitized = re.sub(r"[^a-zA-Z0-9_.-]", "-", sanitized)
|
|
25
|
-
sanitized = re.sub(r"-+", "-", sanitized).strip("-")
|
|
26
|
-
if ".." in sanitized or sanitized.startswith("."):
|
|
27
|
-
return "default"
|
|
28
|
-
return sanitized or "default"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_branch_name() -> str:
|
|
32
|
-
"""Get current git branch name."""
|
|
33
|
-
try:
|
|
34
|
-
result = subprocess.run(
|
|
35
|
-
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
36
|
-
capture_output=True,
|
|
37
|
-
text=True,
|
|
38
|
-
timeout=1,
|
|
39
|
-
)
|
|
40
|
-
if result.returncode == 0:
|
|
41
|
-
return sanitize_branch_name(result.stdout.strip())
|
|
42
|
-
except Exception:
|
|
43
|
-
pass
|
|
44
|
-
return "default"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def has_active_workflow(branch: str) -> bool:
|
|
48
|
-
"""Check if there's an active MAP workflow."""
|
|
49
|
-
project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
50
|
-
state_file = project_dir / ".map" / branch / "workflow_state.json"
|
|
51
|
-
return state_file.exists()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def main() -> None:
|
|
55
|
-
try:
|
|
56
|
-
input_data = json.load(sys.stdin)
|
|
57
|
-
except json.JSONDecodeError:
|
|
58
|
-
print("{}")
|
|
59
|
-
sys.exit(0)
|
|
60
|
-
|
|
61
|
-
tool_name = input_data.get("tool_name", "")
|
|
62
|
-
|
|
63
|
-
# Only for Edit/Write
|
|
64
|
-
if tool_name not in ("Edit", "Write", "MultiEdit"):
|
|
65
|
-
print("{}")
|
|
66
|
-
sys.exit(0)
|
|
67
|
-
|
|
68
|
-
# Only when MAP workflow is active
|
|
69
|
-
branch = get_branch_name()
|
|
70
|
-
if not has_active_workflow(branch):
|
|
71
|
-
print("{}")
|
|
72
|
-
sys.exit(0)
|
|
73
|
-
|
|
74
|
-
# Inject lightweight reminder
|
|
75
|
-
reminder = "[MAP] Code changed. Run tests before committing!"
|
|
76
|
-
output = {
|
|
77
|
-
"hookSpecificOutput": {
|
|
78
|
-
"hookEventName": "PostToolUse",
|
|
79
|
-
"additionalContext": reminder,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
print(json.dumps(output))
|
|
83
|
-
sys.exit(0)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if __name__ == "__main__":
|
|
87
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/agents/documentation-reviewer.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/ralph-iteration-logger.py
RENAMED
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/hooks/workflow-context-injector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/scripts/map_orchestrator.py
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/scripts/map_step_runner.py
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/analyze.sh
RENAMED
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/go.sh
RENAMED
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/map/static-analysis/handlers/rust.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/bash-guidelines.md
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/decomposition-examples.md
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/escalation-matrix.md
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/mcp-usage-examples.md
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/step-state-schema.md
RENAMED
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/references/workflow-state-schema.md
RENAMED
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-cli-reference/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapify_cli-3.3.0 → mapify_cli-3.4.0}/src/mapify_cli/templates/skills/map-workflows-guide/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|