threadsense 0.2.1__tar.gz → 0.3.0__tar.gz
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.
- {threadsense-0.2.1 → threadsense-0.3.0}/PKG-INFO +21 -7
- {threadsense-0.2.1 → threadsense-0.3.0}/README.md +20 -6
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/artifacts.md +3 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/overview.md +6 -5
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/system-design.md +5 -4
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/usage.md +37 -2
- {threadsense-0.2.1 → threadsense-0.3.0}/pyproject.toml +1 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/__init__.py +1 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/cli.py +50 -2
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/config.py +32 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/github_discussions.py +269 -30
- threadsense-0.3.0/src/threadsense/connectors/github_gist.py +504 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/registry.py +6 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/developer_tools.toml +5 -5
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/errors.py +10 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/normalize.py +93 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_bootstrap.py +6 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/regression/expected_outputs/reddit_feedback_thread.json +3 -3
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_analysis_pipeline.py +3 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_config.py +6 -1
- threadsense-0.3.0/tests/unit/test_github_gist_connector.py +355 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/threadsense.toml +11 -2
- {threadsense-0.2.1 → threadsense-0.3.0}/uv.lock +1 -1
- {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/.gitignore +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/approve_all.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/remove_abandoned_files.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/.gitignore +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/LICENSE +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/batch-api-runtime.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/local-runtime-contract.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/output-modes.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/pitch.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/docs/research-reddit.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/scripts/validate.sh +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/__main__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/action_signals.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/api_server.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/batching.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/cli_display.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/cache.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/hackernews.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/reddit.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/analysis_contract.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/defaults.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/financial_markets.toml +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/gaming.toml +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/hiring_careers.toml +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/product_feedback.toml +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/research_academic.toml +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/loader.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/vocabulary.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/compare.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/golden.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/inference_quality.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/metrics.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/contracts.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/local_runtime.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/prompts.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/router.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/logging_config.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/analysis.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/canonical.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/corpus.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/report.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/results.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/observability.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/alignment.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/analyze.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/corpus.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/corpus_index.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/domain_detect.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/replay.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/storage.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/strategies/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/strategies/keyword_heuristic.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/tree.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/versioning.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/preflight.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/py.typed +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/build.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/corpus_render.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/quality.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/render.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/schema_utils.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/workflows.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/README.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/analysis/canonical_feedback_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/batch/reddit_manifest.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/hackernews/raw/story_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/analysis_summary_invalid_response.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/analysis_summary_response.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/chat_completion_request.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/chat_completion_response.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/normalized/.gitkeep +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/.gitkeep +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/deleted_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/large_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/morechildren_response.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/normal_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/removed_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/README.md +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/developer_tools/reddit_feedback_thread.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/manifest.json +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_batch_api.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_canonical_persistence.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_full_pipeline.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_local_inference.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_preflight.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reddit_fetch.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reddit_research.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reporting.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/property/__init__.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/property/test_schema_properties.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/regression/test_analysis_replay.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_alignment.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_api_server.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_artifact_versioning.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_batching.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_canonical_models.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_cli.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_cli_display.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_analysis.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_index.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_model.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_domain_detect.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_errors.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_fetch_cache.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_github_discussions_connector.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_golden_evaluation.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_hackernews_connector.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_inference_quality.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_inference_router.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_local_runtime.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_minhash.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_normalize_pipeline.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_observability.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_reddit_connector.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_replay.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_report_rendering.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_source_registry.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_strategy_comparison.py +0 -0
- {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_tree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: threadsense
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Evidence-first discussion intelligence tooling.
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ Scrapers give you raw payloads. AI summarizers give you prose. Neither gives you
|
|
|
41
41
|
|
|
42
42
|
ThreadSense keeps the evidence chain intact at every stage:
|
|
43
43
|
|
|
44
|
-
1. **Acquire** — source connectors fetch threads from Reddit, Hacker News, and GitHub
|
|
44
|
+
1. **Acquire** — source connectors fetch threads from Reddit, Hacker News, GitHub Discussions, and GitHub Gists
|
|
45
45
|
2. **Normalize** — source-specific payloads are mapped into a canonical thread model with provenance metadata
|
|
46
46
|
3. **Analyze** — deterministic extraction identifies issues, feature requests, themes, and sentiment — each linked to the comment that produced it
|
|
47
47
|
4. **Synthesize** — optional local-model inference adds summaries on top of the deterministic evidence layer
|
|
@@ -63,6 +63,14 @@ uv run threadsense run reddit \
|
|
|
63
63
|
--summary-required
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
Works with GitHub Gists too — flat-comment discussions with code context:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv run threadsense run gist \
|
|
70
|
+
"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f" \
|
|
71
|
+
--format markdown
|
|
72
|
+
```
|
|
73
|
+
|
|
66
74
|
### Cross-thread research
|
|
67
75
|
|
|
68
76
|
Search a topic across multiple subreddits. ThreadSense deterministically selects, ranks, and analyzes matching threads, then synthesizes a corpus-level report.
|
|
@@ -98,10 +106,10 @@ fetch → normalize → analyze → [optional inference] → report
|
|
|
98
106
|
|
|
99
107
|
## Sources and Discovery
|
|
100
108
|
|
|
101
|
-
| Capability | Reddit | Hacker News | GitHub Discussions |
|
|
102
|
-
| --------------- | :----: | :---------: | :----------------: |
|
|
103
|
-
| Thread analysis | yes | yes | yes |
|
|
104
|
-
| Topic research | yes | — | — |
|
|
109
|
+
| Capability | Reddit | Hacker News | GitHub Discussions | GitHub Gists |
|
|
110
|
+
| --------------- | :----: | :---------: | :----------------: | :----------: |
|
|
111
|
+
| Thread analysis | yes | yes | yes | yes |
|
|
112
|
+
| Topic research | yes | — | — | — |
|
|
105
113
|
|
|
106
114
|
## Output Modes
|
|
107
115
|
|
|
@@ -137,7 +145,11 @@ uv run threadsense preflight
|
|
|
137
145
|
uv run threadsense run reddit \
|
|
138
146
|
"https://www.reddit.com/r/ClaudeCode/comments/1ro0qbl/anyone_actually_built_a_second_brain_that_isnt/"
|
|
139
147
|
|
|
140
|
-
# 4.
|
|
148
|
+
# 4. Analyze a GitHub Gist
|
|
149
|
+
uv run threadsense run gist \
|
|
150
|
+
"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f"
|
|
151
|
+
|
|
152
|
+
# 5. Research a topic across subreddits
|
|
141
153
|
uv run threadsense research reddit \
|
|
142
154
|
--query "second brain OR agentic PKM" \
|
|
143
155
|
--subreddit ClaudeCode \
|
|
@@ -213,6 +225,8 @@ uv run pytest
|
|
|
213
225
|
- Topic research is implemented for Reddit; other source discovery workflows are planned
|
|
214
226
|
- Reddit research queries support `OR`/`|` clause unions only (intentionally narrow for deterministic alignment)
|
|
215
227
|
- Corpus reports are Markdown only
|
|
228
|
+
- GitHub Gist comments are flat (no threading); analysis uses temporal/semantic clustering
|
|
229
|
+
- GitHub Gist connector does not retry on transient API errors (502/503)
|
|
216
230
|
- The local API is a trusted local surface, not a hardened public service
|
|
217
231
|
|
|
218
232
|
## Direction
|
|
@@ -8,7 +8,7 @@ Scrapers give you raw payloads. AI summarizers give you prose. Neither gives you
|
|
|
8
8
|
|
|
9
9
|
ThreadSense keeps the evidence chain intact at every stage:
|
|
10
10
|
|
|
11
|
-
1. **Acquire** — source connectors fetch threads from Reddit, Hacker News, and GitHub
|
|
11
|
+
1. **Acquire** — source connectors fetch threads from Reddit, Hacker News, GitHub Discussions, and GitHub Gists
|
|
12
12
|
2. **Normalize** — source-specific payloads are mapped into a canonical thread model with provenance metadata
|
|
13
13
|
3. **Analyze** — deterministic extraction identifies issues, feature requests, themes, and sentiment — each linked to the comment that produced it
|
|
14
14
|
4. **Synthesize** — optional local-model inference adds summaries on top of the deterministic evidence layer
|
|
@@ -30,6 +30,14 @@ uv run threadsense run reddit \
|
|
|
30
30
|
--summary-required
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Works with GitHub Gists too — flat-comment discussions with code context:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv run threadsense run gist \
|
|
37
|
+
"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f" \
|
|
38
|
+
--format markdown
|
|
39
|
+
```
|
|
40
|
+
|
|
33
41
|
### Cross-thread research
|
|
34
42
|
|
|
35
43
|
Search a topic across multiple subreddits. ThreadSense deterministically selects, ranks, and analyzes matching threads, then synthesizes a corpus-level report.
|
|
@@ -65,10 +73,10 @@ fetch → normalize → analyze → [optional inference] → report
|
|
|
65
73
|
|
|
66
74
|
## Sources and Discovery
|
|
67
75
|
|
|
68
|
-
| Capability | Reddit | Hacker News | GitHub Discussions |
|
|
69
|
-
| --------------- | :----: | :---------: | :----------------: |
|
|
70
|
-
| Thread analysis | yes | yes | yes |
|
|
71
|
-
| Topic research | yes | — | — |
|
|
76
|
+
| Capability | Reddit | Hacker News | GitHub Discussions | GitHub Gists |
|
|
77
|
+
| --------------- | :----: | :---------: | :----------------: | :----------: |
|
|
78
|
+
| Thread analysis | yes | yes | yes | yes |
|
|
79
|
+
| Topic research | yes | — | — | — |
|
|
72
80
|
|
|
73
81
|
## Output Modes
|
|
74
82
|
|
|
@@ -104,7 +112,11 @@ uv run threadsense preflight
|
|
|
104
112
|
uv run threadsense run reddit \
|
|
105
113
|
"https://www.reddit.com/r/ClaudeCode/comments/1ro0qbl/anyone_actually_built_a_second_brain_that_isnt/"
|
|
106
114
|
|
|
107
|
-
# 4.
|
|
115
|
+
# 4. Analyze a GitHub Gist
|
|
116
|
+
uv run threadsense run gist \
|
|
117
|
+
"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f"
|
|
118
|
+
|
|
119
|
+
# 5. Research a topic across subreddits
|
|
108
120
|
uv run threadsense research reddit \
|
|
109
121
|
--query "second brain OR agentic PKM" \
|
|
110
122
|
--subreddit ClaudeCode \
|
|
@@ -180,6 +192,8 @@ uv run pytest
|
|
|
180
192
|
- Topic research is implemented for Reddit; other source discovery workflows are planned
|
|
181
193
|
- Reddit research queries support `OR`/`|` clause unions only (intentionally narrow for deterministic alignment)
|
|
182
194
|
- Corpus reports are Markdown only
|
|
195
|
+
- GitHub Gist comments are flat (no threading); analysis uses temporal/semantic clustering
|
|
196
|
+
- GitHub Gist connector does not retry on transient API errors (502/503)
|
|
183
197
|
- The local API is a trusted local surface, not a hardened public service
|
|
184
198
|
|
|
185
199
|
## Direction
|
|
@@ -25,6 +25,7 @@ Examples:
|
|
|
25
25
|
- `.threadsense/raw/reddit/<thread-id>.json`
|
|
26
26
|
- `.threadsense/raw/hackernews/<story-id>.json`
|
|
27
27
|
- `.threadsense/raw/github_discussions/<thread-id>.json`
|
|
28
|
+
- `.threadsense/raw/github_gist/<gist-id>.json`
|
|
28
29
|
|
|
29
30
|
Purpose:
|
|
30
31
|
|
|
@@ -41,6 +42,7 @@ Examples:
|
|
|
41
42
|
- `.threadsense/normalized/reddit/<thread-id>.json`
|
|
42
43
|
- `.threadsense/normalized/hackernews/<thread-id>.json`
|
|
43
44
|
- `.threadsense/normalized/github_discussions/<thread-id>.json`
|
|
45
|
+
- `.threadsense/normalized/github_gist/<gist-id>.json`
|
|
44
46
|
|
|
45
47
|
Purpose:
|
|
46
48
|
|
|
@@ -57,6 +59,7 @@ Examples:
|
|
|
57
59
|
- `.threadsense/analysis/reddit/<thread-id>.json`
|
|
58
60
|
- `.threadsense/analysis/hackernews/<thread-id>.json`
|
|
59
61
|
- `.threadsense/analysis/github_discussions/<thread-id>.json`
|
|
62
|
+
- `.threadsense/analysis/github_gist/<gist-id>.json`
|
|
60
63
|
|
|
61
64
|
Purpose:
|
|
62
65
|
|
|
@@ -6,7 +6,7 @@ ThreadSense is an evidence-first discussion intelligence system for turning publ
|
|
|
6
6
|
|
|
7
7
|
The implemented system supports:
|
|
8
8
|
|
|
9
|
-
- source connectors for Reddit, Hacker News, and GitHub
|
|
9
|
+
- source connectors for Reddit, Hacker News, GitHub Discussions, and GitHub Gists
|
|
10
10
|
- canonical normalization into a shared thread model
|
|
11
11
|
- deterministic analysis with evidence-linked findings
|
|
12
12
|
- optional local-model synthesis for thread and corpus summaries
|
|
@@ -90,9 +90,10 @@ ThreadSense separates source-specific acquisition from canonical normalization.
|
|
|
90
90
|
|
|
91
91
|
Current source support:
|
|
92
92
|
|
|
93
|
-
- Reddit
|
|
94
|
-
- Hacker News
|
|
95
|
-
- GitHub Discussions
|
|
93
|
+
- Reddit — threaded comments via JSON API with "more" expansion
|
|
94
|
+
- Hacker News — recursive tree-based item fetching via Firebase API
|
|
95
|
+
- GitHub Discussions — GraphQL with cursor-based pagination for comments and replies
|
|
96
|
+
- GitHub Gists — REST API with paginated flat comments; authentication optional for public gists
|
|
96
97
|
|
|
97
98
|
Each connector preserves enough source metadata for traceability, while normalization maps the result into a canonical `Thread` object with:
|
|
98
99
|
|
|
@@ -182,7 +183,7 @@ JSON is for machines. Human mode is for operators. Both should expose the same u
|
|
|
182
183
|
ThreadSense is best described as:
|
|
183
184
|
|
|
184
185
|
```text
|
|
185
|
-
multi-source
|
|
186
|
+
multi-source discussion intelligence + corpus synthesis + subreddit topic research
|
|
186
187
|
```
|
|
187
188
|
|
|
188
189
|
It is not just a scraper, and it is not just a summarizer. It is a reproducible pipeline from source thread to inspectable intelligence.
|
|
@@ -49,6 +49,7 @@ subreddit search -> candidate ranking -> selected thread fetch/analyze
|
|
|
49
49
|
- `src/threadsense/connectors/reddit.py`
|
|
50
50
|
- `src/threadsense/connectors/hackernews.py`
|
|
51
51
|
- `src/threadsense/connectors/github_discussions.py`
|
|
52
|
+
- `src/threadsense/connectors/github_gist.py`
|
|
52
53
|
|
|
53
54
|
Responsibilities:
|
|
54
55
|
|
|
@@ -58,11 +59,11 @@ Responsibilities:
|
|
|
58
59
|
- preserve source-specific thread metadata
|
|
59
60
|
- return raw artifacts for later normalization
|
|
60
61
|
|
|
61
|
-
Special
|
|
62
|
+
Special notes:
|
|
62
63
|
|
|
63
|
-
- direct thread fetch is URL-based
|
|
64
|
-
-
|
|
65
|
-
-
|
|
64
|
+
- Reddit: direct thread fetch is URL-based; topic research uses explicit subreddit search with exact time-window filtering after retrieval
|
|
65
|
+
- GitHub Discussions: GraphQL with cursor-based pagination for comments (100/page) and replies; requires authentication token
|
|
66
|
+
- GitHub Gists: REST API with paginated flat comments; authentication optional for public gists; comments have no threading (all depth=0)
|
|
66
67
|
|
|
67
68
|
### Canonical Model
|
|
68
69
|
|
|
@@ -43,7 +43,7 @@ Top-level commands:
|
|
|
43
43
|
### Single-thread run
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
uv run threadsense run
|
|
46
|
+
uv run threadsense run [<source>] <url> \
|
|
47
47
|
[--format markdown|html|json] \
|
|
48
48
|
[--with-summary] \
|
|
49
49
|
[--summary-required] \
|
|
@@ -51,7 +51,9 @@ uv run threadsense run reddit <reddit-url> \
|
|
|
51
51
|
[--flat]
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Source can be `reddit`, `hn`, `github-discussions`, `gist`, or omitted for auto-detection.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
59
|
uv run threadsense run reddit \
|
|
@@ -61,6 +63,12 @@ uv run threadsense run reddit \
|
|
|
61
63
|
--summary-required
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
```bash
|
|
67
|
+
uv run threadsense run gist \
|
|
68
|
+
"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f" \
|
|
69
|
+
--format markdown
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
### Topic research across subreddits
|
|
65
73
|
|
|
66
74
|
```bash
|
|
@@ -125,6 +133,20 @@ uv run threadsense fetch hn <hackernews-item-url> [--output path/to/raw.json]
|
|
|
125
133
|
uv run threadsense fetch github-discussions <discussion-url> [--output path/to/raw.json]
|
|
126
134
|
```
|
|
127
135
|
|
|
136
|
+
### `fetch github-gist`
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv run threadsense fetch github-gist <gist-url> [--output path/to/raw.json]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Aliases: `fetch gist`
|
|
143
|
+
|
|
144
|
+
Notes:
|
|
145
|
+
|
|
146
|
+
- Authentication is optional. Public gists work without a token (60 req/hr rate limit).
|
|
147
|
+
- Set `THREADSENSE_GITHUB_GIST_TOKEN` for authenticated access (5,000 req/hr).
|
|
148
|
+
- Comments are paginated at 100/page automatically.
|
|
149
|
+
|
|
128
150
|
## `normalize`
|
|
129
151
|
|
|
130
152
|
Normalize one raw artifact into the canonical thread schema.
|
|
@@ -147,6 +169,19 @@ uv run threadsense normalize hn --input path/to/raw.json [--output path/to/norma
|
|
|
147
169
|
uv run threadsense normalize github-discussions --input path/to/raw.json [--output path/to/normalized.json]
|
|
148
170
|
```
|
|
149
171
|
|
|
172
|
+
### `normalize github-gist`
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv run threadsense normalize github-gist --input path/to/raw.json [--output path/to/normalized.json]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Aliases: `normalize gist`
|
|
179
|
+
|
|
180
|
+
Notes:
|
|
181
|
+
|
|
182
|
+
- Gist comments are flat — all normalized to `depth=0` with no `parent_comment_id`.
|
|
183
|
+
- Multi-file gists are concatenated into a single body, each file prefixed with its filename.
|
|
184
|
+
|
|
150
185
|
## `analyze`
|
|
151
186
|
|
|
152
187
|
Run deterministic analysis for one canonical thread artifact.
|
|
@@ -49,8 +49,8 @@ from threadsense.workflows import (
|
|
|
49
49
|
normalize_source_thread,
|
|
50
50
|
report_analysis,
|
|
51
51
|
report_corpus,
|
|
52
|
-
run_reddit_research,
|
|
53
52
|
run_reddit_pipeline,
|
|
53
|
+
run_reddit_research,
|
|
54
54
|
run_source_pipeline,
|
|
55
55
|
search_corpora,
|
|
56
56
|
)
|
|
@@ -188,6 +188,20 @@ def _build_fetch_parser(subparsers: argparse._SubParsersAction[argparse.Argument
|
|
|
188
188
|
)
|
|
189
189
|
_add_config_argument(gh_parser)
|
|
190
190
|
_add_no_cache_argument(gh_parser)
|
|
191
|
+
gist_parser = fetch_subparsers.add_parser(
|
|
192
|
+
"github-gist",
|
|
193
|
+
aliases=["gist"],
|
|
194
|
+
help="Fetch one GitHub Gist with comments.",
|
|
195
|
+
)
|
|
196
|
+
gist_parser.add_argument("url", help="Full GitHub Gist URL")
|
|
197
|
+
gist_parser.add_argument(
|
|
198
|
+
"--output",
|
|
199
|
+
"-o",
|
|
200
|
+
type=Path,
|
|
201
|
+
help="Raw artifact output path. Defaults to the configured raw store path.",
|
|
202
|
+
)
|
|
203
|
+
_add_config_argument(gist_parser)
|
|
204
|
+
_add_no_cache_argument(gist_parser)
|
|
191
205
|
|
|
192
206
|
|
|
193
207
|
def _build_normalize_parser(
|
|
@@ -251,6 +265,24 @@ def _build_normalize_parser(
|
|
|
251
265
|
help="Normalized artifact output path. Defaults to the configured normalized store path.",
|
|
252
266
|
)
|
|
253
267
|
_add_config_argument(normalize_gh_parser)
|
|
268
|
+
normalize_gist_parser = normalize_subparsers.add_parser(
|
|
269
|
+
"github-gist",
|
|
270
|
+
aliases=["gist"],
|
|
271
|
+
help="Normalize one GitHub Gist raw artifact.",
|
|
272
|
+
)
|
|
273
|
+
normalize_gist_parser.add_argument(
|
|
274
|
+
"--input",
|
|
275
|
+
type=Path,
|
|
276
|
+
required=True,
|
|
277
|
+
help="Raw GitHub Gist artifact path.",
|
|
278
|
+
)
|
|
279
|
+
normalize_gist_parser.add_argument(
|
|
280
|
+
"--output",
|
|
281
|
+
"-o",
|
|
282
|
+
type=Path,
|
|
283
|
+
help="Normalized artifact output path. Defaults to the configured normalized store path.",
|
|
284
|
+
)
|
|
285
|
+
_add_config_argument(normalize_gist_parser)
|
|
254
286
|
|
|
255
287
|
|
|
256
288
|
def _build_analyze_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -1355,6 +1387,8 @@ def parse_run_target(target: list[str]) -> tuple[str | None, str]:
|
|
|
1355
1387
|
source_name = "hackernews"
|
|
1356
1388
|
if source_name in {"github-discussions", "gh-discussions"}:
|
|
1357
1389
|
source_name = "github_discussions"
|
|
1390
|
+
if source_name in {"github-gist", "gist"}:
|
|
1391
|
+
source_name = "github_gist"
|
|
1358
1392
|
return source_name, target[1]
|
|
1359
1393
|
raise _CommandDispatchError("run")
|
|
1360
1394
|
|
|
@@ -1395,13 +1429,27 @@ def _dispatch_command(args: argparse.Namespace) -> int:
|
|
|
1395
1429
|
source_name="github_discussions",
|
|
1396
1430
|
no_cache=args.no_cache,
|
|
1397
1431
|
)
|
|
1432
|
+
case ("fetch", "github-gist" | "gist", _, _, _):
|
|
1433
|
+
return run_source_fetch(
|
|
1434
|
+
config_path=args.config,
|
|
1435
|
+
url=args.url,
|
|
1436
|
+
output_path=args.output,
|
|
1437
|
+
source_name="github_gist",
|
|
1438
|
+
no_cache=args.no_cache,
|
|
1439
|
+
)
|
|
1398
1440
|
case ("normalize", "reddit", _, _, _):
|
|
1399
1441
|
return run_reddit_normalize(
|
|
1400
1442
|
config_path=args.config,
|
|
1401
1443
|
input_path=args.input,
|
|
1402
1444
|
output_path=args.output,
|
|
1403
1445
|
)
|
|
1404
|
-
case (
|
|
1446
|
+
case (
|
|
1447
|
+
"normalize",
|
|
1448
|
+
"hn" | "hackernews" | "github-discussions" | "gh-discussions" | "github-gist" | "gist",
|
|
1449
|
+
_,
|
|
1450
|
+
_,
|
|
1451
|
+
_,
|
|
1452
|
+
):
|
|
1405
1453
|
return run_source_normalize(
|
|
1406
1454
|
config_path=args.config,
|
|
1407
1455
|
input_path=args.input,
|
|
@@ -50,7 +50,7 @@ class RuntimeConfig(BaseModel):
|
|
|
50
50
|
class SourcePolicyConfig(BaseModel):
|
|
51
51
|
model_config = ConfigDict(frozen=True)
|
|
52
52
|
|
|
53
|
-
enabled_sources: tuple[str, ...] = ("reddit", "hackernews")
|
|
53
|
+
enabled_sources: tuple[str, ...] = ("reddit", "hackernews", "github_discussions", "github_gist")
|
|
54
54
|
|
|
55
55
|
@field_validator("enabled_sources", mode="before")
|
|
56
56
|
@classmethod
|
|
@@ -97,6 +97,30 @@ class GitHubConfig(BaseModel):
|
|
|
97
97
|
return v.strip()
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
class GitHubGistConfig(BaseModel):
|
|
101
|
+
model_config = ConfigDict(frozen=True)
|
|
102
|
+
|
|
103
|
+
base_url: str = "https://api.github.com"
|
|
104
|
+
token: str = ""
|
|
105
|
+
timeout_seconds: float = 15
|
|
106
|
+
comments_per_page: int = 100
|
|
107
|
+
request_delay_seconds: float = 0.5
|
|
108
|
+
max_retries: int = 2
|
|
109
|
+
backoff_seconds: float = 1.0
|
|
110
|
+
|
|
111
|
+
@field_validator("token")
|
|
112
|
+
@classmethod
|
|
113
|
+
def validate_token(cls, v: str) -> str:
|
|
114
|
+
return v.strip()
|
|
115
|
+
|
|
116
|
+
@field_validator("comments_per_page")
|
|
117
|
+
@classmethod
|
|
118
|
+
def validate_comments_per_page(cls, v: int) -> int:
|
|
119
|
+
if v < 1 or v > 100:
|
|
120
|
+
raise ValueError("comments_per_page must be between 1 and 100")
|
|
121
|
+
return v
|
|
122
|
+
|
|
123
|
+
|
|
100
124
|
class CacheConfig(BaseModel):
|
|
101
125
|
model_config = ConfigDict(frozen=True)
|
|
102
126
|
|
|
@@ -194,6 +218,7 @@ class AppConfig(BaseModel):
|
|
|
194
218
|
reddit: RedditConfig = RedditConfig()
|
|
195
219
|
hackernews: HackerNewsConfig = HackerNewsConfig()
|
|
196
220
|
github: GitHubConfig = GitHubConfig()
|
|
221
|
+
github_gist: GitHubGistConfig = GitHubGistConfig()
|
|
197
222
|
cache: CacheConfig = CacheConfig()
|
|
198
223
|
storage: StorageConfig = StorageConfig()
|
|
199
224
|
batch: BatchConfig = BatchConfig()
|
|
@@ -227,6 +252,11 @@ _ENV_MAP: dict[str, tuple[str, ...]] = {
|
|
|
227
252
|
"THREADSENSE_GITHUB_BASE_URL": ("github", "base_url"),
|
|
228
253
|
"THREADSENSE_GITHUB_TOKEN": ("github", "token"),
|
|
229
254
|
"THREADSENSE_GITHUB_TIMEOUT": ("github", "timeout_seconds"),
|
|
255
|
+
"THREADSENSE_GITHUB_GIST_BASE_URL": ("github_gist", "base_url"),
|
|
256
|
+
"THREADSENSE_GITHUB_GIST_TOKEN": ("github_gist", "token"),
|
|
257
|
+
"THREADSENSE_GITHUB_GIST_TIMEOUT": ("github_gist", "timeout_seconds"),
|
|
258
|
+
"THREADSENSE_GITHUB_GIST_COMMENTS_PER_PAGE": ("github_gist", "comments_per_page"),
|
|
259
|
+
"THREADSENSE_GITHUB_GIST_REQUEST_DELAY": ("github_gist", "request_delay_seconds"),
|
|
230
260
|
"THREADSENSE_CACHE_ENABLED": ("cache", "enabled"),
|
|
231
261
|
"THREADSENSE_CACHE_TTL_SECONDS": ("cache", "ttl_seconds"),
|
|
232
262
|
"THREADSENSE_CACHE_DIR": ("cache", "cache_dir"),
|
|
@@ -262,6 +292,7 @@ _TOML_SECTION_MAP: dict[str, str] = {
|
|
|
262
292
|
"reddit": "reddit",
|
|
263
293
|
"hackernews": "hackernews",
|
|
264
294
|
"github": "github",
|
|
295
|
+
"github_gist": "github_gist",
|
|
265
296
|
"cache": "cache",
|
|
266
297
|
"storage": "storage",
|
|
267
298
|
"batch": "batch",
|