gofetch-client 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 (34) hide show
  1. gofetch_client-0.1.0/LICENSE +21 -0
  2. gofetch_client-0.1.0/PKG-INFO +456 -0
  3. gofetch_client-0.1.0/README.md +408 -0
  4. gofetch_client-0.1.0/pyproject.toml +146 -0
  5. gofetch_client-0.1.0/setup.cfg +4 -0
  6. gofetch_client-0.1.0/src/gofetch/__init__.py +94 -0
  7. gofetch_client-0.1.0/src/gofetch/actor.py +312 -0
  8. gofetch_client-0.1.0/src/gofetch/client.py +305 -0
  9. gofetch_client-0.1.0/src/gofetch/constants.py +93 -0
  10. gofetch_client-0.1.0/src/gofetch/dataset.py +219 -0
  11. gofetch_client-0.1.0/src/gofetch/exceptions.py +182 -0
  12. gofetch_client-0.1.0/src/gofetch/http.py +411 -0
  13. gofetch_client-0.1.0/src/gofetch/log.py +72 -0
  14. gofetch_client-0.1.0/src/gofetch/py.typed +0 -0
  15. gofetch_client-0.1.0/src/gofetch/run.py +229 -0
  16. gofetch_client-0.1.0/src/gofetch/scrapers/__init__.py +7 -0
  17. gofetch_client-0.1.0/src/gofetch/scrapers/base.py +48 -0
  18. gofetch_client-0.1.0/src/gofetch/types.py +181 -0
  19. gofetch_client-0.1.0/src/gofetch/webhook.py +268 -0
  20. gofetch_client-0.1.0/src/gofetch/webhook_client.py +277 -0
  21. gofetch_client-0.1.0/src/gofetch_client.egg-info/PKG-INFO +456 -0
  22. gofetch_client-0.1.0/src/gofetch_client.egg-info/SOURCES.txt +32 -0
  23. gofetch_client-0.1.0/src/gofetch_client.egg-info/dependency_links.txt +1 -0
  24. gofetch_client-0.1.0/src/gofetch_client.egg-info/requires.txt +25 -0
  25. gofetch_client-0.1.0/src/gofetch_client.egg-info/top_level.txt +1 -0
  26. gofetch_client-0.1.0/tests/test_actor.py +395 -0
  27. gofetch_client-0.1.0/tests/test_client.py +73 -0
  28. gofetch_client-0.1.0/tests/test_http.py +187 -0
  29. gofetch_client-0.1.0/tests/test_log.py +107 -0
  30. gofetch_client-0.1.0/tests/test_parity.py +87 -0
  31. gofetch_client-0.1.0/tests/test_run.py +191 -0
  32. gofetch_client-0.1.0/tests/test_types.py +76 -0
  33. gofetch_client-0.1.0/tests/test_webhook.py +104 -0
  34. gofetch_client-0.1.0/tests/test_webhook_client.py +275 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yevhenii
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,456 @@
1
+ Metadata-Version: 2.4
2
+ Name: gofetch-client
3
+ Version: 0.1.0
4
+ Summary: Python client for GoFetch.io social media scraping API - Drop-in replacement for apify-client
5
+ Author-email: Yevhenii Molodtsov <yevhenii@go-fetch.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://go-fetch.io
8
+ Project-URL: Documentation, https://github.com/YevheniiM/gofetch-client#readme
9
+ Project-URL: Repository, https://github.com/YevheniiM/gofetch-client
10
+ Project-URL: Issues, https://github.com/YevheniiM/gofetch-client/issues
11
+ Project-URL: Changelog, https://github.com/YevheniiM/gofetch-client/blob/main/CHANGELOG.md
12
+ Keywords: scraping,instagram,tiktok,youtube,social-media,api-client,gofetch,web-scraping
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: httpx>=0.25.0
29
+ Requires-Dist: pydantic>=2.0.0
30
+ Requires-Dist: python-dateutil>=2.8.0
31
+ Requires-Dist: eval_type_backport>=0.1.3; python_version < "3.10"
32
+ Provides-Extra: async
33
+ Requires-Dist: httpx[http2]>=0.25.0; extra == "async"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
37
+ Requires-Dist: pytest-httpx>=0.21.0; extra == "dev"
38
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
39
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
40
+ Requires-Dist: mypy>=1.5.0; extra == "dev"
41
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
42
+ Provides-Extra: docs
43
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
44
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
45
+ Provides-Extra: all
46
+ Requires-Dist: gofetch-client[async,dev,docs]; extra == "all"
47
+ Dynamic: license-file
48
+
49
+ # GoFetch Client
50
+
51
+ [![PyPI version](https://badge.fury.io/py/gofetch-client.svg)](https://badge.fury.io/py/gofetch-client)
52
+ [![Python versions](https://img.shields.io/pypi/pyversions/gofetch-client.svg)](https://pypi.org/project/gofetch-client/)
53
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
54
+
55
+ **Python client for [GoFetch.io](https://go-fetch.io) social media scraping API.**
56
+
57
+ A drop-in replacement for `apify-client` that uses the GoFetch.io infrastructure.
58
+
59
+ ## Features
60
+
61
+ - **Drop-in replacement** for `apify-client` - minimal code changes required
62
+ - **Multiple platforms**: Instagram, TikTok, YouTube
63
+ - **Sync and async** execution modes
64
+ - **Webhook support** for asynchronous job notifications
65
+ - **Full type hints** for better IDE support
66
+ - **Automatic retries** with exponential backoff
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pip install gofetch-client
72
+ ```
73
+
74
+ For async support with HTTP/2:
75
+ ```bash
76
+ pip install gofetch-client[async]
77
+ ```
78
+
79
+ ## Quick Start
80
+
81
+ ### Basic Usage
82
+
83
+ ```python
84
+ from gofetch import GoFetchClient
85
+
86
+ # Initialize client
87
+ client = GoFetchClient(api_key="sk_scr_your_api_key")
88
+
89
+ # Create an actor for Instagram scraping
90
+ actor = client.actor("instagram")
91
+
92
+ # Run synchronously (blocks until complete)
93
+ run = actor.call(run_input={
94
+ "directUrls": ["https://www.instagram.com/nike/"],
95
+ "maxPosts": 10,
96
+ })
97
+
98
+ # Fetch results
99
+ dataset = client.dataset(run["defaultDatasetId"])
100
+ for item in dataset.iterate_items():
101
+ print(item["id"], item.get("caption", "")[:50])
102
+ ```
103
+
104
+ ### Async Execution with Webhooks
105
+
106
+ ```python
107
+ from gofetch import GoFetchClient
108
+
109
+ client = GoFetchClient(api_key="sk_scr_your_api_key")
110
+ actor = client.actor("instagram")
111
+
112
+ # Start async job with webhook notification
113
+ run = actor.start(
114
+ run_input={
115
+ "directUrls": ["https://www.instagram.com/nike/"],
116
+ "maxPosts": 100,
117
+ },
118
+ webhooks=[{
119
+ "request_url": "https://your-app.com/webhook",
120
+ "event_types": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED"]
121
+ }]
122
+ )
123
+
124
+ print(f"Job started: {run['id']}")
125
+ # Your webhook will be called when the job completes
126
+ ```
127
+
128
+ ## Migration from Apify
129
+
130
+ GoFetch Client is designed as a drop-in replacement for `apify-client`.
131
+
132
+ ### Before (Apify)
133
+
134
+ ```python
135
+ from apify_client import ApifyClient
136
+
137
+ client = ApifyClient(token="apify_api_xxx")
138
+ actor = client.actor("apify/instagram-scraper")
139
+ run = actor.call(run_input={"directUrls": [...]})
140
+ dataset = client.dataset(run["defaultDatasetId"])
141
+ items = list(dataset.iterate_items())
142
+ ```
143
+
144
+ ### After (GoFetch)
145
+
146
+ ```python
147
+ from gofetch import GoFetchClient # Only import changes!
148
+
149
+ client = GoFetchClient(api_key="sk_scr_xxx") # Use GoFetch API key
150
+ actor = client.actor("apify/instagram-scraper") # Same actor URL works!
151
+ run = actor.call(run_input={"directUrls": [...]})
152
+ dataset = client.dataset(run["defaultDatasetId"])
153
+ items = list(dataset.iterate_items())
154
+ ```
155
+
156
+ The client automatically translates Apify actor URLs to GoFetch scrapers:
157
+ - `apify/instagram-scraper` → `instagram`
158
+ - `apify/instagram-profile-scraper` → `instagram_profile`
159
+ - `clockworks/tiktok-profile-scraper` → `tiktok`
160
+ - `streamers/youtube-scraper` → `youtube`
161
+
162
+ ## Supported Platforms
163
+
164
+ ### Instagram
165
+
166
+ ```python
167
+ actor = client.actor("instagram")
168
+ run = actor.call(run_input={
169
+ "directUrls": ["https://www.instagram.com/nike/"],
170
+ "onlyPostsNewerThan": "2024-01-01",
171
+ "maxPosts": 50,
172
+ })
173
+ ```
174
+
175
+ ### TikTok
176
+
177
+ ```python
178
+ actor = client.actor("tiktok")
179
+ run = actor.call(run_input={
180
+ "profiles": ["khaby.lame", "charlidamelio"],
181
+ "oldestPostDate": "2024-01-01",
182
+ "maxVideosPerProfile": 50,
183
+ })
184
+ ```
185
+
186
+ ### YouTube
187
+
188
+ ```python
189
+ actor = client.actor("youtube")
190
+ run = actor.call(run_input={
191
+ "startUrls": [{"url": "https://www.youtube.com/@MrBeast"}],
192
+ "oldestPostDate": "2024-01-01",
193
+ })
194
+ ```
195
+
196
+ ## Webhook Handling
197
+
198
+ ### Verifying Webhook Signatures
199
+
200
+ ```python
201
+ from gofetch import verify_webhook_signature
202
+
203
+ def webhook_handler(request):
204
+ payload = request.body
205
+ signature = request.headers.get("X-Webhook-Signature", "")
206
+
207
+ if not verify_webhook_signature(payload, signature, "your_webhook_secret"):
208
+ return Response("Invalid signature", status=401)
209
+
210
+ # Process webhook...
211
+ ```
212
+
213
+ ### Transforming Webhook Payloads
214
+
215
+ ```python
216
+ from gofetch import transform_webhook_payload
217
+
218
+ def webhook_handler(request):
219
+ gofetch_payload = json.loads(request.body)
220
+
221
+ # Transform to Apify-compatible format
222
+ apify_payload = transform_webhook_payload(gofetch_payload)
223
+
224
+ if apify_payload["eventType"] == "ACTOR.RUN.SUCCEEDED":
225
+ dataset_id = apify_payload["resource"]["defaultDatasetId"]
226
+ # Fetch results...
227
+ ```
228
+
229
+ ## Error Handling
230
+
231
+ ```python
232
+ from gofetch import (
233
+ GoFetchClient,
234
+ GoFetchError,
235
+ AuthenticationError,
236
+ RateLimitError,
237
+ JobError,
238
+ TimeoutError,
239
+ )
240
+
241
+ try:
242
+ client = GoFetchClient(api_key="sk_scr_xxx")
243
+ run = client.actor("instagram").call(run_input={...})
244
+
245
+ except AuthenticationError:
246
+ print("Invalid API key")
247
+
248
+ except RateLimitError as e:
249
+ print(f"Rate limited. Retry after {e.retry_after} seconds")
250
+
251
+ except TimeoutError as e:
252
+ print(f"Job timed out: {e.job_id}")
253
+
254
+ except JobError as e:
255
+ print(f"Job failed: {e.error_message}")
256
+
257
+ except GoFetchError as e:
258
+ print(f"GoFetch error: {e.message}")
259
+ ```
260
+
261
+ ## Development
262
+
263
+ ```bash
264
+ # Clone the repository
265
+ git clone https://github.com/YevheniiM/gofetch-client.git
266
+ cd gofetch-client
267
+
268
+ # Install in development mode
269
+ pip install -e ".[dev]"
270
+
271
+ # Run tests
272
+ pytest
273
+
274
+ # Run linting
275
+ ruff check src/
276
+
277
+ # Run type checking
278
+ mypy src/
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Agentic Development Infrastructure
284
+
285
+ This project ships with a complete [Claude Code](https://claude.ai/claude-code) agentic infrastructure — specialized agents, one-command skills, safety hooks, and a regression test framework. Everything lives under `.claude/` and activates automatically when you open a Claude Code session.
286
+
287
+ ```
288
+ .claude/
289
+ ├── settings.local.json # Permissions + hook wiring
290
+ ├── hooks/ # 6 safety & automation hooks
291
+ ├── agents/ # 7 specialized agent definitions
292
+ └── skills/ # 8 invocable skill workflows
293
+ docs/
294
+ └── REGRESSION_TESTING.md # 90 test cases across 8 modules
295
+ ```
296
+
297
+ ### Hooks
298
+
299
+ Hooks run automatically — you never call them directly. They intercept operations in real-time.
300
+
301
+ #### Safety hooks
302
+
303
+ | Hook | Trigger | Behavior |
304
+ |------|---------|----------|
305
+ | `no-ai-attribution.sh` | Before `git commit` | Blocks commits containing `Co-Authored-By` AI references |
306
+ | `destructive-command-confirm.sh` | Before any shell command | Blocks `git push --force`, `reset --hard`, `rm -rf /`, `clean -f`, `branch -D`, `checkout .` |
307
+ | `protect-sensitive-files.sh` | Before editing a file | Blocks edits to `.env`, `.pem`, `.key`, `credentials.*`, `secrets.*` |
308
+
309
+ #### Automation hooks
310
+
311
+ | Hook | Trigger | Behavior |
312
+ |------|---------|----------|
313
+ | `test-before-commit.sh` | Before `git commit` | Non-blocking reminder listing staged `.py` files |
314
+ | `auto-lint.sh` | After editing a `.py` file | Runs `ruff check --fix` silently on the file |
315
+ | `session-context.sh` | Session start | Prints version, branch, working tree, recent commits, available skills/agents |
316
+
317
+ Hooks work invisibly during normal development. For example, editing `src/gofetch/http.py` will trigger `protect-sensitive-files.sh` (passes — not a secret file), then after the edit `auto-lint.sh` fixes import ordering automatically. Committing triggers `no-ai-attribution.sh`, `destructive-command-confirm.sh`, and `test-before-commit.sh` in sequence.
318
+
319
+ ### Agents
320
+
321
+ Seven role-specific experts, each with deep knowledge of their domain and strict boundaries on what they should and shouldn't touch.
322
+
323
+ | Agent | Role | When to Use |
324
+ |-------|------|-------------|
325
+ | **sdk-developer** | Core development | Features, bugs, refactoring across all modules |
326
+ | **api-compatibility-specialist** | Apify interface guardian | Verifying/fixing `apify-client` compatibility |
327
+ | **qa-engineer** | Manual QA (reporter only) | Hands-on testing — produces reports, never modifies code |
328
+ | **test-engineer** | Automated test writer | Writing pytest suites, closing coverage gaps |
329
+ | **async-specialist** | Async/await expert | Async implementations, sync/async parity |
330
+ | **docs-writer** | Documentation | README, CHANGELOG, docstrings, migration guides |
331
+ | **release-manager** | Version + publishing | Version bumps, changelog, tagging, PyPI releases |
332
+
333
+ **The `qa-engineer` is special** — it has strict reporter-only rules. It NEVER modifies source or test files, NEVER suggests patches, and NEVER commits. It runs 12 structured testing phases and produces a severity-rated report. This separation is intentional: QA finds problems, developers fix them.
334
+
335
+ #### Agent selection
336
+
337
+ ```
338
+ Writing/fixing code?
339
+ ├── Async-specific? → async-specialist
340
+ ├── Apify compat? → api-compatibility-specialist
341
+ └── General → sdk-developer
342
+
343
+ Testing?
344
+ ├── Writing tests → test-engineer
345
+ └── Manual QA → qa-engineer
346
+
347
+ Documentation? → docs-writer
348
+ Releasing? → release-manager
349
+ ```
350
+
351
+ ### Skills
352
+
353
+ Skills are one-command workflows invoked with `/skill-name` in Claude Code.
354
+
355
+ | Skill | Purpose | Duration |
356
+ |-------|---------|----------|
357
+ | `/self-test` | Lint + types + targeted tests on changed files | ~30 sec |
358
+ | `/regression-lite` | ~50 core tests from every module | ~15-20 min |
359
+ | `/regression-full` | All 90+ tests with parametrized variants (150+ effective) | ~45-60 min |
360
+ | `/review-changes` | Pre-commit review: security, types, compatibility, style, coverage | ~2-5 min |
361
+ | `/debug` | Hypothesis-driven debugging: reproduce, hypothesize, investigate, isolate | varies |
362
+ | `/fix-from-qa` | Parse a QA report, triage, fix by severity | varies |
363
+ | `/manual-qa [scope]` | Launch qa-engineer with scope: `client`, `http`, `webhook`, `async`, `compat`, `all` | ~20-30 min |
364
+ | `/release [major\|minor\|patch]` | Full release: validate, version, changelog, tag, publish | ~10-15 min |
365
+
366
+ ### Real-World Workflows
367
+
368
+ #### Adding a new feature
369
+
370
+ ```
371
+ 1. Implement in src/gofetch/ (maintain sync/async parity)
372
+ 2. /self-test → quick validation
373
+ 3. /review-changes → pre-commit check (catches compat issues, missing tests)
374
+ 4. Write tests → use test-engineer agent
375
+ 5. Commit
376
+ ```
377
+
378
+ **Example — adding a new scraper type (e.g., Twitter/X):**
379
+
380
+ The `sdk-developer` agent plans changes across `types.py`, `constants.py`, and actor URL resolution. After implementation, `/self-test` catches type errors from the new enum. `/review-changes` flags the missing test coverage. The `test-engineer` agent writes parametrized tests. `/manual-qa compat` verifies the new actor URL resolves correctly through the Apify compatibility layer. On commit, `no-ai-attribution.sh` checks the message and `test-before-commit.sh` lists the staged Python files.
381
+
382
+ #### Debugging a production issue
383
+
384
+ ```
385
+ 1. /debug <description> → structured investigation
386
+ 2. Fix the root cause
387
+ 3. /self-test → verify fix + no regressions
388
+ 4. /regression-lite → broader regression check
389
+ 5. Commit
390
+ ```
391
+
392
+ **Example — "Jobs time out even though the API shows them as completed":**
393
+
394
+ `/debug` reproduces the issue with mock HTTP, generates 3-5 hypotheses ranked by likelihood, and investigates each one. It might find that the polling loop compares raw GoFetch status `"completed"` against Apify status `"SUCCEEDED"` — a mapping issue in `actor.py:_wait_for_completion()`. After fixing, `/self-test` confirms the fix and `/regression-lite` runs all actor polling tests (ACT-04 through ACT-06).
395
+
396
+ #### Full QA cycle before a release
397
+
398
+ ```
399
+ 1. /manual-qa all → 12-phase QA, produces severity-rated report
400
+ 2. /fix-from-qa → parse report, fix SEV-1 first, then SEV-2, SEV-3
401
+ 3. /manual-qa all → re-run to verify all fixes
402
+ 4. /regression-full → 150+ tests, coverage threshold check (80%)
403
+ 5. /release minor → validate, bump, changelog, tag, push
404
+ ```
405
+
406
+ **Example — shipping v0.2.0:**
407
+
408
+ `/manual-qa all` runs through environment verification, unit smoke tests, client instantiation, actor testing, dataset pagination, HTTP retries with mock transports, webhook signatures, exception hierarchy, async clients, edge cases, mypy, and ruff. It produces a report finding 5 issues. `/fix-from-qa` processes the report, fixing the SEV-1 (async client not raising `AuthenticationError`) first, then SEV-2s and SEV-3s — each fix includes a test. Re-running `/manual-qa all` comes back clean. `/regression-full` confirms 150+ tests pass at 83% coverage. `/release minor` bumps `0.1.0 → 0.2.0`, updates both version locations, prepares the changelog, commits, tags, and pauses for confirmation before pushing.
409
+
410
+ #### Maintaining sync/async parity
411
+
412
+ ```
413
+ 1. Change a sync method (e.g., HTTPClient.get)
414
+ 2. /review-changes → flags that AsyncHTTPClient.get wasn't updated
415
+ 3. Mirror the change in the async counterpart
416
+ 4. /manual-qa async → verifies both behave identically
417
+ ```
418
+
419
+ The `async-specialist` agent knows every sync/async class pair and common pitfalls — like accidentally using `time.sleep()` instead of `asyncio.sleep()` in async code, or forgetting to `await` a coroutine.
420
+
421
+ ### Regression Testing
422
+
423
+ The full specification lives at [`docs/REGRESSION_TESTING.md`](docs/REGRESSION_TESTING.md) — 90 test cases with unique IDs:
424
+
425
+ | Module | IDs | Count | Priority |
426
+ |--------|-----|-------|----------|
427
+ | Client | CLI-01..10 | 10 | Medium |
428
+ | Actor | ACT-01..15 | 15 | High |
429
+ | Dataset | DAT-01..10 | 10 | High |
430
+ | HTTP | HTTP-01..12 | 12 | Highest |
431
+ | Webhook | WHK-01..10 | 10 | Medium |
432
+ | Async | ASY-01..15 | 15 | High |
433
+ | Compatibility | CMP-01..10 | 10 | Medium |
434
+ | Exceptions | EXC-01..08 | 8 | Low |
435
+
436
+ Each test specifies preconditions, steps, and expected results. Many use `@pytest.mark.parametrize` — effective count exceeds 150.
437
+
438
+ Reference test IDs in PRs and bug reports: *"This PR fixes the issue exposed by ACT-06 (polling backoff timing)"* or *"Blocked on HTTP-08 — need backoff timing test before shipping retry changes."*
439
+
440
+ ### Customizing the Infrastructure
441
+
442
+ **Add a hook:** Create a script in `.claude/hooks/`, `chmod +x` it, wire it in `settings.local.json` under `PreToolUse`, `PostToolUse`, or `SessionStart`. Exit `0` to allow, `2` to block.
443
+
444
+ **Add an agent:** Create a markdown file in `.claude/agents/` with sections: Identity, Codebase Knowledge, Responsibilities, Boundaries.
445
+
446
+ **Add a skill:** Create `.claude/skills/<name>/SKILL.md` with YAML frontmatter (`name`, `description`, `user_invocable: true`) and step-by-step procedure.
447
+
448
+ ## License
449
+
450
+ MIT License - see [LICENSE](LICENSE) for details.
451
+
452
+ ## Links
453
+
454
+ - **Documentation**: https://github.com/YevheniiM/gofetch-client#readme
455
+ - **Issues**: https://github.com/YevheniiM/gofetch-client/issues
456
+ - **GoFetch.io**: https://go-fetch.io