flonat-research 0.1.0

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 (285) hide show
  1. package/.claude/agents/domain-reviewer.md +336 -0
  2. package/.claude/agents/fixer.md +226 -0
  3. package/.claude/agents/paper-critic.md +370 -0
  4. package/.claude/agents/peer-reviewer.md +289 -0
  5. package/.claude/agents/proposal-reviewer.md +215 -0
  6. package/.claude/agents/referee2-reviewer.md +367 -0
  7. package/.claude/agents/references/journal-referee-profiles.md +354 -0
  8. package/.claude/agents/references/paper-critic/council-personas.md +77 -0
  9. package/.claude/agents/references/paper-critic/council-prompts.md +198 -0
  10. package/.claude/agents/references/peer-reviewer/report-template.md +199 -0
  11. package/.claude/agents/references/peer-reviewer/sa-prompts.md +260 -0
  12. package/.claude/agents/references/peer-reviewer/security-scan.md +188 -0
  13. package/.claude/agents/references/proposal-reviewer/report-template.md +144 -0
  14. package/.claude/agents/references/proposal-reviewer/sa-prompts.md +149 -0
  15. package/.claude/agents/references/referee-config.md +114 -0
  16. package/.claude/agents/references/referee2-reviewer/audit-checklists.md +287 -0
  17. package/.claude/agents/references/referee2-reviewer/report-template.md +334 -0
  18. package/.claude/rules/design-before-results.md +52 -0
  19. package/.claude/rules/ignore-agents-md.md +17 -0
  20. package/.claude/rules/ignore-gemini-md.md +17 -0
  21. package/.claude/rules/lean-claude-md.md +45 -0
  22. package/.claude/rules/learn-tags.md +99 -0
  23. package/.claude/rules/overleaf-separation.md +67 -0
  24. package/.claude/rules/plan-first.md +175 -0
  25. package/.claude/rules/read-docs-first.md +50 -0
  26. package/.claude/rules/scope-discipline.md +28 -0
  27. package/.claude/settings.json +125 -0
  28. package/.context/current-focus.md +33 -0
  29. package/.context/preferences/priorities.md +36 -0
  30. package/.context/preferences/task-naming.md +28 -0
  31. package/.context/profile.md +29 -0
  32. package/.context/projects/_index.md +41 -0
  33. package/.context/projects/papers/nudge-exp.md +22 -0
  34. package/.context/projects/papers/uncertainty.md +31 -0
  35. package/.context/resources/claude-scientific-writer-review.md +48 -0
  36. package/.context/resources/cunningham-multi-analyst-agents.md +104 -0
  37. package/.context/resources/cunningham-multilang-code-audit.md +62 -0
  38. package/.context/resources/google-ai-co-scientist-review.md +72 -0
  39. package/.context/resources/karpathy-llm-council-review.md +58 -0
  40. package/.context/resources/multi-coder-reliability-protocol.md +175 -0
  41. package/.context/resources/pedro-santanna-takeaways.md +96 -0
  42. package/.context/resources/venue-rankings/abs_ajg_2024.csv +1823 -0
  43. package/.context/resources/venue-rankings/abs_ajg_2024_econ.csv +356 -0
  44. package/.context/resources/venue-rankings/cabs_4_4star_theory.csv +40 -0
  45. package/.context/resources/venue-rankings/core_2026.csv +801 -0
  46. package/.context/resources/venue-rankings.md +147 -0
  47. package/.context/workflows/README.md +69 -0
  48. package/.context/workflows/daily-review.md +91 -0
  49. package/.context/workflows/meeting-actions.md +108 -0
  50. package/.context/workflows/replication-protocol.md +155 -0
  51. package/.context/workflows/weekly-review.md +113 -0
  52. package/.mcp-server-biblio/formatters.py +158 -0
  53. package/.mcp-server-biblio/pyproject.toml +11 -0
  54. package/.mcp-server-biblio/server.py +678 -0
  55. package/.mcp-server-biblio/sources/__init__.py +14 -0
  56. package/.mcp-server-biblio/sources/base.py +73 -0
  57. package/.mcp-server-biblio/sources/formatters.py +83 -0
  58. package/.mcp-server-biblio/sources/models.py +22 -0
  59. package/.mcp-server-biblio/sources/multi_source.py +243 -0
  60. package/.mcp-server-biblio/sources/openalex_source.py +183 -0
  61. package/.mcp-server-biblio/sources/scopus_source.py +309 -0
  62. package/.mcp-server-biblio/sources/wos_source.py +508 -0
  63. package/.mcp-server-biblio/uv.lock +896 -0
  64. package/.scripts/README.md +161 -0
  65. package/.scripts/ai_pattern_density.py +446 -0
  66. package/.scripts/conf +445 -0
  67. package/.scripts/config.py +122 -0
  68. package/.scripts/count_inventory.py +275 -0
  69. package/.scripts/daily_digest.py +288 -0
  70. package/.scripts/done +177 -0
  71. package/.scripts/extract_meeting_actions.py +223 -0
  72. package/.scripts/focus +176 -0
  73. package/.scripts/generate-codex-agents-md.py +217 -0
  74. package/.scripts/inbox +194 -0
  75. package/.scripts/notion_helpers.py +325 -0
  76. package/.scripts/openalex/query_helpers.py +306 -0
  77. package/.scripts/papers +227 -0
  78. package/.scripts/query +223 -0
  79. package/.scripts/session-history.py +201 -0
  80. package/.scripts/skill-health.py +516 -0
  81. package/.scripts/skill-log-miner.py +273 -0
  82. package/.scripts/sync-to-codex.sh +252 -0
  83. package/.scripts/task +213 -0
  84. package/.scripts/tasks +190 -0
  85. package/.scripts/week +206 -0
  86. package/CLAUDE.md +197 -0
  87. package/LICENSE +21 -0
  88. package/MEMORY.md +38 -0
  89. package/README.md +269 -0
  90. package/docs/agents.md +44 -0
  91. package/docs/bibliography-setup.md +55 -0
  92. package/docs/council-mode.md +36 -0
  93. package/docs/getting-started.md +245 -0
  94. package/docs/hooks.md +38 -0
  95. package/docs/mcp-servers.md +82 -0
  96. package/docs/notion-setup.md +109 -0
  97. package/docs/rules.md +33 -0
  98. package/docs/scripts.md +303 -0
  99. package/docs/setup-overview/setup-overview.pdf +0 -0
  100. package/docs/skills.md +70 -0
  101. package/docs/system.md +159 -0
  102. package/hooks/block-destructive-git.sh +66 -0
  103. package/hooks/context-monitor.py +114 -0
  104. package/hooks/postcompact-restore.py +157 -0
  105. package/hooks/precompact-autosave.py +181 -0
  106. package/hooks/promise-checker.sh +124 -0
  107. package/hooks/protect-source-files.sh +81 -0
  108. package/hooks/resume-context-loader.sh +53 -0
  109. package/hooks/startup-context-loader.sh +102 -0
  110. package/package.json +51 -0
  111. package/packages/cli-council/.github/workflows/claude-code-review.yml +44 -0
  112. package/packages/cli-council/.github/workflows/claude.yml +50 -0
  113. package/packages/cli-council/README.md +100 -0
  114. package/packages/cli-council/pyproject.toml +43 -0
  115. package/packages/cli-council/src/cli_council/__init__.py +19 -0
  116. package/packages/cli-council/src/cli_council/__main__.py +185 -0
  117. package/packages/cli-council/src/cli_council/backends/__init__.py +8 -0
  118. package/packages/cli-council/src/cli_council/backends/base.py +81 -0
  119. package/packages/cli-council/src/cli_council/backends/claude.py +25 -0
  120. package/packages/cli-council/src/cli_council/backends/codex.py +27 -0
  121. package/packages/cli-council/src/cli_council/backends/gemini.py +26 -0
  122. package/packages/cli-council/src/cli_council/checkpoint.py +212 -0
  123. package/packages/cli-council/src/cli_council/config.py +51 -0
  124. package/packages/cli-council/src/cli_council/council.py +391 -0
  125. package/packages/cli-council/src/cli_council/models.py +46 -0
  126. package/packages/llm-council/.github/workflows/claude-code-review.yml +44 -0
  127. package/packages/llm-council/.github/workflows/claude.yml +50 -0
  128. package/packages/llm-council/README.md +453 -0
  129. package/packages/llm-council/pyproject.toml +42 -0
  130. package/packages/llm-council/src/llm_council/__init__.py +23 -0
  131. package/packages/llm-council/src/llm_council/__main__.py +259 -0
  132. package/packages/llm-council/src/llm_council/checkpoint.py +193 -0
  133. package/packages/llm-council/src/llm_council/client.py +253 -0
  134. package/packages/llm-council/src/llm_council/config.py +232 -0
  135. package/packages/llm-council/src/llm_council/council.py +482 -0
  136. package/packages/llm-council/src/llm_council/models.py +46 -0
  137. package/packages/mcp-bibliography/MEMORY.md +31 -0
  138. package/packages/mcp-bibliography/_app.py +226 -0
  139. package/packages/mcp-bibliography/formatters.py +158 -0
  140. package/packages/mcp-bibliography/log/2026-03-13-2100.md +35 -0
  141. package/packages/mcp-bibliography/pyproject.toml +15 -0
  142. package/packages/mcp-bibliography/run.sh +20 -0
  143. package/packages/mcp-bibliography/scholarly_formatters.py +83 -0
  144. package/packages/mcp-bibliography/server.py +1857 -0
  145. package/packages/mcp-bibliography/tools/__init__.py +28 -0
  146. package/packages/mcp-bibliography/tools/_registry.py +19 -0
  147. package/packages/mcp-bibliography/tools/altmetric.py +107 -0
  148. package/packages/mcp-bibliography/tools/core.py +92 -0
  149. package/packages/mcp-bibliography/tools/dblp.py +52 -0
  150. package/packages/mcp-bibliography/tools/openalex.py +296 -0
  151. package/packages/mcp-bibliography/tools/opencitations.py +102 -0
  152. package/packages/mcp-bibliography/tools/openreview.py +179 -0
  153. package/packages/mcp-bibliography/tools/orcid.py +131 -0
  154. package/packages/mcp-bibliography/tools/scholarly.py +575 -0
  155. package/packages/mcp-bibliography/tools/unpaywall.py +63 -0
  156. package/packages/mcp-bibliography/tools/zenodo.py +123 -0
  157. package/packages/mcp-bibliography/uv.lock +711 -0
  158. package/scripts/setup.sh +143 -0
  159. package/skills/beamer-deck/SKILL.md +199 -0
  160. package/skills/beamer-deck/references/quality-rubric.md +54 -0
  161. package/skills/beamer-deck/references/review-prompts.md +106 -0
  162. package/skills/bib-validate/SKILL.md +261 -0
  163. package/skills/bib-validate/references/council-mode.md +34 -0
  164. package/skills/bib-validate/references/deep-verify.md +79 -0
  165. package/skills/bib-validate/references/fix-mode.md +36 -0
  166. package/skills/bib-validate/references/openalex-verification.md +45 -0
  167. package/skills/bib-validate/references/preprint-check.md +31 -0
  168. package/skills/bib-validate/references/ref-manager-crossref.md +41 -0
  169. package/skills/bib-validate/references/report-template.md +82 -0
  170. package/skills/code-archaeology/SKILL.md +141 -0
  171. package/skills/code-review/SKILL.md +265 -0
  172. package/skills/code-review/references/quality-rubric.md +67 -0
  173. package/skills/consolidate-memory/SKILL.md +208 -0
  174. package/skills/context-status/SKILL.md +126 -0
  175. package/skills/creation-guard/SKILL.md +230 -0
  176. package/skills/devils-advocate/SKILL.md +130 -0
  177. package/skills/devils-advocate/references/competing-hypotheses.md +83 -0
  178. package/skills/init-project/SKILL.md +115 -0
  179. package/skills/init-project-course/references/memory-and-settings.md +92 -0
  180. package/skills/init-project-course/references/organise-templates.md +94 -0
  181. package/skills/init-project-course/skill.md +147 -0
  182. package/skills/init-project-light/skill.md +139 -0
  183. package/skills/init-project-research/SKILL.md +368 -0
  184. package/skills/init-project-research/references/atlas-pipeline-sync.md +70 -0
  185. package/skills/init-project-research/references/atlas-schema.md +81 -0
  186. package/skills/init-project-research/references/confirmation-report.md +39 -0
  187. package/skills/init-project-research/references/domain-profile-template.md +104 -0
  188. package/skills/init-project-research/references/interview-round3.md +34 -0
  189. package/skills/init-project-research/references/literature-discovery.md +43 -0
  190. package/skills/init-project-research/references/scaffold-details.md +197 -0
  191. package/skills/init-project-research/templates/field-calibration.md +60 -0
  192. package/skills/init-project-research/templates/pipeline-manifest.md +63 -0
  193. package/skills/init-project-research/templates/run-all.sh +116 -0
  194. package/skills/init-project-research/templates/seed-files.md +337 -0
  195. package/skills/insights-deck/SKILL.md +151 -0
  196. package/skills/interview-me/SKILL.md +157 -0
  197. package/skills/latex/SKILL.md +141 -0
  198. package/skills/latex/references/latex-configs.md +183 -0
  199. package/skills/latex-autofix/SKILL.md +230 -0
  200. package/skills/latex-autofix/references/known-errors.md +183 -0
  201. package/skills/latex-autofix/references/quality-rubric.md +50 -0
  202. package/skills/latex-health-check/SKILL.md +161 -0
  203. package/skills/learn/SKILL.md +220 -0
  204. package/skills/learn/scripts/validate_skill.py +265 -0
  205. package/skills/lessons-learned/SKILL.md +201 -0
  206. package/skills/literature/SKILL.md +335 -0
  207. package/skills/literature/references/agent-templates.md +393 -0
  208. package/skills/literature/references/bibliometric-apis.md +44 -0
  209. package/skills/literature/references/cli-council-search.md +79 -0
  210. package/skills/literature/references/openalex-api-guide.md +371 -0
  211. package/skills/literature/references/openalex-common-queries.md +381 -0
  212. package/skills/literature/references/openalex-workflows.md +248 -0
  213. package/skills/literature/references/reference-manager-sync.md +36 -0
  214. package/skills/literature/references/scopus-api-guide.md +208 -0
  215. package/skills/literature/references/wos-api-guide.md +308 -0
  216. package/skills/multi-perspective/SKILL.md +311 -0
  217. package/skills/multi-perspective/references/computational-many-analysts.md +77 -0
  218. package/skills/pipeline-manifest/SKILL.md +226 -0
  219. package/skills/pre-submission-report/SKILL.md +153 -0
  220. package/skills/process-reviews/SKILL.md +244 -0
  221. package/skills/process-reviews/references/rr-routing.md +101 -0
  222. package/skills/project-deck/SKILL.md +87 -0
  223. package/skills/project-safety/SKILL.md +135 -0
  224. package/skills/proofread/SKILL.md +254 -0
  225. package/skills/proofread/references/quality-rubric.md +104 -0
  226. package/skills/python-env/SKILL.md +57 -0
  227. package/skills/quarto-deck/SKILL.md +226 -0
  228. package/skills/quarto-deck/references/markdown-format.md +143 -0
  229. package/skills/quarto-deck/references/quality-rubric.md +54 -0
  230. package/skills/save-context/SKILL.md +174 -0
  231. package/skills/session-log/SKILL.md +98 -0
  232. package/skills/shared/concept-validation-gate.md +161 -0
  233. package/skills/shared/council-protocol.md +265 -0
  234. package/skills/shared/distribution-diagnostics.md +164 -0
  235. package/skills/shared/engagement-stratified-sampling.md +218 -0
  236. package/skills/shared/escalation-protocol.md +74 -0
  237. package/skills/shared/external-audit-protocol.md +205 -0
  238. package/skills/shared/intercoder-reliability.md +256 -0
  239. package/skills/shared/mcp-degradation.md +81 -0
  240. package/skills/shared/method-probing-questions.md +163 -0
  241. package/skills/shared/multi-language-conventions.md +143 -0
  242. package/skills/shared/paid-api-safety.md +174 -0
  243. package/skills/shared/palettes.md +90 -0
  244. package/skills/shared/progressive-disclosure.md +92 -0
  245. package/skills/shared/project-documentation-content.md +443 -0
  246. package/skills/shared/project-documentation-format.md +281 -0
  247. package/skills/shared/project-documentation.md +100 -0
  248. package/skills/shared/publication-output.md +138 -0
  249. package/skills/shared/quality-scoring.md +70 -0
  250. package/skills/shared/reference-resolution.md +77 -0
  251. package/skills/shared/research-quality-rubric.md +165 -0
  252. package/skills/shared/rhetoric-principles.md +54 -0
  253. package/skills/shared/skill-design-patterns.md +272 -0
  254. package/skills/shared/skill-index.md +240 -0
  255. package/skills/shared/system-documentation.md +334 -0
  256. package/skills/shared/tikz-rules.md +402 -0
  257. package/skills/shared/validation-tiers.md +121 -0
  258. package/skills/shared/venue-guides/README.md +46 -0
  259. package/skills/shared/venue-guides/cell_press_style.md +483 -0
  260. package/skills/shared/venue-guides/conferences_formatting.md +564 -0
  261. package/skills/shared/venue-guides/cs_conference_style.md +463 -0
  262. package/skills/shared/venue-guides/examples/cell_summary_example.md +247 -0
  263. package/skills/shared/venue-guides/examples/medical_structured_abstract.md +313 -0
  264. package/skills/shared/venue-guides/examples/nature_abstract_examples.md +213 -0
  265. package/skills/shared/venue-guides/examples/neurips_introduction_example.md +245 -0
  266. package/skills/shared/venue-guides/journals_formatting.md +486 -0
  267. package/skills/shared/venue-guides/medical_journal_styles.md +535 -0
  268. package/skills/shared/venue-guides/ml_conference_style.md +556 -0
  269. package/skills/shared/venue-guides/nature_science_style.md +405 -0
  270. package/skills/shared/venue-guides/reviewer_expectations.md +417 -0
  271. package/skills/shared/venue-guides/venue_writing_styles.md +321 -0
  272. package/skills/split-pdf/SKILL.md +172 -0
  273. package/skills/split-pdf/methodology.md +48 -0
  274. package/skills/sync-notion/SKILL.md +93 -0
  275. package/skills/system-audit/SKILL.md +157 -0
  276. package/skills/system-audit/references/sub-agent-prompts.md +294 -0
  277. package/skills/task-management/SKILL.md +131 -0
  278. package/skills/update-focus/SKILL.md +204 -0
  279. package/skills/update-project-doc/SKILL.md +194 -0
  280. package/skills/validate-bib/SKILL.md +242 -0
  281. package/skills/validate-bib/references/council-mode.md +34 -0
  282. package/skills/validate-bib/references/deep-verify.md +71 -0
  283. package/skills/validate-bib/references/openalex-verification.md +45 -0
  284. package/skills/validate-bib/references/preprint-check.md +31 -0
  285. package/skills/validate-bib/references/report-template.md +62 -0
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ import os, sys
3
+ _cfg = os.path.expanduser("~/.config/task-mgmt/path")
4
+ if not os.path.exists(_cfg) or not os.path.exists(open(_cfg).read().strip()): sys.exit(0)
5
+ TASK_MGMT = open(_cfg).read().strip()
6
+ """context-monitor.py
7
+ PostToolUse hook — tracks tool call count as a heuristic for context usage.
8
+
9
+ Fires on Bash|Task tool calls. Uses tool-call counting as a proxy for context
10
+ window consumption (150 tool calls ~ 100% context).
11
+
12
+ Three thresholds:
13
+ - 60% (~90 calls): Info — suggest saving key state
14
+ - 80% (~120 calls): Warning — auto-compact approaching
15
+ - 90% (~135 calls): Critical — complete current task
16
+
17
+ Each warning fires once per session. 60s throttle below warning level.
18
+ Outputs as systemMessage (non-blocking).
19
+ """
20
+
21
+ import hashlib
22
+ import json
23
+ import os
24
+ import sys
25
+ import time
26
+ from pathlib import Path
27
+
28
+ # --- Configuration ---
29
+ MAX_TOOL_CALLS = 150 # Conservative: 150 calls ~ 100% context
30
+ THRESHOLDS = [
31
+ (0.90, "critical", "Context at ~90%. Complete current task. Run `/context-status` to review preservation state."),
32
+ (0.80, "warning", "Context at ~80%. Auto-compact approaching. Ensure plan + session log are current."),
33
+ (0.60, "info", "Context at ~60%. Consider saving key state with `/update-focus` or `/session-log`."),
34
+ ]
35
+ THROTTLE_SECONDS = 60 # Minimum seconds between info-level messages
36
+
37
+ # --- Paths ---
38
+ SESSIONS_BASE = Path.home() / ".claude" / "sessions"
39
+
40
+
41
+ def project_hash() -> str:
42
+ """Deterministic hash of the project directory."""
43
+ project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
44
+ return hashlib.sha256(project_dir.encode()).hexdigest()[:12]
45
+
46
+
47
+ def session_dir() -> Path:
48
+ """Get or create the session state directory."""
49
+ d = SESSIONS_BASE / project_hash()
50
+ d.mkdir(parents=True, exist_ok=True)
51
+ return d
52
+
53
+
54
+ def load_state(sdir: Path) -> dict:
55
+ """Load the monitor state file, or return defaults."""
56
+ state_file = sdir / "context-monitor-state.json"
57
+ if state_file.is_file():
58
+ try:
59
+ return json.loads(state_file.read_text(encoding="utf-8"))
60
+ except (json.JSONDecodeError, OSError):
61
+ pass
62
+ return {"tool_calls": 0, "fired": [], "last_message_time": 0}
63
+
64
+
65
+ def save_state(sdir: Path, state: dict) -> None:
66
+ """Persist monitor state."""
67
+ state_file = sdir / "context-monitor-state.json"
68
+ state_file.write_text(json.dumps(state, indent=2), encoding="utf-8")
69
+
70
+
71
+ def main():
72
+ hook_input = json.loads(sys.stdin.read())
73
+
74
+ sdir = session_dir()
75
+ state = load_state(sdir)
76
+
77
+ # Increment call count
78
+ state["tool_calls"] = state.get("tool_calls", 0) + 1
79
+ count = state["tool_calls"]
80
+ pct = count / MAX_TOOL_CALLS
81
+
82
+ fired = set(state.get("fired", []))
83
+ now = time.time()
84
+ last_msg = state.get("last_message_time", 0)
85
+
86
+ message = None
87
+
88
+ for threshold, level, msg in THRESHOLDS:
89
+ if pct >= threshold and level not in fired:
90
+ message = msg
91
+ fired.add(level)
92
+ state["last_message_time"] = now
93
+ break
94
+
95
+ # Throttle: don't emit info-level messages more than once per THROTTLE_SECONDS
96
+ if message and "info" in fired and len(fired) == 1:
97
+ if (now - last_msg) < THROTTLE_SECONDS and last_msg > 0:
98
+ message = None
99
+
100
+ state["fired"] = list(fired)
101
+ save_state(sdir, state)
102
+
103
+ if message:
104
+ output = {"systemMessage": f"[Context Monitor] {message}"}
105
+ print(json.dumps(output))
106
+ sys.exit(2) # Exit code 2 = message visible in transcript
107
+ else:
108
+ # Silent — no output needed
109
+ print(json.dumps({}))
110
+ sys.exit(0)
111
+
112
+
113
+ if __name__ == "__main__":
114
+ main()
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python3
2
+ import os, sys
3
+ _cfg = os.path.expanduser("~/.config/task-mgmt/path")
4
+ if not os.path.exists(_cfg) or not os.path.exists(open(_cfg).read().strip()): sys.exit(0)
5
+ TASK_MGMT = open(_cfg).read().strip()
6
+ """postcompact-restore.py
7
+ SessionStart hook (compact matcher) — restores state after context compression.
8
+
9
+ Reads the pre-compact-state.json saved by precompact-autosave.py, re-scans
10
+ disk for active plan state, and outputs a formatted restoration message as
11
+ additionalContext so Claude immediately knows where things stand.
12
+
13
+ Deletes the state file after reading to avoid stale restores.
14
+ """
15
+
16
+ import hashlib
17
+ import json
18
+ import os
19
+ import re
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ TASK_MGMT = Path(TASK_MGMT)
24
+ LOG_DIR = TASK_MGMT / "log"
25
+ PLANS_DIR = LOG_DIR / "plans"
26
+ FOCUS_FILE = TASK_MGMT / ".context" / "current-focus.md"
27
+ SESSIONS_BASE = Path.home() / ".claude" / "sessions"
28
+
29
+
30
+ def project_hash() -> str:
31
+ """Deterministic hash of the project directory."""
32
+ project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
33
+ return hashlib.sha256(project_dir.encode()).hexdigest()[:12]
34
+
35
+
36
+ def latest_file(directory: Path, pattern: str = "*.md") -> Path | None:
37
+ """Return the most recently modified file matching pattern, or None."""
38
+ if not directory.is_dir():
39
+ return None
40
+ files = sorted(directory.glob(pattern), key=lambda f: f.stat().st_mtime, reverse=True)
41
+ return files[0] if files else None
42
+
43
+
44
+ def rescan_active_plan() -> dict | None:
45
+ """Re-scan disk for the active plan (may have been updated since pre-compact save)."""
46
+ plan_file = latest_file(PLANS_DIR)
47
+ if not plan_file:
48
+ return None
49
+
50
+ text = plan_file.read_text(encoding="utf-8", errors="replace")
51
+ text_lower = text.lower()
52
+
53
+ if "completed" in text_lower or "done" in text_lower:
54
+ return None
55
+
56
+ status = "DRAFT"
57
+ if "approved" in text_lower:
58
+ status = "APPROVED"
59
+
60
+ first_unchecked = None
61
+ for line in text.splitlines():
62
+ if re.match(r"\s*-\s*\[\s*\]", line):
63
+ first_unchecked = line.strip()
64
+ break
65
+
66
+ return {
67
+ "file": plan_file.name,
68
+ "status": status,
69
+ "first_unchecked": first_unchecked,
70
+ }
71
+
72
+
73
+ def format_restoration(state: dict, live_plan: dict | None) -> str:
74
+ """Build the formatted restoration message."""
75
+ lines = ["## Post-Compaction State Restoration", ""]
76
+
77
+ # Pre-compaction timestamp
78
+ lines.append(f"**Compacted at:** {state.get('timestamp', 'unknown')}")
79
+ lines.append(f"**Working directory:** {state.get('cwd', 'unknown')}")
80
+ lines.append("")
81
+
82
+ # Current focus
83
+ focus = state.get("current_focus_headline")
84
+ if focus:
85
+ lines.append("### Current Focus")
86
+ lines.append(focus)
87
+ lines.append("")
88
+
89
+ # Active plan
90
+ plan = live_plan or state.get("active_plan")
91
+ if plan:
92
+ lines.append("### Active Plan")
93
+ lines.append(f"- **File:** `log/plans/{plan['file']}`")
94
+ lines.append(f"- **Status:** {plan['status']}")
95
+ if plan.get("first_unchecked"):
96
+ lines.append(f"- **Next step:** {plan['first_unchecked']}")
97
+ lines.append("")
98
+
99
+ # Latest session log
100
+ log_name = state.get("latest_session_log")
101
+ if log_name:
102
+ lines.append(f"### Latest Session Log")
103
+ lines.append(f"- **File:** `log/{log_name}`")
104
+ lines.append("")
105
+
106
+ # Recent decisions
107
+ decisions = state.get("recent_decisions", [])
108
+ if decisions:
109
+ lines.append("### Recent Decisions (pre-compaction)")
110
+ for d in decisions:
111
+ lines.append(f"- {d}")
112
+ lines.append("")
113
+
114
+ # Recovery actions
115
+ lines.append("### Recovery Actions")
116
+ lines.append("1. Read the active plan file to restore full implementation context")
117
+ lines.append("2. Read the latest session log to understand recent progress")
118
+ lines.append("3. Continue from the next unchecked step in the plan")
119
+
120
+ return "\n".join(lines)
121
+
122
+
123
+ def main():
124
+ # Read hook input (unused but consumed from stdin)
125
+ sys.stdin.read()
126
+
127
+ phash = project_hash()
128
+ state_file = SESSIONS_BASE / phash / "pre-compact-state.json"
129
+
130
+ if not state_file.is_file():
131
+ # No pre-compact state found — nothing to restore
132
+ sys.exit(0)
133
+
134
+ state = json.loads(state_file.read_text(encoding="utf-8"))
135
+
136
+ # Re-scan disk for live plan state
137
+ live_plan = rescan_active_plan()
138
+
139
+ # Build restoration message
140
+ message = format_restoration(state, live_plan)
141
+
142
+ # Delete state file after reading
143
+ state_file.unlink()
144
+
145
+ # Output as additionalContext
146
+ output = {
147
+ "hookSpecificOutput": {
148
+ "hookEventName": "SessionStart",
149
+ "additionalContext": message,
150
+ }
151
+ }
152
+ print(json.dumps(output, ensure_ascii=False))
153
+ sys.exit(0)
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ import os, sys
3
+ _cfg = os.path.expanduser("~/.config/task-mgmt/path")
4
+ if not os.path.exists(_cfg) or not os.path.exists(open(_cfg).read().strip()): sys.exit(0)
5
+ TASK_MGMT = open(_cfg).read().strip()
6
+ """precompact-autosave.py
7
+ PreCompact hook — saves state before context compression.
8
+
9
+ Two outputs:
10
+ 1. Human-readable snapshot → log/{TIMESTAMP}-compact.md (existing behaviour)
11
+ 2. Machine-readable state → ~/.claude/sessions/{hash}/pre-compact-state.json
12
+ (new — read back by postcompact-restore.py after compaction)
13
+ """
14
+
15
+ import hashlib
16
+ import json
17
+ import os
18
+ import re
19
+ import sys
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+
23
+ TASK_MGMT = Path(TASK_MGMT)
24
+ LOG_DIR = TASK_MGMT / "log"
25
+ PLANS_DIR = LOG_DIR / "plans"
26
+ FOCUS_FILE = TASK_MGMT / ".context" / "current-focus.md"
27
+ SESSIONS_BASE = Path.home() / ".claude" / "sessions"
28
+
29
+
30
+ def project_hash() -> str:
31
+ """Deterministic hash of the project directory (same approach as Pedro's)."""
32
+ project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
33
+ return hashlib.sha256(project_dir.encode()).hexdigest()[:12]
34
+
35
+
36
+ def latest_file(directory: Path, pattern: str = "*.md") -> Path | None:
37
+ """Return the most recently modified file matching pattern, or None."""
38
+ if not directory.is_dir():
39
+ return None
40
+ files = sorted(directory.glob(pattern), key=lambda f: f.stat().st_mtime, reverse=True)
41
+ return files[0] if files else None
42
+
43
+
44
+ def active_plan() -> dict | None:
45
+ """Find the latest plan file and extract its status + first unchecked item."""
46
+ plan_file = latest_file(PLANS_DIR)
47
+ if not plan_file:
48
+ return None
49
+
50
+ text = plan_file.read_text(encoding="utf-8", errors="replace")
51
+
52
+ # Determine status from content markers
53
+ status = "DRAFT"
54
+ text_lower = text.lower()
55
+ if "approved" in text_lower:
56
+ status = "APPROVED"
57
+ if "completed" in text_lower or "done" in text_lower:
58
+ return None # Skip completed plans
59
+
60
+ # Find first unchecked item
61
+ first_unchecked = None
62
+ for line in text.splitlines():
63
+ if re.match(r"\s*-\s*\[\s*\]", line):
64
+ first_unchecked = line.strip()
65
+ break
66
+
67
+ return {
68
+ "file": plan_file.name,
69
+ "status": status,
70
+ "first_unchecked": first_unchecked,
71
+ }
72
+
73
+
74
+ def current_focus_headline() -> str | None:
75
+ """Read the first 5 lines of current-focus.md."""
76
+ if not FOCUS_FILE.is_file():
77
+ return None
78
+ lines = FOCUS_FILE.read_text(encoding="utf-8", errors="replace").splitlines()[:5]
79
+ return "\n".join(lines) if lines else None
80
+
81
+
82
+ def recent_decisions(log_file: Path | None) -> list[str]:
83
+ """Scan last 50 lines of the latest session log for decision patterns."""
84
+ if not log_file or not log_file.is_file():
85
+ return []
86
+
87
+ lines = log_file.read_text(encoding="utf-8", errors="replace").splitlines()[-50:]
88
+ pattern = re.compile(r"(Decision:|Decided:|Chose:|decision:|decided:|chose:)", re.IGNORECASE)
89
+ decisions = []
90
+ for line in lines:
91
+ if pattern.search(line):
92
+ decisions.append(line.strip())
93
+ return decisions[:10] # Cap at 10
94
+
95
+
96
+ def save_human_snapshot(hook_input: dict, timestamp: str) -> str:
97
+ """Save the human-readable markdown snapshot (existing behaviour)."""
98
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
99
+
100
+ session_id = hook_input.get("session_id", "unknown")
101
+ cwd = hook_input.get("cwd", "unknown")
102
+ trigger = hook_input.get("trigger", "unknown")
103
+ transcript = hook_input.get("transcript_path", "unknown")
104
+
105
+ now_display = datetime.now().strftime("%Y-%m-%d %H:%M")
106
+ save_file = LOG_DIR / f"{timestamp}-compact.md"
107
+
108
+ content = f"""# Auto-save before context compaction
109
+
110
+ - **Timestamp:** {now_display}
111
+ - **Session:** {session_id}
112
+ - **Working directory:** {cwd}
113
+ - **Trigger:** {trigger}
114
+
115
+ > This file was auto-generated by the PreCompact hook.
116
+ > Read the session transcript for full context.
117
+ > Transcript: {transcript}
118
+ """
119
+ save_file.write_text(content, encoding="utf-8")
120
+ return save_file.name
121
+
122
+
123
+ def save_machine_state(hook_input: dict, timestamp: str) -> Path:
124
+ """Save machine-readable JSON state for post-compact restoration."""
125
+ phash = project_hash()
126
+ state_dir = SESSIONS_BASE / phash
127
+ state_dir.mkdir(parents=True, exist_ok=True)
128
+ state_file = state_dir / "pre-compact-state.json"
129
+
130
+ log_file = latest_file(LOG_DIR, "*.md")
131
+ # Skip compact snapshots when finding the latest session log
132
+ log_files = sorted(LOG_DIR.glob("*.md"), key=lambda f: f.stat().st_mtime, reverse=True)
133
+ session_log = None
134
+ for f in log_files:
135
+ if "-compact" not in f.name:
136
+ session_log = f
137
+ break
138
+
139
+ plan = active_plan()
140
+ focus = current_focus_headline()
141
+ decisions = recent_decisions(session_log)
142
+
143
+ state = {
144
+ "timestamp": timestamp,
145
+ "session_id": hook_input.get("session_id", "unknown"),
146
+ "cwd": hook_input.get("cwd", "unknown"),
147
+ "active_plan": plan,
148
+ "latest_session_log": session_log.name if session_log else None,
149
+ "current_focus_headline": focus,
150
+ "recent_decisions": decisions,
151
+ }
152
+
153
+ state_file.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
154
+ return state_file
155
+
156
+
157
+ def main():
158
+ hook_input = json.loads(sys.stdin.read())
159
+ timestamp = datetime.now().strftime("%Y-%m-%d-%H%M")
160
+
161
+ # 1. Human-readable snapshot (existing)
162
+ snapshot_name = save_human_snapshot(hook_input, timestamp)
163
+
164
+ # 2. Machine-readable state (new)
165
+ state_file = save_machine_state(hook_input, timestamp)
166
+
167
+ # Output hook response
168
+ output = {
169
+ "systemMessage": (
170
+ f"Auto-saved pre-compaction snapshot to log/{snapshot_name} "
171
+ f"and state to {state_file}"
172
+ )
173
+ }
174
+ print(json.dumps(output))
175
+
176
+ # Exit code 2 = message visible in transcript
177
+ sys.exit(2)
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
@@ -0,0 +1,124 @@
1
+ #!/bin/bash
2
+ # Skip on non-Mac environments (cloud, mobile)
3
+ source "$(dirname "$0")/resolve-task-mgmt.sh" || exit 0
4
+ # promise-checker.sh
5
+ # Stop hook — catches "performative compliance": Claude says it remembered/noted/saved
6
+ # something but never actually called Edit or Write.
7
+ #
8
+ # Scans the last assistant turn for promise patterns.
9
+ # If promises found without corresponding Edit/Write tool calls → blocks.
10
+
11
+ INPUT=$(cat)
12
+
13
+ # Prevent infinite loops
14
+ STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
15
+ if [ "$STOP_ACTIVE" = "true" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
20
+ if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
21
+ exit 0
22
+ fi
23
+
24
+ # --- Extract the last assistant turn ---
25
+ # Read transcript from the end, collect all lines until we hit a user message.
26
+ # This captures the full turn (multiple assistant messages + tool results).
27
+ LAST_TURN=$(tac "$TRANSCRIPT_PATH" 2>/dev/null | while IFS= read -r line; do
28
+ MSG_TYPE=$(echo "$line" | jq -r '.type // empty' 2>/dev/null)
29
+ if [ "$MSG_TYPE" = "user_message" ]; then
30
+ break
31
+ fi
32
+ echo "$line"
33
+ done)
34
+
35
+ if [ -z "$LAST_TURN" ]; then
36
+ exit 0
37
+ fi
38
+
39
+ # --- Check for Edit/Write tool calls in this turn ---
40
+ HAS_WRITE=$(echo "$LAST_TURN" | \
41
+ jq -r '.content[]? | select(.type == "tool_use") | .name' 2>/dev/null | \
42
+ grep -qiE '^(Edit|Write|NotebookEdit)$' && echo "yes" || echo "no")
43
+
44
+ # --- Extract all text content from assistant messages in this turn ---
45
+ TEXT_CONTENT=$(echo "$LAST_TURN" | \
46
+ jq -r 'select(.type == "assistant_message") | .content[]? | select(.type == "text") | .text' 2>/dev/null)
47
+
48
+ if [ -z "$TEXT_CONTENT" ]; then
49
+ exit 0
50
+ fi
51
+
52
+ # --- Promise patterns ---
53
+ # Phrases where Claude claims to have stored/remembered/noted something,
54
+ # or promises to do so. Case-insensitive matching applied later.
55
+
56
+ PROMISE_PATTERNS=(
57
+ # Future promises to store
58
+ "I'll remember"
59
+ "I'll note that"
60
+ "I'll write that down"
61
+ "I'll save that"
62
+ "I'll record"
63
+ "I'll store"
64
+ "I'll make a note"
65
+ "I'll add that to"
66
+ "I'll put that in"
67
+ "I'll log that"
68
+ "I'll update.*memory"
69
+ "I'll update.*context"
70
+ "I'll update.*MEMORY"
71
+ "I'll keep that in mind"
72
+ "let me note that"
73
+ "let me save"
74
+ "let me record"
75
+ "let me write that"
76
+ # Past claims of having stored
77
+ "I've noted"
78
+ "I've recorded"
79
+ "I've saved that"
80
+ "I've stored"
81
+ "I've memorized"
82
+ "I've written that down"
83
+ "I've added that"
84
+ "I've updated.*memory"
85
+ "I've updated.*MEMORY"
86
+ "I've updated.*context"
87
+ "I've logged"
88
+ "I've made a note"
89
+ "I've taken note"
90
+ "I've put that in"
91
+ # Short forms
92
+ "^Noted[.!]"
93
+ "Noted —"
94
+ "Noted,"
95
+ "duly noted"
96
+ )
97
+
98
+ # Build a single regex from patterns
99
+ REGEX=$(printf '%s|' "${PROMISE_PATTERNS[@]}")
100
+ REGEX="${REGEX%|}" # Remove trailing pipe
101
+
102
+ # Check for promise patterns (case-insensitive)
103
+ MATCHED=$(echo "$TEXT_CONTENT" | grep -iE "$REGEX" | head -3)
104
+
105
+ if [ -z "$MATCHED" ]; then
106
+ # No promises found — all clear
107
+ exit 0
108
+ fi
109
+
110
+ # --- Verdict ---
111
+ if [ "$HAS_WRITE" = "no" ]; then
112
+ # Promises found but no write actions — block!
113
+ SNIPPET=$(echo "$MATCHED" | head -1 | cut -c1-80)
114
+ cat <<EOF
115
+ {
116
+ "decision": "block",
117
+ "reason": "Promise without action: you said \"${SNIPPET}\" but no Edit/Write tool was called. Actually write it down (MEMORY.md, context file, or wherever appropriate)."
118
+ }
119
+ EOF
120
+ exit 0
121
+ fi
122
+
123
+ # Promises found AND write actions exist — all good
124
+ exit 0
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ # Skip on non-Mac environments (cloud, mobile)
3
+ source "$(dirname "$0")/resolve-task-mgmt.sh" || exit 0
4
+ # protect-source-files.sh
5
+ # PreToolUse hook for Edit|Write — prompts confirmation for files outside
6
+ # the current project, ~/.claude/, and the Task Management directory.
7
+ # Soft block (permissionDecision: "ask"), not hard block.
8
+
9
+ INPUT=$(cat)
10
+
11
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
12
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
13
+
14
+ # If no file path, allow (shouldn't happen but be safe)
15
+ if [ -z "$FILE_PATH" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ # Resolve to absolute paths
20
+ FILE_PATH=$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && echo "$(pwd)/$(basename "$FILE_PATH")" || echo "$FILE_PATH")
21
+ CWD=$(cd "$CWD" 2>/dev/null && pwd || echo "$CWD")
22
+ CLAUDE_DIR="$HOME/.claude"
23
+
24
+ # Allow: file is inside CWD (logical path)
25
+ if [[ "$FILE_PATH" == "$CWD"/* ]]; then
26
+ exit 0
27
+ fi
28
+
29
+ # Allow: file is inside CWD (resolved/physical path, handles symlinks)
30
+ RESOLVED_CWD=$(cd "$CWD" 2>/dev/null && pwd -P || echo "$CWD")
31
+ if [[ "$FILE_PATH" == "$RESOLVED_CWD"/* ]]; then
32
+ exit 0
33
+ fi
34
+
35
+ # Allow: file is inside any symlink target reachable from CWD (one level deep)
36
+ # This handles e.g. paper/ -> Overleaf/ where resolved paths differ from CWD
37
+ for LINK in "$CWD"/*/; do
38
+ if [ -L "${LINK%/}" ]; then
39
+ LINK_TARGET=$(cd "${LINK%/}" 2>/dev/null && pwd -P || continue)
40
+ if [[ -n "$LINK_TARGET" ]] && [[ "$FILE_PATH" == "$LINK_TARGET"/* ]]; then
41
+ exit 0
42
+ fi
43
+ fi
44
+ done
45
+
46
+ # Allow: file is under ~/.claude/ (settings, memory, skills)
47
+ if [[ "$FILE_PATH" == "$CLAUDE_DIR"/* ]]; then
48
+ exit 0
49
+ fi
50
+
51
+ # Allow: file is under Task Management directory (context library)
52
+ if [[ "$FILE_PATH" == "$TASK_MGMT"/* ]]; then
53
+ exit 0
54
+ fi
55
+
56
+ # Block: skill files outside Task Management (skills are global only)
57
+ if [[ "$FILE_PATH" == */skills/*/SKILL.md ]] && [[ "$FILE_PATH" != "$TASK_MGMT"/skills/* ]]; then
58
+ cat <<BLOCK
59
+ {
60
+ "hookSpecificOutput": {
61
+ "hookEventName": "PreToolUse",
62
+ "permissionDecision": "deny",
63
+ "permissionDecisionReason": "Skills must be created in Task Management/skills/, not locally. This file would be created at: $FILE_PATH"
64
+ }
65
+ }
66
+ BLOCK
67
+ exit 0
68
+ fi
69
+
70
+ # Outside all known safe zones — ask for confirmation
71
+ cat <<EOF
72
+ {
73
+ "hookSpecificOutput": {
74
+ "hookEventName": "PreToolUse",
75
+ "permissionDecision": "ask",
76
+ "permissionDecisionReason": "File is outside current project: $FILE_PATH — confirm?"
77
+ }
78
+ }
79
+ EOF
80
+
81
+ exit 0