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
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill Log Miner — Extracts skill invocation data from session logs.
|
|
4
|
+
|
|
5
|
+
Fallback observation method when the Skill tool doesn't emit hook events.
|
|
6
|
+
Scans log/*.md files for `/skill-name` references, extracts structured
|
|
7
|
+
invocation records, and writes to ~/.claude/ecc/observations-*.jsonl.
|
|
8
|
+
|
|
9
|
+
Idempotent: tracks which log files have been mined in a watermark file.
|
|
10
|
+
Re-running only processes new logs.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
uv run python .scripts/skill-log-miner.py # mine new logs
|
|
14
|
+
uv run python .scripts/skill-log-miner.py --backfill # re-mine all logs
|
|
15
|
+
uv run python .scripts/skill-log-miner.py --dry-run # show what would be mined
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# ── Config ──────────────────────────────────────────────────────────────────
|
|
28
|
+
ECC_DIR = Path.home() / ".claude" / "ecc"
|
|
29
|
+
WATERMARK_FILE = ECC_DIR / "miner-watermark.json"
|
|
30
|
+
|
|
31
|
+
# Where to find session logs — resolve from config
|
|
32
|
+
def get_task_mgmt() -> Path | None:
|
|
33
|
+
cfg = Path.home() / ".config" / "task-mgmt" / "path"
|
|
34
|
+
if cfg.exists():
|
|
35
|
+
p = Path(cfg.read_text().strip())
|
|
36
|
+
if p.is_dir():
|
|
37
|
+
return p
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
# Also check project-specific log directories
|
|
41
|
+
def find_log_dirs() -> list[Path]:
|
|
42
|
+
dirs = []
|
|
43
|
+
tm = get_task_mgmt()
|
|
44
|
+
if tm:
|
|
45
|
+
log_dir = tm / "log"
|
|
46
|
+
if log_dir.is_dir():
|
|
47
|
+
dirs.append(log_dir)
|
|
48
|
+
return dirs
|
|
49
|
+
|
|
50
|
+
# Skill name pattern: `/skill-name` in backticks or at line start
|
|
51
|
+
# Matches: `/proofread`, `/latex-autofix`, `/bib-validate`
|
|
52
|
+
# Excludes: `/path/to/file`, `/dev/null`, `/usr/bin`, `/tmp/`
|
|
53
|
+
SKILL_PATTERN = re.compile(
|
|
54
|
+
r'`/([a-z][a-z0-9-]+)`' # backtick-quoted: `/proofread`
|
|
55
|
+
r'|(?:^|\s)/([a-z][a-z0-9-]+)' # bare: /proofread at line start or after space
|
|
56
|
+
, re.MULTILINE
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Known non-skill patterns to exclude
|
|
60
|
+
NON_SKILLS = {
|
|
61
|
+
"dev", "tmp", "usr", "bin", "etc", "var", "opt", "home",
|
|
62
|
+
"Users", "Library", "Applications", "System", "Volumes",
|
|
63
|
+
"clear", "help", "compact", "cost", "doctor", "init",
|
|
64
|
+
"login", "logout", "status", "version", "model",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Date pattern from log filenames: YYYY-MM-DD-HHMM.md or YYYY-MM-DD-HHMM-compact.md
|
|
68
|
+
LOG_DATE_RE = re.compile(r"(\d{4}-\d{2}-\d{2})-(\d{2})(\d{2})")
|
|
69
|
+
|
|
70
|
+
# Project header in session logs
|
|
71
|
+
PROJECT_RE = re.compile(r"^## Project:\s*(.+)", re.MULTILINE)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def sha256_prefix(value: str, length: int = 12) -> str:
|
|
75
|
+
return hashlib.sha256(value.encode()).hexdigest()[:length]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_known_skills() -> set[str]:
|
|
79
|
+
"""Load valid skill names from ~/.claude/skills/."""
|
|
80
|
+
skills_dir = Path.home() / ".claude" / "skills"
|
|
81
|
+
if not skills_dir.is_dir():
|
|
82
|
+
return set()
|
|
83
|
+
return {d.name for d in skills_dir.iterdir() if d.is_dir() and (d / "SKILL.md").exists()}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def extract_skills_from_log(filepath: Path, known_skills: set[str]) -> list[dict]:
|
|
87
|
+
"""Extract skill invocations from a session log file."""
|
|
88
|
+
try:
|
|
89
|
+
content = filepath.read_text(encoding="utf-8", errors="replace")
|
|
90
|
+
except OSError:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
# Skip compact logs (auto-generated, minimal skill data)
|
|
94
|
+
if "-compact" in filepath.name:
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
# Extract date from filename
|
|
98
|
+
date_match = LOG_DATE_RE.search(filepath.stem)
|
|
99
|
+
if not date_match:
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
date_str = date_match.group(1)
|
|
103
|
+
hour = date_match.group(2)
|
|
104
|
+
minute = date_match.group(3)
|
|
105
|
+
try:
|
|
106
|
+
log_date = datetime.strptime(f"{date_str}T{hour}:{minute}:00", "%Y-%m-%dT%H:%M:%S")
|
|
107
|
+
log_date = log_date.replace(tzinfo=timezone.utc)
|
|
108
|
+
except ValueError:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
# Extract project name
|
|
112
|
+
project_match = PROJECT_RE.search(content)
|
|
113
|
+
project_label = project_match.group(1).strip() if project_match else "unknown"
|
|
114
|
+
|
|
115
|
+
# Find all skill references
|
|
116
|
+
found_skills: dict[str, int] = {}
|
|
117
|
+
for match in SKILL_PATTERN.finditer(content):
|
|
118
|
+
skill_name = match.group(1) or match.group(2)
|
|
119
|
+
if not skill_name:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# Filter out non-skills
|
|
123
|
+
if skill_name in NON_SKILLS:
|
|
124
|
+
continue
|
|
125
|
+
if len(skill_name) < 2:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Prefer known skills, but also accept plausible names
|
|
129
|
+
# (handles renamed/deleted skills in historical logs)
|
|
130
|
+
if skill_name in known_skills or len(skill_name) >= 3:
|
|
131
|
+
found_skills[skill_name] = found_skills.get(skill_name, 0) + 1
|
|
132
|
+
|
|
133
|
+
# Create events
|
|
134
|
+
events = []
|
|
135
|
+
for skill_name, mention_count in found_skills.items():
|
|
136
|
+
# Validate against known skills (strict mode for non-obvious names)
|
|
137
|
+
if skill_name not in known_skills and len(skill_name) < 5:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
event = {
|
|
141
|
+
"schema": "skill-event.v1",
|
|
142
|
+
"phase": "mined",
|
|
143
|
+
"skill": skill_name,
|
|
144
|
+
"session_hash": sha256_prefix(filepath.name),
|
|
145
|
+
"timestamp": log_date.isoformat(),
|
|
146
|
+
"project_label": project_label,
|
|
147
|
+
"project_hash": sha256_prefix(project_label),
|
|
148
|
+
"source_file": str(filepath.name),
|
|
149
|
+
"mention_count": mention_count,
|
|
150
|
+
"agent_context": "direct",
|
|
151
|
+
}
|
|
152
|
+
events.append(event)
|
|
153
|
+
|
|
154
|
+
return events
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def load_watermark() -> dict:
|
|
158
|
+
"""Load the watermark file tracking which logs have been mined."""
|
|
159
|
+
if WATERMARK_FILE.exists():
|
|
160
|
+
try:
|
|
161
|
+
return json.loads(WATERMARK_FILE.read_text())
|
|
162
|
+
except (json.JSONDecodeError, OSError):
|
|
163
|
+
pass
|
|
164
|
+
return {"mined_files": [], "last_run": None}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def save_watermark(watermark: dict):
|
|
168
|
+
"""Save the watermark file."""
|
|
169
|
+
ECC_DIR.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
os.chmod(ECC_DIR, 0o700)
|
|
171
|
+
old_umask = os.umask(0o077)
|
|
172
|
+
try:
|
|
173
|
+
WATERMARK_FILE.write_text(json.dumps(watermark, indent=2))
|
|
174
|
+
finally:
|
|
175
|
+
os.umask(old_umask)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def write_events(events: list[dict]):
|
|
179
|
+
"""Write events to daily JSONL files."""
|
|
180
|
+
if not events:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
ECC_DIR.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
os.chmod(ECC_DIR, 0o700)
|
|
185
|
+
|
|
186
|
+
# Group events by date (from their timestamp)
|
|
187
|
+
by_date: dict[str, list[dict]] = {}
|
|
188
|
+
for event in events:
|
|
189
|
+
try:
|
|
190
|
+
ts = datetime.fromisoformat(event["timestamp"])
|
|
191
|
+
date_key = ts.strftime("%Y-%m-%d")
|
|
192
|
+
except (KeyError, ValueError):
|
|
193
|
+
date_key = datetime.now().strftime("%Y-%m-%d")
|
|
194
|
+
by_date.setdefault(date_key, []).append(event)
|
|
195
|
+
|
|
196
|
+
old_umask = os.umask(0o077)
|
|
197
|
+
try:
|
|
198
|
+
for date_key, date_events in by_date.items():
|
|
199
|
+
filepath = ECC_DIR / f"observations-{date_key}.jsonl"
|
|
200
|
+
with open(filepath, "a") as f:
|
|
201
|
+
for event in date_events:
|
|
202
|
+
f.write(json.dumps(event, separators=(",", ":")) + "\n")
|
|
203
|
+
finally:
|
|
204
|
+
os.umask(old_umask)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def main():
|
|
208
|
+
parser = argparse.ArgumentParser(description="Mine session logs for skill invocations")
|
|
209
|
+
parser.add_argument("--backfill", action="store_true", help="Re-mine all logs (ignore watermark)")
|
|
210
|
+
parser.add_argument("--dry-run", action="store_true", help="Show what would be mined without writing")
|
|
211
|
+
args = parser.parse_args()
|
|
212
|
+
|
|
213
|
+
known_skills = load_known_skills()
|
|
214
|
+
if not known_skills:
|
|
215
|
+
print("Warning: no skills found in ~/.claude/skills/", file=sys.stderr)
|
|
216
|
+
|
|
217
|
+
log_dirs = find_log_dirs()
|
|
218
|
+
if not log_dirs:
|
|
219
|
+
print("No log directories found.", file=sys.stderr)
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
|
|
222
|
+
watermark = load_watermark()
|
|
223
|
+
mined_set = set(watermark.get("mined_files", [])) if not args.backfill else set()
|
|
224
|
+
|
|
225
|
+
# Collect all log files
|
|
226
|
+
all_logs = []
|
|
227
|
+
for log_dir in log_dirs:
|
|
228
|
+
for filepath in sorted(log_dir.glob("*.md")):
|
|
229
|
+
if filepath.name not in mined_set:
|
|
230
|
+
all_logs.append(filepath)
|
|
231
|
+
|
|
232
|
+
if not all_logs:
|
|
233
|
+
print("No new session logs to mine.")
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
# Mine each log
|
|
237
|
+
total_events = 0
|
|
238
|
+
total_skills = set()
|
|
239
|
+
all_events = []
|
|
240
|
+
newly_mined = []
|
|
241
|
+
|
|
242
|
+
for filepath in all_logs:
|
|
243
|
+
events = extract_skills_from_log(filepath, known_skills)
|
|
244
|
+
if events:
|
|
245
|
+
all_events.extend(events)
|
|
246
|
+
total_events += len(events)
|
|
247
|
+
for e in events:
|
|
248
|
+
total_skills.add(e["skill"])
|
|
249
|
+
if args.dry_run:
|
|
250
|
+
skills_in_log = [e["skill"] for e in events]
|
|
251
|
+
print(f" {filepath.name}: {', '.join(skills_in_log)}")
|
|
252
|
+
newly_mined.append(filepath.name)
|
|
253
|
+
|
|
254
|
+
if args.dry_run:
|
|
255
|
+
print(f"\nDry run: {total_events} events from {len(newly_mined)} logs, "
|
|
256
|
+
f"{len(total_skills)} unique skills")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Write events
|
|
260
|
+
write_events(all_events)
|
|
261
|
+
|
|
262
|
+
# Update watermark
|
|
263
|
+
mined_set.update(newly_mined)
|
|
264
|
+
watermark["mined_files"] = sorted(mined_set)
|
|
265
|
+
watermark["last_run"] = datetime.now(timezone.utc).isoformat()
|
|
266
|
+
save_watermark(watermark)
|
|
267
|
+
|
|
268
|
+
print(f"Mined {total_events} skill references from {len(newly_mined)} logs "
|
|
269
|
+
f"({len(total_skills)} unique skills)")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
main()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Sync Claude Code infrastructure to Codex CLI
|
|
3
|
+
# Usage: bash .scripts/sync-to-codex.sh [--dry-run]
|
|
4
|
+
#
|
|
5
|
+
# Phases:
|
|
6
|
+
# 1. Backup existing ~/.codex/ state
|
|
7
|
+
# 2. Symlink skills from ~/.claude/skills/ → ~/.codex/skills/
|
|
8
|
+
# 3. Generate ~/.codex/AGENTS.md from agents + rules
|
|
9
|
+
# 4. Update ~/.codex/config.toml (model, sandbox, MCP servers)
|
|
10
|
+
# 5. Validate
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
DRY_RUN=false
|
|
15
|
+
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
|
16
|
+
|
|
17
|
+
CLAUDE_HOME="$HOME/.claude"
|
|
18
|
+
CODEX_HOME="$HOME/.codex"
|
|
19
|
+
SKILLS_SRC="$CLAUDE_HOME/skills"
|
|
20
|
+
SKILLS_DST="$CODEX_HOME/skills"
|
|
21
|
+
AGENTS_SRC="$CLAUDE_HOME/agents"
|
|
22
|
+
RULES_SRC="$CLAUDE_HOME/rules"
|
|
23
|
+
|
|
24
|
+
# Resolve Task Management root
|
|
25
|
+
_CONFIG="$HOME/.config/task-mgmt/path"
|
|
26
|
+
if [[ ! -f "$_CONFIG" ]]; then echo "Missing $_CONFIG" >&2; exit 1; fi
|
|
27
|
+
TM="$(head -1 "$_CONFIG" | tr -d '\n')"
|
|
28
|
+
|
|
29
|
+
# Colours
|
|
30
|
+
GREEN='\033[0;32m'
|
|
31
|
+
YELLOW='\033[1;33m'
|
|
32
|
+
RED='\033[0;31m'
|
|
33
|
+
NC='\033[0m'
|
|
34
|
+
|
|
35
|
+
info() { echo -e "${GREEN}✓${NC} $*"; }
|
|
36
|
+
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
|
37
|
+
error() { echo -e "${RED}✗${NC} $*" >&2; }
|
|
38
|
+
|
|
39
|
+
# ── Phase 0: Pre-flight checks ──────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
if [[ ! -d "$CODEX_HOME" ]]; then
|
|
42
|
+
error "Codex home directory not found at $CODEX_HOME"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! command -v codex &>/dev/null; then
|
|
47
|
+
error "Codex CLI not found in PATH"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [[ ! -d "$SKILLS_SRC" ]]; then
|
|
52
|
+
error "Claude skills directory not found at $SKILLS_SRC"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "Syncing Claude Code → Codex CLI"
|
|
57
|
+
echo " Claude home: $CLAUDE_HOME"
|
|
58
|
+
echo " Codex home: $CODEX_HOME"
|
|
59
|
+
echo " Dry run: $DRY_RUN"
|
|
60
|
+
echo ""
|
|
61
|
+
|
|
62
|
+
# ── Phase 1: Backup ─────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
BACKUP_DIR="$CODEX_HOME/backups/$(date +%Y%m%d-%H%M%S)"
|
|
65
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
66
|
+
mkdir -p "$BACKUP_DIR"
|
|
67
|
+
# Back up AGENTS.md and config.toml if they exist
|
|
68
|
+
[[ -f "$CODEX_HOME/AGENTS.md" ]] && cp "$CODEX_HOME/AGENTS.md" "$BACKUP_DIR/"
|
|
69
|
+
[[ -f "$CODEX_HOME/config.toml" ]] && cp "$CODEX_HOME/config.toml" "$BACKUP_DIR/"
|
|
70
|
+
info "Backup saved to $BACKUP_DIR"
|
|
71
|
+
else
|
|
72
|
+
info "[dry-run] Would backup AGENTS.md and config.toml to $BACKUP_DIR"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ── Phase 2: Symlink skills ─────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
echo ""
|
|
78
|
+
echo "Phase 2: Skills"
|
|
79
|
+
|
|
80
|
+
mkdir -p "$SKILLS_DST"
|
|
81
|
+
|
|
82
|
+
# Track counts
|
|
83
|
+
LINKED=0
|
|
84
|
+
SKIPPED=0
|
|
85
|
+
REMOVED=0
|
|
86
|
+
|
|
87
|
+
# Remove stale symlinks (point to skills that no longer exist in Claude)
|
|
88
|
+
for link in "$SKILLS_DST"/*/; do
|
|
89
|
+
[[ ! -L "${link%/}" ]] && continue # skip non-symlinks
|
|
90
|
+
link_name="$(basename "${link%/}")"
|
|
91
|
+
# Skip system skills
|
|
92
|
+
[[ "$link_name" == ".system" ]] && continue
|
|
93
|
+
# Check if symlink target still exists
|
|
94
|
+
if [[ ! -e "${link%/}" ]]; then
|
|
95
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
96
|
+
rm "$SKILLS_DST/$link_name"
|
|
97
|
+
((REMOVED++))
|
|
98
|
+
else
|
|
99
|
+
warn "[dry-run] Would remove stale symlink: $link_name"
|
|
100
|
+
((REMOVED++))
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# Create symlinks for each Claude skill
|
|
106
|
+
for skill_dir in "$SKILLS_SRC"/*/; do
|
|
107
|
+
[[ ! -d "$skill_dir" ]] && continue
|
|
108
|
+
skill_name="$(basename "$skill_dir")"
|
|
109
|
+
|
|
110
|
+
# Skip hidden directories
|
|
111
|
+
[[ "$skill_name" == .* ]] && continue
|
|
112
|
+
|
|
113
|
+
# Check for SKILL.md (required)
|
|
114
|
+
if [[ ! -f "$skill_dir/SKILL.md" ]]; then
|
|
115
|
+
warn "Skipping $skill_name (no SKILL.md)"
|
|
116
|
+
((SKIPPED++))
|
|
117
|
+
continue
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Check for collision with system skills
|
|
121
|
+
if [[ -d "$SKILLS_DST/.system/$skill_name" ]]; then
|
|
122
|
+
warn "Skipping $skill_name (conflicts with Codex system skill)"
|
|
123
|
+
((SKIPPED++))
|
|
124
|
+
continue
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Create or update symlink
|
|
128
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
129
|
+
ln -sfn "$skill_dir" "$SKILLS_DST/$skill_name"
|
|
130
|
+
((LINKED++))
|
|
131
|
+
else
|
|
132
|
+
if [[ -L "$SKILLS_DST/$skill_name" ]]; then
|
|
133
|
+
((LINKED++))
|
|
134
|
+
else
|
|
135
|
+
info "[dry-run] Would link: $skill_name"
|
|
136
|
+
((LINKED++))
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
info "Skills: $LINKED linked, $SKIPPED skipped, $REMOVED stale removed"
|
|
142
|
+
|
|
143
|
+
# ── Phase 3: Generate AGENTS.md ─────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
echo ""
|
|
146
|
+
echo "Phase 3: AGENTS.md"
|
|
147
|
+
|
|
148
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
149
|
+
uv run python "$TM/.scripts/generate-codex-agents-md.py" \
|
|
150
|
+
--agents-dir "$AGENTS_SRC" \
|
|
151
|
+
--rules-dir "$RULES_SRC" \
|
|
152
|
+
--output "$CODEX_HOME/AGENTS.md"
|
|
153
|
+
info "Generated $CODEX_HOME/AGENTS.md"
|
|
154
|
+
else
|
|
155
|
+
info "[dry-run] Would generate AGENTS.md from $AGENTS_SRC + $RULES_SRC"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# ── Phase 4: Update config.toml ─────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
echo ""
|
|
161
|
+
echo "Phase 4: config.toml"
|
|
162
|
+
|
|
163
|
+
CONFIG_FILE="$CODEX_HOME/config.toml"
|
|
164
|
+
|
|
165
|
+
# Read existing config to preserve user settings (model, trust, migrations)
|
|
166
|
+
# We only add/update the sections we manage; we don't overwrite the whole file.
|
|
167
|
+
|
|
168
|
+
# Check if our managed sections already exist
|
|
169
|
+
if grep -q "# --- Managed by sync-to-codex ---" "$CONFIG_FILE" 2>/dev/null; then
|
|
170
|
+
# Remove our managed block and re-append
|
|
171
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
172
|
+
sed -i '' '/^# --- Managed by sync-to-codex ---$/,/^# --- End managed ---$/d' "$CONFIG_FILE"
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
MANAGED_BLOCK=$(cat <<'TOML'
|
|
177
|
+
# --- Managed by sync-to-codex ---
|
|
178
|
+
# Do not edit this section manually; it is regenerated by sync-to-codex.sh
|
|
179
|
+
|
|
180
|
+
[sandbox]
|
|
181
|
+
# Default to workspace-write for interactive use
|
|
182
|
+
permissions = ["disk-full-read-access"]
|
|
183
|
+
|
|
184
|
+
[history]
|
|
185
|
+
# Persist conversation history
|
|
186
|
+
persistence = "save"
|
|
187
|
+
|
|
188
|
+
# --- End managed ---
|
|
189
|
+
TOML
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
193
|
+
echo "" >> "$CONFIG_FILE"
|
|
194
|
+
echo "$MANAGED_BLOCK" >> "$CONFIG_FILE"
|
|
195
|
+
info "Updated config.toml (managed block appended)"
|
|
196
|
+
else
|
|
197
|
+
info "[dry-run] Would append managed block to config.toml"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# ── Phase 5: Validation ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
echo ""
|
|
203
|
+
echo "Phase 5: Validation"
|
|
204
|
+
|
|
205
|
+
ERRORS=0
|
|
206
|
+
|
|
207
|
+
# Check skills count
|
|
208
|
+
SKILL_COUNT=$(find "$SKILLS_DST" -maxdepth 1 -type l | wc -l | tr -d ' ')
|
|
209
|
+
if [[ "$SKILL_COUNT" -gt 0 ]]; then
|
|
210
|
+
info "Skills directory: $SKILL_COUNT symlinks"
|
|
211
|
+
else
|
|
212
|
+
error "No skill symlinks found"
|
|
213
|
+
((ERRORS++))
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Check AGENTS.md exists and is non-empty
|
|
217
|
+
if [[ -f "$CODEX_HOME/AGENTS.md" && -s "$CODEX_HOME/AGENTS.md" ]]; then
|
|
218
|
+
AGENTS_LINES=$(wc -l < "$CODEX_HOME/AGENTS.md" | tr -d ' ')
|
|
219
|
+
info "AGENTS.md: $AGENTS_LINES lines"
|
|
220
|
+
else
|
|
221
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
222
|
+
error "AGENTS.md missing or empty"
|
|
223
|
+
((ERRORS++))
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# Check config.toml is parseable (basic check)
|
|
228
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
229
|
+
if python3 -c "
|
|
230
|
+
import tomllib, sys
|
|
231
|
+
with open('$CONFIG_FILE', 'rb') as f:
|
|
232
|
+
tomllib.load(f)
|
|
233
|
+
" 2>/dev/null; then
|
|
234
|
+
info "config.toml: valid TOML"
|
|
235
|
+
else
|
|
236
|
+
error "config.toml: invalid TOML"
|
|
237
|
+
((ERRORS++))
|
|
238
|
+
fi
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Check Codex CLI version
|
|
242
|
+
CODEX_VERSION=$(codex --version 2>/dev/null || echo "unknown")
|
|
243
|
+
info "Codex CLI: $CODEX_VERSION"
|
|
244
|
+
|
|
245
|
+
# Summary
|
|
246
|
+
echo ""
|
|
247
|
+
if [[ "$ERRORS" -eq 0 ]]; then
|
|
248
|
+
echo -e "${GREEN}Sync complete — no errors.${NC}"
|
|
249
|
+
else
|
|
250
|
+
echo -e "${RED}Sync completed with $ERRORS error(s).${NC}"
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|