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,81 @@
|
|
|
1
|
+
"""Abstract base for CLI backends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from time import perf_counter
|
|
11
|
+
|
|
12
|
+
from cli_council.config import STAGE_TIMEOUT
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CLIBackend(ABC):
|
|
18
|
+
"""Base class for CLI tool backends."""
|
|
19
|
+
|
|
20
|
+
name: str = ""
|
|
21
|
+
command: str = ""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def build_args(self, prompt: str, *, model: str | None = None) -> list[str]:
|
|
25
|
+
"""Build the full argument list for subprocess execution."""
|
|
26
|
+
|
|
27
|
+
def is_available(self) -> bool:
|
|
28
|
+
"""Check if the CLI tool is installed and on PATH."""
|
|
29
|
+
return shutil.which(self.command) is not None
|
|
30
|
+
|
|
31
|
+
async def run(
|
|
32
|
+
self,
|
|
33
|
+
prompt: str,
|
|
34
|
+
*,
|
|
35
|
+
model: str | None = None,
|
|
36
|
+
timeout: int = STAGE_TIMEOUT,
|
|
37
|
+
cwd: str | None = None,
|
|
38
|
+
) -> tuple[str, int]:
|
|
39
|
+
"""Execute the CLI tool and return (output_text, elapsed_ms).
|
|
40
|
+
|
|
41
|
+
Raises RuntimeError if the command fails or times out.
|
|
42
|
+
"""
|
|
43
|
+
args = self.build_args(prompt, model=model)
|
|
44
|
+
logger.info("Running %s: %s", self.name, " ".join(args[:4]) + "...")
|
|
45
|
+
|
|
46
|
+
# Clean environment: unset CLAUDECODE to allow nested claude calls
|
|
47
|
+
env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}
|
|
48
|
+
|
|
49
|
+
t0 = perf_counter()
|
|
50
|
+
try:
|
|
51
|
+
proc = await asyncio.create_subprocess_exec(
|
|
52
|
+
*args,
|
|
53
|
+
stdout=asyncio.subprocess.PIPE,
|
|
54
|
+
stderr=asyncio.subprocess.PIPE,
|
|
55
|
+
cwd=cwd,
|
|
56
|
+
env=env,
|
|
57
|
+
)
|
|
58
|
+
stdout, stderr = await asyncio.wait_for(
|
|
59
|
+
proc.communicate(), timeout=timeout,
|
|
60
|
+
)
|
|
61
|
+
except asyncio.TimeoutError:
|
|
62
|
+
proc.kill()
|
|
63
|
+
elapsed = int((perf_counter() - t0) * 1000)
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"{self.name} timed out after {timeout}s"
|
|
66
|
+
) from None
|
|
67
|
+
|
|
68
|
+
elapsed = int((perf_counter() - t0) * 1000)
|
|
69
|
+
output = stdout.decode("utf-8", errors="replace").strip()
|
|
70
|
+
|
|
71
|
+
if proc.returncode != 0:
|
|
72
|
+
err = stderr.decode("utf-8", errors="replace").strip()
|
|
73
|
+
logger.warning(
|
|
74
|
+
"%s exited with code %d: %s",
|
|
75
|
+
self.name, proc.returncode, err[:200],
|
|
76
|
+
)
|
|
77
|
+
# Some backends write useful output to stderr on non-zero exit
|
|
78
|
+
if not output and err:
|
|
79
|
+
output = err
|
|
80
|
+
|
|
81
|
+
return output, elapsed
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Claude Code CLI backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from cli_council.backends.base import CLIBackend
|
|
6
|
+
from cli_council.config import BACKENDS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClaudeBackend(CLIBackend):
|
|
10
|
+
"""Wrapper for Anthropic's Claude Code CLI (claude -p)."""
|
|
11
|
+
|
|
12
|
+
name = "claude"
|
|
13
|
+
command = "claude"
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: str | None = None) -> None:
|
|
16
|
+
spec = BACKENDS["claude"]
|
|
17
|
+
self.default_model = model or spec.default_model
|
|
18
|
+
|
|
19
|
+
def build_args(self, prompt: str, *, model: str | None = None) -> list[str]:
|
|
20
|
+
effective_model = model or self.default_model
|
|
21
|
+
return [
|
|
22
|
+
"claude",
|
|
23
|
+
"-p", prompt,
|
|
24
|
+
"--model", effective_model,
|
|
25
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Codex CLI backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from cli_council.backends.base import CLIBackend
|
|
6
|
+
from cli_council.config import BACKENDS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodexBackend(CLIBackend):
|
|
10
|
+
"""Wrapper for OpenAI's Codex CLI (codex exec)."""
|
|
11
|
+
|
|
12
|
+
name = "codex"
|
|
13
|
+
command = "codex"
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: str | None = None) -> None:
|
|
16
|
+
spec = BACKENDS["codex"]
|
|
17
|
+
self.default_model = model or spec.default_model
|
|
18
|
+
|
|
19
|
+
def build_args(self, prompt: str, *, model: str | None = None) -> list[str]:
|
|
20
|
+
effective_model = model or self.default_model
|
|
21
|
+
args = ["codex", "exec"]
|
|
22
|
+
# Skip -m flag when using default — ChatGPT accounts don't support
|
|
23
|
+
# explicit model selection but use the best available (GPT-5).
|
|
24
|
+
if effective_model != "default":
|
|
25
|
+
args.extend(["-m", effective_model])
|
|
26
|
+
args.extend(["--full-auto", prompt])
|
|
27
|
+
return args
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Gemini CLI backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from cli_council.backends.base import CLIBackend
|
|
6
|
+
from cli_council.config import BACKENDS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GeminiBackend(CLIBackend):
|
|
10
|
+
"""Wrapper for Google's Gemini CLI (gemini -p)."""
|
|
11
|
+
|
|
12
|
+
name = "gemini"
|
|
13
|
+
command = "gemini"
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: str | None = None) -> None:
|
|
16
|
+
spec = BACKENDS["gemini"]
|
|
17
|
+
self.default_model = model or spec.default_model
|
|
18
|
+
|
|
19
|
+
def build_args(self, prompt: str, *, model: str | None = None) -> list[str]:
|
|
20
|
+
effective_model = model or self.default_model
|
|
21
|
+
return [
|
|
22
|
+
"gemini",
|
|
23
|
+
"-p", prompt,
|
|
24
|
+
"-m", effective_model,
|
|
25
|
+
"-o", "text",
|
|
26
|
+
]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Checkpoint and file-based coordination for council runs.
|
|
2
|
+
|
|
3
|
+
Inspired by:
|
|
4
|
+
- Owlex (agentic-mcp-tools/owlex): session resumption across deliberation rounds
|
|
5
|
+
- agents-council (MrLesk/agents-council): atomic file writes, cursor-based state
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
- Atomic JSON writes (tmp + fsync + rename) for crash safety
|
|
9
|
+
- Stage checkpointing: save after each stage, resume from last completed stage
|
|
10
|
+
- Pending participant tracking
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import tempfile
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Default checkpoint directory (gitignored in projects)
|
|
26
|
+
DEFAULT_CHECKPOINT_DIR = ".council"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class CheckpointMeta:
|
|
31
|
+
"""Metadata for a checkpoint file."""
|
|
32
|
+
|
|
33
|
+
run_id: str
|
|
34
|
+
stage: int # 1, 2, or 3
|
|
35
|
+
timestamp: str
|
|
36
|
+
backends: list[str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _atomic_write_json(path: Path, data: dict) -> None:
|
|
40
|
+
"""Write JSON atomically: tmp file -> fsync -> rename.
|
|
41
|
+
|
|
42
|
+
If the process crashes mid-write, the original file is preserved.
|
|
43
|
+
"""
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
46
|
+
dir=str(path.parent), suffix=".tmp", prefix=".ckpt-",
|
|
47
|
+
)
|
|
48
|
+
try:
|
|
49
|
+
with os.fdopen(fd, "w") as f:
|
|
50
|
+
json.dump(data, f, indent=2, default=str)
|
|
51
|
+
f.flush()
|
|
52
|
+
os.fsync(f.fileno())
|
|
53
|
+
os.rename(tmp_path, str(path))
|
|
54
|
+
except BaseException:
|
|
55
|
+
# Clean up temp file on any failure
|
|
56
|
+
try:
|
|
57
|
+
os.unlink(tmp_path)
|
|
58
|
+
except OSError:
|
|
59
|
+
pass
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read_json(path: Path) -> dict | None:
|
|
64
|
+
"""Read a JSON checkpoint file, returning None if missing or corrupt."""
|
|
65
|
+
if not path.exists():
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
return json.loads(path.read_text())
|
|
69
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
70
|
+
logger.warning("Corrupt checkpoint %s: %s", path, exc)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _make_run_id() -> str:
|
|
75
|
+
"""Generate a timestamped run ID."""
|
|
76
|
+
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class CouncilCheckpointer:
|
|
81
|
+
"""Manages checkpoint state for a council run.
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
ckpt = CouncilCheckpointer(checkpoint_dir="/path/to/.council")
|
|
85
|
+
# Saves after each stage; can resume from last completed stage.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
checkpoint_dir: Path
|
|
89
|
+
run_id: str = field(default_factory=_make_run_id)
|
|
90
|
+
|
|
91
|
+
def __post_init__(self) -> None:
|
|
92
|
+
self.checkpoint_dir = Path(self.checkpoint_dir)
|
|
93
|
+
|
|
94
|
+
# ---- Save ----
|
|
95
|
+
|
|
96
|
+
def save_stage1(self, assessments: list[dict], backends: list[str]) -> Path:
|
|
97
|
+
"""Save Stage 1 assessments to checkpoint."""
|
|
98
|
+
path = self._stage_path(1)
|
|
99
|
+
data = {
|
|
100
|
+
"meta": {
|
|
101
|
+
"run_id": self.run_id,
|
|
102
|
+
"stage": 1,
|
|
103
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
104
|
+
"backends": backends,
|
|
105
|
+
},
|
|
106
|
+
"assessments": assessments,
|
|
107
|
+
}
|
|
108
|
+
_atomic_write_json(path, data)
|
|
109
|
+
logger.info("Checkpoint saved: Stage 1 → %s", path)
|
|
110
|
+
return path
|
|
111
|
+
|
|
112
|
+
def save_stage2(
|
|
113
|
+
self,
|
|
114
|
+
peer_reviews: list[dict],
|
|
115
|
+
backends: list[str],
|
|
116
|
+
aggregate_rankings: list[dict] | None = None,
|
|
117
|
+
) -> Path:
|
|
118
|
+
"""Save Stage 2 peer reviews to checkpoint."""
|
|
119
|
+
path = self._stage_path(2)
|
|
120
|
+
data = {
|
|
121
|
+
"meta": {
|
|
122
|
+
"run_id": self.run_id,
|
|
123
|
+
"stage": 2,
|
|
124
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
125
|
+
"backends": backends,
|
|
126
|
+
},
|
|
127
|
+
"peer_reviews": peer_reviews,
|
|
128
|
+
"aggregate_rankings": aggregate_rankings or [],
|
|
129
|
+
}
|
|
130
|
+
_atomic_write_json(path, data)
|
|
131
|
+
logger.info("Checkpoint saved: Stage 2 → %s", path)
|
|
132
|
+
return path
|
|
133
|
+
|
|
134
|
+
def save_stage3(self, synthesis: dict | str, chairman: str) -> Path:
|
|
135
|
+
"""Save Stage 3 synthesis to checkpoint."""
|
|
136
|
+
path = self._stage_path(3)
|
|
137
|
+
data = {
|
|
138
|
+
"meta": {
|
|
139
|
+
"run_id": self.run_id,
|
|
140
|
+
"stage": 3,
|
|
141
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
142
|
+
"chairman": chairman,
|
|
143
|
+
},
|
|
144
|
+
"synthesis": synthesis,
|
|
145
|
+
}
|
|
146
|
+
_atomic_write_json(path, data)
|
|
147
|
+
logger.info("Checkpoint saved: Stage 3 → %s", path)
|
|
148
|
+
return path
|
|
149
|
+
|
|
150
|
+
# ---- Load / Resume ----
|
|
151
|
+
|
|
152
|
+
def last_completed_stage(self) -> int:
|
|
153
|
+
"""Return the highest completed stage (0 if none)."""
|
|
154
|
+
for stage in (3, 2, 1):
|
|
155
|
+
if self._stage_path(stage).exists():
|
|
156
|
+
return stage
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
def load_stage1(self) -> list[dict] | None:
|
|
160
|
+
"""Load Stage 1 assessments from checkpoint."""
|
|
161
|
+
data = _read_json(self._stage_path(1))
|
|
162
|
+
if data is None:
|
|
163
|
+
return None
|
|
164
|
+
return data.get("assessments")
|
|
165
|
+
|
|
166
|
+
def load_stage2(self) -> tuple[list[dict], list[dict]] | None:
|
|
167
|
+
"""Load Stage 2 peer reviews + aggregate rankings from checkpoint."""
|
|
168
|
+
data = _read_json(self._stage_path(2))
|
|
169
|
+
if data is None:
|
|
170
|
+
return None
|
|
171
|
+
return data.get("peer_reviews", []), data.get("aggregate_rankings", [])
|
|
172
|
+
|
|
173
|
+
def load_stage3(self) -> dict | str | None:
|
|
174
|
+
"""Load Stage 3 synthesis from checkpoint."""
|
|
175
|
+
data = _read_json(self._stage_path(3))
|
|
176
|
+
if data is None:
|
|
177
|
+
return None
|
|
178
|
+
return data.get("synthesis")
|
|
179
|
+
|
|
180
|
+
# ---- Pending participants ----
|
|
181
|
+
|
|
182
|
+
def pending_participants(
|
|
183
|
+
self, all_backends: list[str], responded: list[str],
|
|
184
|
+
) -> list[str]:
|
|
185
|
+
"""Return backends that haven't responded yet."""
|
|
186
|
+
return [b for b in all_backends if b not in set(responded)]
|
|
187
|
+
|
|
188
|
+
# ---- Discovery ----
|
|
189
|
+
|
|
190
|
+
def find_latest_run(self) -> str | None:
|
|
191
|
+
"""Find the most recent run_id in the checkpoint directory."""
|
|
192
|
+
if not self.checkpoint_dir.exists():
|
|
193
|
+
return None
|
|
194
|
+
# Look for stage-1 files (every run has one)
|
|
195
|
+
stage1_files = sorted(self.checkpoint_dir.glob("*-stage1.json"), reverse=True)
|
|
196
|
+
if not stage1_files:
|
|
197
|
+
return None
|
|
198
|
+
# Extract run_id from filename: {run_id}-stage1.json
|
|
199
|
+
return stage1_files[0].stem.replace("-stage1", "")
|
|
200
|
+
|
|
201
|
+
def clean(self) -> None:
|
|
202
|
+
"""Remove all checkpoint files for this run."""
|
|
203
|
+
for stage in (1, 2, 3):
|
|
204
|
+
path = self._stage_path(stage)
|
|
205
|
+
if path.exists():
|
|
206
|
+
path.unlink()
|
|
207
|
+
logger.info("Removed checkpoint: %s", path)
|
|
208
|
+
|
|
209
|
+
# ---- Internal ----
|
|
210
|
+
|
|
211
|
+
def _stage_path(self, stage: int) -> Path:
|
|
212
|
+
return self.checkpoint_dir / f"{self.run_id}-stage{stage}.json"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Backend registry and defaults."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class BackendSpec:
|
|
10
|
+
"""Specification for a CLI backend."""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
command: str # binary name
|
|
14
|
+
headless_flag: str # flag for non-interactive mode
|
|
15
|
+
model_flag: str # flag to select model (empty if not supported)
|
|
16
|
+
default_model: str # default model name
|
|
17
|
+
output_flag: str # flag for output format (empty if not needed)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
BACKENDS: dict[str, BackendSpec] = {
|
|
21
|
+
"gemini": BackendSpec(
|
|
22
|
+
name="gemini",
|
|
23
|
+
command="gemini",
|
|
24
|
+
headless_flag="-p",
|
|
25
|
+
model_flag="-m",
|
|
26
|
+
default_model="gemini-2.5-pro",
|
|
27
|
+
output_flag="-o text",
|
|
28
|
+
),
|
|
29
|
+
"codex": BackendSpec(
|
|
30
|
+
name="codex",
|
|
31
|
+
command="codex",
|
|
32
|
+
headless_flag="exec",
|
|
33
|
+
model_flag="-m",
|
|
34
|
+
default_model="default",
|
|
35
|
+
output_flag="",
|
|
36
|
+
),
|
|
37
|
+
"claude": BackendSpec(
|
|
38
|
+
name="claude",
|
|
39
|
+
command="claude",
|
|
40
|
+
headless_flag="-p",
|
|
41
|
+
model_flag="--model",
|
|
42
|
+
default_model="claude-sonnet-4-6",
|
|
43
|
+
output_flag="",
|
|
44
|
+
),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
DEFAULT_COUNCIL_BACKENDS: list[str] = ["gemini", "codex", "claude"]
|
|
48
|
+
DEFAULT_CHAIRMAN: str = "claude"
|
|
49
|
+
|
|
50
|
+
# Subprocess timeout in seconds per stage
|
|
51
|
+
STAGE_TIMEOUT: int = 120
|