metalworks 0.0.1__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 (217) hide show
  1. metalworks-0.0.1/.claude-plugin/marketplace.json +18 -0
  2. metalworks-0.0.1/.github/workflows/ci.yml +49 -0
  3. metalworks-0.0.1/.github/workflows/release.yml +36 -0
  4. metalworks-0.0.1/.gitignore +28 -0
  5. metalworks-0.0.1/CHANGELOG.md +188 -0
  6. metalworks-0.0.1/CODE_OF_CONDUCT.md +41 -0
  7. metalworks-0.0.1/CONTRIBUTING.md +110 -0
  8. metalworks-0.0.1/LICENSE +21 -0
  9. metalworks-0.0.1/PKG-INFO +265 -0
  10. metalworks-0.0.1/README.md +194 -0
  11. metalworks-0.0.1/SECURITY.md +53 -0
  12. metalworks-0.0.1/USAGE_POLICY.md +62 -0
  13. metalworks-0.0.1/docs/ai-agents.md +68 -0
  14. metalworks-0.0.1/docs/build-spec.md +124 -0
  15. metalworks-0.0.1/docs/build-your-own.md +84 -0
  16. metalworks-0.0.1/docs/claude-code.md +112 -0
  17. metalworks-0.0.1/docs/cli.md +134 -0
  18. metalworks-0.0.1/docs/configuration.md +102 -0
  19. metalworks-0.0.1/docs/content-seo.md +89 -0
  20. metalworks-0.0.1/docs/custom-chatmodel.md +74 -0
  21. metalworks-0.0.1/docs/custom-corpus.md +63 -0
  22. metalworks-0.0.1/docs/custom-store.md +55 -0
  23. metalworks-0.0.1/docs/data-model.md +89 -0
  24. metalworks-0.0.1/docs/demand-research.md +102 -0
  25. metalworks-0.0.1/docs/design.md +120 -0
  26. metalworks-0.0.1/docs/extending.md +97 -0
  27. metalworks-0.0.1/docs/how-it-works.md +48 -0
  28. metalworks-0.0.1/docs/index.md +85 -0
  29. metalworks-0.0.1/docs/installation.md +90 -0
  30. metalworks-0.0.1/docs/launch.md +92 -0
  31. metalworks-0.0.1/docs/mcp-tools.md +104 -0
  32. metalworks-0.0.1/docs/positioning.md +120 -0
  33. metalworks-0.0.1/docs/projects.md +111 -0
  34. metalworks-0.0.1/docs/protocols.md +101 -0
  35. metalworks-0.0.1/docs/python-sdk.md +325 -0
  36. metalworks-0.0.1/docs/quickstart.md +92 -0
  37. metalworks-0.0.1/docs/reddit-engagement.md +109 -0
  38. metalworks-0.0.1/docs/walkthrough.md +110 -0
  39. metalworks-0.0.1/docs.json +128 -0
  40. metalworks-0.0.1/llms.txt +68 -0
  41. metalworks-0.0.1/plugin/.claude-plugin/plugin.json +14 -0
  42. metalworks-0.0.1/plugin/.mcp.json +19 -0
  43. metalworks-0.0.1/plugin/README.md +81 -0
  44. metalworks-0.0.1/plugin/bin/launch +22 -0
  45. metalworks-0.0.1/plugin/hooks/hooks.json +14 -0
  46. metalworks-0.0.1/plugin/skills/build-spec/SKILL.md +66 -0
  47. metalworks-0.0.1/plugin/skills/competitor-map/SKILL.md +43 -0
  48. metalworks-0.0.1/plugin/skills/content-plan/SKILL.md +58 -0
  49. metalworks-0.0.1/plugin/skills/demand-report/SKILL.md +40 -0
  50. metalworks-0.0.1/plugin/skills/draft-reply/SKILL.md +43 -0
  51. metalworks-0.0.1/plugin/skills/find-threads/SKILL.md +32 -0
  52. metalworks-0.0.1/plugin/skills/generate-site/SKILL.md +59 -0
  53. metalworks-0.0.1/plugin/skills/launch-kit/SKILL.md +52 -0
  54. metalworks-0.0.1/plugin/skills/position-wedge/SKILL.md +48 -0
  55. metalworks-0.0.1/plugin/skills/subreddit-intel/SKILL.md +33 -0
  56. metalworks-0.0.1/plugin/skills/surface-and-ux/SKILL.md +48 -0
  57. metalworks-0.0.1/pyproject.toml +91 -0
  58. metalworks-0.0.1/scripts/gen_ts_types.py +334 -0
  59. metalworks-0.0.1/src/metalworks/__init__.py +24 -0
  60. metalworks-0.0.1/src/metalworks/_genai_client.py +64 -0
  61. metalworks-0.0.1/src/metalworks/build/__init__.py +29 -0
  62. metalworks-0.0.1/src/metalworks/build/_templates.py +212 -0
  63. metalworks-0.0.1/src/metalworks/build/scaffold.py +228 -0
  64. metalworks-0.0.1/src/metalworks/build/spec.py +235 -0
  65. metalworks-0.0.1/src/metalworks/cli/__init__.py +1226 -0
  66. metalworks-0.0.1/src/metalworks/cli/_demo/__init__.py +129 -0
  67. metalworks-0.0.1/src/metalworks/cli/_demo/scripted.py +317 -0
  68. metalworks-0.0.1/src/metalworks/client.py +601 -0
  69. metalworks-0.0.1/src/metalworks/config.py +338 -0
  70. metalworks-0.0.1/src/metalworks/contract/__init__.py +172 -0
  71. metalworks-0.0.1/src/metalworks/contract/build.py +76 -0
  72. metalworks-0.0.1/src/metalworks/contract/bundle.py +52 -0
  73. metalworks-0.0.1/src/metalworks/contract/evidence.py +65 -0
  74. metalworks-0.0.1/src/metalworks/contract/landscape.py +89 -0
  75. metalworks-0.0.1/src/metalworks/contract/launch.py +114 -0
  76. metalworks-0.0.1/src/metalworks/contract/marketing.py +116 -0
  77. metalworks-0.0.1/src/metalworks/contract/positioning.py +109 -0
  78. metalworks-0.0.1/src/metalworks/contract/reddit.py +216 -0
  79. metalworks-0.0.1/src/metalworks/contract/research.py +631 -0
  80. metalworks-0.0.1/src/metalworks/contract/schema/demand_report.schema.json +1119 -0
  81. metalworks-0.0.1/src/metalworks/contract/schema/discovery_context.schema.json +98 -0
  82. metalworks-0.0.1/src/metalworks/contract/schema/opportunity.schema.json +225 -0
  83. metalworks-0.0.1/src/metalworks/contract/schema/research_brief.schema.json +218 -0
  84. metalworks-0.0.1/src/metalworks/contract/site.py +94 -0
  85. metalworks-0.0.1/src/metalworks/contract/surface.py +129 -0
  86. metalworks-0.0.1/src/metalworks/discovery/__init__.py +34 -0
  87. metalworks-0.0.1/src/metalworks/discovery/deps.py +71 -0
  88. metalworks-0.0.1/src/metalworks/discovery/judge.py +99 -0
  89. metalworks-0.0.1/src/metalworks/discovery/prompts.py +349 -0
  90. metalworks-0.0.1/src/metalworks/discovery/service.py +296 -0
  91. metalworks-0.0.1/src/metalworks/embeddings/__init__.py +86 -0
  92. metalworks-0.0.1/src/metalworks/embeddings/adapters/__init__.py +5 -0
  93. metalworks-0.0.1/src/metalworks/embeddings/adapters/google.py +80 -0
  94. metalworks-0.0.1/src/metalworks/embeddings/adapters/openai.py +63 -0
  95. metalworks-0.0.1/src/metalworks/errors.py +147 -0
  96. metalworks-0.0.1/src/metalworks/llm/__init__.py +37 -0
  97. metalworks-0.0.1/src/metalworks/llm/adapters/__init__.py +6 -0
  98. metalworks-0.0.1/src/metalworks/llm/adapters/_retry.py +60 -0
  99. metalworks-0.0.1/src/metalworks/llm/adapters/anthropic.py +275 -0
  100. metalworks-0.0.1/src/metalworks/llm/adapters/google.py +310 -0
  101. metalworks-0.0.1/src/metalworks/llm/adapters/openai.py +260 -0
  102. metalworks-0.0.1/src/metalworks/llm/fake.py +121 -0
  103. metalworks-0.0.1/src/metalworks/llm/protocol.py +156 -0
  104. metalworks-0.0.1/src/metalworks/llm/structured.py +130 -0
  105. metalworks-0.0.1/src/metalworks/mcp/__init__.py +38 -0
  106. metalworks-0.0.1/src/metalworks/mcp/jobs.py +100 -0
  107. metalworks-0.0.1/src/metalworks/mcp/server.py +307 -0
  108. metalworks-0.0.1/src/metalworks/mcp/tools.py +759 -0
  109. metalworks-0.0.1/src/metalworks/project.py +183 -0
  110. metalworks-0.0.1/src/metalworks/py.typed +0 -0
  111. metalworks-0.0.1/src/metalworks/reddit/__init__.py +41 -0
  112. metalworks-0.0.1/src/metalworks/reddit/audit.py +37 -0
  113. metalworks-0.0.1/src/metalworks/reddit/compliance.py +287 -0
  114. metalworks-0.0.1/src/metalworks/reddit/fetcher.py +203 -0
  115. metalworks-0.0.1/src/metalworks/reddit/inbox.py +206 -0
  116. metalworks-0.0.1/src/metalworks/reddit/oauth.py +378 -0
  117. metalworks-0.0.1/src/metalworks/reddit/ratelimit.py +112 -0
  118. metalworks-0.0.1/src/metalworks/reddit/search.py +301 -0
  119. metalworks-0.0.1/src/metalworks/reddit/subreddit.py +231 -0
  120. metalworks-0.0.1/src/metalworks/research/__init__.py +41 -0
  121. metalworks-0.0.1/src/metalworks/research/arctic/__init__.py +27 -0
  122. metalworks-0.0.1/src/metalworks/research/arctic/api.py +227 -0
  123. metalworks-0.0.1/src/metalworks/research/arctic/hydration.py +202 -0
  124. metalworks-0.0.1/src/metalworks/research/arctic/mirror_reader.py +302 -0
  125. metalworks-0.0.1/src/metalworks/research/arctic/reader.py +256 -0
  126. metalworks-0.0.1/src/metalworks/research/deps.py +95 -0
  127. metalworks-0.0.1/src/metalworks/research/embedding_cache.py +53 -0
  128. metalworks-0.0.1/src/metalworks/research/exploration/__init__.py +111 -0
  129. metalworks-0.0.1/src/metalworks/research/exploration/corpus_shape.py +106 -0
  130. metalworks-0.0.1/src/metalworks/research/exploration/embedding_triage.py +235 -0
  131. metalworks-0.0.1/src/metalworks/research/exploration/llm_classifier.py +250 -0
  132. metalworks-0.0.1/src/metalworks/research/landscape.py +335 -0
  133. metalworks-0.0.1/src/metalworks/research/launch.py +312 -0
  134. metalworks-0.0.1/src/metalworks/research/marketing.py +193 -0
  135. metalworks-0.0.1/src/metalworks/research/pipeline.py +300 -0
  136. metalworks-0.0.1/src/metalworks/research/planner/__init__.py +33 -0
  137. metalworks-0.0.1/src/metalworks/research/planner/auto.py +84 -0
  138. metalworks-0.0.1/src/metalworks/research/planner/brief_assembler.py +163 -0
  139. metalworks-0.0.1/src/metalworks/research/planner/decision_brief.py +125 -0
  140. metalworks-0.0.1/src/metalworks/research/planner/llm_planner.py +482 -0
  141. metalworks-0.0.1/src/metalworks/research/planner/question_bank.py +200 -0
  142. metalworks-0.0.1/src/metalworks/research/planner/store.py +73 -0
  143. metalworks-0.0.1/src/metalworks/research/planner/subreddit_picker.py +151 -0
  144. metalworks-0.0.1/src/metalworks/research/site.py +420 -0
  145. metalworks-0.0.1/src/metalworks/research/surface.py +303 -0
  146. metalworks-0.0.1/src/metalworks/research/synthesis/__init__.py +224 -0
  147. metalworks-0.0.1/src/metalworks/research/synthesis/audience.py +213 -0
  148. metalworks-0.0.1/src/metalworks/research/synthesis/cluster_ranker.py +234 -0
  149. metalworks-0.0.1/src/metalworks/research/synthesis/embed_group.py +65 -0
  150. metalworks-0.0.1/src/metalworks/research/synthesis/loader.py +98 -0
  151. metalworks-0.0.1/src/metalworks/research/synthesis/market.py +39 -0
  152. metalworks-0.0.1/src/metalworks/research/synthesis/positioning.py +295 -0
  153. metalworks-0.0.1/src/metalworks/research/synthesis/pricing.py +149 -0
  154. metalworks-0.0.1/src/metalworks/research/synthesis/segments.py +125 -0
  155. metalworks-0.0.1/src/metalworks/research/synthesis/verdict.py +45 -0
  156. metalworks-0.0.1/src/metalworks/research/triangulate/__init__.py +24 -0
  157. metalworks-0.0.1/src/metalworks/research/triangulate/confidence_weighter.py +81 -0
  158. metalworks-0.0.1/src/metalworks/research/triangulate/triangulator.py +359 -0
  159. metalworks-0.0.1/src/metalworks/research/types.py +176 -0
  160. metalworks-0.0.1/src/metalworks/research/web.py +394 -0
  161. metalworks-0.0.1/src/metalworks/runs.py +97 -0
  162. metalworks-0.0.1/src/metalworks/search/__init__.py +46 -0
  163. metalworks-0.0.1/src/metalworks/search/adapters/__init__.py +5 -0
  164. metalworks-0.0.1/src/metalworks/search/adapters/exa.py +66 -0
  165. metalworks-0.0.1/src/metalworks/search/adapters/tavily.py +63 -0
  166. metalworks-0.0.1/src/metalworks/stores/__init__.py +47 -0
  167. metalworks-0.0.1/src/metalworks/stores/crypto.py +70 -0
  168. metalworks-0.0.1/src/metalworks/stores/filestore.py +66 -0
  169. metalworks-0.0.1/src/metalworks/stores/memory.py +188 -0
  170. metalworks-0.0.1/src/metalworks/stores/repos.py +238 -0
  171. metalworks-0.0.1/src/metalworks/stores/sqlite.py +391 -0
  172. metalworks-0.0.1/src/metalworks/stores/vectors.py +74 -0
  173. metalworks-0.0.1/src/metalworks/testing/__init__.py +210 -0
  174. metalworks-0.0.1/tests/test_adapters.py +546 -0
  175. metalworks-0.0.1/tests/test_arctic_mirror_reader.py +276 -0
  176. metalworks-0.0.1/tests/test_build.py +481 -0
  177. metalworks-0.0.1/tests/test_cli.py +189 -0
  178. metalworks-0.0.1/tests/test_client.py +160 -0
  179. metalworks-0.0.1/tests/test_config_resolution.py +187 -0
  180. metalworks-0.0.1/tests/test_contract.py +89 -0
  181. metalworks-0.0.1/tests/test_crypto.py +54 -0
  182. metalworks-0.0.1/tests/test_discovery_prompts.py +100 -0
  183. metalworks-0.0.1/tests/test_discovery_service.py +389 -0
  184. metalworks-0.0.1/tests/test_embedding_cache.py +81 -0
  185. metalworks-0.0.1/tests/test_embeddings.py +38 -0
  186. metalworks-0.0.1/tests/test_embeddings_store.py +101 -0
  187. metalworks-0.0.1/tests/test_evidence.py +179 -0
  188. metalworks-0.0.1/tests/test_filestore.py +60 -0
  189. metalworks-0.0.1/tests/test_imports.py +58 -0
  190. metalworks-0.0.1/tests/test_llm_protocol.py +120 -0
  191. metalworks-0.0.1/tests/test_mcp_server.py +219 -0
  192. metalworks-0.0.1/tests/test_project.py +119 -0
  193. metalworks-0.0.1/tests/test_public_testing_module.py +34 -0
  194. metalworks-0.0.1/tests/test_reddit_compliance.py +107 -0
  195. metalworks-0.0.1/tests/test_reddit_fetcher.py +148 -0
  196. metalworks-0.0.1/tests/test_reddit_inbox.py +137 -0
  197. metalworks-0.0.1/tests/test_reddit_oauth.py +194 -0
  198. metalworks-0.0.1/tests/test_reddit_ratelimit.py +76 -0
  199. metalworks-0.0.1/tests/test_reddit_search.py +140 -0
  200. metalworks-0.0.1/tests/test_reddit_subreddit.py +143 -0
  201. metalworks-0.0.1/tests/test_research_arctic.py +467 -0
  202. metalworks-0.0.1/tests/test_research_exploration.py +258 -0
  203. metalworks-0.0.1/tests/test_research_landscape.py +259 -0
  204. metalworks-0.0.1/tests/test_research_launch.py +272 -0
  205. metalworks-0.0.1/tests/test_research_marketing.py +265 -0
  206. metalworks-0.0.1/tests/test_research_pipeline_e2e.py +268 -0
  207. metalworks-0.0.1/tests/test_research_planner.py +311 -0
  208. metalworks-0.0.1/tests/test_research_positioning.py +345 -0
  209. metalworks-0.0.1/tests/test_research_site.py +398 -0
  210. metalworks-0.0.1/tests/test_research_surface.py +273 -0
  211. metalworks-0.0.1/tests/test_research_synthesis.py +394 -0
  212. metalworks-0.0.1/tests/test_research_triangulate.py +264 -0
  213. metalworks-0.0.1/tests/test_research_web.py +200 -0
  214. metalworks-0.0.1/tests/test_runs.py +153 -0
  215. metalworks-0.0.1/tests/test_stores_conformance.py +244 -0
  216. metalworks-0.0.1/tests/test_vertex_support.py +241 -0
  217. metalworks-0.0.1/ts/contract.ts +722 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "lab2a",
3
+ "owner": {
4
+ "name": "Lab2A",
5
+ "email": "hello@lab2a.dev"
6
+ },
7
+ "description": "Lab2A plugins. metalworks: Reddit demand research and engagement from Claude Code.",
8
+ "plugins": [
9
+ {
10
+ "name": "metalworks",
11
+ "source": "./plugin",
12
+ "description": "Reddit demand reports, thread discovery, and gated reply drafting via the metalworks MCP server. Zero-key data tools plus key-gated pipelines.",
13
+ "repository": "https://github.com/Lab2A/metalworks",
14
+ "license": "MIT",
15
+ "keywords": ["reddit", "marketing", "research", "demand", "mcp"]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,49 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: test (py${{ matrix.python }}, ${{ matrix.extras }})
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python: ["3.11", "3.12", "3.13"]
16
+ extras: ["bare", "all"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v5
20
+ with:
21
+ python-version: ${{ matrix.python }}
22
+ - run: uv venv --clear
23
+ - name: Install (bare)
24
+ if: matrix.extras == 'bare'
25
+ run: uv pip install -e ".[dev]"
26
+ - name: Install (all extras)
27
+ if: matrix.extras == 'all'
28
+ run: uv pip install -e ".[all,dev]"
29
+ # Offline by default: pytest-socket blocks all network in CI tests.
30
+ - name: Test
31
+ run: uv run pytest -q
32
+ - name: Console script smoke
33
+ run: uv run metalworks version
34
+
35
+ lint:
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v5
40
+ with:
41
+ python-version: "3.12"
42
+ - run: uv venv --clear
43
+ # All extras so pyright resolves real provider SDK imports (crypto,
44
+ # supabase, etc.). Adapters keep SDK objects behind Any regardless, but
45
+ # the few real `from x import y` lines need x present to typecheck.
46
+ - run: uv pip install -e ".[all,dev]"
47
+ - run: uv run ruff check .
48
+ - run: uv run ruff format --check .
49
+ - run: uv run pyright
@@ -0,0 +1,36 @@
1
+ name: release
2
+
3
+ # Tag vX.Y.Z -> build -> publish to PyPI via trusted publishing (OIDC).
4
+ # Requires the PyPI project to have Lab2A/metalworks configured as a
5
+ # trusted publisher for this workflow file.
6
+
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - run: uv build
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: dist
23
+ path: dist/
24
+
25
+ publish:
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write
31
+ steps:
32
+ - uses: actions/download-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ /build/ # packaging output only — anchored so it never shadows src/metalworks/build/
7
+ .venv/
8
+ .python-version
9
+
10
+ # Tooling caches
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ .pyright/
14
+ .coverage
15
+
16
+ # Env / secrets — never committed
17
+ .env
18
+ .env.*
19
+ !.env.example
20
+
21
+ # OS
22
+ .DS_Store
23
+
24
+ # gstack local runtime state (browse daemon, tokens) — never committed
25
+ .gstack/
26
+
27
+ # Gate / scratch run artifacts (local evidence, not committed)
28
+ .gate/
@@ -0,0 +1,188 @@
1
+ # Changelog
2
+
3
+ All notable changes to metalworks are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims
5
+ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once it
6
+ reaches 1.0. Below 1.0, anything outside `metalworks.contract` and the MCP tool
7
+ contracts may change in any release.
8
+
9
+ ## [Unreleased]
10
+
11
+ Pre-release foundations. Nothing here is stable yet.
12
+
13
+ ### Added
14
+
15
+ - **Contract** (`metalworks.contract`): Pydantic models for the research and
16
+ Reddit surfaces (`DemandReport`, `ResearchBrief`, `Opportunity`,
17
+ `RedditPost`, `RedditComment`, `SubredditIntel`, `InboxItem`,
18
+ `ComplianceVerdict`, `DiscoveryContext`, ...), plus a TypeScript-type +
19
+ JSON-schema generator with diff-gated snapshots.
20
+ - **Protocols + adapters**: own versioned `ChatModel` / `GroundedChatModel` /
21
+ `SearchProvider` / `EmbeddingProvider` protocols with a structured-output
22
+ ladder, plus thin adapters over official SDKs behind extras
23
+ (`anthropic`, `openai`, `google`, `exa`, `tavily`). Google grounding converts
24
+ provider byte offsets to character offsets so non-ASCII provenance stays
25
+ correct.
26
+ - **Stores**: typed repos (`CorpusRepo`, `BriefRepo`, `RunRepo`, `AccountRepo`,
27
+ `OpportunityRepo`, `InboxRepo`) with `MemoryStores` + `SqliteStores` in core
28
+ (hosted backends live downstream via the same protocols). `CorpusRepo` carries
29
+ a local vector memory — embeddings as float64 blobs + brute-force numpy cosine,
30
+ no new core dependency. New `ArtifactStore` protocol + files-first `FileStore`
31
+ for Tier-2 pillar artifacts. `TokenCipher` for OAuth tokens at rest. Public
32
+ conformance suite in `metalworks.testing`.
33
+ - **Research vertical** (`metalworks.research`): brief planner, Arctic Shift
34
+ corpus reader (HF Parquet submissions + live API comments), hybrid triage,
35
+ clustered synthesis with exact-matched quotes, web research (grounded +
36
+ external), cross-stream triangulation, and `run_research` end to end.
37
+ - **Reddit core** (`metalworks.reddit`): rate limiter (token bucket honoring
38
+ Reddit's headers), OAuth + posting on httpx with typed errors, search,
39
+ metrics, inbox classification, subreddit intel, and a deterministic
40
+ compliance gate.
41
+ - **Project layer** (`.metalworks/`): a project is a directory like `.git`.
42
+ `metalworks init` creates it with a `project.json` manifest, a gitignored
43
+ `corpus.db` (corpus + embeddings), committed `runs/<id>/research.{md,json}`,
44
+ and an `artifacts/` tree. A casual `Metalworks().research(...)` with no project
45
+ present leaves zero footprint. Non-secret config lives in
46
+ `.metalworks/config.toml` (a legacy cwd `metalworks.toml` is still read).
47
+ - **Evidence chain**: content-addressed stable ids on `QuoteCitation` /
48
+ `WebFinding` / `PriceEvidence`, a `metalworks.contract.evidence` module
49
+ (`EvidenceRef` / `EvidenceRecord`), and a `DemandReport.evidence` accessor —
50
+ the spine downstream pillars resolve grounded claims against.
51
+ - **Embedding reuse**: the research pipeline persists corpus embeddings and
52
+ reuses them across runs (keyed on the embedding model identity), so a re-run on
53
+ the same corpus re-embeds only the query.
54
+ - **Front door**: `Metalworks().research(question, subreddits=...)` returns a
55
+ frozen `Research` bundle, and the CLI gains `metalworks research run
56
+ --question "..."` (no `brief.json` round-trip for the common case).
57
+ - **Vertex AI**: the Google chat + embedding adapters authenticate via Vertex
58
+ (Application Default Credentials) when `GOOGLE_GENAI_USE_VERTEXAI=true`
59
+ (`VERTEX_PROJECT_ID`/`GOOGLE_CLOUD_PROJECT` + `VERTEX_LOCATION`), not just an
60
+ API key — `build_genai_client` in `metalworks._genai_client`; provider
61
+ resolution routes to Google under Vertex even with no `GOOGLE_API_KEY`.
62
+ - **Marketing site (Pillar E)**: `build_marketing_site(deps, report, positioning=None)
63
+ -> MarketingSite` + `render_site_html(site, report)` (`metalworks.research.site`).
64
+ Top 3 clusters by demand_score; one constrained LLM call assigns each a
65
+ SiteSection role and picks a VERBATIM fragment; the builder re-runs exact-match
66
+ against the cluster's real `QuoteCitation.text` and DROPS any section that
67
+ isn't a verbatim substring (no-quote-no-section). Connective copy ships
68
+ `provenance="connective"` with no refs and is gated claim-free. Hero = the
69
+ highest-distinct-author cluster. `render_site_html` emits one self-contained
70
+ `index.html` with a permalink footnote (+ `data-evidence`) per verbatim
71
+ section. New contract `metalworks.contract.site` (`MarketingSite` /
72
+ `SiteSection`). CLI `metalworks research site`, MCP Tier-2 `site_render`, skill
73
+ `generate-site`.
74
+ - **Launch (Pillar F)**: `build_launch_assets(deps, report, positioning) ->
75
+ list[LaunchAsset]` + `plan_channels(report) -> ChannelPlan`
76
+ (`metalworks.research.launch`). Refuses (returns []) on a no-go report
77
+ (negative verdict or no cluster ≥2 distinct authors). One LLM call per surface
78
+ (Product Hunt / Show HN / X thread) → title + body + variants + claims; each
79
+ claim is grounded to a real quote and carries a `ClaimCitation` with char-offset
80
+ spans into the body (`body[span_start:span_end] == claim_text`) — unresolvable
81
+ claims are dropped. Bodies run through the compliance gate best-effort.
82
+ Drafting-only: `plan_channels` marks every `ChannelStep` `requires_human` +
83
+ `posting_gated`; the library never posts. New contract
84
+ `metalworks.contract.launch` (`LaunchAsset` / `ClaimCitation` / `ChannelPlan` /
85
+ `ChannelStep`). CLI `metalworks research launch`, MCP Tier-2 `launch_assets_build`
86
+ + Tier-1 `channel_plan_build`, skill `launch-kit`.
87
+ - **Content/SEO (Pillar G)**: `content_plan_from_report(report) -> ContentPlan`
88
+ (`metalworks.research.marketing`) — PURE deterministic, zero-key, no LLM. One
89
+ `ContentPage` per cluster (normalized target_phrase, heuristic page_kind,
90
+ real-count `stat_anchors`, FAQ from `ResearchBrief.must_address` verbatim) plus
91
+ a `CitationStrategy` whose `reddit_targets` are real quote permalinks.
92
+ `render_content_markdown` + `render_faq_jsonld` (a mechanical FAQPage stub).
93
+ Makes no ranking promises; never invents a keyword or quote. New contract
94
+ `metalworks.contract.marketing` (`ContentPlan` / `ContentPage` / `FaqItem` /
95
+ `CitationStrategy`). CLI `metalworks research content-plan`, MCP **Tier-1**
96
+ (zero-key) `content_plan_from_report`, skill `content-plan`.
97
+ - **Build (Pillar D)**: `build_spec_from_report(deps, report, positioning=None,
98
+ surface="web", *, stack="empty") -> BuildSpec` + `scaffold(spec, report, dest,
99
+ *, base) -> list[Path]` (`metalworks.build`). One LLM call maps demand clusters
100
+ to candidate features; grounding is DETERMINISTIC — each feature is attached to
101
+ its `source_cluster_rank`'s verbatim quotes and DROPPED if that cluster is
102
+ invalid or quote-less (no-cite-no-feature), so the model cannot smuggle in an
103
+ un-grounded feature. Personas derive from the report's audience segments;
104
+ pricing tiers copy through from the report's price evidence (never recomputed).
105
+ An infra error (404/auth) propagates rather than being relabelled a thin-demand
106
+ `partial`. `scaffold` writes a deterministic build harness for the user's OWN
107
+ coding agent — `CLAUDE.md` (cite-or-die Rule 0), `docs/SPEC.md`, a frozen
108
+ `docs/EVIDENCE.md` quote+permalink table, a build-pack of skills
109
+ (`scaffold-startup` / `spec-from-report` / `cite-or-die`), a `cite_or_die.py`
110
+ PostToolUse lint, and `.mcp.json` — but writes NO product code (`--base` is a
111
+ stack hint, not vendored boilerplate). New contract `metalworks.contract.build`
112
+ (`BuildSpec` / `FeatureSpec` / `BuildPersona` / `PricingTier`). CLI `metalworks
113
+ build init`, MCP **Tier-2** `build_spec`, skill `build-spec`.
114
+ - **Surface + UX (Pillar C, Design stage)**: `decide_surface(deps, report,
115
+ positioning) -> SurfaceRecommendation` + `build_ux_skeleton(deps, report,
116
+ positioning, surface) -> UxSkeleton` (`metalworks.research.surface`). A FIXED
117
+ five-dimension rubric (where-are-the-users, technical sophistication, usage
118
+ frequency, realtime/hardware, distribution) drives the surface pick; one LLM
119
+ call phrases each dimension's finding + the chosen surface, and the service
120
+ GROUNDS each by cosine-matching to the report's real evidence — a dimension
121
+ with no match is marked `is_assumption`, and `confidence` is service-assigned
122
+ from grounded coverage. UX screens with no backing voice ship `validated=False`
123
+ (an explicit hypothesis). Text + structure only (no pixels); the `DesignBrief`
124
+ handoff is explicitly ungrounded. New contract `metalworks.contract.surface`
125
+ (`SurfaceRecommendation` / `UxSkeleton` / `RubricDimension` / `Screen` /
126
+ `TradeOff` / `DesignBrief`); `Research.competitors` and `.positioning` are both
127
+ real fields now. Surfaced via `metalworks surface <report_id>` CLI, the
128
+ synchronous `surface_recommend` + `ux_skeleton_build` MCP tools, and the
129
+ `surface-and-ux` skill.
130
+ - **Landscape (Pillar A)**: `run_competitor_map(deps, report) -> CompetitorMap`
131
+ (`metalworks.research.landscape`) maps the competitive set — direct, adjacent,
132
+ and the mandatory status-quo "do nothing" alternative — with an exploitable,
133
+ EVIDENCED gap per competitor. Four deterministic stages: grounded enumeration
134
+ (names with zero grounding chunks dropped; degrades to an ungrounded structured
135
+ call marked `partial`); per-competitor strength/gap harvest; cosine
136
+ complaint-matching of each gap against the report's real evidence (cluster
137
+ quotes first, then web findings); assemble, dropping any gap with no resolvable
138
+ evidence (no-quote-no-gap). `severity` is service-assigned from the matched
139
+ complaint's distinct-author breadth, never LLM. The status-quo alternative is
140
+ built deterministically from the top clusters (always verbatim-grounded). New
141
+ contract `metalworks.contract.landscape` (`CompetitorMap` / `Competitor` /
142
+ `GapClaim` / `StrengthClaim`), every gap an `EvidenceRef`; `Research.competitors`
143
+ is now a real optional field. Surfaced via the `metalworks competitor-map
144
+ <report_id>` CLI, the synchronous `competitor_map_from_report` MCP tool, and the
145
+ `competitor-map` skill.
146
+ - **Positioning (Pillar B)**: `build_positioning_brief(deps, report) -> PositioningBrief`
147
+ (`metalworks.research.synthesis.positioning`) turns a demand report into a
148
+ grounded Dunford wedge + price hypothesis. Wedge SELECTION is deterministic —
149
+ it stands on an `InsightCluster` the web stream is `silent_web`/`disagree` on at
150
+ ≥ MEDIUM signal (a pain competitors miss), ranked by `demand_score`; no white
151
+ space → an honest null brief. Exactly one LLM call phrases the three free-text
152
+ slots (constrained to the Dunford template) and a second pass verifies each
153
+ clause is entailed by its cited quotes (marks the brief `partial` if not). The
154
+ price band is copied through from `PriceFinding`, never recomputed. New
155
+ contract `metalworks.contract.positioning` (`PositioningBrief` / `WedgeClaim` /
156
+ `PriceHypothesis`), every slot an `EvidenceRef`; `Research.positioning` is now a
157
+ real optional field. Surfaced via the `metalworks position <report_id>` CLI,
158
+ the synchronous `positioning_from_report` MCP tool, and the `position-wedge` skill.
159
+ - **Supabase mirror reader**: `ArcticMirrorReader` (`metalworks[supabase]`) reads
160
+ the Arctic submission corpus from a Supabase Storage bucket — months from the
161
+ `arctic_shift_pulls` table, shards listed and signed at query time, DuckDB
162
+ reading the signed URLs with `WHERE subreddit`/`id IN` pushdown. A faster
163
+ alternative to the HF mirror that removes HF as a runtime dependency;
164
+ implements `CorpusReader` and is selected at runtime with
165
+ `ARCTIC_SHIFT_SOURCE=mirror`.
166
+
167
+ ### Changed
168
+
169
+ - `Metalworks.research()` now returns a `Research` bundle instead of a bare
170
+ `DemandReport`; the report is on `.demand` and the grounded evidence on
171
+ `.evidence`. This stabilizes the stage-1 front-door shape before the 0.1.0 tag.
172
+ - Renamed the low-level discovery export `generate_reply` → `draft_reply` (the
173
+ MCP tool name is unchanged).
174
+ - Cut `SupabaseStores` from the OSS core; hosted store backends bind to the
175
+ same repo protocols downstream. The `[supabase]` extra now scopes the Arctic
176
+ mirror reader (above) instead of the dropped stores.
177
+ - The Google adapters respect Vertex's request ceilings: embeddings are batched
178
+ at 100 instances/request (Vertex caps at 250) and chat clamps
179
+ `max_output_tokens` to 65536, so large triage/synthesis calls no longer 400.
180
+ - Tightened package public surfaces: demoted internal plumbing out of the
181
+ `reddit` / `research` / `discovery` package `__all__`s (still importable from
182
+ their submodules).
183
+
184
+ ### Notes
185
+
186
+ - A bare `import metalworks` pulls in no provider SDKs; CI asserts this.
187
+ - The fabricated-persona / account-backstory tooling from the source pipeline
188
+ was deliberately not open-sourced. See `USAGE_POLICY.md`.
@@ -0,0 +1,41 @@
1
+ # Code of Conduct
2
+
3
+ ## Our pledge
4
+
5
+ We as members, contributors, and maintainers pledge to make participation in
6
+ the metalworks project a harassment-free experience for everyone, regardless of
7
+ age, body size, visible or invisible disability, ethnicity, sex
8
+ characteristics, gender identity and expression, level of experience,
9
+ education, socio-economic status, nationality, personal appearance, race,
10
+ religion, or sexual identity and orientation.
11
+
12
+ ## Our standards
13
+
14
+ Examples of behavior that contributes to a positive environment:
15
+
16
+ - Being respectful of differing opinions, viewpoints, and experiences.
17
+ - Giving and gracefully accepting constructive feedback.
18
+ - Accepting responsibility, apologizing to those affected by our mistakes, and
19
+ learning from the experience.
20
+ - Focusing on what is best for the community.
21
+
22
+ Examples of unacceptable behavior:
23
+
24
+ - Sexualized language or imagery, and sexual attention or advances of any kind.
25
+ - Trolling, insulting or derogatory comments, and personal or political attacks.
26
+ - Public or private harassment.
27
+ - Publishing others' private information without explicit permission.
28
+ - Other conduct which could reasonably be considered inappropriate in a
29
+ professional setting.
30
+
31
+ ## Enforcement
32
+
33
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
34
+ reported to the maintainers at **conduct@lab2a.dev** (update this address).
35
+ All complaints will be reviewed and investigated promptly and fairly. The
36
+ maintainers are obligated to respect the privacy and security of the reporter.
37
+
38
+ ## Attribution
39
+
40
+ This Code of Conduct is adapted from the
41
+ [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,110 @@
1
+ # Contributing to metalworks
2
+
3
+ Thanks for helping. metalworks is pre-release, so the most useful contributions
4
+ right now are provider adapters, storage backends, bug reports against the
5
+ shipped pieces, and docs fixes. Please read [USAGE_POLICY.md](USAGE_POLICY.md)
6
+ and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) first.
7
+
8
+ ## Dev setup
9
+
10
+ We use [uv](https://docs.astral.sh/uv/).
11
+
12
+ ```bash
13
+ uv venv
14
+ uv pip install -e ".[all,dev]"
15
+ ```
16
+
17
+ `[all,dev]` gives you every provider adapter plus the test and lint toolchain.
18
+
19
+ ## The gate
20
+
21
+ Everything must pass before a PR lands:
22
+
23
+ ```bash
24
+ ruff check .
25
+ ruff format --check .
26
+ pyright
27
+ pytest -q
28
+ ```
29
+
30
+ `pyright` runs in strict mode over `src`. The test suite runs offline:
31
+ `pytest-socket` disables network, recorded fixtures (respx cassettes, committed
32
+ Parquet shards, recorded grounding responses) stand in for live services.
33
+
34
+ ## The extras model
35
+
36
+ Core depends only on pydantic, httpx, typing-extensions, typer, and rich.
37
+ Everything that pulls a provider SDK or a heavy dependency (duckdb, supabase,
38
+ the LLM SDKs) lives behind an extra.
39
+
40
+ Provider SDKs are **lazy-imported behind their extra**. The import happens
41
+ inside the method that needs it, not at module top level, so:
42
+
43
+ - `import metalworks` never requires a provider SDK. CI asserts a bare import
44
+ pulls in zero provider modules.
45
+ - A missing extra raises `MissingExtraError` carrying the exact
46
+ `pip install "metalworks[...]"` command, instead of a raw `ModuleNotFoundError`.
47
+
48
+ When you add an import of an optional dependency, wrap it:
49
+
50
+ ```python
51
+ try:
52
+ import duckdb
53
+ except ImportError as exc:
54
+ raise MissingExtraError("arctic", package="duckdb") from exc
55
+ ```
56
+
57
+ ## Adding a provider adapter
58
+
59
+ 1. Implement the protocol. For a chat adapter, implement `ChatModel`
60
+ (`complete_text`, `complete_structured`, the `model_id` /
61
+ `capabilities` / `protocol_version` attributes). Add `complete_grounded` and
62
+ set `capabilities.native_grounding` only if the provider does native web
63
+ grounding, and carry the full `GroundedResult` provenance (chunks plus
64
+ character-offset supports, converting from the provider's offsets).
65
+ 2. Lazy-import the SDK behind your extra and raise `MissingExtraError` when it
66
+ is absent.
67
+ 3. Read credentials from the environment, raise `MissingKeyError` (naming the
68
+ env var) when absent. Never read keys from config or at import time.
69
+ 4. Run the conformance suite against your adapter:
70
+
71
+ ```python
72
+ from metalworks.testing import FakeChatModel # reference behavior
73
+ # repo backends:
74
+ from metalworks.testing import check_all_repos
75
+ check_all_repos(MyBackend())
76
+ ```
77
+
78
+ `check_all_repos` includes the >1000-row pagination case that catches
79
+ silently-truncating backends. Match the semantics the fakes and built-in
80
+ backends demonstrate.
81
+
82
+ See [docs/how-to-custom-chatmodel.md](docs/how-to-custom-chatmodel.md) for a
83
+ worked example.
84
+
85
+ ## Rules that are not negotiable
86
+
87
+ - **No module-level singletons.** No constructing clients, repos, or models at
88
+ import time.
89
+ - **No env reads at import.** Read environment variables inside functions, never
90
+ at module scope. A clean-env import test walks every submodule and asserts zero
91
+ exceptions and zero network with no environment variables set.
92
+ - **Protocols are versioned as a unit.** Additive keyword-only parameters with
93
+ defaults are a minor bump; anything breaking is a major bump.
94
+
95
+ ## CI matrix
96
+
97
+ CI runs on Python 3.11, 3.12, and 3.13, each against a bare install and an
98
+ `[all]` install. The bare leg proves core imports clean with no provider
99
+ dependencies; the `[all]` leg runs the full suite.
100
+
101
+ ## Releases and version pinning
102
+
103
+ The Claude Code plugin launches the MCP server with a version-pinned `uvx`
104
+ command. The plugin and the PyPI package bump in lockstep: when you cut a PyPI
105
+ release, the plugin's pinned version moves with it, in the same release. Do not
106
+ ship one without the other.
107
+
108
+ Follow the deprecation policy: emit a `DeprecationWarning` at least one minor
109
+ version before removing anything, and call out breaking changes in
110
+ [CHANGELOG.md](CHANGELOG.md) with migration notes.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lab2A
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.