history-graph-protocol 0.2.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.
Files changed (142) hide show
  1. history_graph_protocol-0.2.0/.claude/hooks/post_bash_hgp.py +78 -0
  2. history_graph_protocol-0.2.0/.claude/hooks/pre_bash_hgp.py +138 -0
  3. history_graph_protocol-0.2.0/.claude/hooks/pre_tool_use_hgp.py +46 -0
  4. history_graph_protocol-0.2.0/.claude/settings.json +35 -0
  5. history_graph_protocol-0.2.0/.claude/settings.local.json +14 -0
  6. history_graph_protocol-0.2.0/.gemini/hooks/post_bash_hgp.py +82 -0
  7. history_graph_protocol-0.2.0/.gemini/hooks/pre_bash_hgp.py +138 -0
  8. history_graph_protocol-0.2.0/.gemini/hooks/pre_tool_use_hgp.py +48 -0
  9. history_graph_protocol-0.2.0/.gemini/settings.json +38 -0
  10. history_graph_protocol-0.2.0/.gitignore +13 -0
  11. history_graph_protocol-0.2.0/.python-version +1 -0
  12. history_graph_protocol-0.2.0/CLAUDE.md +78 -0
  13. history_graph_protocol-0.2.0/HGP_Master_Plan.md +66 -0
  14. history_graph_protocol-0.2.0/HGP_Technical_Design.md +993 -0
  15. history_graph_protocol-0.2.0/LICENSE +21 -0
  16. history_graph_protocol-0.2.0/PKG-INFO +228 -0
  17. history_graph_protocol-0.2.0/README.md +201 -0
  18. history_graph_protocol-0.2.0/conversation_export.md +1316 -0
  19. history_graph_protocol-0.2.0/docs/CONTRIBUTING.md +178 -0
  20. history_graph_protocol-0.2.0/docs/architecture.md +547 -0
  21. history_graph_protocol-0.2.0/docs/plans/2026-03-02-hgp-v1-implementation.md +2018 -0
  22. history_graph_protocol-0.2.0/docs/plans/2026-03-06-v2-memory-tier.md +841 -0
  23. history_graph_protocol-0.2.0/docs/plans/2026-03-19-v3-evidence-trail.md +245 -0
  24. history_graph_protocol-0.2.0/docs/plans/2026-03-22-v3-evidence-trail.md +245 -0
  25. history_graph_protocol-0.2.0/docs/plans/2026-03-25-documentation.md +512 -0
  26. history_graph_protocol-0.2.0/docs/plans/2026-04-01-v4-file-tracking.md +323 -0
  27. history_graph_protocol-0.2.0/docs/plans/2026-04-02-120316-v4-file-tracking-followup-remediation.md +220 -0
  28. history_graph_protocol-0.2.0/docs/plans/2026-04-02-122419-v4-file-tracking-second-followup-remediation.md +162 -0
  29. history_graph_protocol-0.2.0/docs/plans/2026-04-02-125505-v4-file-tracking-third-followup-remediation.md +177 -0
  30. history_graph_protocol-0.2.0/docs/plans/2026-04-02-170041-v4-file-tracking-fourth-followup-remediation.md +178 -0
  31. history_graph_protocol-0.2.0/docs/plans/2026-04-02-171348-v4-file-tracking-fifth-followup-remediation.md +118 -0
  32. history_graph_protocol-0.2.0/docs/plans/2026-04-02-171348-v5-reconciler-and-bash-hooks-remediation.md +166 -0
  33. history_graph_protocol-0.2.0/docs/plans/2026-04-02-v4-file-tracking-remediation.md +270 -0
  34. history_graph_protocol-0.2.0/docs/plans/2026-04-03-144423-v5-reconciler-and-bash-hooks-remediation-plan.md +170 -0
  35. history_graph_protocol-0.2.0/docs/plans/2026-04-03-145145-v5-three-commit-remediation-plan.md +159 -0
  36. history_graph_protocol-0.2.0/docs/plans/2026-04-03-151825-8aaaa60-followup-plan.md +135 -0
  37. history_graph_protocol-0.2.0/docs/plans/2026-04-03-154847-ebb5960-followup-plan.md +88 -0
  38. history_graph_protocol-0.2.0/docs/plans/2026-04-03-161740-overall-codebase-remediation-plan.md +189 -0
  39. history_graph_protocol-0.2.0/docs/plans/2026-04-03-163319-overall-codebase-remediation-plan-subagents.md +206 -0
  40. history_graph_protocol-0.2.0/docs/plans/2026-04-03-165841-00bc020-followup-plan.md +70 -0
  41. history_graph_protocol-0.2.0/docs/plans/2026-04-03-170820-9d9fe96-followup-plan.md +46 -0
  42. history_graph_protocol-0.2.0/docs/plans/2026-04-03-full-audit-remediation-plan.md +692 -0
  43. history_graph_protocol-0.2.0/docs/plans/2026-04-03-v5-reconciler-and-bash-hooks.md +322 -0
  44. history_graph_protocol-0.2.0/docs/plans/2026-04-04-001348-0ab4763-followup-plan.md +111 -0
  45. history_graph_protocol-0.2.0/docs/plans/2026-04-04-002912-4e206ba-followup-plan.md +66 -0
  46. history_graph_protocol-0.2.0/docs/plans/2026-04-04-003533-1a7cfac-followup-plan.md +38 -0
  47. history_graph_protocol-0.2.0/docs/plans/2026-04-04-005926-d00a855-followup-plan.md +197 -0
  48. history_graph_protocol-0.2.0/docs/plans/2026-04-04-011012-0ab4763-d00a855-followup-plan.md +180 -0
  49. history_graph_protocol-0.2.0/docs/plans/2026-04-04-011953-d276ec9-followup-plan.md +95 -0
  50. history_graph_protocol-0.2.0/docs/plans/2026-04-05-230725-5f86cd1-followup-plan.md +38 -0
  51. history_graph_protocol-0.2.0/docs/plans/2026-04-05-232532-59f4967-followup-plan.md +65 -0
  52. history_graph_protocol-0.2.0/docs/plans/2026-04-05-233553-67da2f4-followup-plan.md +48 -0
  53. history_graph_protocol-0.2.0/docs/plans/2026-04-05-234435-634bed7-followup-plan.md +52 -0
  54. history_graph_protocol-0.2.0/docs/plans/2026-04-05-235109-33091d1-followup-plan.md +39 -0
  55. history_graph_protocol-0.2.0/docs/plans/2026-04-07-010026-522f901-followup-plan.md +80 -0
  56. history_graph_protocol-0.2.0/docs/plans/2026-04-07-011841-2031368-followup-plan.md +63 -0
  57. history_graph_protocol-0.2.0/docs/plans/2026-04-07-112432-4cc9755-followup-plan.md +41 -0
  58. history_graph_protocol-0.2.0/docs/plans/2026-04-07-120244-264c5ae-followup-plan.md +48 -0
  59. history_graph_protocol-0.2.0/docs/plans/2026-04-07-130954-e470064-followup-plan.md +40 -0
  60. history_graph_protocol-0.2.0/docs/plans/2026-04-07-140903-43d5ec6-followup-plan.md +69 -0
  61. history_graph_protocol-0.2.0/docs/plans/2026-04-07-153656-0a96afc-followup-plan.md +63 -0
  62. history_graph_protocol-0.2.0/docs/plans/2026-04-07-155219-b0e75ce-followup-plan.md +51 -0
  63. history_graph_protocol-0.2.0/docs/plans/2026-04-07-hgp-phase2-worktree-isolation-options.md +202 -0
  64. history_graph_protocol-0.2.0/docs/plans/2026-04-07-hgp-storage-topology-recommendation.md +362 -0
  65. history_graph_protocol-0.2.0/docs/plans/2026-04-07-task42-repo-local-storage-plan.md +253 -0
  66. history_graph_protocol-0.2.0/docs/reviews/2026-03-19-v2-main-merge-review.txt +72 -0
  67. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-120316-v4-file-tracking-followup-review.md +147 -0
  68. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-122419-v4-file-tracking-second-followup-review.md +133 -0
  69. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-125505-v4-file-tracking-third-followup-review.md +146 -0
  70. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-170041-v4-file-tracking-fourth-followup-review.md +147 -0
  71. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-171348-v4-file-tracking-fifth-followup-review.md +86 -0
  72. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-171348-v5-reconciler-and-bash-hooks-review.md +153 -0
  73. history_graph_protocol-0.2.0/docs/reviews/2026-04-02-v4-file-tracking-review.md +108 -0
  74. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-144423-v5-reconciler-and-bash-hooks-remediation-review.md +163 -0
  75. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-145145-v5-three-commit-review.md +151 -0
  76. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-151825-8aaaa60-review.md +130 -0
  77. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-154847-ebb5960-review.md +100 -0
  78. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-161740-overall-codebase-review.md +166 -0
  79. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-163319-overall-codebase-review-subagents.md +201 -0
  80. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-165841-00bc020-review.md +122 -0
  81. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-170820-9d9fe96-review.md +65 -0
  82. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-audit-architecture.md +228 -0
  83. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-audit-code-quality.md +289 -0
  84. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-audit-security.md +200 -0
  85. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-audit-silent-failures.md +467 -0
  86. history_graph_protocol-0.2.0/docs/reviews/2026-04-03-audit-test-coverage.md +485 -0
  87. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-001348-0ab4763-review.md +143 -0
  88. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-002912-4e206ba-review.md +97 -0
  89. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-003533-1a7cfac-review.md +69 -0
  90. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-005926-d00a855-review.md +222 -0
  91. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-011012-0ab4763-d00a855-review.md +156 -0
  92. history_graph_protocol-0.2.0/docs/reviews/2026-04-04-011953-d276ec9-review.md +129 -0
  93. history_graph_protocol-0.2.0/docs/reviews/2026-04-05-230725-5f86cd1-review.md +68 -0
  94. history_graph_protocol-0.2.0/docs/reviews/2026-04-05-232532-59f4967-review.md +75 -0
  95. history_graph_protocol-0.2.0/docs/reviews/2026-04-05-233553-67da2f4-review.md +63 -0
  96. history_graph_protocol-0.2.0/docs/reviews/2026-04-05-234435-634bed7-review.md +93 -0
  97. history_graph_protocol-0.2.0/docs/reviews/2026-04-05-235109-33091d1-review.md +64 -0
  98. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-010026-522f901-review.md +102 -0
  99. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-011841-2031368-review.md +91 -0
  100. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-112432-4cc9755-review.md +67 -0
  101. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-120244-264c5ae-review.md +59 -0
  102. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-130954-e470064-review.md +61 -0
  103. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-140903-43d5ec6-review.md +63 -0
  104. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-153656-0a96afc-review.md +112 -0
  105. history_graph_protocol-0.2.0/docs/reviews/2026-04-07-155219-b0e75ce-review.md +80 -0
  106. history_graph_protocol-0.2.0/docs/tools-reference.md +1074 -0
  107. history_graph_protocol-0.2.0/docs/usage-patterns.md +612 -0
  108. history_graph_protocol-0.2.0/pyproject.toml +59 -0
  109. history_graph_protocol-0.2.0/src/hgp/__init__.py +3 -0
  110. history_graph_protocol-0.2.0/src/hgp/cas.py +103 -0
  111. history_graph_protocol-0.2.0/src/hgp/dag.py +153 -0
  112. history_graph_protocol-0.2.0/src/hgp/db.py +618 -0
  113. history_graph_protocol-0.2.0/src/hgp/errors.py +43 -0
  114. history_graph_protocol-0.2.0/src/hgp/hooks/__init__.py +0 -0
  115. history_graph_protocol-0.2.0/src/hgp/hooks/claude/__init__.py +0 -0
  116. history_graph_protocol-0.2.0/src/hgp/hooks/claude/post_bash_hgp.py +78 -0
  117. history_graph_protocol-0.2.0/src/hgp/hooks/claude/pre_bash_hgp.py +138 -0
  118. history_graph_protocol-0.2.0/src/hgp/hooks/claude/pre_tool_use_hgp.py +46 -0
  119. history_graph_protocol-0.2.0/src/hgp/hooks/gemini/__init__.py +0 -0
  120. history_graph_protocol-0.2.0/src/hgp/hooks/gemini/post_bash_hgp.py +82 -0
  121. history_graph_protocol-0.2.0/src/hgp/hooks/gemini/pre_bash_hgp.py +138 -0
  122. history_graph_protocol-0.2.0/src/hgp/hooks/gemini/pre_tool_use_hgp.py +48 -0
  123. history_graph_protocol-0.2.0/src/hgp/lease.py +133 -0
  124. history_graph_protocol-0.2.0/src/hgp/models.py +153 -0
  125. history_graph_protocol-0.2.0/src/hgp/project.py +71 -0
  126. history_graph_protocol-0.2.0/src/hgp/reconciler.py +174 -0
  127. history_graph_protocol-0.2.0/src/hgp/server.py +1085 -0
  128. history_graph_protocol-0.2.0/tests/__init__.py +0 -0
  129. history_graph_protocol-0.2.0/tests/conftest.py +29 -0
  130. history_graph_protocol-0.2.0/tests/test_bash_hooks.py +279 -0
  131. history_graph_protocol-0.2.0/tests/test_cas.py +131 -0
  132. history_graph_protocol-0.2.0/tests/test_dag.py +208 -0
  133. history_graph_protocol-0.2.0/tests/test_db.py +933 -0
  134. history_graph_protocol-0.2.0/tests/test_file_ops.py +841 -0
  135. history_graph_protocol-0.2.0/tests/test_install_hooks.py +90 -0
  136. history_graph_protocol-0.2.0/tests/test_integration.py +167 -0
  137. history_graph_protocol-0.2.0/tests/test_lease.py +67 -0
  138. history_graph_protocol-0.2.0/tests/test_models.py +129 -0
  139. history_graph_protocol-0.2.0/tests/test_project.py +39 -0
  140. history_graph_protocol-0.2.0/tests/test_reconciler.py +541 -0
  141. history_graph_protocol-0.2.0/tests/test_server_tools.py +1317 -0
  142. history_graph_protocol-0.2.0/uv.lock +806 -0
@@ -0,0 +1,78 @@
1
+ """PostToolUse hook for Bash: detect and report actual file changes after Bash commands.
2
+
3
+ Only runs git status when the Pre-Bash hook wrote a marker file indicating a
4
+ potentially mutating command was about to execute. This avoids the overhead of
5
+ git status on every read-only Bash call.
6
+
7
+ Known limitation: .gitignore'd files won't appear in the report.
8
+
9
+ Marker file: /tmp/.hgp_bash_mutating_<ppid>
10
+ """
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import sys
15
+
16
+ _TIMEOUT_SECS = 2
17
+
18
+
19
+ def _marker_path() -> str:
20
+ return f"/tmp/.hgp_bash_mutating_{os.getppid()}"
21
+
22
+
23
+ def _consume_marker() -> bool:
24
+ """Return True and remove marker if it exists, False otherwise."""
25
+ path = _marker_path()
26
+ try:
27
+ os.unlink(path)
28
+ return True
29
+ except FileNotFoundError:
30
+ return False
31
+
32
+
33
+ def _git_changed_files(cwd: str) -> list[str]:
34
+ """Run git status --porcelain and return list of changed file entries."""
35
+ try:
36
+ result = subprocess.run(
37
+ ["git", "status", "--porcelain"],
38
+ cwd=cwd,
39
+ capture_output=True,
40
+ text=True,
41
+ timeout=_TIMEOUT_SECS,
42
+ )
43
+ if result.returncode != 0:
44
+ return []
45
+ lines = [l for l in result.stdout.splitlines() if l.strip()]
46
+ return lines
47
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
48
+ return []
49
+
50
+
51
+ def main() -> None:
52
+ try:
53
+ event = json.loads(sys.stdin.read())
54
+ except (json.JSONDecodeError, ValueError):
55
+ sys.exit(0)
56
+
57
+ if event.get("tool_name") != "Bash":
58
+ sys.exit(0)
59
+
60
+ if not _consume_marker():
61
+ # No marker → Pre hook didn't flag this as mutating; skip git status
62
+ sys.exit(0)
63
+
64
+ cwd = os.getcwd()
65
+ changed = _git_changed_files(cwd)
66
+ if not changed:
67
+ sys.exit(0)
68
+
69
+ lines_str = "\n ".join(changed)
70
+ print(
71
+ f"[HGP] Bash command changed tracked files (use hgp_* tools for history):\n {lines_str}",
72
+ file=sys.stderr,
73
+ )
74
+ sys.exit(0)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,138 @@
1
+ """PreToolUse hook for Bash: warn when shell commands may mutate files outside HGP.
2
+
3
+ This hook is non-blocking (always exit 0). It prints a warning to stderr when
4
+ it detects shell patterns that typically write or delete files, reminding the
5
+ agent to use hgp_* tools for tracked file operations.
6
+
7
+ When a mutating pattern is detected, a marker file is written to /tmp so the
8
+ Post-Bash hook can run 'git status' to report actual changes.
9
+
10
+ Marker file: /tmp/.hgp_bash_mutating_<ppid>
11
+ """
12
+ import json
13
+ import os
14
+ import re
15
+ import sys
16
+
17
+ # ── Read-only command prefixes — skip pattern matching for these ──────────────
18
+ _READONLY_PREFIXES = (
19
+ "git log",
20
+ "git status",
21
+ "git diff",
22
+ "git show",
23
+ "git branch",
24
+ "git tag",
25
+ "git remote",
26
+ "git fetch",
27
+ "git ls",
28
+ "git stash list",
29
+ "ls ",
30
+ "ls\t",
31
+ "head ",
32
+ "tail ",
33
+ "grep ",
34
+ "rg ",
35
+ "find ",
36
+ "wc ",
37
+ "diff ",
38
+ "less ",
39
+ "more ",
40
+ "file ",
41
+ "stat ",
42
+ "pwd",
43
+ "date",
44
+ "which ",
45
+ "type ",
46
+ "uname",
47
+ )
48
+
49
+ # ── Mutating patterns (regex) ─────────────────────────────────────────────────
50
+ _HIGH_PATTERNS = [
51
+ re.compile(r"\bcp\b"),
52
+ re.compile(r"\bmv\b"),
53
+ re.compile(r"\brm\b"),
54
+ re.compile(r"\btee\b"),
55
+ re.compile(r"\btouch\b"),
56
+ re.compile(r"\binstall\b"),
57
+ re.compile(r"\bmkdir\b"),
58
+ re.compile(r"\brmdir\b"),
59
+ re.compile(r"\bchmod\b"),
60
+ re.compile(r"\bchown\b"),
61
+ re.compile(r"\bln\b"),
62
+ re.compile(r"\btruncate\b"),
63
+ # git commands that rewrite working-tree files
64
+ re.compile(r"\bgit\s+checkout\b"),
65
+ re.compile(r"\bgit\s+restore\b"),
66
+ re.compile(r"\bgit\s+switch\b"),
67
+ re.compile(r"\bgit\s+apply\b"),
68
+ re.compile(r"\bgit\s+revert\b"),
69
+ re.compile(r"\bgit\s+merge\b"),
70
+ re.compile(r"\bgit\s+rebase\b"),
71
+ re.compile(r"\bgit\s+reset\b"),
72
+ # patch tools
73
+ re.compile(r"\bpatch\b"),
74
+ ]
75
+
76
+ _MEDIUM_PATTERNS = [
77
+ re.compile(r"(?<![|&])\s*>(?!>)"), # stdout redirect (not >>)
78
+ re.compile(r">>"), # append redirect
79
+ re.compile(r"\bsed\s+-i\b"),
80
+ re.compile(r"\bdd\b"),
81
+ re.compile(r"\bawk\b.*>"), # awk with redirect
82
+ ]
83
+
84
+
85
+ def _is_readonly(command: str) -> bool:
86
+ stripped = command.lstrip()
87
+ return any(stripped.startswith(p) for p in _READONLY_PREFIXES)
88
+
89
+
90
+ def _detect_mutating(command: str) -> str | None:
91
+ """Return the first matched pattern description, or None if command looks safe."""
92
+ for pat in _HIGH_PATTERNS:
93
+ if pat.search(command):
94
+ return pat.pattern
95
+ for pat in _MEDIUM_PATTERNS:
96
+ if pat.search(command):
97
+ return pat.pattern
98
+ return None
99
+
100
+
101
+ def _marker_path() -> str:
102
+ return f"/tmp/.hgp_bash_mutating_{os.getppid()}"
103
+
104
+
105
+ def main() -> None:
106
+ try:
107
+ event = json.loads(sys.stdin.read())
108
+ except (json.JSONDecodeError, ValueError):
109
+ sys.exit(0)
110
+
111
+ if event.get("tool_name") != "Bash":
112
+ sys.exit(0)
113
+
114
+ command: str = event.get("tool_input", {}).get("command", "")
115
+ if not command or _is_readonly(command):
116
+ sys.exit(0)
117
+
118
+ matched = _detect_mutating(command)
119
+ if matched is None:
120
+ sys.exit(0)
121
+
122
+ # Write marker so Post-Bash hook knows to run git status
123
+ try:
124
+ open(_marker_path(), "w").close()
125
+ except OSError:
126
+ pass # /tmp not writable — skip gating, hook still warns
127
+
128
+ print(
129
+ f"[HGP] Bash command may mutate files (matched: {matched!r}). "
130
+ "If this writes or deletes tracked files, prefer hgp_* tools so the "
131
+ "operation is recorded in HGP history.",
132
+ file=sys.stderr,
133
+ )
134
+ sys.exit(0)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,46 @@
1
+ """PreToolUse hook: warn when native Write/Edit is used instead of hgp_* tools.
2
+
3
+ Exit 0 = allow the tool call (non-blocking by default).
4
+ Print to stderr = message shown to the agent as a warning.
5
+ Set HGP_HOOK_BLOCK=1 to make the hook reject native file tool calls.
6
+ """
7
+ import json
8
+ import os
9
+ import sys
10
+
11
+ HGP_TOOLS = {
12
+ "Write": "hgp_write_file",
13
+ "Edit": "hgp_edit_file",
14
+ "MultiEdit": "hgp_edit_file",
15
+ }
16
+
17
+ BLOCK_MODE = os.environ.get("HGP_HOOK_BLOCK", "0") == "1"
18
+
19
+
20
+ def main() -> None:
21
+ try:
22
+ event = json.loads(sys.stdin.read())
23
+ except json.JSONDecodeError:
24
+ sys.exit(0)
25
+
26
+ tool_name = event.get("tool_name", "")
27
+ if tool_name not in HGP_TOOLS:
28
+ sys.exit(0)
29
+
30
+ hgp_equiv = HGP_TOOLS[tool_name]
31
+ msg = (
32
+ f"[HGP] Native `{tool_name}` detected. "
33
+ f"Use `{hgp_equiv}` instead to record this file operation in HGP history. "
34
+ f"Set HGP_HOOK_BLOCK=1 to enforce this as an error."
35
+ )
36
+ print(msg, file=sys.stderr)
37
+
38
+ if BLOCK_MODE:
39
+ # Exit 2 = block; Claude Code reads stderr (already printed above), ignores stdout
40
+ sys.exit(2)
41
+
42
+ sys.exit(0)
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,35 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Write|Edit|MultiEdit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "python3 .claude/hooks/pre_tool_use_hgp.py"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "matcher": "Bash",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "python3 .claude/hooks/pre_bash_hgp.py"
19
+ }
20
+ ]
21
+ }
22
+ ],
23
+ "PostToolUse": [
24
+ {
25
+ "matcher": "Bash",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "python3 .claude/hooks/post_bash_hgp.py"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run:*)",
5
+ "Bash(git:*)",
6
+ "Bash(python -m pytest tests/test_file_ops.py tests/test_project.py tests/test_db.py -v --tb=short)",
7
+ "WebSearch",
8
+ "WebFetch(domain:geminicli.com)",
9
+ "WebFetch(domain:github.com)",
10
+ "mcp__plugin_context7_context7__resolve-library-id",
11
+ "mcp__plugin_context7_context7__query-docs"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,82 @@
1
+ """AfterTool hook for Gemini CLI: detect and report actual file changes after shell commands.
2
+
3
+ Only runs git status when the BeforeTool hook wrote a marker file indicating a
4
+ potentially mutating command was about to execute. This avoids the overhead of
5
+ git status on every read-only shell call.
6
+
7
+ Gemini CLI protocol (always exit 0):
8
+ Report: stdout JSON {"systemMessage": "..."}
9
+ Pass-through: no stdout output
10
+
11
+ Known limitation: .gitignore'd files won't appear in the report.
12
+
13
+ Marker file: /tmp/.hgp_bash_mutating_<ppid>
14
+ """
15
+ import json
16
+ import os
17
+ import subprocess
18
+ import sys
19
+
20
+ _TIMEOUT_SECS = 2
21
+
22
+
23
+ def _marker_path() -> str:
24
+ return f"/tmp/.hgp_bash_mutating_{os.getppid()}"
25
+
26
+
27
+ def _consume_marker() -> bool:
28
+ """Return True and remove marker if it exists, False otherwise."""
29
+ path = _marker_path()
30
+ try:
31
+ os.unlink(path)
32
+ return True
33
+ except FileNotFoundError:
34
+ return False
35
+
36
+
37
+ def _git_changed_files(cwd: str) -> list[str]:
38
+ """Run git status --porcelain and return list of changed file entries."""
39
+ try:
40
+ result = subprocess.run(
41
+ ["git", "status", "--porcelain"],
42
+ cwd=cwd,
43
+ capture_output=True,
44
+ text=True,
45
+ timeout=_TIMEOUT_SECS,
46
+ )
47
+ if result.returncode != 0:
48
+ return []
49
+ lines = [l for l in result.stdout.splitlines() if l.strip()]
50
+ return lines
51
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
52
+ return []
53
+
54
+
55
+ def main() -> None:
56
+ try:
57
+ event = json.loads(sys.stdin.read())
58
+ except (json.JSONDecodeError, ValueError):
59
+ sys.exit(0)
60
+
61
+ if event.get("tool_name") != "shell":
62
+ sys.exit(0)
63
+
64
+ if not _consume_marker():
65
+ # No marker → BeforeTool hook didn't flag this as mutating; skip git status
66
+ sys.exit(0)
67
+
68
+ cwd = os.getcwd()
69
+ changed = _git_changed_files(cwd)
70
+ if not changed:
71
+ sys.exit(0)
72
+
73
+ lines_str = "\n ".join(changed)
74
+ msg = (
75
+ f"[HGP] Bash command changed tracked files (use hgp_* tools for history):\n {lines_str}"
76
+ )
77
+ print(json.dumps({"systemMessage": msg}))
78
+ sys.exit(0)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,138 @@
1
+ """BeforeTool hook for Gemini CLI: warn when Bash commands may mutate files outside HGP.
2
+
3
+ Gemini CLI protocol (always exit 0):
4
+ Warn: stdout JSON {"systemMessage": "..."}
5
+ Pass-through: no stdout output
6
+
7
+ When a mutating pattern is detected, a marker file is written to /tmp so the
8
+ AfterTool hook can run 'git status' to report actual changes.
9
+
10
+ Marker file: /tmp/.hgp_bash_mutating_<ppid>
11
+ """
12
+ import json
13
+ import os
14
+ import re
15
+ import sys
16
+
17
+ # ── Read-only command prefixes — skip pattern matching for these ──────────────
18
+ _READONLY_PREFIXES = (
19
+ "git log",
20
+ "git status",
21
+ "git diff",
22
+ "git show",
23
+ "git branch",
24
+ "git tag",
25
+ "git remote",
26
+ "git fetch",
27
+ "git ls",
28
+ "git stash list",
29
+ "ls ",
30
+ "ls\t",
31
+ "head ",
32
+ "tail ",
33
+ "grep ",
34
+ "rg ",
35
+ "find ",
36
+ "wc ",
37
+ "diff ",
38
+ "less ",
39
+ "more ",
40
+ "file ",
41
+ "stat ",
42
+ "pwd",
43
+ "date",
44
+ "which ",
45
+ "type ",
46
+ "uname",
47
+ )
48
+
49
+ # ── Mutating patterns (regex) ─────────────────────────────────────────────────
50
+ _HIGH_PATTERNS = [
51
+ re.compile(r"\bcp\b"),
52
+ re.compile(r"\bmv\b"),
53
+ re.compile(r"\brm\b"),
54
+ re.compile(r"\btee\b"),
55
+ re.compile(r"\btouch\b"),
56
+ re.compile(r"\binstall\b"),
57
+ re.compile(r"\bmkdir\b"),
58
+ re.compile(r"\brmdir\b"),
59
+ re.compile(r"\bchmod\b"),
60
+ re.compile(r"\bchown\b"),
61
+ re.compile(r"\bln\b"),
62
+ re.compile(r"\btruncate\b"),
63
+ # git commands that rewrite working-tree files
64
+ re.compile(r"\bgit\s+checkout\b"),
65
+ re.compile(r"\bgit\s+restore\b"),
66
+ re.compile(r"\bgit\s+switch\b"),
67
+ re.compile(r"\bgit\s+apply\b"),
68
+ re.compile(r"\bgit\s+revert\b"),
69
+ re.compile(r"\bgit\s+merge\b"),
70
+ re.compile(r"\bgit\s+rebase\b"),
71
+ re.compile(r"\bgit\s+reset\b"),
72
+ # patch tools
73
+ re.compile(r"\bpatch\b"),
74
+ ]
75
+
76
+ _MEDIUM_PATTERNS = [
77
+ re.compile(r"(?<![|&])\s*>(?!>)"), # stdout redirect (not >>)
78
+ re.compile(r">>"), # append redirect
79
+ re.compile(r"\bsed\s+-i\b"),
80
+ re.compile(r"\bdd\b"),
81
+ re.compile(r"\bawk\b.*>"), # awk with redirect
82
+ ]
83
+
84
+
85
+ def _is_readonly(command: str) -> bool:
86
+ stripped = command.lstrip()
87
+ return any(stripped.startswith(p) for p in _READONLY_PREFIXES)
88
+
89
+
90
+ def _detect_mutating(command: str) -> str | None:
91
+ """Return the first matched pattern description, or None if command looks safe."""
92
+ for pat in _HIGH_PATTERNS:
93
+ if pat.search(command):
94
+ return pat.pattern
95
+ for pat in _MEDIUM_PATTERNS:
96
+ if pat.search(command):
97
+ return pat.pattern
98
+ return None
99
+
100
+
101
+ def _marker_path() -> str:
102
+ return f"/tmp/.hgp_bash_mutating_{os.getppid()}"
103
+
104
+
105
+ def main() -> None:
106
+ try:
107
+ event = json.loads(sys.stdin.read())
108
+ except (json.JSONDecodeError, ValueError):
109
+ sys.exit(0)
110
+
111
+ if event.get("tool_name") != "shell":
112
+ sys.exit(0)
113
+
114
+ command: str = event.get("tool_input", {}).get("command", "")
115
+ if not command or _is_readonly(command):
116
+ sys.exit(0)
117
+
118
+ matched = _detect_mutating(command)
119
+ if matched is None:
120
+ sys.exit(0)
121
+
122
+ # Write marker so AfterTool hook knows to run git status
123
+ try:
124
+ open(_marker_path(), "w").close()
125
+ except OSError:
126
+ pass # /tmp not writable — skip gating, hook still warns
127
+
128
+ msg = (
129
+ f"[HGP] Bash command may mutate files (matched: {matched!r}). "
130
+ "If this writes or deletes tracked files, prefer hgp_* tools so the "
131
+ "operation is recorded in HGP history."
132
+ )
133
+ print(json.dumps({"systemMessage": msg}))
134
+ sys.exit(0)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,48 @@
1
+ """BeforeTool hook for Gemini CLI: warn/block when native file tools are used.
2
+
3
+ Gemini CLI protocol (all responses exit 0):
4
+ Warn mode (default): stdout JSON {"systemMessage": "..."}
5
+ Block mode (HGP_HOOK_BLOCK=1): stdout JSON {"decision": "deny", "reason": "..."}
6
+ Pass-through: no stdout output
7
+
8
+ Set HGP_HOOK_BLOCK=1 to enforce blocking instead of warning.
9
+ """
10
+ import json
11
+ import os
12
+ import sys
13
+
14
+ HGP_TOOLS = {
15
+ "write_file": "hgp_write_file",
16
+ "replace": "hgp_edit_file",
17
+ }
18
+
19
+ BLOCK_MODE = os.environ.get("HGP_HOOK_BLOCK", "0") == "1"
20
+
21
+
22
+ def main() -> None:
23
+ try:
24
+ event = json.loads(sys.stdin.read())
25
+ except json.JSONDecodeError:
26
+ sys.exit(0)
27
+
28
+ tool_name = event.get("tool_name", "")
29
+ if tool_name not in HGP_TOOLS:
30
+ sys.exit(0)
31
+
32
+ hgp_equiv = HGP_TOOLS[tool_name]
33
+ msg = (
34
+ f"[HGP] Native `{tool_name}` detected. "
35
+ f"Use `{hgp_equiv}` instead to record this file operation in HGP history. "
36
+ f"Set HGP_HOOK_BLOCK=1 to enforce this as an error."
37
+ )
38
+
39
+ if BLOCK_MODE:
40
+ print(json.dumps({"decision": "deny", "reason": msg}))
41
+ else:
42
+ print(json.dumps({"systemMessage": msg}))
43
+
44
+ sys.exit(0)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -0,0 +1,38 @@
1
+ {
2
+ "hooks": {
3
+ "BeforeTool": [
4
+ {
5
+ "matcher": "^(write_file|replace)$",
6
+ "hooks": [
7
+ {
8
+ "name": "hgp-enforcement",
9
+ "type": "command",
10
+ "command": "python3 .gemini/hooks/pre_tool_use_hgp.py"
11
+ }
12
+ ]
13
+ },
14
+ {
15
+ "matcher": "^shell$",
16
+ "hooks": [
17
+ {
18
+ "name": "hgp-bash-pre",
19
+ "type": "command",
20
+ "command": "python3 .gemini/hooks/pre_bash_hgp.py"
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "AfterTool": [
26
+ {
27
+ "matcher": "^shell$",
28
+ "hooks": [
29
+ {
30
+ "name": "hgp-bash-post",
31
+ "type": "command",
32
+ "command": "python3 .gemini/hooks/post_bash_hgp.py"
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
38
+ }
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ # HGP repo-local storage (not committed to git)
6
+ .hgp/
7
+ *.db-wal
8
+ *.db-shm
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .pytest_cache/
12
+ .worktrees/
13
+ SESSION_HANDOVER.md
@@ -0,0 +1 @@
1
+ 3.12