braindump-ai 0.1.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 (75) hide show
  1. braindump_ai-0.1.0/.claude/agents/feature-development.md +0 -0
  2. braindump_ai-0.1.0/.claude/settings.json +14 -0
  3. braindump_ai-0.1.0/.claude/skills/code-quality/SKILL.md +37 -0
  4. braindump_ai-0.1.0/.claude/skills/package-structure/SKILL.md +25 -0
  5. braindump_ai-0.1.0/.claude/skills/python-dev/SKILL.md +170 -0
  6. braindump_ai-0.1.0/.claude/skills/python-dev/python_template.md +24 -0
  7. braindump_ai-0.1.0/.claude/skills/react-dev/SKILL.md +175 -0
  8. braindump_ai-0.1.0/.github/workflows/pr.yml +66 -0
  9. braindump_ai-0.1.0/.github/workflows/publish.yml +57 -0
  10. braindump_ai-0.1.0/.gitignore +215 -0
  11. braindump_ai-0.1.0/.python-version +1 -0
  12. braindump_ai-0.1.0/CLAUDE.md +120 -0
  13. braindump_ai-0.1.0/LICENSE +201 -0
  14. braindump_ai-0.1.0/PKG-INFO +113 -0
  15. braindump_ai-0.1.0/README.md +98 -0
  16. braindump_ai-0.1.0/frontend/eslint.config.js +20 -0
  17. braindump_ai-0.1.0/frontend/index.html +13 -0
  18. braindump_ai-0.1.0/frontend/package-lock.json +3088 -0
  19. braindump_ai-0.1.0/frontend/package.json +34 -0
  20. braindump_ai-0.1.0/frontend/src/App.css +263 -0
  21. braindump_ai-0.1.0/frontend/src/App.tsx +316 -0
  22. braindump_ai-0.1.0/frontend/src/api.ts +150 -0
  23. braindump_ai-0.1.0/frontend/src/assets/favicon.png +0 -0
  24. braindump_ai-0.1.0/frontend/src/assets/logo.png +0 -0
  25. braindump_ai-0.1.0/frontend/src/components/ErrorToast.css +109 -0
  26. braindump_ai-0.1.0/frontend/src/components/ErrorToast.tsx +64 -0
  27. braindump_ai-0.1.0/frontend/src/components/GraphView.css +36 -0
  28. braindump_ai-0.1.0/frontend/src/components/GraphView.tsx +201 -0
  29. braindump_ai-0.1.0/frontend/src/components/HierarchyView.css +132 -0
  30. braindump_ai-0.1.0/frontend/src/components/HierarchyView.tsx +209 -0
  31. braindump_ai-0.1.0/frontend/src/components/MarkdownPreview.css +48 -0
  32. braindump_ai-0.1.0/frontend/src/components/MarkdownPreview.tsx +58 -0
  33. braindump_ai-0.1.0/frontend/src/components/QueryBar.css +312 -0
  34. braindump_ai-0.1.0/frontend/src/components/QueryBar.tsx +180 -0
  35. braindump_ai-0.1.0/frontend/src/components/SearchBar.css +42 -0
  36. braindump_ai-0.1.0/frontend/src/components/SearchBar.tsx +23 -0
  37. braindump_ai-0.1.0/frontend/src/components/SpikeDetail.css +125 -0
  38. braindump_ai-0.1.0/frontend/src/components/SpikeDetail.tsx +91 -0
  39. braindump_ai-0.1.0/frontend/src/components/SpikeEditor.css +171 -0
  40. braindump_ai-0.1.0/frontend/src/components/SpikeEditor.tsx +153 -0
  41. braindump_ai-0.1.0/frontend/src/components/SpikeList.css +96 -0
  42. braindump_ai-0.1.0/frontend/src/components/SpikeList.tsx +39 -0
  43. braindump_ai-0.1.0/frontend/src/components/StatusBar.css +247 -0
  44. braindump_ai-0.1.0/frontend/src/components/StatusBar.tsx +224 -0
  45. braindump_ai-0.1.0/frontend/src/components/TagsInput.css +96 -0
  46. braindump_ai-0.1.0/frontend/src/components/TagsInput.tsx +116 -0
  47. braindump_ai-0.1.0/frontend/src/main.tsx +10 -0
  48. braindump_ai-0.1.0/frontend/src/types.ts +29 -0
  49. braindump_ai-0.1.0/frontend/src/utils.ts +7 -0
  50. braindump_ai-0.1.0/frontend/src/vite-env.d.ts +16 -0
  51. braindump_ai-0.1.0/frontend/tsconfig.json +17 -0
  52. braindump_ai-0.1.0/frontend/vite.config.ts +29 -0
  53. braindump_ai-0.1.0/pyproject.toml +101 -0
  54. braindump_ai-0.1.0/src/braindump/__init__.py +13 -0
  55. braindump_ai-0.1.0/src/braindump/app.py +589 -0
  56. braindump_ai-0.1.0/src/braindump/dirs.py +115 -0
  57. braindump_ai-0.1.0/src/braindump/health.py +226 -0
  58. braindump_ai-0.1.0/src/braindump/llm.py +187 -0
  59. braindump_ai-0.1.0/src/braindump/main.py +259 -0
  60. braindump_ai-0.1.0/src/braindump/query.py +204 -0
  61. braindump_ai-0.1.0/src/braindump/storage.py +415 -0
  62. braindump_ai-0.1.0/src/braindump/txlog.py +209 -0
  63. braindump_ai-0.1.0/src/braindump/types.py +198 -0
  64. braindump_ai-0.1.0/src/braindump/wiki.py +742 -0
  65. braindump_ai-0.1.0/tests/conftest.py +59 -0
  66. braindump_ai-0.1.0/tests/test_app.py +265 -0
  67. braindump_ai-0.1.0/tests/test_health.py +141 -0
  68. braindump_ai-0.1.0/tests/test_llm.py +216 -0
  69. braindump_ai-0.1.0/tests/test_main.py +83 -0
  70. braindump_ai-0.1.0/tests/test_query.py +118 -0
  71. braindump_ai-0.1.0/tests/test_storage.py +470 -0
  72. braindump_ai-0.1.0/tests/test_wiki.py +356 -0
  73. braindump_ai-0.1.0/tools/check.py +150 -0
  74. braindump_ai-0.1.0/tools/hatch_build.py +39 -0
  75. braindump_ai-0.1.0/uv.lock +1048 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run ruff:*)",
5
+ "Bash(uv run ty:*)",
6
+ "Bash(uv run pytest:*)",
7
+ "Bash(uv run bandit:*)",
8
+ "Bash(uv run tools/check.py:*)",
9
+ "Bash(npx tsc:*)",
10
+ "Bash(npm run lint:*)",
11
+ "Bash(npm run build:*)"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: code-quality
3
+ description: >
4
+ Activate this skill for any task updating the source code.
5
+
6
+ This includes:
7
+ - creating a new source file
8
+ - updating an existing source file
9
+ - writing new tests
10
+ - update existing tests
11
+ ---
12
+
13
+ # Code Quality Guardian
14
+
15
+ This sections summarizes the checks that must pass before finalizing a code change.
16
+
17
+ ## Software Quality Gate
18
+
19
+ There is a single entry point to run all SW quality gates for this project:
20
+
21
+ - [ ] **Software Quality Gate**: ``uv run tools/check.py``
22
+
23
+ The individual commands for finer-grain control are listed in the subsequent subsections.
24
+
25
+ ## Python Checklist
26
+
27
+ - [ ] **Python Formatting**: ``uv run ruff format``
28
+ - [ ] **Python Linting**: ``uv run ruff check``
29
+ - [ ] **Python Type Checking**: ``uv run ty check``
30
+ - [ ] **Python Tests**: ``uv run pytest tests``
31
+ - [ ] **Python Security**: ``uv run bandit``
32
+
33
+ ## React and Typescript
34
+
35
+ - [ ] **TS Type Checking**: `cd frontend && npx tsc --noEmit`
36
+ - [ ] **Linting**: `cd frontend && npm run lint`
37
+ - [ ] **Build**: `cd frontend && npm run build`
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: package-structure
3
+ description: >
4
+ Activate this skill for any task adding new files to the repository.
5
+ ---
6
+
7
+ # Package Structure
8
+
9
+ ## Project structure
10
+
11
+ ```
12
+ braindump/
13
+ ├── src/braindump/ # Python backend package (FastAPI, RAG pipeline, LLM backends)
14
+ ├── frontend/
15
+ │ └── src/
16
+ │ └── components/ # React components (one .tsx + .css pair per component)
17
+ ├── tests/ # Pytest integration tests
18
+ ├── tools/ # Build hooks and dev helper scripts
19
+ └── pyproject.toml # Package config, hatch build, ruff, ty, bandit
20
+ ```
21
+
22
+ ## Key conventions
23
+
24
+ - New backend modules go in `src/braindump/`; register new API routes in `app.py`.
25
+ - New React components go in `frontend/src/components/` with a matching `.css` file.
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: python-dev
3
+ description: >
4
+ Activate this skill for any task involving writing Python source code (extension: .py)
5
+
6
+ This includes:
7
+ - writing new module
8
+ - fixing bugs
9
+ - adding type hints
10
+ - writing docstrings
11
+ - creating unit tests
12
+ - structuring packages
13
+ - reviewing Python code.
14
+ ---
15
+
16
+ # Python Development Skill
17
+
18
+ ## Core Principles
19
+
20
+ 1. **Always write idiomatic Python** — follow PEP 8 and PEP 20.
21
+ 2. **Prefer clarity over cleverness** — readable code is maintainable code.
22
+ 3. **Type hints by default** — add type hints for all functions.
23
+ 4. **Fail loudly** — raise specific exceptions with helpful messages rather than silently swallowing errors.
24
+ 5. **Test-first mindset** — suggest or produce `pytest` tests alongside non-trivial implementations.
25
+ 6. **Assert code** — use `assert` statements only for internal assertions and type deductions.
26
+
27
+ ---
28
+
29
+ ## General Python Coding Guidelines
30
+
31
+ Before finalising any Python output, verify:
32
+
33
+ - [ ] **File Structure** — use the Python structure from [python_template.md](python_template.md)
34
+ - [ ] **Identifier** — use snake_case names, use a leading underscore for private constants and functions
35
+ - [ ] **PEP 8 compliant** — 4-space indent
36
+ - [ ] **Type-annotated** — all function signatures have parameter and return types
37
+ - [ ] **Docstrings** — public functions/classes have Google-style
38
+ - [ ] **Error handling** — no bare `except:`, raise specific exception types
39
+ - [ ] **No mutable defaults** — never `def f(x=[]):`, use `None` sentinel instead
40
+ - [ ] **f-strings over %/format** — prefer f-strings
41
+ - [ ] **Context managers** — use `with` for files, DB connections, locks
42
+ - [ ] **Init Files** — use ``__init__.py`` files only for exposing constants and functions, do not add code
43
+
44
+
45
+ ## Test Guidelines
46
+
47
+ - [ ] **Test Functions** — use free-floating test functions instead of nested functions in test classes
48
+ - [ ] **Public Functions** — write tests for new public functions
49
+ - [ ] **Private Functions** — write tests for private functions if suitable to reduce test variation on the public function level
50
+ - [ ] **Test Updates** — modify existing tests as least as possible to assure software stability
51
+
52
+
53
+ ### Project-Specific Guidelines
54
+
55
+ Use these guidelines in addition to the general coding guidelines:
56
+
57
+ - [ ] **Pydantic** — use `pydantic` types for file I/O and for `fastapi` routes.
58
+
59
+ ---
60
+
61
+ ## Patterns & Best Practices
62
+
63
+ ### Idiomatic Constructs
64
+
65
+ ```python
66
+ # Prefer enumerate over manual indexing
67
+ for i, item in enumerate(items):
68
+ ...
69
+
70
+ # Prefer unpacking over indexing
71
+ first, *rest = collection
72
+
73
+ # Use walrus operator for assignment in conditions (Python 3.8+)
74
+ if m := pattern.match(line):
75
+ process(m.group(0))
76
+
77
+ # Prefer dataclasses for plain data containers
78
+ from dataclasses import dataclass, field
79
+
80
+ @dataclass
81
+ class Config:
82
+ host: str = "localhost"
83
+ port: int = 8080
84
+ tags: list[str] = field(default_factory=list)
85
+ ```
86
+
87
+ ### Error Handling
88
+
89
+ ```python
90
+ # Always be specific
91
+ try:
92
+ result = risky_call()
93
+ except (ValueError, KeyError) as exc:
94
+ raise RuntimeError(f"Failed to process item: {exc}") from exc
95
+ ```
96
+
97
+ ### Typing Patterns
98
+
99
+ ```python
100
+
101
+ from typing import TYPE_CHECKING
102
+
103
+ if TYPE_CHECKING:
104
+ from collections.abc import Sequence
105
+
106
+ def process(items: Sequence[str]) -> dict[str, int]:
107
+ return {item: len(item) for item in items}
108
+ ```
109
+
110
+ ### File I/O
111
+
112
+ ```python
113
+ # Always specify encoding
114
+ file_path.read_text(encoding="utf-8")
115
+
116
+ # Use pathlib over os.path
117
+ from pathlib import Path
118
+
119
+ config_path = Path(__file__).parent / "config.yaml"
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Testing
125
+
126
+ Default test framework: **pytest**. Place tests in `tests/` mirroring the source layout.
127
+ Test files start with `test_<module>.py`.
128
+
129
+ ```python
130
+ # tests/test_example.py
131
+ import pytest
132
+ from mymodule import my_function
133
+
134
+ def test_happy_path():
135
+ assert my_function("input") == "expected"
136
+
137
+ def test_raises_on_bad_input():
138
+ with pytest.raises(ValueError, match="must be positive"):
139
+ my_function(-1)
140
+
141
+ @pytest.mark.parametrize("val,expected", [
142
+ ("a", 1),
143
+ ("ab", 2),
144
+ ("", 0),
145
+ ])
146
+ def test_parametrized(val, expected):
147
+ assert len(val) == expected
148
+ ```
149
+
150
+ Run tests:
151
+ ```bash
152
+ uv run python -m pytest -v
153
+ # With coverage:
154
+ uv run python -m pytest --cov=src --cov-report=term-missing
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Debugging Tips
160
+
161
+ ```bash
162
+ # Run with verbose traceback
163
+ python -m traceback script.py
164
+
165
+ # Quick REPL inspection
166
+ python -c "import module; print(dir(module))"
167
+
168
+ # Profile a script
169
+ python -m cProfile -s cumulative script.py | head -20
170
+ ```
@@ -0,0 +1,24 @@
1
+ # Python Template File
2
+
3
+ The line width is 120 characters.
4
+ The file layout shall correspond to:
5
+
6
+ ```python
7
+ <License Header>
8
+ <Includes>
9
+
10
+ #######################################################################################################################
11
+ # Public Interface #
12
+ #######################################################################################################################
13
+
14
+ <Public Interface>
15
+ <Public Constants>
16
+ <Public Functions>
17
+
18
+ #######################################################################################################################
19
+ # Implementation #
20
+ #######################################################################################################################
21
+
22
+ <Implementation>
23
+ <Private Constants>
24
+ <Private Functions>
@@ -0,0 +1,175 @@
1
+ ---
2
+ name: react-dev
3
+ description: >
4
+ Activate this skill for any task involving writing React/TypeScript source code
5
+ in the frontend/ directory.
6
+
7
+ This includes:
8
+ - adding new components
9
+ - fixing bugs
10
+ - adding types
11
+ - updating styles
12
+ - writing or updating tests
13
+ - reviewing frontend code
14
+ ---
15
+
16
+ # React Development Skill
17
+
18
+ ## Core Principles
19
+
20
+ 1. **TypeScript only** — no `.js` or `.jsx` files; every file is `.ts` or `.tsx`.
21
+ 2. **Strict mode** — `tsconfig.json` enables `"strict": true`; never use `any` unless absolutely unavoidable and always add a comment explaining why.
22
+ 3. **Functional components only** — no class components; use hooks for all state and side-effects.
23
+ 4. **Co-locate styles** — every component has a matching `.css` file; no inline `style={{}}` except for dynamic values that cannot be expressed in CSS.
24
+ 5. **Fail visibly** — surface errors through `useErrorToast()` so users always see what went wrong.
25
+
26
+ ---
27
+
28
+ ## General Coding Guidelines
29
+
30
+ Before finalising any frontend output, verify:
31
+
32
+ - [ ] **File extension** — `.ts` for pure logic, `.tsx` for JSX-containing files
33
+ - [ ] **No JS files** — `eslint.config.js` is the only `.js` file and is tooling config, not app code
34
+ - [ ] **Named exports** — prefer named exports for components; default export only for the component itself (required by react-refresh)
35
+ - [ ] **Typed props** — every component has an explicit `interface Props { … }` or inline type
36
+ - [ ] **No `any`** — use `unknown` + narrowing, generics, or precise union types instead
37
+ - [ ] **Hook rules** — hooks called unconditionally at the top of the component; never inside loops, conditions, or nested functions
38
+ - [ ] **Exhaustive deps** — `useEffect` / `useCallback` / `useMemo` dependency arrays are complete; add a comment if a dep is intentionally omitted
39
+ - [ ] **Event handlers** — typed with `React.MouseEvent`, `React.ChangeEvent<HTMLInputElement>`, etc.
40
+ - [ ] **Keys** — list renders use stable, unique keys (IDs, not array indices)
41
+ - [ ] **Accessibility** — interactive elements have `aria-label` or visible label; icon-only buttons have `title` and `aria-label`
42
+ - [ ] **CSS class naming** — kebab-case; scoped with a component prefix (e.g. `.query-bar`, `.query-input`)
43
+
44
+ ---
45
+
46
+ ## Project-Specific Guidelines
47
+
48
+ - [ ] **API calls** — always go through `api.ts`; never call `fetch` directly in a component
49
+ - [ ] **Error handling** — wrap async calls in `try/catch` and call `pushError()` from `useErrorToast()`
50
+ - [ ] **Types** — shared domain types live in `types.ts`; API-response shapes live in `api.ts` alongside the call that returns them
51
+ - [ ] **State lifting** — local state stays local; lift only when two or more siblings need it
52
+ - [ ] **No prop drilling past two levels** — use React context (following the `ErrorToast` provider pattern) for cross-cutting concerns
53
+
54
+ ---
55
+
56
+ ## Patterns & Best Practices
57
+
58
+ ### Component structure
59
+
60
+ ```tsx
61
+ // components/MyWidget.tsx
62
+ import { useState, useCallback } from 'react'
63
+ import { useErrorToast } from './ErrorToast'
64
+ import './MyWidget.css'
65
+
66
+ interface Props {
67
+ label: string
68
+ onConfirm: (value: string) => void
69
+ }
70
+
71
+ export default function MyWidget({ label, onConfirm }: Props) {
72
+ const { pushError } = useErrorToast()
73
+ const [value, setValue] = useState('')
74
+
75
+ const handleSubmit = useCallback(async () => {
76
+ try {
77
+ await someApiCall(value)
78
+ onConfirm(value)
79
+ } catch (err: unknown) {
80
+ pushError('Failed to submit', String(err))
81
+ }
82
+ }, [value, onConfirm])
83
+
84
+ return (
85
+ <div className="my-widget">
86
+ <label className="my-widget-label">{label}</label>
87
+ <input
88
+ className="my-widget-input"
89
+ value={value}
90
+ onChange={e => setValue(e.target.value)}
91
+ onKeyDown={e => e.key === 'Enter' && handleSubmit()}
92
+ />
93
+ <button className="my-widget-btn" onClick={handleSubmit}>
94
+ Confirm
95
+ </button>
96
+ </div>
97
+ )
98
+ }
99
+ ```
100
+
101
+ ### Typing async state
102
+
103
+ ```tsx
104
+ // Explicit loading / error / data pattern
105
+ const [data, setData] = useState<Spike[] | null>(null)
106
+ const [loading, setLoading] = useState(false)
107
+
108
+ useEffect(() => {
109
+ let cancelled = false
110
+ setLoading(true)
111
+ fetchSpikes()
112
+ .then(spikes => { if (!cancelled) setData(spikes) })
113
+ .catch(err => pushError('Load failed', String(err)))
114
+ .finally(() => { if (!cancelled) setLoading(false) })
115
+ return () => { cancelled = true }
116
+ }, [])
117
+ ```
118
+
119
+ ### Context provider pattern
120
+
121
+ ```tsx
122
+ // Follow the ErrorToast pattern for cross-cutting concerns:
123
+ // 1. Define the context value interface
124
+ // 2. Create the context with a safe no-op default
125
+ // 3. Export a custom hook `useFoo()`
126
+ // 4. Export a `FooProvider` that wraps children
127
+
128
+ interface FooContextValue { doSomething: () => void }
129
+ const FooContext = createContext<FooContextValue>({ doSomething: () => {} })
130
+ export function useFoo() { return useContext(FooContext) }
131
+ export function FooProvider({ children }: { children: React.ReactNode }) { … }
132
+ ```
133
+
134
+ ### Adding an API call
135
+
136
+ ```ts
137
+ // api.ts — add alongside the function that uses the new shape
138
+ export interface MyNewResponse {
139
+ id: string
140
+ value: number
141
+ }
142
+
143
+ export async function fetchMyThing(id: string): Promise<MyNewResponse> {
144
+ return request<MyNewResponse>(`/my-thing/${id}`)
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Tooling
151
+
152
+ | Tool | Purpose | Run |
153
+ |------|---------|-----|
154
+ | **Vite** | Dev server (port 5173) + production build | `npm run dev` / `npm run build` |
155
+ | **TypeScript** | Type checking | `npx tsc --noEmit` |
156
+ | **ESLint** | Linting (typescript-eslint + react-hooks + react-refresh) | `npm run lint` |
157
+
158
+ The Vite dev server proxies `/api` → `http://localhost:8000` and `/api` websockets, so the backend must be running on port 8000 during development.
159
+
160
+ Production assets are compiled into `frontend/dist/` and bundled into the wheel by `tools/hatch_build.py`.
161
+
162
+ ---
163
+
164
+ ## Debugging Tips
165
+
166
+ ```bash
167
+ # Type-check without building
168
+ npx tsc --noEmit
169
+
170
+ # Lint the source
171
+ npm run lint
172
+
173
+ # Inspect the production bundle contents
174
+ npx vite-bundle-visualizer # or: unzip -l ../dist/*.whl | grep frontend
175
+ ```
@@ -0,0 +1,66 @@
1
+ name: Pull Request
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ backend:
12
+ name: Backend
13
+ runs-on: ubuntu-latest
14
+ timeout-minutes: 15
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.13"
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --all-groups
28
+
29
+ - name: Lint (ruff)
30
+ run: uv run ruff check src/ tests/
31
+
32
+ - name: Format check (ruff)
33
+ run: uv run ruff format --check src/ tests/
34
+
35
+ - name: Type check (ty)
36
+ run: uv run ty check src/
37
+
38
+ - name: Security scan (bandit)
39
+ run: uv run bandit -r src/ -c pyproject.toml
40
+
41
+ - name: Tests (pytest)
42
+ run: uv run pytest tests/
43
+
44
+ frontend:
45
+ name: Frontend
46
+ runs-on: ubuntu-latest
47
+ timeout-minutes: 10
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Set up Node.js
52
+ uses: actions/setup-node@v4
53
+ with:
54
+ node-version: "20"
55
+
56
+ - name: Install dependencies
57
+ working-directory: frontend
58
+ run: npm ci
59
+
60
+ - name: Lint (eslint)
61
+ working-directory: frontend
62
+ run: npm run lint
63
+
64
+ - name: Type check & build (tsc + vite)
65
+ working-directory: frontend
66
+ run: npm run build
@@ -0,0 +1,57 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write # required for PyPI trusted publishing (OIDC)
10
+
11
+ jobs:
12
+ build:
13
+ name: Build
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 15
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: "20"
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v5
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.13"
31
+
32
+ - name: Build wheel and sdist
33
+ run: uv build
34
+
35
+ - name: Upload dist artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ name: Publish to PyPI
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ timeout-minutes: 10
46
+ environment:
47
+ name: pypi
48
+ url: https://pypi.org/p/braindump
49
+ steps:
50
+ - name: Download dist artifacts
51
+ uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Publish to PyPI
57
+ uses: pypa/gh-action-pypi-publish@release/v1