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,161 @@
|
|
|
1
|
+
# Automation Scripts
|
|
2
|
+
|
|
3
|
+
> Python utilities for task management automation.
|
|
4
|
+
|
|
5
|
+
> **⚠️ DEPRECATED (March 2026):** The Notion-based CLI tools below are superseded by the Research Vault (`~/Research-Vault`) and the `taskflow` MCP server. They remain here for reference but are no longer the active workflow. Use `taskflow` MCP tools instead.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
### CLI Tools (Legacy — Notion-based)
|
|
10
|
+
|
|
11
|
+
| Script | Purpose | Replacement |
|
|
12
|
+
|--------|---------|-------------|
|
|
13
|
+
| `task` | Create a new task | `mcp__taskflow__create_task` |
|
|
14
|
+
| `tasks` | Query and list tasks | `mcp__taskflow__list_tasks` |
|
|
15
|
+
| `done` | Mark tasks as completed | `mcp__taskflow__complete_task` |
|
|
16
|
+
| `focus` | Update current focus file | Still active (no Notion dependency) |
|
|
17
|
+
| `papers` | Research pipeline management | `mcp__taskflow__list_papers` |
|
|
18
|
+
| `inbox` | Process tasks without dates | `mcp__taskflow__search_tasks` |
|
|
19
|
+
| `week` | Weekly summary | Vault-based weekly review |
|
|
20
|
+
| `conf` | Conference tracking | `mcp__taskflow__list_venues` |
|
|
21
|
+
| `query` | Search context files | Still active (no Notion dependency) |
|
|
22
|
+
|
|
23
|
+
### Helper Modules (Legacy)
|
|
24
|
+
|
|
25
|
+
| Script | Purpose | Status |
|
|
26
|
+
|--------|---------|--------|
|
|
27
|
+
| `config.py` | Configuration (auto-loads `.env`) | Deprecated — vault needs no API token |
|
|
28
|
+
| `notion_helpers.py` | Utility functions for Notion API | Deprecated — replaced by taskflow MCP |
|
|
29
|
+
| `extract_meeting_actions.py` | Extract action items from meeting transcripts | Still useful (output now goes to vault) |
|
|
30
|
+
| `daily_digest.py` | Generate daily planning briefings | Deprecated — use vault tasks directly |
|
|
31
|
+
|
|
32
|
+
### Shared Libraries
|
|
33
|
+
|
|
34
|
+
| Directory | Purpose |
|
|
35
|
+
|-----------|---------|
|
|
36
|
+
| `openalex/` | OpenAlex scholarly API client — shared across `/literature`, `/bib-validate`, `/split-pdf`, and the OpenAlex MCP server |
|
|
37
|
+
|
|
38
|
+
## How They Work with Claude
|
|
39
|
+
|
|
40
|
+
These scripts are designed for **Claude-assisted automation**, not fully autonomous execution. When you ask Claude to help with tasks like:
|
|
41
|
+
|
|
42
|
+
- "Help me plan my day"
|
|
43
|
+
- "Extract action items from yesterday's meeting"
|
|
44
|
+
- "What tasks are overdue?"
|
|
45
|
+
|
|
46
|
+
Claude uses these scripts as reference for:
|
|
47
|
+
1. **What to query** from Notion
|
|
48
|
+
2. **How to format** results
|
|
49
|
+
3. **What questions** to ask you
|
|
50
|
+
4. **How to create** new tasks
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
### 1. Notion Integration
|
|
55
|
+
|
|
56
|
+
The scripts auto-load the `.env` file from the project root.
|
|
57
|
+
|
|
58
|
+
**Option A: Use .env (recommended)**
|
|
59
|
+
```bash
|
|
60
|
+
# .env file already exists with your token
|
|
61
|
+
cd "Task Management"
|
|
62
|
+
uv run python .scripts/tasks
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Option B: Environment variable**
|
|
66
|
+
```bash
|
|
67
|
+
export NOTION_TOKEN="your-token-here"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Option C: Source .env manually**
|
|
71
|
+
```bash
|
|
72
|
+
source .env
|
|
73
|
+
python .scripts/tasks
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Make sure to share your databases with the integration in Notion.
|
|
77
|
+
|
|
78
|
+
### 2. Database IDs
|
|
79
|
+
|
|
80
|
+
The `config.py` file contains your database IDs:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
DATABASES = {
|
|
84
|
+
"tasks_tracker": "YOUR-TASKS-DATABASE-ID-HERE",
|
|
85
|
+
"research_pipeline": "YOUR-PIPELINE-DATABASE-ID-HERE",
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### With Claude/Cowork (Recommended)
|
|
92
|
+
|
|
93
|
+
Just ask Claude naturally:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
"Help me plan my day"
|
|
97
|
+
→ Claude reads context, queries Notion, asks you questions, creates a plan
|
|
98
|
+
|
|
99
|
+
"Extract actions from my meeting with [Supervisor] yesterday"
|
|
100
|
+
→ Claude finds the transcript, extracts actions, creates tasks
|
|
101
|
+
|
|
102
|
+
"What's overdue?"
|
|
103
|
+
→ Claude queries Tasks Tracker and summarises
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Standalone (Advanced)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Daily digest
|
|
110
|
+
python daily_digest.py --date "2026-01-29"
|
|
111
|
+
|
|
112
|
+
# Meeting actions (dry run)
|
|
113
|
+
python extract_meeting_actions.py --dry-run
|
|
114
|
+
|
|
115
|
+
# Show Claude instructions
|
|
116
|
+
python daily_digest.py --instructions
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Extending
|
|
120
|
+
|
|
121
|
+
### Adding a New Workflow
|
|
122
|
+
|
|
123
|
+
1. Create a new script in `.scripts/`
|
|
124
|
+
2. Add a corresponding workflow file in `.context/workflows/`
|
|
125
|
+
3. Document how Claude should use it
|
|
126
|
+
|
|
127
|
+
### Adding New Task Types
|
|
128
|
+
|
|
129
|
+
Edit `config.py`:
|
|
130
|
+
```python
|
|
131
|
+
TASK_TYPES = [
|
|
132
|
+
# ... existing types
|
|
133
|
+
"🆕 New Type",
|
|
134
|
+
]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Then update the Notion database to include the new option.
|
|
138
|
+
|
|
139
|
+
## Troubleshooting
|
|
140
|
+
|
|
141
|
+
### "NOTION_TOKEN not set"
|
|
142
|
+
The scripts work best with Claude's built-in Notion integration. For standalone use, you need to set up a Notion integration token.
|
|
143
|
+
|
|
144
|
+
### Tasks not appearing
|
|
145
|
+
Make sure:
|
|
146
|
+
1. The database is shared with your Notion integration
|
|
147
|
+
2. The database IDs in `config.py` match your actual databases
|
|
148
|
+
|
|
149
|
+
### Claude doesn't follow the workflow
|
|
150
|
+
Point Claude to the specific workflow file:
|
|
151
|
+
```
|
|
152
|
+
"Read .context/workflows/daily-review.md and help me plan my day"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Files Changed
|
|
156
|
+
|
|
157
|
+
When running these scripts or using the workflows, the following may be updated:
|
|
158
|
+
|
|
159
|
+
- `.context/current-focus.md` - Updated after planning sessions
|
|
160
|
+
- Notion Tasks Tracker - New tasks created
|
|
161
|
+
- Notion Research Pipeline - Paper status updates
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AI Pattern Density Scanner
|
|
4
|
+
|
|
5
|
+
Scans text or LaTeX files for AI-generated writing patterns and reports density scores.
|
|
6
|
+
Based on Weiai Wayne Xu's CommScribe anti-AI pattern catalogue (286 patterns, 10 categories).
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
uv run python .scripts/ai_pattern_density.py <file> # Scan a file
|
|
10
|
+
uv run python .scripts/ai_pattern_density.py <file> --sections # Per-section breakdown (LaTeX)
|
|
11
|
+
uv run python .scripts/ai_pattern_density.py <file> --json # Machine-readable output
|
|
12
|
+
uv run python .scripts/ai_pattern_density.py --stdin # Read from stdin
|
|
13
|
+
|
|
14
|
+
Density thresholds (patterns per 100 words):
|
|
15
|
+
< 0.5 Excellent Indistinguishable from human
|
|
16
|
+
0.5-1.0 Good Minor patterns, likely passes detection
|
|
17
|
+
1.0-2.0 Caution Some patterns, may flag detectors
|
|
18
|
+
2.0-3.0 Poor Noticeable AI patterns
|
|
19
|
+
> 3.0 Bad Clearly AI-generated
|
|
20
|
+
|
|
21
|
+
Attribution: Pattern catalogue from github.com/weiaiwayne/commscribe
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
import json
|
|
27
|
+
import argparse
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Pattern catalogue (10 categories, 286 patterns)
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class PatternCategory:
|
|
39
|
+
name: str
|
|
40
|
+
patterns: list[str]
|
|
41
|
+
# Patterns that need word-boundary matching (short words that would false-positive)
|
|
42
|
+
word_boundary: bool = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
CATEGORIES: list[PatternCategory] = [
|
|
46
|
+
PatternCategory(
|
|
47
|
+
name="Generic Openers",
|
|
48
|
+
patterns=[
|
|
49
|
+
"in today's", "in recent years", "in the realm of", "in the world of",
|
|
50
|
+
"in the context of", "in the field of", "in this day and age",
|
|
51
|
+
"throughout history", "since the dawn of time", "as we navigate",
|
|
52
|
+
"as we delve into", "as technology continues to evolve",
|
|
53
|
+
"with the rise of", "with the advent of",
|
|
54
|
+
"with the increasing importance of", "given the importance of",
|
|
55
|
+
"when it comes to", "when we think about",
|
|
56
|
+
"it is no secret that", "it goes without saying that",
|
|
57
|
+
"it is widely acknowledged that", "it is commonly known that",
|
|
58
|
+
"it is well established that",
|
|
59
|
+
],
|
|
60
|
+
),
|
|
61
|
+
PatternCategory(
|
|
62
|
+
name="Importance/Noting Phrases",
|
|
63
|
+
patterns=[
|
|
64
|
+
"it is important to note that", "it is worth noting that",
|
|
65
|
+
"it should be noted that", "it is crucial to understand that",
|
|
66
|
+
"it is essential to recognize that", "it is vital to consider that",
|
|
67
|
+
"it bears mentioning that", "it must be emphasized that",
|
|
68
|
+
"it cannot be overstated that",
|
|
69
|
+
"what's important here is", "the key point is that",
|
|
70
|
+
"the crucial aspect is", "one important thing to consider is",
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
PatternCategory(
|
|
74
|
+
name="Overused Transitions",
|
|
75
|
+
patterns=[
|
|
76
|
+
"furthermore,", "moreover,", "additionally,", "in addition,",
|
|
77
|
+
"consequently,", "subsequently,", "accordingly,",
|
|
78
|
+
"as a result,", "in conclusion,", "to summarize,", "in summary,",
|
|
79
|
+
"to sum up,", "all in all,", "on the other hand,", "conversely,",
|
|
80
|
+
"nevertheless,", "nonetheless,", "that being said,",
|
|
81
|
+
"with that said,", "having said that,", "that said,",
|
|
82
|
+
"be that as it may,", "first and foremost,", "last but not least,",
|
|
83
|
+
],
|
|
84
|
+
),
|
|
85
|
+
PatternCategory(
|
|
86
|
+
name="Excessive Hedging",
|
|
87
|
+
patterns=[
|
|
88
|
+
"may or may not", "could potentially", "might possibly",
|
|
89
|
+
"to some extent", "in some ways", "in certain respects",
|
|
90
|
+
"to a certain degree", "it seems that", "it appears that",
|
|
91
|
+
"it would seem that", "one could argue that",
|
|
92
|
+
"some might say that", "there is a possibility that",
|
|
93
|
+
"it is possible that", "it is likely that", "has the potential to",
|
|
94
|
+
],
|
|
95
|
+
),
|
|
96
|
+
PatternCategory(
|
|
97
|
+
name="Filler/Padding Phrases",
|
|
98
|
+
patterns=[
|
|
99
|
+
"a wide range of", "a variety of", "a plethora of", "a myriad of",
|
|
100
|
+
"a multitude of", "an array of", "a vast array of",
|
|
101
|
+
"many different", "several key",
|
|
102
|
+
"plays a role in", "plays a crucial role in",
|
|
103
|
+
"plays a vital role in", "plays an important role in",
|
|
104
|
+
"is considered to be", "can be seen as", "is known to be",
|
|
105
|
+
"is said to be", "in terms of", "with regard to",
|
|
106
|
+
"with respect to", "in relation to", "pertaining to",
|
|
107
|
+
"the fact that", "due to the fact that", "owing to the fact that",
|
|
108
|
+
"despite the fact that", "in light of the fact that",
|
|
109
|
+
"given the fact that",
|
|
110
|
+
],
|
|
111
|
+
),
|
|
112
|
+
PatternCategory(
|
|
113
|
+
name="Structural Patterns",
|
|
114
|
+
patterns=[
|
|
115
|
+
"let's dive in", "let's explore", "let's delve into",
|
|
116
|
+
"let's take a closer look", "let's examine", "let's unpack",
|
|
117
|
+
"let me explain", "allow me to", "i'd be happy to",
|
|
118
|
+
"great question", "that's a great question",
|
|
119
|
+
"here's the thing", "here's what you need to know",
|
|
120
|
+
"the bottom line is", "at the end of the day",
|
|
121
|
+
"when all is said and done", "moving forward", "going forward",
|
|
122
|
+
"looking ahead", "consider the following",
|
|
123
|
+
"the following points illustrate",
|
|
124
|
+
],
|
|
125
|
+
),
|
|
126
|
+
PatternCategory(
|
|
127
|
+
name="Inflated Adjectives",
|
|
128
|
+
word_boundary=True,
|
|
129
|
+
patterns=[
|
|
130
|
+
"groundbreaking", "revolutionary", "transformative",
|
|
131
|
+
"game-changing", "cutting-edge", "state-of-the-art",
|
|
132
|
+
"world-class", "best-in-class", "holistic", "synergistic",
|
|
133
|
+
"seamlessly", "effortlessly",
|
|
134
|
+
],
|
|
135
|
+
),
|
|
136
|
+
PatternCategory(
|
|
137
|
+
name="Academic AI Patterns",
|
|
138
|
+
patterns=[
|
|
139
|
+
"this paper explores", "this study aims to",
|
|
140
|
+
"this research investigates", "the purpose of this study is to",
|
|
141
|
+
"the aim of this paper is to", "we aim to contribute to",
|
|
142
|
+
"this work contributes to", "fills a gap in the literature",
|
|
143
|
+
"addresses a gap in", "contributes to our understanding of",
|
|
144
|
+
"sheds light on", "provides insights into",
|
|
145
|
+
"offers a nuanced understanding of",
|
|
146
|
+
"provides a comprehensive overview of",
|
|
147
|
+
"presents a systematic analysis of",
|
|
148
|
+
"in line with previous research", "consistent with prior studies",
|
|
149
|
+
"the findings suggest that", "the results indicate that",
|
|
150
|
+
"the data reveal that", "our analysis shows that",
|
|
151
|
+
"future research should", "further research is needed",
|
|
152
|
+
"more research is warranted", "limitations notwithstanding",
|
|
153
|
+
"despite these limitations",
|
|
154
|
+
],
|
|
155
|
+
),
|
|
156
|
+
PatternCategory(
|
|
157
|
+
name="Conclusion Clichés",
|
|
158
|
+
patterns=[
|
|
159
|
+
"in conclusion,", "to conclude,", "in closing,",
|
|
160
|
+
"all in all,", "taken together,", "in the final analysis,",
|
|
161
|
+
"at the end of the day,", "when all is said and done,",
|
|
162
|
+
"the bottom line is", "the takeaway is", "the key takeaway is",
|
|
163
|
+
"what we can learn from this is", "this goes to show that",
|
|
164
|
+
"this highlights the importance of",
|
|
165
|
+
"this underscores the need for", "as we move forward,",
|
|
166
|
+
"only time will tell", "remains to be seen",
|
|
167
|
+
"the jury is still out",
|
|
168
|
+
],
|
|
169
|
+
),
|
|
170
|
+
PatternCategory(
|
|
171
|
+
name="AI Vocabulary (high-frequency)",
|
|
172
|
+
word_boundary=True,
|
|
173
|
+
patterns=[
|
|
174
|
+
"delve", "delving", "delved",
|
|
175
|
+
"landscape", "tapestry", "beacon",
|
|
176
|
+
"pivotal", "commendable", "meticulous",
|
|
177
|
+
"intricate", "nuanced", "multifaceted",
|
|
178
|
+
"underscores", "underscored", "underscoring",
|
|
179
|
+
"foster", "fostering", "fostered",
|
|
180
|
+
"leverage", "leveraging", "leveraged",
|
|
181
|
+
"navigate", "navigating", "navigated",
|
|
182
|
+
"embark", "embarking", "embarked",
|
|
183
|
+
"realm", "endeavor", "endeavors",
|
|
184
|
+
"aligns", "crucial",
|
|
185
|
+
"robust", "comprehensive",
|
|
186
|
+
"innovative", "novel",
|
|
187
|
+
],
|
|
188
|
+
),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# LaTeX stripping
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
def strip_latex(text: str) -> str:
|
|
197
|
+
"""Remove LaTeX commands, environments, and math to get prose-only text."""
|
|
198
|
+
# Remove comments
|
|
199
|
+
text = re.sub(r'%.*$', '', text, flags=re.MULTILINE)
|
|
200
|
+
# Remove \begin{...} and \end{...}
|
|
201
|
+
text = re.sub(r'\\(begin|end)\{[^}]*\}', '', text)
|
|
202
|
+
# Remove display math
|
|
203
|
+
text = re.sub(r'\$\$.*?\$\$', '', text, flags=re.DOTALL)
|
|
204
|
+
text = re.sub(r'\\\[.*?\\\]', '', text, flags=re.DOTALL)
|
|
205
|
+
# Remove inline math
|
|
206
|
+
text = re.sub(r'\$[^$]+\$', '', text)
|
|
207
|
+
# Remove common commands with arguments: \cmd{arg}
|
|
208
|
+
text = re.sub(r'\\(?:cite|ref|label|eqref|cref|Cref|autoref|pageref|nameref|footnote|texttt|textbf|textit|emph|url|href|includegraphics|input|bibliography|bibliographystyle)\{[^}]*\}', '', text)
|
|
209
|
+
# Remove \cmd[opt]{arg}
|
|
210
|
+
text = re.sub(r'\\[a-zA-Z]+\[[^\]]*\]\{[^}]*\}', '', text)
|
|
211
|
+
# Remove remaining \cmd{arg} but keep the arg text for structural commands
|
|
212
|
+
text = re.sub(r'\\(?:section|subsection|subsubsection|paragraph|title|author|caption)\*?\{([^}]*)\}', r'\1', text)
|
|
213
|
+
# Remove other \commands
|
|
214
|
+
text = re.sub(r'\\[a-zA-Z]+\*?', '', text)
|
|
215
|
+
# Remove braces
|
|
216
|
+
text = re.sub(r'[{}]', '', text)
|
|
217
|
+
# Remove figure/table floats content (captions already extracted)
|
|
218
|
+
text = re.sub(r'\\begin\{(?:figure|table)\}.*?\\end\{(?:figure|table)\}', '', text, flags=re.DOTALL)
|
|
219
|
+
# Collapse whitespace
|
|
220
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
|
221
|
+
return text
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def extract_sections(text: str) -> dict[str, str]:
|
|
225
|
+
"""Split LaTeX text into sections by \\section headings."""
|
|
226
|
+
pattern = r'\\section\*?\{([^}]+)\}'
|
|
227
|
+
splits = re.split(pattern, text)
|
|
228
|
+
|
|
229
|
+
sections: dict[str, str] = {}
|
|
230
|
+
if len(splits) < 2:
|
|
231
|
+
return {"Full Document": text}
|
|
232
|
+
|
|
233
|
+
# Content before first section
|
|
234
|
+
preamble = splits[0].strip()
|
|
235
|
+
if preamble and len(preamble.split()) > 20:
|
|
236
|
+
sections["(Preamble)"] = preamble
|
|
237
|
+
|
|
238
|
+
for i in range(1, len(splits), 2):
|
|
239
|
+
name = splits[i].strip()
|
|
240
|
+
body = splits[i + 1] if i + 1 < len(splits) else ""
|
|
241
|
+
sections[name] = body.strip()
|
|
242
|
+
|
|
243
|
+
return sections
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Pattern matching
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class Match:
|
|
252
|
+
category: str
|
|
253
|
+
pattern: str
|
|
254
|
+
count: int
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass
|
|
258
|
+
class ScanResult:
|
|
259
|
+
text_name: str
|
|
260
|
+
word_count: int
|
|
261
|
+
total_matches: int
|
|
262
|
+
density: float
|
|
263
|
+
rating: str
|
|
264
|
+
matches_by_category: dict[str, list[Match]] = field(default_factory=dict)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def classify_density(density: float) -> str:
|
|
268
|
+
if density < 0.5:
|
|
269
|
+
return "Excellent"
|
|
270
|
+
elif density < 1.0:
|
|
271
|
+
return "Good"
|
|
272
|
+
elif density < 2.0:
|
|
273
|
+
return "Caution"
|
|
274
|
+
elif density < 3.0:
|
|
275
|
+
return "Poor"
|
|
276
|
+
else:
|
|
277
|
+
return "Bad"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def scan_text(text: str, name: str = "text") -> ScanResult:
|
|
281
|
+
"""Scan text for AI patterns and return results."""
|
|
282
|
+
words = text.split()
|
|
283
|
+
word_count = len(words)
|
|
284
|
+
if word_count == 0:
|
|
285
|
+
return ScanResult(name, 0, 0, 0.0, "Excellent")
|
|
286
|
+
|
|
287
|
+
text_lower = text.lower()
|
|
288
|
+
total = 0
|
|
289
|
+
by_category: dict[str, list[Match]] = {}
|
|
290
|
+
|
|
291
|
+
for cat in CATEGORIES:
|
|
292
|
+
cat_matches: list[Match] = []
|
|
293
|
+
for pattern in cat.patterns:
|
|
294
|
+
p = pattern.lower().rstrip(',').rstrip('.')
|
|
295
|
+
if cat.word_boundary:
|
|
296
|
+
count = len(re.findall(rf'\b{re.escape(p)}\b', text_lower))
|
|
297
|
+
else:
|
|
298
|
+
count = text_lower.count(p)
|
|
299
|
+
if count > 0:
|
|
300
|
+
cat_matches.append(Match(cat.name, pattern, count))
|
|
301
|
+
total += count
|
|
302
|
+
if cat_matches:
|
|
303
|
+
by_category[cat.name] = cat_matches
|
|
304
|
+
|
|
305
|
+
density = (total / word_count) * 100
|
|
306
|
+
return ScanResult(name, word_count, total, density, classify_density(density), by_category)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
# Output formatting
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
RATING_SYMBOLS = {
|
|
314
|
+
"Excellent": "✅",
|
|
315
|
+
"Good": "🟢",
|
|
316
|
+
"Caution": "🟡",
|
|
317
|
+
"Poor": "🟠",
|
|
318
|
+
"Bad": "🔴",
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def format_result(result: ScanResult, verbose: bool = True) -> str:
|
|
323
|
+
lines: list[str] = []
|
|
324
|
+
sym = RATING_SYMBOLS.get(result.rating, "")
|
|
325
|
+
lines.append(f"\n{'='*60}")
|
|
326
|
+
lines.append(f" {result.text_name}")
|
|
327
|
+
lines.append(f"{'='*60}")
|
|
328
|
+
lines.append(f" Words: {result.word_count:,}")
|
|
329
|
+
lines.append(f" Matches: {result.total_matches}")
|
|
330
|
+
lines.append(f" Density: {result.density:.2f} per 100 words")
|
|
331
|
+
lines.append(f" Rating: {sym} {result.rating}")
|
|
332
|
+
lines.append(f"{'='*60}")
|
|
333
|
+
|
|
334
|
+
if verbose and result.matches_by_category:
|
|
335
|
+
lines.append("")
|
|
336
|
+
for cat_name, matches in sorted(
|
|
337
|
+
result.matches_by_category.items(),
|
|
338
|
+
key=lambda x: sum(m.count for m in x[1]),
|
|
339
|
+
reverse=True,
|
|
340
|
+
):
|
|
341
|
+
cat_total = sum(m.count for m in matches)
|
|
342
|
+
lines.append(f" {cat_name} ({cat_total} hits)")
|
|
343
|
+
for m in sorted(matches, key=lambda x: x.count, reverse=True):
|
|
344
|
+
marker = f"x{m.count}" if m.count > 1 else ""
|
|
345
|
+
lines.append(f" - \"{m.pattern}\" {marker}".rstrip())
|
|
346
|
+
lines.append("")
|
|
347
|
+
|
|
348
|
+
return "\n".join(lines)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def format_summary_table(results: list[ScanResult]) -> str:
|
|
352
|
+
"""Format multiple results as a compact table."""
|
|
353
|
+
lines: list[str] = []
|
|
354
|
+
lines.append("")
|
|
355
|
+
lines.append(f" {'Section':<30} {'Words':>6} {'Hits':>5} {'Density':>8} {'Rating':<10}")
|
|
356
|
+
lines.append(f" {'-'*30} {'-'*6} {'-'*5} {'-'*8} {'-'*10}")
|
|
357
|
+
for r in results:
|
|
358
|
+
sym = RATING_SYMBOLS.get(r.rating, "")
|
|
359
|
+
lines.append(
|
|
360
|
+
f" {r.text_name:<30} {r.word_count:>6,} {r.total_matches:>5} "
|
|
361
|
+
f"{r.density:>7.2f} {sym} {r.rating:<10}"
|
|
362
|
+
)
|
|
363
|
+
return "\n".join(lines)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def result_to_dict(result: ScanResult) -> dict:
|
|
367
|
+
return {
|
|
368
|
+
"name": result.text_name,
|
|
369
|
+
"word_count": result.word_count,
|
|
370
|
+
"total_matches": result.total_matches,
|
|
371
|
+
"density": round(result.density, 3),
|
|
372
|
+
"rating": result.rating,
|
|
373
|
+
"categories": {
|
|
374
|
+
cat: [{"pattern": m.pattern, "count": m.count} for m in matches]
|
|
375
|
+
for cat, matches in result.matches_by_category.items()
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
# Main
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
def main():
|
|
385
|
+
parser = argparse.ArgumentParser(
|
|
386
|
+
description="Scan text for AI-generated writing patterns and report density.",
|
|
387
|
+
epilog="Thresholds: <0.5 Excellent, 0.5-1.0 Good, 1.0-2.0 Caution, 2.0-3.0 Poor, >3.0 Bad",
|
|
388
|
+
)
|
|
389
|
+
parser.add_argument("file", nargs="?", help="File to scan (.tex or plain text)")
|
|
390
|
+
parser.add_argument("--stdin", action="store_true", help="Read from stdin")
|
|
391
|
+
parser.add_argument("--sections", action="store_true", help="Per-section breakdown (LaTeX only)")
|
|
392
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
393
|
+
parser.add_argument("--quiet", action="store_true", help="Summary only, no per-pattern detail")
|
|
394
|
+
args = parser.parse_args()
|
|
395
|
+
|
|
396
|
+
if args.stdin:
|
|
397
|
+
raw = sys.stdin.read()
|
|
398
|
+
fname = "stdin"
|
|
399
|
+
elif args.file:
|
|
400
|
+
path = Path(args.file)
|
|
401
|
+
if not path.exists():
|
|
402
|
+
print(f"Error: {path} not found", file=sys.stderr)
|
|
403
|
+
sys.exit(1)
|
|
404
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
405
|
+
fname = path.name
|
|
406
|
+
else:
|
|
407
|
+
parser.print_help()
|
|
408
|
+
sys.exit(1)
|
|
409
|
+
|
|
410
|
+
is_latex = fname.endswith(".tex") or "\\documentclass" in raw[:500]
|
|
411
|
+
|
|
412
|
+
if is_latex and args.sections:
|
|
413
|
+
sections = extract_sections(raw)
|
|
414
|
+
results: list[ScanResult] = []
|
|
415
|
+
for sec_name, sec_text in sections.items():
|
|
416
|
+
prose = strip_latex(sec_text)
|
|
417
|
+
results.append(scan_text(prose, sec_name))
|
|
418
|
+
|
|
419
|
+
# Overall
|
|
420
|
+
full_prose = strip_latex(raw)
|
|
421
|
+
overall = scan_text(full_prose, f"OVERALL ({fname})")
|
|
422
|
+
results.append(overall)
|
|
423
|
+
|
|
424
|
+
if args.json:
|
|
425
|
+
print(json.dumps([result_to_dict(r) for r in results], indent=2))
|
|
426
|
+
else:
|
|
427
|
+
print(format_summary_table(results))
|
|
428
|
+
if not args.quiet:
|
|
429
|
+
# Show detail for overall only
|
|
430
|
+
print(format_result(overall, verbose=True))
|
|
431
|
+
else:
|
|
432
|
+
prose = strip_latex(raw) if is_latex else raw
|
|
433
|
+
result = scan_text(prose, fname)
|
|
434
|
+
|
|
435
|
+
if args.json:
|
|
436
|
+
print(json.dumps(result_to_dict(result), indent=2))
|
|
437
|
+
else:
|
|
438
|
+
print(format_result(result, verbose=not args.quiet))
|
|
439
|
+
|
|
440
|
+
# Exit code: 0 if Good or better, 1 if Caution or worse
|
|
441
|
+
final = overall if (is_latex and args.sections) else result # noqa: F821
|
|
442
|
+
sys.exit(0 if final.density < 1.0 else 1)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
if __name__ == "__main__":
|
|
446
|
+
main()
|