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.
Files changed (150) hide show
  1. {threadsense-0.2.1 → threadsense-0.3.0}/PKG-INFO +21 -7
  2. {threadsense-0.2.1 → threadsense-0.3.0}/README.md +20 -6
  3. {threadsense-0.2.1 → threadsense-0.3.0}/docs/artifacts.md +3 -0
  4. {threadsense-0.2.1 → threadsense-0.3.0}/docs/overview.md +6 -5
  5. {threadsense-0.2.1 → threadsense-0.3.0}/docs/system-design.md +5 -4
  6. {threadsense-0.2.1 → threadsense-0.3.0}/docs/usage.md +37 -2
  7. {threadsense-0.2.1 → threadsense-0.3.0}/pyproject.toml +1 -1
  8. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/__init__.py +1 -1
  9. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/cli.py +50 -2
  10. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/config.py +32 -1
  11. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/github_discussions.py +269 -30
  12. threadsense-0.3.0/src/threadsense/connectors/github_gist.py +504 -0
  13. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/registry.py +6 -1
  14. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/developer_tools.toml +5 -5
  15. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/errors.py +10 -0
  16. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/normalize.py +93 -0
  17. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_bootstrap.py +6 -1
  18. {threadsense-0.2.1 → threadsense-0.3.0}/tests/regression/expected_outputs/reddit_feedback_thread.json +3 -3
  19. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_analysis_pipeline.py +3 -0
  20. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_config.py +6 -1
  21. threadsense-0.3.0/tests/unit/test_github_gist_connector.py +355 -0
  22. {threadsense-0.2.1 → threadsense-0.3.0}/threadsense.toml +11 -2
  23. {threadsense-0.2.1 → threadsense-0.3.0}/uv.lock +1 -1
  24. {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/.gitignore +0 -0
  25. {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/approve_all.py +0 -0
  26. {threadsense-0.2.1 → threadsense-0.3.0}/.approval_tests_temp/remove_abandoned_files.py +0 -0
  27. {threadsense-0.2.1 → threadsense-0.3.0}/.gitignore +0 -0
  28. {threadsense-0.2.1 → threadsense-0.3.0}/LICENSE +0 -0
  29. {threadsense-0.2.1 → threadsense-0.3.0}/docs/batch-api-runtime.md +0 -0
  30. {threadsense-0.2.1 → threadsense-0.3.0}/docs/local-runtime-contract.md +0 -0
  31. {threadsense-0.2.1 → threadsense-0.3.0}/docs/output-modes.md +0 -0
  32. {threadsense-0.2.1 → threadsense-0.3.0}/docs/pitch.md +0 -0
  33. {threadsense-0.2.1 → threadsense-0.3.0}/docs/research-reddit.md +0 -0
  34. {threadsense-0.2.1 → threadsense-0.3.0}/scripts/validate.sh +0 -0
  35. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/__main__.py +0 -0
  36. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/action_signals.py +0 -0
  37. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/api_server.py +0 -0
  38. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/batching.py +0 -0
  39. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/cli_display.py +0 -0
  40. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/__init__.py +0 -0
  41. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/cache.py +0 -0
  42. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/hackernews.py +0 -0
  43. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/connectors/reddit.py +0 -0
  44. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/__init__.py +0 -0
  45. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/analysis_contract.py +0 -0
  46. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/contracts/defaults.py +0 -0
  47. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/__init__.py +0 -0
  48. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/financial_markets.toml +0 -0
  49. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/gaming.toml +0 -0
  50. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/hiring_careers.toml +0 -0
  51. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/product_feedback.toml +0 -0
  52. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/definitions/research_academic.toml +0 -0
  53. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/loader.py +0 -0
  54. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/domains/vocabulary.py +0 -0
  55. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/__init__.py +0 -0
  56. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/compare.py +0 -0
  57. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/golden.py +0 -0
  58. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/inference_quality.py +0 -0
  59. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/evaluation/metrics.py +0 -0
  60. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/__init__.py +0 -0
  61. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/contracts.py +0 -0
  62. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/local_runtime.py +0 -0
  63. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/prompts.py +0 -0
  64. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/inference/router.py +0 -0
  65. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/logging_config.py +0 -0
  66. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/__init__.py +0 -0
  67. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/analysis.py +0 -0
  68. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/canonical.py +0 -0
  69. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/corpus.py +0 -0
  70. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/report.py +0 -0
  71. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/models/results.py +0 -0
  72. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/observability.py +0 -0
  73. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/__init__.py +0 -0
  74. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/alignment.py +0 -0
  75. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/analyze.py +0 -0
  76. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/corpus.py +0 -0
  77. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/corpus_index.py +0 -0
  78. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/domain_detect.py +0 -0
  79. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/replay.py +0 -0
  80. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/storage.py +0 -0
  81. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/strategies/__init__.py +0 -0
  82. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/strategies/keyword_heuristic.py +0 -0
  83. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/tree.py +0 -0
  84. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/pipeline/versioning.py +0 -0
  85. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/preflight.py +0 -0
  86. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/py.typed +0 -0
  87. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/__init__.py +0 -0
  88. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/build.py +0 -0
  89. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/corpus_render.py +0 -0
  90. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/quality.py +0 -0
  91. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/reporting/render.py +0 -0
  92. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/schema_utils.py +0 -0
  93. {threadsense-0.2.1 → threadsense-0.3.0}/src/threadsense/workflows.py +0 -0
  94. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/README.md +0 -0
  95. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/analysis/canonical_feedback_thread.json +0 -0
  96. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/batch/reddit_manifest.json +0 -0
  97. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/hackernews/raw/story_thread.json +0 -0
  98. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/analysis_summary_invalid_response.json +0 -0
  99. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/analysis_summary_response.json +0 -0
  100. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/chat_completion_request.json +0 -0
  101. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/inference/chat_completion_response.json +0 -0
  102. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/normalized/.gitkeep +0 -0
  103. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/.gitkeep +0 -0
  104. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/deleted_thread.json +0 -0
  105. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/large_thread.json +0 -0
  106. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/morechildren_response.json +0 -0
  107. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/normal_thread.json +0 -0
  108. {threadsense-0.2.1 → threadsense-0.3.0}/tests/fixtures/reddit/raw/removed_thread.json +0 -0
  109. {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/README.md +0 -0
  110. {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/developer_tools/reddit_feedback_thread.json +0 -0
  111. {threadsense-0.2.1 → threadsense-0.3.0}/tests/golden/manifest.json +0 -0
  112. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_batch_api.py +0 -0
  113. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_canonical_persistence.py +0 -0
  114. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_full_pipeline.py +0 -0
  115. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_local_inference.py +0 -0
  116. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_preflight.py +0 -0
  117. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reddit_fetch.py +0 -0
  118. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reddit_research.py +0 -0
  119. {threadsense-0.2.1 → threadsense-0.3.0}/tests/integration/test_reporting.py +0 -0
  120. {threadsense-0.2.1 → threadsense-0.3.0}/tests/property/__init__.py +0 -0
  121. {threadsense-0.2.1 → threadsense-0.3.0}/tests/property/test_schema_properties.py +0 -0
  122. {threadsense-0.2.1 → threadsense-0.3.0}/tests/regression/test_analysis_replay.py +0 -0
  123. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_alignment.py +0 -0
  124. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_api_server.py +0 -0
  125. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_artifact_versioning.py +0 -0
  126. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_batching.py +0 -0
  127. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_canonical_models.py +0 -0
  128. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_cli.py +0 -0
  129. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_cli_display.py +0 -0
  130. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_analysis.py +0 -0
  131. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_index.py +0 -0
  132. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_corpus_model.py +0 -0
  133. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_domain_detect.py +0 -0
  134. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_errors.py +0 -0
  135. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_fetch_cache.py +0 -0
  136. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_github_discussions_connector.py +0 -0
  137. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_golden_evaluation.py +0 -0
  138. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_hackernews_connector.py +0 -0
  139. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_inference_quality.py +0 -0
  140. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_inference_router.py +0 -0
  141. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_local_runtime.py +0 -0
  142. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_minhash.py +0 -0
  143. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_normalize_pipeline.py +0 -0
  144. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_observability.py +0 -0
  145. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_reddit_connector.py +0 -0
  146. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_replay.py +0 -0
  147. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_report_rendering.py +0 -0
  148. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_source_registry.py +0 -0
  149. {threadsense-0.2.1 → threadsense-0.3.0}/tests/unit/test_strategy_comparison.py +0 -0
  150. {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.2.1
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 Discussions
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. Research a topic across subreddits
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 Discussions
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. Research a topic across subreddits
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 Discussions
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 thread analysis + corpus synthesis + subreddit topic research
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 note for Reddit:
62
+ Special notes:
62
63
 
63
- - direct thread fetch is URL-based
64
- - topic research uses explicit subreddit search
65
- - exact time-window filtering is applied after Reddit retrieval
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 reddit <reddit-url> \
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
- Example:
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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "threadsense"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  description = "Evidence-first discussion intelligence tooling."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.3.0"
@@ -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 ("normalize", "hn" | "hackernews" | "github-discussions" | "gh-discussions", _, _, _):
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",