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.
- package/.claude/agents/domain-reviewer.md +336 -0
- package/.claude/agents/fixer.md +226 -0
- package/.claude/agents/paper-critic.md +370 -0
- package/.claude/agents/peer-reviewer.md +289 -0
- package/.claude/agents/proposal-reviewer.md +215 -0
- package/.claude/agents/referee2-reviewer.md +367 -0
- package/.claude/agents/references/journal-referee-profiles.md +354 -0
- package/.claude/agents/references/paper-critic/council-personas.md +77 -0
- package/.claude/agents/references/paper-critic/council-prompts.md +198 -0
- package/.claude/agents/references/peer-reviewer/report-template.md +199 -0
- package/.claude/agents/references/peer-reviewer/sa-prompts.md +260 -0
- package/.claude/agents/references/peer-reviewer/security-scan.md +188 -0
- package/.claude/agents/references/proposal-reviewer/report-template.md +144 -0
- package/.claude/agents/references/proposal-reviewer/sa-prompts.md +149 -0
- package/.claude/agents/references/referee-config.md +114 -0
- package/.claude/agents/references/referee2-reviewer/audit-checklists.md +287 -0
- package/.claude/agents/references/referee2-reviewer/report-template.md +334 -0
- package/.claude/rules/design-before-results.md +52 -0
- package/.claude/rules/ignore-agents-md.md +17 -0
- package/.claude/rules/ignore-gemini-md.md +17 -0
- package/.claude/rules/lean-claude-md.md +45 -0
- package/.claude/rules/learn-tags.md +99 -0
- package/.claude/rules/overleaf-separation.md +67 -0
- package/.claude/rules/plan-first.md +175 -0
- package/.claude/rules/read-docs-first.md +50 -0
- package/.claude/rules/scope-discipline.md +28 -0
- package/.claude/settings.json +125 -0
- package/.context/current-focus.md +33 -0
- package/.context/preferences/priorities.md +36 -0
- package/.context/preferences/task-naming.md +28 -0
- package/.context/profile.md +29 -0
- package/.context/projects/_index.md +41 -0
- package/.context/projects/papers/nudge-exp.md +22 -0
- package/.context/projects/papers/uncertainty.md +31 -0
- package/.context/resources/claude-scientific-writer-review.md +48 -0
- package/.context/resources/cunningham-multi-analyst-agents.md +104 -0
- package/.context/resources/cunningham-multilang-code-audit.md +62 -0
- package/.context/resources/google-ai-co-scientist-review.md +72 -0
- package/.context/resources/karpathy-llm-council-review.md +58 -0
- package/.context/resources/multi-coder-reliability-protocol.md +175 -0
- package/.context/resources/pedro-santanna-takeaways.md +96 -0
- package/.context/resources/venue-rankings/abs_ajg_2024.csv +1823 -0
- package/.context/resources/venue-rankings/abs_ajg_2024_econ.csv +356 -0
- package/.context/resources/venue-rankings/cabs_4_4star_theory.csv +40 -0
- package/.context/resources/venue-rankings/core_2026.csv +801 -0
- package/.context/resources/venue-rankings.md +147 -0
- package/.context/workflows/README.md +69 -0
- package/.context/workflows/daily-review.md +91 -0
- package/.context/workflows/meeting-actions.md +108 -0
- package/.context/workflows/replication-protocol.md +155 -0
- package/.context/workflows/weekly-review.md +113 -0
- package/.mcp-server-biblio/formatters.py +158 -0
- package/.mcp-server-biblio/pyproject.toml +11 -0
- package/.mcp-server-biblio/server.py +678 -0
- package/.mcp-server-biblio/sources/__init__.py +14 -0
- package/.mcp-server-biblio/sources/base.py +73 -0
- package/.mcp-server-biblio/sources/formatters.py +83 -0
- package/.mcp-server-biblio/sources/models.py +22 -0
- package/.mcp-server-biblio/sources/multi_source.py +243 -0
- package/.mcp-server-biblio/sources/openalex_source.py +183 -0
- package/.mcp-server-biblio/sources/scopus_source.py +309 -0
- package/.mcp-server-biblio/sources/wos_source.py +508 -0
- package/.mcp-server-biblio/uv.lock +896 -0
- package/.scripts/README.md +161 -0
- package/.scripts/ai_pattern_density.py +446 -0
- package/.scripts/conf +445 -0
- package/.scripts/config.py +122 -0
- package/.scripts/count_inventory.py +275 -0
- package/.scripts/daily_digest.py +288 -0
- package/.scripts/done +177 -0
- package/.scripts/extract_meeting_actions.py +223 -0
- package/.scripts/focus +176 -0
- package/.scripts/generate-codex-agents-md.py +217 -0
- package/.scripts/inbox +194 -0
- package/.scripts/notion_helpers.py +325 -0
- package/.scripts/openalex/query_helpers.py +306 -0
- package/.scripts/papers +227 -0
- package/.scripts/query +223 -0
- package/.scripts/session-history.py +201 -0
- package/.scripts/skill-health.py +516 -0
- package/.scripts/skill-log-miner.py +273 -0
- package/.scripts/sync-to-codex.sh +252 -0
- package/.scripts/task +213 -0
- package/.scripts/tasks +190 -0
- package/.scripts/week +206 -0
- package/CLAUDE.md +197 -0
- package/LICENSE +21 -0
- package/MEMORY.md +38 -0
- package/README.md +269 -0
- package/docs/agents.md +44 -0
- package/docs/bibliography-setup.md +55 -0
- package/docs/council-mode.md +36 -0
- package/docs/getting-started.md +245 -0
- package/docs/hooks.md +38 -0
- package/docs/mcp-servers.md +82 -0
- package/docs/notion-setup.md +109 -0
- package/docs/rules.md +33 -0
- package/docs/scripts.md +303 -0
- package/docs/setup-overview/setup-overview.pdf +0 -0
- package/docs/skills.md +70 -0
- package/docs/system.md +159 -0
- package/hooks/block-destructive-git.sh +66 -0
- package/hooks/context-monitor.py +114 -0
- package/hooks/postcompact-restore.py +157 -0
- package/hooks/precompact-autosave.py +181 -0
- package/hooks/promise-checker.sh +124 -0
- package/hooks/protect-source-files.sh +81 -0
- package/hooks/resume-context-loader.sh +53 -0
- package/hooks/startup-context-loader.sh +102 -0
- package/package.json +51 -0
- package/packages/cli-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/cli-council/.github/workflows/claude.yml +50 -0
- package/packages/cli-council/README.md +100 -0
- package/packages/cli-council/pyproject.toml +43 -0
- package/packages/cli-council/src/cli_council/__init__.py +19 -0
- package/packages/cli-council/src/cli_council/__main__.py +185 -0
- package/packages/cli-council/src/cli_council/backends/__init__.py +8 -0
- package/packages/cli-council/src/cli_council/backends/base.py +81 -0
- package/packages/cli-council/src/cli_council/backends/claude.py +25 -0
- package/packages/cli-council/src/cli_council/backends/codex.py +27 -0
- package/packages/cli-council/src/cli_council/backends/gemini.py +26 -0
- package/packages/cli-council/src/cli_council/checkpoint.py +212 -0
- package/packages/cli-council/src/cli_council/config.py +51 -0
- package/packages/cli-council/src/cli_council/council.py +391 -0
- package/packages/cli-council/src/cli_council/models.py +46 -0
- package/packages/llm-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/llm-council/.github/workflows/claude.yml +50 -0
- package/packages/llm-council/README.md +453 -0
- package/packages/llm-council/pyproject.toml +42 -0
- package/packages/llm-council/src/llm_council/__init__.py +23 -0
- package/packages/llm-council/src/llm_council/__main__.py +259 -0
- package/packages/llm-council/src/llm_council/checkpoint.py +193 -0
- package/packages/llm-council/src/llm_council/client.py +253 -0
- package/packages/llm-council/src/llm_council/config.py +232 -0
- package/packages/llm-council/src/llm_council/council.py +482 -0
- package/packages/llm-council/src/llm_council/models.py +46 -0
- package/packages/mcp-bibliography/MEMORY.md +31 -0
- package/packages/mcp-bibliography/_app.py +226 -0
- package/packages/mcp-bibliography/formatters.py +158 -0
- package/packages/mcp-bibliography/log/2026-03-13-2100.md +35 -0
- package/packages/mcp-bibliography/pyproject.toml +15 -0
- package/packages/mcp-bibliography/run.sh +20 -0
- package/packages/mcp-bibliography/scholarly_formatters.py +83 -0
- package/packages/mcp-bibliography/server.py +1857 -0
- package/packages/mcp-bibliography/tools/__init__.py +28 -0
- package/packages/mcp-bibliography/tools/_registry.py +19 -0
- package/packages/mcp-bibliography/tools/altmetric.py +107 -0
- package/packages/mcp-bibliography/tools/core.py +92 -0
- package/packages/mcp-bibliography/tools/dblp.py +52 -0
- package/packages/mcp-bibliography/tools/openalex.py +296 -0
- package/packages/mcp-bibliography/tools/opencitations.py +102 -0
- package/packages/mcp-bibliography/tools/openreview.py +179 -0
- package/packages/mcp-bibliography/tools/orcid.py +131 -0
- package/packages/mcp-bibliography/tools/scholarly.py +575 -0
- package/packages/mcp-bibliography/tools/unpaywall.py +63 -0
- package/packages/mcp-bibliography/tools/zenodo.py +123 -0
- package/packages/mcp-bibliography/uv.lock +711 -0
- package/scripts/setup.sh +143 -0
- package/skills/beamer-deck/SKILL.md +199 -0
- package/skills/beamer-deck/references/quality-rubric.md +54 -0
- package/skills/beamer-deck/references/review-prompts.md +106 -0
- package/skills/bib-validate/SKILL.md +261 -0
- package/skills/bib-validate/references/council-mode.md +34 -0
- package/skills/bib-validate/references/deep-verify.md +79 -0
- package/skills/bib-validate/references/fix-mode.md +36 -0
- package/skills/bib-validate/references/openalex-verification.md +45 -0
- package/skills/bib-validate/references/preprint-check.md +31 -0
- package/skills/bib-validate/references/ref-manager-crossref.md +41 -0
- package/skills/bib-validate/references/report-template.md +82 -0
- package/skills/code-archaeology/SKILL.md +141 -0
- package/skills/code-review/SKILL.md +265 -0
- package/skills/code-review/references/quality-rubric.md +67 -0
- package/skills/consolidate-memory/SKILL.md +208 -0
- package/skills/context-status/SKILL.md +126 -0
- package/skills/creation-guard/SKILL.md +230 -0
- package/skills/devils-advocate/SKILL.md +130 -0
- package/skills/devils-advocate/references/competing-hypotheses.md +83 -0
- package/skills/init-project/SKILL.md +115 -0
- package/skills/init-project-course/references/memory-and-settings.md +92 -0
- package/skills/init-project-course/references/organise-templates.md +94 -0
- package/skills/init-project-course/skill.md +147 -0
- package/skills/init-project-light/skill.md +139 -0
- package/skills/init-project-research/SKILL.md +368 -0
- package/skills/init-project-research/references/atlas-pipeline-sync.md +70 -0
- package/skills/init-project-research/references/atlas-schema.md +81 -0
- package/skills/init-project-research/references/confirmation-report.md +39 -0
- package/skills/init-project-research/references/domain-profile-template.md +104 -0
- package/skills/init-project-research/references/interview-round3.md +34 -0
- package/skills/init-project-research/references/literature-discovery.md +43 -0
- package/skills/init-project-research/references/scaffold-details.md +197 -0
- package/skills/init-project-research/templates/field-calibration.md +60 -0
- package/skills/init-project-research/templates/pipeline-manifest.md +63 -0
- package/skills/init-project-research/templates/run-all.sh +116 -0
- package/skills/init-project-research/templates/seed-files.md +337 -0
- package/skills/insights-deck/SKILL.md +151 -0
- package/skills/interview-me/SKILL.md +157 -0
- package/skills/latex/SKILL.md +141 -0
- package/skills/latex/references/latex-configs.md +183 -0
- package/skills/latex-autofix/SKILL.md +230 -0
- package/skills/latex-autofix/references/known-errors.md +183 -0
- package/skills/latex-autofix/references/quality-rubric.md +50 -0
- package/skills/latex-health-check/SKILL.md +161 -0
- package/skills/learn/SKILL.md +220 -0
- package/skills/learn/scripts/validate_skill.py +265 -0
- package/skills/lessons-learned/SKILL.md +201 -0
- package/skills/literature/SKILL.md +335 -0
- package/skills/literature/references/agent-templates.md +393 -0
- package/skills/literature/references/bibliometric-apis.md +44 -0
- package/skills/literature/references/cli-council-search.md +79 -0
- package/skills/literature/references/openalex-api-guide.md +371 -0
- package/skills/literature/references/openalex-common-queries.md +381 -0
- package/skills/literature/references/openalex-workflows.md +248 -0
- package/skills/literature/references/reference-manager-sync.md +36 -0
- package/skills/literature/references/scopus-api-guide.md +208 -0
- package/skills/literature/references/wos-api-guide.md +308 -0
- package/skills/multi-perspective/SKILL.md +311 -0
- package/skills/multi-perspective/references/computational-many-analysts.md +77 -0
- package/skills/pipeline-manifest/SKILL.md +226 -0
- package/skills/pre-submission-report/SKILL.md +153 -0
- package/skills/process-reviews/SKILL.md +244 -0
- package/skills/process-reviews/references/rr-routing.md +101 -0
- package/skills/project-deck/SKILL.md +87 -0
- package/skills/project-safety/SKILL.md +135 -0
- package/skills/proofread/SKILL.md +254 -0
- package/skills/proofread/references/quality-rubric.md +104 -0
- package/skills/python-env/SKILL.md +57 -0
- package/skills/quarto-deck/SKILL.md +226 -0
- package/skills/quarto-deck/references/markdown-format.md +143 -0
- package/skills/quarto-deck/references/quality-rubric.md +54 -0
- package/skills/save-context/SKILL.md +174 -0
- package/skills/session-log/SKILL.md +98 -0
- package/skills/shared/concept-validation-gate.md +161 -0
- package/skills/shared/council-protocol.md +265 -0
- package/skills/shared/distribution-diagnostics.md +164 -0
- package/skills/shared/engagement-stratified-sampling.md +218 -0
- package/skills/shared/escalation-protocol.md +74 -0
- package/skills/shared/external-audit-protocol.md +205 -0
- package/skills/shared/intercoder-reliability.md +256 -0
- package/skills/shared/mcp-degradation.md +81 -0
- package/skills/shared/method-probing-questions.md +163 -0
- package/skills/shared/multi-language-conventions.md +143 -0
- package/skills/shared/paid-api-safety.md +174 -0
- package/skills/shared/palettes.md +90 -0
- package/skills/shared/progressive-disclosure.md +92 -0
- package/skills/shared/project-documentation-content.md +443 -0
- package/skills/shared/project-documentation-format.md +281 -0
- package/skills/shared/project-documentation.md +100 -0
- package/skills/shared/publication-output.md +138 -0
- package/skills/shared/quality-scoring.md +70 -0
- package/skills/shared/reference-resolution.md +77 -0
- package/skills/shared/research-quality-rubric.md +165 -0
- package/skills/shared/rhetoric-principles.md +54 -0
- package/skills/shared/skill-design-patterns.md +272 -0
- package/skills/shared/skill-index.md +240 -0
- package/skills/shared/system-documentation.md +334 -0
- package/skills/shared/tikz-rules.md +402 -0
- package/skills/shared/validation-tiers.md +121 -0
- package/skills/shared/venue-guides/README.md +46 -0
- package/skills/shared/venue-guides/cell_press_style.md +483 -0
- package/skills/shared/venue-guides/conferences_formatting.md +564 -0
- package/skills/shared/venue-guides/cs_conference_style.md +463 -0
- package/skills/shared/venue-guides/examples/cell_summary_example.md +247 -0
- package/skills/shared/venue-guides/examples/medical_structured_abstract.md +313 -0
- package/skills/shared/venue-guides/examples/nature_abstract_examples.md +213 -0
- package/skills/shared/venue-guides/examples/neurips_introduction_example.md +245 -0
- package/skills/shared/venue-guides/journals_formatting.md +486 -0
- package/skills/shared/venue-guides/medical_journal_styles.md +535 -0
- package/skills/shared/venue-guides/ml_conference_style.md +556 -0
- package/skills/shared/venue-guides/nature_science_style.md +405 -0
- package/skills/shared/venue-guides/reviewer_expectations.md +417 -0
- package/skills/shared/venue-guides/venue_writing_styles.md +321 -0
- package/skills/split-pdf/SKILL.md +172 -0
- package/skills/split-pdf/methodology.md +48 -0
- package/skills/sync-notion/SKILL.md +93 -0
- package/skills/system-audit/SKILL.md +157 -0
- package/skills/system-audit/references/sub-agent-prompts.md +294 -0
- package/skills/task-management/SKILL.md +131 -0
- package/skills/update-focus/SKILL.md +204 -0
- package/skills/update-project-doc/SKILL.md +194 -0
- package/skills/validate-bib/SKILL.md +242 -0
- package/skills/validate-bib/references/council-mode.md +34 -0
- package/skills/validate-bib/references/deep-verify.md +71 -0
- package/skills/validate-bib/references/openalex-verification.md +45 -0
- package/skills/validate-bib/references/preprint-check.md +31 -0
- package/skills/validate-bib/references/report-template.md +62 -0
package/.scripts/papers
ADDED
|
@@ -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()
|