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,227 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ View and update research pipeline in Notion.
4
+
5
+ Usage:
6
+ papers # Show all papers
7
+ papers --active # Show papers in progress
8
+ papers --update "[Journal]" "R&R" # Update paper stage
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import sys
15
+ import urllib.request
16
+ import urllib.error
17
+
18
+
19
+ NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
20
+ DATABASE_ID = "YOUR-PIPELINE-DATABASE-ID-HERE" # Research Pipeline
21
+
22
+ STAGES = [
23
+ "Idea",
24
+ "Literature Review",
25
+ "Drafting",
26
+ "Internal Review",
27
+ "Submitted",
28
+ "Under Review",
29
+ "R&R",
30
+ "Revising",
31
+ "Accepted",
32
+ "Published",
33
+ ]
34
+
35
+
36
+ def query_papers():
37
+ """Query papers from Notion."""
38
+ if not NOTION_TOKEN:
39
+ print("❌ NOTION_TOKEN not set")
40
+ sys.exit(1)
41
+
42
+ data = {
43
+ "sorts": [
44
+ {"property": "Priority", "direction": "ascending"}
45
+ ]
46
+ }
47
+
48
+ req = urllib.request.Request(
49
+ f"https://api.notion.com/v1/databases/{DATABASE_ID}/query",
50
+ data=json.dumps(data).encode("utf-8"),
51
+ headers={
52
+ "Authorization": f"Bearer {NOTION_TOKEN}",
53
+ "Content-Type": "application/json",
54
+ "Notion-Version": "2022-06-28",
55
+ },
56
+ method="POST",
57
+ )
58
+
59
+ try:
60
+ with urllib.request.urlopen(req) as response:
61
+ return json.loads(response.read().decode("utf-8"))
62
+ except urllib.error.HTTPError as e:
63
+ error_body = json.loads(e.read().decode("utf-8"))
64
+ print(f"❌ Error: {e.code}")
65
+ print(error_body.get("message", str(error_body)))
66
+ sys.exit(1)
67
+
68
+
69
+ def get_property(page, prop_name, prop_type):
70
+ """Extract property value from a Notion page."""
71
+ prop = page.get("properties", {}).get(prop_name, {})
72
+
73
+ if prop_type == "title":
74
+ title_list = prop.get("title", [])
75
+ return title_list[0].get("plain_text", "") if title_list else ""
76
+ elif prop_type == "select":
77
+ select = prop.get("select")
78
+ return select.get("name", "") if select else ""
79
+ elif prop_type == "status":
80
+ status = prop.get("status")
81
+ return status.get("name", "") if status else ""
82
+ elif prop_type == "rich_text":
83
+ text_list = prop.get("rich_text", [])
84
+ return text_list[0].get("plain_text", "") if text_list else ""
85
+ return ""
86
+
87
+
88
+ def update_paper_stage(page_id, new_stage):
89
+ """Update a paper's stage."""
90
+ # Note: Stage might be a status or select property
91
+ data = {
92
+ "properties": {
93
+ "Stage": {"status": {"name": new_stage}}
94
+ }
95
+ }
96
+
97
+ req = urllib.request.Request(
98
+ f"https://api.notion.com/v1/pages/{page_id}",
99
+ data=json.dumps(data).encode("utf-8"),
100
+ headers={
101
+ "Authorization": f"Bearer {NOTION_TOKEN}",
102
+ "Content-Type": "application/json",
103
+ "Notion-Version": "2022-06-28",
104
+ },
105
+ method="PATCH",
106
+ )
107
+
108
+ try:
109
+ with urllib.request.urlopen(req) as response:
110
+ return True
111
+ except urllib.error.HTTPError as e:
112
+ error_body = json.loads(e.read().decode("utf-8"))
113
+ print(f"❌ Error: {e.code}")
114
+ print(error_body.get("message", str(error_body)))
115
+ return False
116
+
117
+
118
+ def format_paper(page):
119
+ """Format a paper for display."""
120
+ title = get_property(page, "Paper Title", "title")
121
+ stage = get_property(page, "Stage", "status")
122
+ journal = get_property(page, "Target Journal", "rich_text")
123
+ priority = get_property(page, "Priority", "select")
124
+ uni = get_property(page, "University", "select")
125
+
126
+ priority_emoji = {"High": "🔴", "Medium": "🟡", "Low": "🟢"}.get(priority, "⚪")
127
+
128
+ stage_emoji = {
129
+ "Idea": "💡",
130
+ "Literature Review": "📚",
131
+ "Drafting": "✍️",
132
+ "Internal Review": "👀",
133
+ "Submitted": "📤",
134
+ "Under Review": "⏳",
135
+ "R&R": "🔄",
136
+ "Revising": "✍️",
137
+ "Accepted": "✅",
138
+ "Published": "🎉",
139
+ }.get(stage, "📄")
140
+
141
+ line = f"{priority_emoji} {stage_emoji} {title}"
142
+ if stage:
143
+ line += f" [{stage}]"
144
+ if journal:
145
+ line += f" → {journal}"
146
+
147
+ return line
148
+
149
+
150
+ def main():
151
+ parser = argparse.ArgumentParser(description="Research pipeline manager")
152
+ parser.add_argument("--active", action="store_true", help="Show only active papers")
153
+ parser.add_argument("--update", nargs=2, metavar=("SEARCH", "STAGE"), help="Update paper stage")
154
+ parser.add_argument("--stages", action="store_true", help="List available stages")
155
+
156
+ args = parser.parse_args()
157
+
158
+ if args.stages:
159
+ print("Available stages:")
160
+ for s in STAGES:
161
+ print(f" - {s}")
162
+ return
163
+
164
+ if args.update:
165
+ search_term, new_stage = args.update
166
+
167
+ # Validate stage
168
+ if new_stage not in STAGES:
169
+ print(f"❌ Invalid stage: {new_stage}")
170
+ print(f"Valid stages: {', '.join(STAGES)}")
171
+ return
172
+
173
+ result = query_papers()
174
+ pages = result.get("results", [])
175
+
176
+ # Find matching paper
177
+ search_lower = search_term.lower()
178
+ matches = [p for p in pages if search_lower in get_property(p, "Paper Title", "title").lower()]
179
+
180
+ if not matches:
181
+ print(f"No papers found matching '{search_term}'")
182
+ return
183
+
184
+ if len(matches) == 1:
185
+ page = matches[0]
186
+ title = get_property(page, "Paper Title", "title")
187
+ if update_paper_stage(page["id"], new_stage):
188
+ print(f"✅ Updated: {title} → {new_stage}")
189
+ else:
190
+ print(f"Found {len(matches)} matching papers:")
191
+ for i, page in enumerate(matches, 1):
192
+ title = get_property(page, "Paper Title", "title")
193
+ print(f"{i}. {title}")
194
+
195
+ try:
196
+ choice = input("Which one? (number): ").strip()
197
+ idx = int(choice) - 1
198
+ if 0 <= idx < len(matches):
199
+ page = matches[idx]
200
+ title = get_property(page, "Paper Title", "title")
201
+ if update_paper_stage(page["id"], new_stage):
202
+ print(f"✅ Updated: {title} → {new_stage}")
203
+ except (ValueError, KeyboardInterrupt):
204
+ print("\nCancelled.")
205
+ return
206
+
207
+ # List papers
208
+ result = query_papers()
209
+ pages = result.get("results", [])
210
+
211
+ if args.active:
212
+ active_stages = ["Literature Review", "Drafting", "Internal Review", "R&R", "Revising"]
213
+ pages = [p for p in pages if get_property(p, "Stage", "status") in active_stages]
214
+
215
+ if not pages:
216
+ print("No papers found.")
217
+ return
218
+
219
+ print(f"📚 Research Pipeline ({len(pages)} papers)")
220
+ print("-" * 50)
221
+
222
+ for page in pages:
223
+ print(format_paper(page))
224
+
225
+
226
+ if __name__ == "__main__":
227
+ main()
package/.scripts/query ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Query your task management system from the terminal.
4
+
5
+ Usage:
6
+ query "game theory" # search across files and Notion
7
+ query --files "keyword" # search only local context files
8
+ query --notion "topic" # search only Notion
9
+ query --papers "collaboration" # search Research Pipeline
10
+ query --tasks "meeting" # search Tasks Tracker
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import os
16
+ import sys
17
+ import urllib.request
18
+ import urllib.error
19
+ from pathlib import Path
20
+
21
+ NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
22
+ BASE_DIR = Path(__file__).resolve().parent.parent
23
+ CONTEXT_PATH = BASE_DIR / ".context"
24
+
25
+ # Database IDs
26
+ TASKS_DB = "f8e5b15d28db4e2bb430b5fbc580f7eb"
27
+ PAPERS_DB = "YOUR-PIPELINE-DATABASE-ID-HERE"
28
+ CONFERENCES_DB = "YOUR-CONFERENCES-DATABASE-ID-HERE"
29
+
30
+
31
+ def search_files(query):
32
+ """Search local context files for a query."""
33
+ results = []
34
+ query_lower = query.lower()
35
+
36
+ if not CONTEXT_PATH.exists():
37
+ return results
38
+
39
+ for file_path in CONTEXT_PATH.rglob("*.md"):
40
+ try:
41
+ content = file_path.read_text()
42
+ if query_lower in content.lower():
43
+ # Find matching lines
44
+ lines = content.split("\n")
45
+ matches = []
46
+ for i, line in enumerate(lines):
47
+ if query_lower in line.lower():
48
+ matches.append((i + 1, line.strip()))
49
+
50
+ results.append({
51
+ "file": str(file_path.relative_to(CONTEXT_PATH)),
52
+ "matches": matches[:5] # Limit to 5 matches per file
53
+ })
54
+ except Exception:
55
+ pass
56
+
57
+ return results
58
+
59
+
60
+ def search_notion(query, database_id=None):
61
+ """Search Notion for a query."""
62
+ if not NOTION_TOKEN:
63
+ return {"error": "NOTION_TOKEN not set"}
64
+
65
+ # Use Notion search API
66
+ data = {
67
+ "query": query,
68
+ "page_size": 10
69
+ }
70
+
71
+ if database_id:
72
+ data["filter"] = {"property": "object", "value": "page"}
73
+
74
+ req = urllib.request.Request(
75
+ "https://api.notion.com/v1/search",
76
+ data=json.dumps(data).encode("utf-8"),
77
+ headers={
78
+ "Authorization": f"Bearer {NOTION_TOKEN}",
79
+ "Content-Type": "application/json",
80
+ "Notion-Version": "2022-06-28",
81
+ },
82
+ method="POST",
83
+ )
84
+
85
+ try:
86
+ with urllib.request.urlopen(req) as response:
87
+ result = json.loads(response.read().decode("utf-8"))
88
+ pages = []
89
+ for page in result.get("results", []):
90
+ title = ""
91
+ props = page.get("properties", {})
92
+
93
+ # Try to find title property
94
+ for prop_name, prop_value in props.items():
95
+ if prop_value.get("type") == "title":
96
+ title_list = prop_value.get("title", [])
97
+ if title_list:
98
+ title = title_list[0].get("plain_text", "")
99
+ break
100
+
101
+ if not title:
102
+ title = page.get("url", "Untitled")
103
+
104
+ pages.append({
105
+ "title": title,
106
+ "url": page.get("url", ""),
107
+ "type": page.get("object", "page"),
108
+ "parent": page.get("parent", {}).get("type", "")
109
+ })
110
+
111
+ return {"pages": pages}
112
+ except urllib.error.HTTPError as e:
113
+ return {"error": f"Notion API error: {e.code}"}
114
+
115
+
116
+ def query_database(database_id, title_property="Name"):
117
+ """Query a specific Notion database."""
118
+ if not NOTION_TOKEN:
119
+ return {"error": "NOTION_TOKEN not set"}
120
+
121
+ req = urllib.request.Request(
122
+ f"https://api.notion.com/v1/databases/{database_id}/query",
123
+ data=json.dumps({"page_size": 100}).encode("utf-8"),
124
+ headers={
125
+ "Authorization": f"Bearer {NOTION_TOKEN}",
126
+ "Content-Type": "application/json",
127
+ "Notion-Version": "2022-06-28",
128
+ },
129
+ method="POST",
130
+ )
131
+
132
+ try:
133
+ with urllib.request.urlopen(req) as response:
134
+ return json.loads(response.read().decode("utf-8"))
135
+ except urllib.error.HTTPError as e:
136
+ return {"error": f"Notion API error: {e.code}"}
137
+
138
+
139
+ def print_file_results(results, query):
140
+ """Print file search results."""
141
+ if not results:
142
+ print(f" No matches in context files")
143
+ return
144
+
145
+ for result in results:
146
+ print(f"\n 📄 {result['file']}")
147
+ for line_num, line in result['matches']:
148
+ # Highlight the query in the line
149
+ print(f" L{line_num}: {line[:80]}{'...' if len(line) > 80 else ''}")
150
+
151
+
152
+ def print_notion_results(results):
153
+ """Print Notion search results."""
154
+ if "error" in results:
155
+ print(f" ❌ {results['error']}")
156
+ return
157
+
158
+ pages = results.get("pages", [])
159
+ if not pages:
160
+ print(f" No matches in Notion")
161
+ return
162
+
163
+ for page in pages:
164
+ print(f"\n 📝 {page['title']}")
165
+ print(f" {page['url']}")
166
+
167
+
168
+ def main():
169
+ parser = argparse.ArgumentParser(description="Query your task management system")
170
+ parser.add_argument("query", nargs="?", help="Search query")
171
+ parser.add_argument("--files", action="store_true", help="Search only local files")
172
+ parser.add_argument("--notion", action="store_true", help="Search only Notion")
173
+ parser.add_argument("--papers", action="store_true", help="Search Research Pipeline")
174
+ parser.add_argument("--tasks", action="store_true", help="Search Tasks Tracker")
175
+ parser.add_argument("--conferences", action="store_true", help="Search Conferences")
176
+
177
+ args = parser.parse_args()
178
+
179
+ if not args.query:
180
+ parser.print_help()
181
+ return
182
+
183
+ query = args.query
184
+ print(f"🔍 Searching for: \"{query}\"")
185
+ print("=" * 50)
186
+
187
+ # Determine what to search
188
+ search_all = not (args.files or args.notion or args.papers or args.tasks or args.conferences)
189
+
190
+ # Search local files
191
+ if args.files or search_all:
192
+ print("\n📁 Context Files:")
193
+ file_results = search_files(query)
194
+ print_file_results(file_results, query)
195
+
196
+ # Search Notion
197
+ if args.notion or search_all:
198
+ print("\n☁️ Notion:")
199
+ notion_results = search_notion(query)
200
+ print_notion_results(notion_results)
201
+
202
+ # Search specific databases
203
+ if args.papers:
204
+ print("\n📚 Research Pipeline:")
205
+ notion_results = search_notion(query)
206
+ # Filter to only show papers
207
+ print_notion_results(notion_results)
208
+
209
+ if args.tasks:
210
+ print("\n✅ Tasks:")
211
+ notion_results = search_notion(query)
212
+ print_notion_results(notion_results)
213
+
214
+ if args.conferences:
215
+ print("\n📅 Conferences:")
216
+ notion_results = search_notion(query)
217
+ print_notion_results(notion_results)
218
+
219
+ print()
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python3
2
+ """Query session history from skill-outcomes.jsonl via SQLite.
3
+
4
+ Indexes the JSONL file into an in-memory SQLite database for fast queries.
5
+ The DB is rebuilt on each invocation (the source file is small).
6
+
7
+ Usage:
8
+ uv run python .scripts/session-history.py # show all
9
+ uv run python .scripts/session-history.py --skill proofread # filter by skill
10
+ uv run python .scripts/session-history.py --project Task-Management
11
+ uv run python .scripts/session-history.py --since 2026-03-15
12
+ uv run python .scripts/session-history.py --outcome error
13
+ uv run python .scripts/session-history.py --stats # summary stats
14
+ uv run python .scripts/session-history.py --top-skills # most-used skills
15
+ uv run python .scripts/session-history.py --by-project # breakdown by project
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import json
22
+ import sqlite3
23
+ import sys
24
+ from datetime import datetime
25
+ from pathlib import Path
26
+
27
+
28
+ JSONL_PATH = Path.home() / ".claude" / "ecc" / "skill-outcomes.jsonl"
29
+
30
+
31
+ def load_db() -> sqlite3.Connection:
32
+ """Load JSONL into an in-memory SQLite database."""
33
+ conn = sqlite3.connect(":memory:")
34
+ conn.row_factory = sqlite3.Row
35
+ conn.execute("""
36
+ CREATE TABLE outcomes (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ skill TEXT NOT NULL,
39
+ timestamp TEXT NOT NULL,
40
+ outcome TEXT NOT NULL,
41
+ session TEXT,
42
+ project TEXT,
43
+ note TEXT
44
+ )
45
+ """)
46
+ conn.execute("CREATE INDEX idx_skill ON outcomes(skill)")
47
+ conn.execute("CREATE INDEX idx_project ON outcomes(project)")
48
+ conn.execute("CREATE INDEX idx_timestamp ON outcomes(timestamp)")
49
+ conn.execute("CREATE INDEX idx_outcome ON outcomes(outcome)")
50
+
51
+ if not JSONL_PATH.exists():
52
+ print(f"No history file at {JSONL_PATH}", file=sys.stderr)
53
+ return conn
54
+
55
+ with open(JSONL_PATH) as f:
56
+ for line in f:
57
+ line = line.strip()
58
+ if not line:
59
+ continue
60
+ try:
61
+ row = json.loads(line)
62
+ conn.execute(
63
+ "INSERT INTO outcomes (skill, timestamp, outcome, session, project, note) VALUES (?, ?, ?, ?, ?, ?)",
64
+ (
65
+ row.get("skill", ""),
66
+ row.get("timestamp", ""),
67
+ row.get("outcome", ""),
68
+ row.get("session", ""),
69
+ row.get("project", ""),
70
+ row.get("note", ""),
71
+ ),
72
+ )
73
+ except (json.JSONDecodeError, KeyError):
74
+ continue
75
+ conn.commit()
76
+ return conn
77
+
78
+
79
+ def fmt_table(rows: list[sqlite3.Row], columns: list[str]) -> str:
80
+ """Format rows as a simple table."""
81
+ if not rows:
82
+ return "No results."
83
+
84
+ widths = [len(c) for c in columns]
85
+ str_rows = []
86
+ for row in rows:
87
+ vals = [str(row[c] or "") for c in columns]
88
+ str_rows.append(vals)
89
+ for i, v in enumerate(vals):
90
+ widths[i] = max(widths[i], len(v))
91
+
92
+ header = " ".join(c.ljust(widths[i]) for i, c in enumerate(columns))
93
+ sep = " ".join("-" * widths[i] for i in range(len(columns)))
94
+ lines = [header, sep]
95
+ for vals in str_rows:
96
+ lines.append(" ".join(vals[i].ljust(widths[i]) for i in range(len(columns))))
97
+ return "\n".join(lines)
98
+
99
+
100
+ def cmd_list(conn: sqlite3.Connection, args: argparse.Namespace) -> None:
101
+ """List outcomes with optional filters."""
102
+ clauses = []
103
+ params: list[str] = []
104
+
105
+ if args.skill:
106
+ clauses.append("skill LIKE ?")
107
+ params.append(f"%{args.skill}%")
108
+ if args.project:
109
+ clauses.append("project LIKE ?")
110
+ params.append(f"%{args.project}%")
111
+ if args.outcome:
112
+ clauses.append("outcome = ?")
113
+ params.append(args.outcome)
114
+ if args.since:
115
+ clauses.append("timestamp >= ?")
116
+ params.append(args.since)
117
+
118
+ where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
119
+ query = f"SELECT timestamp, skill, project, outcome, note FROM outcomes {where} ORDER BY timestamp DESC"
120
+
121
+ if args.limit:
122
+ query += f" LIMIT {args.limit}"
123
+
124
+ rows = conn.execute(query, params).fetchall()
125
+ print(fmt_table(rows, ["timestamp", "skill", "project", "outcome", "note"]))
126
+
127
+
128
+ def cmd_stats(conn: sqlite3.Connection, _args: argparse.Namespace) -> None:
129
+ """Show summary statistics."""
130
+ total = conn.execute("SELECT COUNT(*) as n FROM outcomes").fetchone()["n"]
131
+ success = conn.execute("SELECT COUNT(*) as n FROM outcomes WHERE outcome='success'").fetchone()["n"]
132
+ error = conn.execute("SELECT COUNT(*) as n FROM outcomes WHERE outcome='error'").fetchone()["n"]
133
+ partial = conn.execute("SELECT COUNT(*) as n FROM outcomes WHERE outcome='partial'").fetchone()["n"]
134
+ skills = conn.execute("SELECT COUNT(DISTINCT skill) as n FROM outcomes").fetchone()["n"]
135
+ projects = conn.execute("SELECT COUNT(DISTINCT project) as n FROM outcomes WHERE project != ''").fetchone()["n"]
136
+
137
+ first = conn.execute("SELECT MIN(timestamp) as t FROM outcomes").fetchone()["t"]
138
+ last = conn.execute("SELECT MAX(timestamp) as t FROM outcomes").fetchone()["t"]
139
+
140
+ print(f"Total invocations: {total}")
141
+ print(f"Unique skills: {skills}")
142
+ print(f"Unique projects: {projects}")
143
+ print(f"Success: {success} ({100*success//max(total,1)}%)")
144
+ print(f"Error: {error} ({100*error//max(total,1)}%)")
145
+ print(f"Partial: {partial} ({100*partial//max(total,1)}%)")
146
+ print(f"First recorded: {first or 'N/A'}")
147
+ print(f"Last recorded: {last or 'N/A'}")
148
+
149
+
150
+ def cmd_top_skills(conn: sqlite3.Connection, args: argparse.Namespace) -> None:
151
+ """Show most-used skills."""
152
+ limit = args.limit or 20
153
+ rows = conn.execute(
154
+ """SELECT skill,
155
+ COUNT(*) as invocations,
156
+ SUM(CASE WHEN outcome='success' THEN 1 ELSE 0 END) as ok,
157
+ SUM(CASE WHEN outcome='error' THEN 1 ELSE 0 END) as err
158
+ FROM outcomes GROUP BY skill ORDER BY invocations DESC LIMIT ?""",
159
+ (limit,),
160
+ ).fetchall()
161
+ print(fmt_table(rows, ["skill", "invocations", "ok", "err"]))
162
+
163
+
164
+ def cmd_by_project(conn: sqlite3.Connection, _args: argparse.Namespace) -> None:
165
+ """Show breakdown by project."""
166
+ rows = conn.execute(
167
+ """SELECT project,
168
+ COUNT(*) as invocations,
169
+ COUNT(DISTINCT skill) as skills_used,
170
+ MAX(timestamp) as last_activity
171
+ FROM outcomes WHERE project != '' GROUP BY project ORDER BY invocations DESC""",
172
+ ).fetchall()
173
+ print(fmt_table(rows, ["project", "invocations", "skills_used", "last_activity"]))
174
+
175
+
176
+ def main() -> None:
177
+ parser = argparse.ArgumentParser(description="Query session history")
178
+ parser.add_argument("--skill", help="Filter by skill name (substring match)")
179
+ parser.add_argument("--project", help="Filter by project name (substring match)")
180
+ parser.add_argument("--outcome", choices=["success", "error", "partial"], help="Filter by outcome")
181
+ parser.add_argument("--since", help="Show entries since date (ISO format, e.g. 2026-03-15)")
182
+ parser.add_argument("--limit", type=int, help="Max rows to show")
183
+ parser.add_argument("--stats", action="store_true", help="Show summary statistics")
184
+ parser.add_argument("--top-skills", action="store_true", help="Show most-used skills")
185
+ parser.add_argument("--by-project", action="store_true", help="Show breakdown by project")
186
+ args = parser.parse_args()
187
+
188
+ conn = load_db()
189
+
190
+ if args.stats:
191
+ cmd_stats(conn, args)
192
+ elif args.top_skills:
193
+ cmd_top_skills(conn, args)
194
+ elif args.by_project:
195
+ cmd_by_project(conn, args)
196
+ else:
197
+ cmd_list(conn, args)
198
+
199
+
200
+ if __name__ == "__main__":
201
+ main()