careerrag 1.0.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 (36) hide show
  1. careerrag-1.0.0/.github/workflows/dryclean.yml +63 -0
  2. careerrag-1.0.0/.github/workflows/publish.yml +57 -0
  3. careerrag-1.0.0/.gitignore +14 -0
  4. careerrag-1.0.0/CHANGELOG.md +22 -0
  5. careerrag-1.0.0/CLAUDE.md +433 -0
  6. careerrag-1.0.0/LICENSE +21 -0
  7. careerrag-1.0.0/Makefile +22 -0
  8. careerrag-1.0.0/PKG-INFO +210 -0
  9. careerrag-1.0.0/README.md +182 -0
  10. careerrag-1.0.0/pyproject.toml +47 -0
  11. careerrag-1.0.0/src/careerrag/__init__.py +1 -0
  12. careerrag-1.0.0/src/careerrag/__main__.py +93 -0
  13. careerrag-1.0.0/src/careerrag/config.py +37 -0
  14. careerrag-1.0.0/src/careerrag/frontend/static/css/chat.css +443 -0
  15. careerrag-1.0.0/src/careerrag/frontend/static/js/initialize-chat.js +320 -0
  16. careerrag-1.0.0/src/careerrag/frontend/templates/chat.html +105 -0
  17. careerrag-1.0.0/src/careerrag/rag/__init__.py +1 -0
  18. careerrag-1.0.0/src/careerrag/rag/chunker.py +144 -0
  19. careerrag-1.0.0/src/careerrag/rag/fusion.py +30 -0
  20. careerrag-1.0.0/src/careerrag/rag/generator.py +67 -0
  21. careerrag-1.0.0/src/careerrag/rag/indexer.py +47 -0
  22. careerrag-1.0.0/src/careerrag/rag/keyword.py +35 -0
  23. careerrag-1.0.0/src/careerrag/rag/loader.py +75 -0
  24. careerrag-1.0.0/src/careerrag/rag/pipeline.py +46 -0
  25. careerrag-1.0.0/src/careerrag/rag/prompt.py +50 -0
  26. careerrag-1.0.0/src/careerrag/rag/reranker.py +26 -0
  27. careerrag-1.0.0/src/careerrag/rag/retriever.py +73 -0
  28. careerrag-1.0.0/src/careerrag/rag/selector.py +59 -0
  29. careerrag-1.0.0/src/careerrag/rag/util.py +62 -0
  30. careerrag-1.0.0/src/careerrag/rag/vector.py +29 -0
  31. careerrag-1.0.0/src/careerrag/server/__init__.py +1 -0
  32. careerrag-1.0.0/src/careerrag/server/app.py +68 -0
  33. careerrag-1.0.0/tests/__init__.py +1 -0
  34. careerrag-1.0.0/tests/fixtures/sample-resume.docx +0 -0
  35. careerrag-1.0.0/tests/fixtures/sample-resume.pdf +0 -0
  36. careerrag-1.0.0/tests/test_pipeline.py +125 -0
@@ -0,0 +1,63 @@
1
+ name: dryclean CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ types: [opened, synchronize, reopened, ready_for_review]
8
+ branches: [main]
9
+ issue_comment:
10
+ types: [created]
11
+
12
+ jobs:
13
+ quality:
14
+ name: dryclean Quality Checks
15
+ if: github.event_name != 'issue_comment'
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v6
20
+ - name: Run quality checks
21
+ uses: arup-kumar-maiti/dryclean/ci@v1
22
+
23
+ review:
24
+ name: dryclean PR Review
25
+ if: github.event_name == 'pull_request'
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ id-token: write
31
+ issues: write
32
+ pull-requests: write
33
+ steps:
34
+ - name: Checkout repository
35
+ uses: actions/checkout@v6
36
+ with:
37
+ fetch-depth: 0
38
+ - name: Run Claude PR review
39
+ uses: arup-kumar-maiti/dryclean/review@v1
40
+ with:
41
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
42
+
43
+ describe:
44
+ name: dryclean PR Description
45
+ if: |
46
+ (github.event_name == 'pull_request' && github.event.action == 'opened' && (github.event.pull_request.body == null || github.event.pull_request.body == '')) ||
47
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude describe'))
48
+ runs-on: ubuntu-latest
49
+ permissions:
50
+ actions: read
51
+ contents: read
52
+ id-token: write
53
+ issues: write
54
+ pull-requests: write
55
+ steps:
56
+ - name: Checkout repository
57
+ uses: actions/checkout@v6
58
+ with:
59
+ fetch-depth: 0
60
+ - name: Run Claude PR description
61
+ uses: arup-kumar-maiti/dryclean/describe@v1
62
+ with:
63
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
@@ -0,0 +1,57 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v[0-9]+.[0-9]+.[0-9]+'
7
+
8
+ jobs:
9
+ publish:
10
+ name: Build and Publish
11
+ if: github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v')
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v6
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Verify tag is on main
24
+ shell: bash
25
+ run: |
26
+ tag_commit=$(git rev-list -n 1 "${{ github.ref_name }}")
27
+ main_commits=$(git rev-list origin/main)
28
+ if ! echo "$main_commits" | grep -q "$tag_commit"; then
29
+ echo "::error::Tag ${{ github.ref_name }} is not on main branch"
30
+ exit 1
31
+ fi
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v6
35
+ with:
36
+ python-version: '3.11'
37
+
38
+ - name: Install build tools
39
+ shell: bash
40
+ run: pip install build
41
+
42
+ - name: Build package
43
+ shell: bash
44
+ run: python -m build
45
+
46
+ - name: Publish to PyPI
47
+ uses: pypa/gh-action-pypi-publish@release/v1
48
+
49
+ - name: Create GitHub release
50
+ shell: bash
51
+ env:
52
+ GH_TOKEN: ${{ github.token }}
53
+ run: |
54
+ version="${{ github.ref_name }}"
55
+ [ -f CHANGELOG.md ] || { echo "CHANGELOG.md not found."; exit 1; }
56
+ notes=$(awk "/^## \[${version#v}\]/{found=1; next} /^## \[/{found=0} found" CHANGELOG.md)
57
+ gh release create "$version" --title "$version" --notes "$notes"
@@ -0,0 +1,14 @@
1
+ **/__pycache__/
2
+ *.egg-info/
3
+ *.pyc
4
+ .careerrag/
5
+ .DS_Store
6
+ .env
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .venv/
10
+ build/
11
+ dist/
12
+ htmlcov/
13
+ node_modules/
14
+ venv/
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
+ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [1.0.0] - 2026-05-04
7
+
8
+ ### Added
9
+
10
+ - Chat web UI with FastAPI, SSE streaming, and markdown rendering.
11
+ - ChromaDB vector store with auto-embeddings via sentence-transformers.
12
+ - CLI with init, index, query, and serve commands.
13
+ - Cross-encoder reranking for precise relevance judgments.
14
+ - Document loading via Docling for PDF, DOCX, Markdown, and plain text.
15
+ - Hybrid retrieval with vector search and BM25 keyword search.
16
+ - LLM streaming via Ollama and Claude.
17
+ - MMR diversity selection across overlapping documents.
18
+ - Reciprocal rank fusion of ranked results.
19
+ - Secret management via environment variables.
20
+ - Section-aware chunker with paragraph merging, sentence-level splitting, and overlap.
21
+ - System prompt with career-specific guardrails.
22
+ - YAML configuration file with auto-defaults.
@@ -0,0 +1,433 @@
1
+ # CLAUDE.md
2
+
3
+ Non-negotiable rules for this repo. Language-specific details are in `docs/<language>/CODE_GUIDELINES.md` and `docs/<language>/COMMENTING_GUIDELINES.md`.
4
+
5
+ - `[CI]` — build fails automatically. No way to merge a violation.
6
+ - `[Review]` — self-enforce first. Caught by the automated Claude PR reviewer and human review as backup.
7
+ - `[CI · Review]` — mixed section: each bullet inside carries its own per-rule tag.
8
+
9
+ ---
10
+
11
+ ## Quick reference
12
+
13
+ | Convention | CSS | HTML | JavaScript | Python | Shell |
14
+ |-------------------|----------------------|--------------------|----------------------|--------------------|--------------------------------|
15
+ | File naming | `kebab-case` | `kebab-case` | `kebab-case` | `snake_case` | `kebab-case` |
16
+ | Variable naming | — | — | `camelCase` | `snake_case` | `lower_snake` / `UPPER_SNAKE` |
17
+ | Function naming | — | — | `camelCase` | `snake_case` | — |
18
+ | Constant naming | — | — | `UPPER_SNAKE_CASE` | `UPPER_SNAKE_CASE` | `UPPER_SNAKE_CASE` |
19
+ | Class naming | `kebab-case` | — | — | `PascalCase` | — |
20
+ | Custom properties | `--kebab-case` | — | — | — | — |
21
+ | Data attributes | — | `data-kebab-case` | — | — | — |
22
+ | Quotes | Double | Double | Single | Double | — |
23
+ | Imports | — | — | built-in → 3rd → int | stdlib → 3rd → int | — |
24
+ | Formatter | prettier | prettier | prettier | ruff | — |
25
+ | Linter | stylelint | htmlhint | eslint | ruff | shellcheck |
26
+ | Type checker | — | — | — | mypy (strict) | — |
27
+ | Docstrings | — | — | JSDoc | Docstrings | — |
28
+
29
+ ---
30
+
31
+ ## Universal rules (all languages)
32
+
33
+ ### Pre-edit checklist
34
+
35
+ Before writing or modifying any source file:
36
+
37
+ - [ ] Filename follows the language convention (see quick reference)
38
+ - [ ] Every function: ≤ 30 lines, ≤ 4 args, one responsibility
39
+ - [ ] No magic numbers / strings — extract to a module-top constant
40
+ - [ ] Imports grouped with one blank line between groups
41
+ - [ ] Comments only for *why*, on the line **above**, never inline, ≤ 15% density
42
+ - [ ] No trailing whitespace, newline at EOF
43
+ - [ ] All non-code text follows Tone rules
44
+ - [ ] Ordering follows Ordering rules
45
+
46
+ ### Naming `[CI · Review]`
47
+
48
+ - Full words — abbreviations only if more recognizable than the full form (e.g. `api`, `db`, `id`, `ip`, `url`), never lazy shorthand (e.g. `btn`, `cfg`, `ctx`, `err`, `msg`, `req`, `res`, `usr`). External API field names are fine if documented with a comment. `[Review]`
49
+ - Unclear name? **Rename first, before doing anything else.** `[Review]`
50
+
51
+ ### Constants `[CI · Review]`
52
+
53
+ - No magic numbers or strings anywhere. Extract every literal to a named constant. `[Review]`
54
+ - In comparisons. `[Review]`
55
+ - In defaults and format strings. Inline strings in error messages are fine. `[Review]`
56
+ - **Local** to one module → top of that module. `[CI]`
57
+
58
+ ### Functions `[CI · Review]`
59
+
60
+ - **≤ 4 arguments.** `[CI]`
61
+ - **≤ 30 lines.** `[CI]`
62
+ - **One responsibility per function.** `[Review]`
63
+ - **Never return both `None`/`undefined` and a value** from the same function. `[Review]` (`[CI]` in JS via `consistent-return`)
64
+
65
+ ### Error handling `[Review]`
66
+
67
+ - Raise / throw **specific** errors, not generic `Exception` or `Error`.
68
+ - Never swallow silently.
69
+ - Language-specific CI rules are in each language section below.
70
+
71
+ ### Tone `[Review]`
72
+
73
+ All non-code text uses **imperative voice, present tense**. No gerunds, no passive, no third-person.
74
+
75
+ - Docstrings → imperative verb, one sentence, trailing period → `Return the contents of a template file.`, not `Returns the contents…` or `Gets the contents…`.
76
+ - Names and labels (e.g. workflow steps, action descriptions, CLI help) → imperative verb + object, no period → `Run quality checks`, not `Runs quality checks` or `Running quality checks`.
77
+ - User-facing messages (error, info, warning) → full sentence, sentence case, trailing period → `Pre-commit not found. Skipping.`, not `pre-commit not found`.
78
+ - Changelog entries under Added / Removed → noun phrase, no verb → `CLI with init and run commands.`, not `Add CLI with…`.
79
+ - Changelog entries under Fixed / Changed → imperative verb → `Restrict trigger to semver tags.`, not `Trigger restricted to…`.
80
+ - No filler, no hedging ("might", "could", "please"), no meta-commentary ("This will…", "Let's…").
81
+
82
+ ### Comment rules `[CI · Review]`
83
+
84
+ **Comment on _why_. Never on _what_.** `[Review]`
85
+
86
+ Comment ONLY for one of these five reasons:
87
+ 1. Non-obvious behavior
88
+ 2. External constraints / API quirks
89
+ 3. Deliberate non-obvious decisions / tradeoffs
90
+ 4. Known limitations accepted for now
91
+ 5. Regex or genuinely complex expressions
92
+
93
+ NEVER comment to:
94
+ - Restate what the code does `[Review]`
95
+ - Restate type information `[Review]`
96
+ - Add section dividers `[CI]`
97
+
98
+ Comment format:
99
+ - Full sentences, capital first letter. `[Review]`
100
+ - Single-line: no trailing period. Multi-line: period on each line. `[CI]`
101
+ - Comment goes on the line **above** the code. **Never inline.** `[CI]`
102
+ - Max 15% comment-to-code ratio per file. `[CI]`
103
+
104
+ ### Formatting `[CI]`
105
+
106
+ - No trailing whitespace.
107
+ - Newline at end of every file.
108
+ - The language linter is the source of truth — **never** hand-tweak its output.
109
+
110
+ ### Ordering `[Review]`
111
+
112
+ **Alphabetical** unless a stronger ordering exists:
113
+
114
+ - Config and declaration keys → alphabetical.
115
+ - Lists with no natural order → alphabetical.
116
+ - Imports have their own rule (see language sections) — that takes precedence.
117
+
118
+ **Code guidelines** (sections, bullets, sub-bullets) → natural reading order, not alphabetical. Order by what you encounter or do first when reading or writing a file.
119
+
120
+ ```
121
+ Sections: Stack → Naming → Imports → Constants → … → Comments → Formatting
122
+ ```
123
+
124
+ **Functions in a module** → callees before callers (leaf-first), grouped by call chain:
125
+
126
+ ```
127
+ _parse() ← called by validate
128
+ validate() ← called by main (1st)
129
+ _format() ← called by render
130
+ render() ← called by main (2nd)
131
+ main() ← entry point, always last
132
+ ```
133
+
134
+ ### Git
135
+
136
+ - **Never commit to `main`.** Branch first.
137
+ - Commit prefix: `ci`, `docs`, `feat`, `fix`, `init`, `refactor`, `test`.
138
+ - `git commit --no-verify` exists for emergencies but **CI re-runs every check**. Don't use it to dodge a real failure.
139
+
140
+ ### Changelog `[Review]`
141
+
142
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Update `CHANGELOG.md` before every version tag.
143
+
144
+ - Categories in this order: **Added**, **Changed**, **Removed**, **Fixed** — include only what applies.
145
+ - Entry tone rules are in the Tone section above.
146
+
147
+ ---
148
+
149
+ ## CSS rules
150
+
151
+ Apply these when editing `.css` files, in addition to universal rules.
152
+
153
+ ### Anti-patterns — DON'T
154
+
155
+ - Deep nesting (> 3 levels) → flatten selectors.
156
+ - ID selectors for styling → use class selectors.
157
+ - Magic numbers (`padding: 37px`) → use custom properties.
158
+ - Qualified selectors (`div.class`) → use class alone.
159
+ - Using `!important` → fix specificity instead.
160
+
161
+ ### Naming `[CI · Review]`
162
+
163
+ - Files: `kebab-case`, singular noun. `[Review]`
164
+ - Classes: `kebab-case`. `[CI]`
165
+ - Custom properties: `--kebab-case`. `[CI]`
166
+
167
+ ### Custom properties `[CI · Review]`
168
+
169
+ - Local → narrowest applicable scope. `[Review]`
170
+ - Shared → `:root`. `[Review]`
171
+
172
+ ### Selectors `[CI · Review]`
173
+
174
+ - Prefer class selectors over element selectors. `[Review]`
175
+ - No ID selectors for styling — use classes instead. `[CI]`
176
+ - No qualified selectors unless specificity requires it. `[CI]`
177
+ - Max 3 levels of nesting. `[CI]`
178
+ - No `!important` — fix specificity instead. `[CI]`
179
+
180
+ ### Formatting `[CI]`
181
+
182
+ - One declaration per line.
183
+ - **Double quotes**.
184
+ - `prettier` formats, `stylelint` lints — no manual overrides.
185
+
186
+ ---
187
+
188
+ ## HTML rules
189
+
190
+ Apply these when editing `.html` files, in addition to universal rules.
191
+
192
+ ### Anti-patterns — DON'T
193
+
194
+ - Deprecated elements (`<center>`, `<font>`) → use CSS.
195
+ - `disabled="true"` → just `disabled` (boolean attribute).
196
+ - Generic `<div>` and `<span>` for everything → use semantic elements.
197
+ - Inline event handlers (`onclick`, `onload`) → use JavaScript event listeners.
198
+ - Inline styles → use CSS.
199
+ - Missing `alt` on `<img>` → always provide alt text.
200
+
201
+ ### Naming `[CI · Review]`
202
+
203
+ - Files: `kebab-case`, singular noun. `[Review]`
204
+ - Data attributes: `data-kebab-case`. `[CI]`
205
+
206
+ ### Elements `[CI · Review]`
207
+
208
+ - Semantic elements over generic `<div>` and `<span>`. `[Review]`
209
+ - No deprecated elements. `[CI]`
210
+ - Alt text on all `<img>` elements. `[CI]`
211
+ - No inline styles. `[CI]`
212
+ - No inline event handlers. `[CI]`
213
+
214
+ ### Formatting `[CI]`
215
+
216
+ - Boolean attributes: no value (`disabled`, not `disabled="true"`).
217
+ - Double quotes.
218
+ - `prettier` formats, `htmlhint` lints — no manual overrides.
219
+
220
+ ---
221
+
222
+ ## JavaScript rules
223
+
224
+ Apply these when editing `.js` files, in addition to universal rules.
225
+
226
+ ### Anti-patterns — DON'T
227
+
228
+ - Adding a 5th parameter → wrap in an options object.
229
+ - Double quotes (`"foo"`) → always single.
230
+ - Empty `catch` blocks → re-throw or log.
231
+ - Returning `undefined` from one branch and a value from another → pick one shape.
232
+ - Using `console.log` → use `process.stdout.write` / `process.stderr.write`.
233
+ - Using ESM `import` → use `require` (CommonJS).
234
+
235
+ ### Naming `[CI · Review]`
236
+
237
+ - Files: `kebab-case`. Components: singular noun. Scripts: verb phrase. `[Review]`
238
+ - Variables: `camelCase`. `[CI]`
239
+ - Constants: `UPPER_SNAKE_CASE`. `[Review]`
240
+ - Functions: `camelCase`, verb or verb phrase. `[Review]`
241
+
242
+ ### Imports `[CI]`
243
+
244
+ - Order: **built-in → third-party → internal**.
245
+ - `require` only, no ESM `import`.
246
+ - No duplicate imports. No unused imports.
247
+
248
+ ### Constants `[CI · Review]`
249
+
250
+ - In comparisons. `[CI]`
251
+
252
+ ### Functions `[CI · Review]`
253
+
254
+ - 5+ args → use an **options object**. `[Review]`
255
+
256
+ ### Error handling `[CI · Review]`
257
+
258
+ - No empty `catch` blocks without re-throwing or logging. `[CI]`
259
+
260
+ ### Output `[CI]`
261
+
262
+ - `process.stdout.write` / `process.stderr.write`, not `console.log`.
263
+
264
+ ### Formatting `[CI]`
265
+
266
+ - **Single quotes** for all strings.
267
+ - `prettier` formats, `eslint` lints — no manual overrides.
268
+
269
+ ### JSDoc `[Review]`
270
+
271
+ - Public functions: one-line JSDoc.
272
+ - Internal functions: no JSDoc.
273
+
274
+ ```javascript
275
+ // Public
276
+ /** Return the parsed config from the given file path. */
277
+ function readConfig(configPath) {
278
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
279
+ }
280
+
281
+ // Internal
282
+ function buildDefaultConfig() {
283
+ return { version: 1, enabled: true };
284
+ }
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Python rules
290
+
291
+ Apply these when editing `.py` files, in addition to universal rules.
292
+
293
+ ### Anti-patterns — DON'T
294
+
295
+ - `# noqa` / `# type: ignore` → fix the code, don't suppress.
296
+ - Adding `Args:` / `Returns:` / `Raises:` blocks to docstrings → annotations cover it.
297
+ - Adding a 5th parameter → wrap in a dataclass / Pydantic model.
298
+ - Defining a custom exception outside `exception.py` → all exceptions live there.
299
+ - `from foo import *` or unused imports → both fail CI.
300
+ - Returning `User | None` from one branch and `User` from another → pick one shape.
301
+ - Single quotes (`'foo'`) → always double.
302
+ - Splitting a long function with `# region` / `# step 1` → extract real helpers.
303
+ - `try: ... except Exception: pass` → raise specific, don't swallow.
304
+
305
+ ### Naming `[CI · Review]`
306
+
307
+ - Files: `snake_case`, singular noun. `[Review]`
308
+ - Variables: `snake_case`. `[CI]`
309
+ - Constants: `UPPER_SNAKE_CASE`. `[CI]`
310
+ - Classes: `PascalCase`, noun or noun phrase. `[Review]`
311
+ - Functions: `snake_case`, verb or verb phrase. `[Review]`
312
+
313
+ ### Imports `[CI]`
314
+
315
+ - Order: **stdlib → third-party → internal**.
316
+ - No wildcard imports. No duplicate imports. No unused imports.
317
+
318
+ ### Constants `[CI · Review]`
319
+
320
+ - Shared constants → `constant.py`. `[Review]`
321
+
322
+ ### Types `[CI · Review]`
323
+
324
+ - Every function signature fully annotated, **including return type**. `[CI]`
325
+ - Boundary data (API / service / config) → **Pydantic model**. `[Review]`
326
+ - Internal data structures → **dataclass**. `[Review]`
327
+ - `mypy --strict` must pass. `[CI]`
328
+
329
+ ### Functions `[CI · Review]`
330
+
331
+ - Return type **always annotated**. `[CI]`
332
+ - 5+ args → wrap in a **dataclass or Pydantic model**. `[Review]`
333
+
334
+ ```python
335
+ # Wrong: 5 args
336
+ def send_email(recipient, subject, body, cc, priority):
337
+ ...
338
+
339
+ # Right: model
340
+ @dataclass
341
+ class EmailRequest:
342
+ recipient: str
343
+ subject: str
344
+ body: str
345
+ cc: str
346
+ priority: int
347
+
348
+ def send_email(request: EmailRequest) -> bool:
349
+ ...
350
+ ```
351
+
352
+ ### Error handling `[CI · Review]`
353
+
354
+ - No bare `except:` / `except Exception:` unless you re-raise or log. `[CI]`
355
+ - **Every custom exception class** lives in `exception.py`. `[CI]`
356
+
357
+ ```python
358
+ # Wrong
359
+ try:
360
+ response = client.get(url)
361
+ except Exception:
362
+ return None
363
+
364
+ # Right
365
+ try:
366
+ response = client.get(url)
367
+ except TimeoutError as exc:
368
+ raise ServiceTimeout(url) from exc
369
+ ```
370
+
371
+ ### Formatting `[CI]`
372
+
373
+ - **One** blank line between methods inside a class.
374
+ - **Two** blank lines between top-level definitions.
375
+ - **Double quotes** for all strings.
376
+ - `ruff format` is final.
377
+
378
+ ### Docstrings `[CI · Review]`
379
+
380
+ - **Public functions** → exactly **one line**. `[Review]`
381
+ - **Internal helpers** (`_` prefix) → **no docstring at all**. `[CI]`
382
+ - **No `Args:`, `Returns:`, `Raises:` blocks. Ever.** `[CI]`
383
+
384
+ ```python
385
+ # Public
386
+ def find_user(user_id: str) -> User:
387
+ """Return the user matching the given ID."""
388
+
389
+ # Internal
390
+ def _normalize_email(email: str) -> str:
391
+ return email.strip().lower()
392
+ ```
393
+
394
+ ---
395
+
396
+ ## Shell rules
397
+
398
+ Apply these when editing `.sh` files, in addition to universal rules.
399
+
400
+ ### Anti-patterns — DON'T
401
+
402
+ - Missing `set -e` → every shell file must fail on first error.
403
+ - Missing executable permission → `.sh` files must have `+x`.
404
+ - Silent `exit 1` without a message → print what went wrong first.
405
+ - Unquoted variable expansions (`$VAR` instead of `"$VAR"`) → word splitting bugs.
406
+ - Using `eval` with dynamic input → command injection risk.
407
+
408
+ ### Naming `[CI · Review]`
409
+
410
+ - Files: `kebab-case`, verb or verb phrase. `[Review]`
411
+ - Local variables: `lower_snake_case`. `[Review]`
412
+ - Exported/environment variables: `UPPER_SNAKE_CASE`. `[Review]`
413
+
414
+ ### Shebangs `[CI]`
415
+
416
+ - `#!/bin/bash` on line 1.
417
+ - `set -e` immediately after.
418
+
419
+ ### Constants `[Review]`
420
+
421
+ - No magic numbers or strings. Use named variables.
422
+
423
+ ### Error handling `[CI · Review]`
424
+
425
+ - No `eval` with dynamic input. `[CI]`
426
+ - Check file/dir existence before operations. `[Review]`
427
+ - Print a descriptive error message before exiting on failure. `[Review]`
428
+
429
+ ### Formatting `[CI]`
430
+
431
+ - Executable permission on all `.sh` files.
432
+ - Quote all variable expansions (`"$VAR"`, not `$VAR`).
433
+ - `shellcheck` is final.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arup Kumar Maiti
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.
@@ -0,0 +1,22 @@
1
+ .PHONY: setup install check test clean
2
+
3
+ PYTHON := $(shell command -v python3 || command -v python)
4
+
5
+ setup:
6
+ $(PYTHON) -m venv .venv
7
+ .venv/bin/pip install dryclean
8
+ .venv/bin/pip install -e ".[dev]"
9
+ .venv/bin/dryclean init
10
+ @echo "Run 'source .venv/bin/activate' to activate the environment"
11
+
12
+ install:
13
+ .venv/bin/pip install -e ".[dev]"
14
+
15
+ check:
16
+ .venv/bin/dryclean run
17
+
18
+ test:
19
+ .venv/bin/pytest tests/
20
+
21
+ clean:
22
+ rm -rf *.egg-info/ .careerrag/ .mypy_cache/ .pytest_cache/ .ruff_cache/ build/ dist/