opencode-skills-collection 4.0.47 → 4.0.49
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.
- package/bundled-skills/.antigravity-install-manifest.json +11 -2
- package/bundled-skills/agent-qa-authoring/SKILL.md +3 -1
- package/bundled-skills/agy-delegate/SKILL.md +1 -1
- package/bundled-skills/aider-delegate/SKILL.md +1 -1
- package/bundled-skills/api-rate-limit-handler/SKILL.md +1 -1
- package/bundled-skills/atlas-cloud-media/SKILL.md +52 -18
- package/bundled-skills/babysit-pr/SKILL.md +1 -1
- package/bundled-skills/boost-asio-pro/references/pre-cpp20.md +8 -1
- package/bundled-skills/check-identity-pack/SKILL.md +88 -0
- package/bundled-skills/claude-delegate/SKILL.md +1 -1
- package/bundled-skills/cline-delegate/SKILL.md +1 -1
- package/bundled-skills/codex-delegate/SKILL.md +1 -1
- package/bundled-skills/commandcode-delegate/SKILL.md +1 -1
- package/bundled-skills/copilot-delegate/SKILL.md +1 -1
- package/bundled-skills/cursor-delegate/SKILL.md +1 -1
- package/bundled-skills/detect-ai-text/SKILL.md +91 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/aas-core.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +3 -3
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/extract-document-data/SKILL.md +99 -0
- package/bundled-skills/falsify/SKILL.md +18 -1
- package/bundled-skills/find-matching-tenders/SKILL.md +100 -0
- package/bundled-skills/graceful-shutdown/SKILL.md +76 -70
- package/bundled-skills/grok-delegate/SKILL.md +1 -1
- package/bundled-skills/inngest/SKILL.md +2 -0
- package/bundled-skills/kimi-delegate/SKILL.md +1 -1
- package/bundled-skills/liuguang-banlan-ui/SKILL.md +6 -4
- package/bundled-skills/liuguang-banlan-ui/scripts/manifest_parser.py +178 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/measure_preview.py +14 -17
- package/bundled-skills/liuguang-banlan-ui/scripts/validate_manifest.py +12 -16
- package/bundled-skills/lovable-cleanup/SKILL.md +32 -6
- package/bundled-skills/lovable-cleanup/references/favicon-vercel-cleanup.md +106 -0
- package/bundled-skills/lovable-cleanup/scripts/write-transparent-favicon.js +88 -0
- package/bundled-skills/multi-source-search/scripts/validate_report.py +5 -0
- package/bundled-skills/omp-delegate/SKILL.md +1 -1
- package/bundled-skills/opencode-delegate/SKILL.md +1 -1
- package/bundled-skills/pentest-tools/references/pentest-ai-agents-matrix.md +8 -5
- package/bundled-skills/pi-delegate/SKILL.md +1 -1
- package/bundled-skills/prompt-caching/SKILL.md +3 -1
- package/bundled-skills/qoder-delegate/SKILL.md +1 -1
- package/bundled-skills/screen-adverse-media/SKILL.md +100 -0
- package/bundled-skills/slideops/SKILL.md +201 -0
- package/bundled-skills/trigger-dev/SKILL.md +2 -0
- package/bundled-skills/unsloth-finetuning/SKILL.md +45 -2
- package/bundled-skills/upstash-qstash/SKILL.md +1 -1
- package/bundled-skills/upstash-ratelimit/SKILL.md +186 -0
- package/bundled-skills/upstash-redis/SKILL.md +156 -0
- package/bundled-skills/verify-citations/SKILL.md +88 -0
- package/bundled-skills/verify-document/SKILL.md +94 -0
- package/bundled-skills/vibe-delegate/SKILL.md +1 -1
- package/bundled-skills/warp-delegate/SKILL.md +1 -1
- package/bundled-skills/zcode-delegate/SKILL.md +1 -1
- package/package.json +1 -1
- package/skills_index.json +347 -54
- package/bundled-skills/ui-slop-score/SKILL.md +0 -80
|
@@ -39,7 +39,7 @@ If you came in through a **Claude Code** or **Codex** plugin instead of AAS Core
|
|
|
39
39
|
|
|
40
40
|
When you ran `npx agentic-awesome-skills` or cloned the repository, you:
|
|
41
41
|
|
|
42
|
-
✅ **Downloaded 2,
|
|
42
|
+
✅ **Downloaded 2,107+ skill files** to your computer (default: `~/.agents/skills/`; or a custom path like `~/.agent/skills/` if you used `--path`)
|
|
43
43
|
✅ **Made them available** to your AI assistant
|
|
44
44
|
❌ **Did NOT enable them all automatically** (they're just sitting there, waiting)
|
|
45
45
|
|
|
@@ -231,7 +231,7 @@ Let's actually use a skill right now. Follow these steps:
|
|
|
231
231
|
|
|
232
232
|
## Direct-install Step 5: Pick Skills Manually
|
|
233
233
|
|
|
234
|
-
Don't try to use all 2,
|
|
234
|
+
Don't try to use all 2,107+ skills at once. Here's a sensible approach:
|
|
235
235
|
|
|
236
236
|
If you want a tool-specific starting point before choosing skills, use:
|
|
237
237
|
|
|
@@ -362,7 +362,7 @@ Usually no, but if your AI doesn't recognize a skill:
|
|
|
362
362
|
|
|
363
363
|
### "Can I load all skills into the model at once?"
|
|
364
364
|
|
|
365
|
-
No. Even though you have 2,
|
|
365
|
+
No. Even though you have 2,107+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block.
|
|
366
366
|
|
|
367
367
|
The intended pattern is:
|
|
368
368
|
|
|
@@ -40,7 +40,7 @@ agentic-awesome-skills/
|
|
|
40
40
|
├── 📄 CONTRIBUTING.md ← Contributor workflow
|
|
41
41
|
├── 📄 CATALOG.md ← Full generated catalog
|
|
42
42
|
│
|
|
43
|
-
├── 📁 skills/ ← 2,
|
|
43
|
+
├── 📁 skills/ ← 2,107+ skills live here
|
|
44
44
|
│ │
|
|
45
45
|
│ ├── 📁 brainstorming/
|
|
46
46
|
│ │ └── 📄 SKILL.md ← Skill definition
|
|
@@ -53,7 +53,7 @@ agentic-awesome-skills/
|
|
|
53
53
|
│ │ └── 📁 2d-games/
|
|
54
54
|
│ │ └── 📄 SKILL.md ← Nested skills also supported
|
|
55
55
|
│ │
|
|
56
|
-
│ └── ... (2,
|
|
56
|
+
│ └── ... (2,107+ total)
|
|
57
57
|
│
|
|
58
58
|
├── 📁 apps/
|
|
59
59
|
│ └── 📁 web-app/ ← Interactive browser
|
|
@@ -106,7 +106,7 @@ agentic-awesome-skills/
|
|
|
106
106
|
|
|
107
107
|
```
|
|
108
108
|
┌─────────────────────────┐
|
|
109
|
-
│ 2,
|
|
109
|
+
│ 2,107+ SKILLS │
|
|
110
110
|
└────────────┬────────────┘
|
|
111
111
|
│
|
|
112
112
|
┌────────────────────────┼────────────────────────┐
|
|
@@ -207,7 +207,7 @@ If you want a workspace-style manual install instead, cloning into `.agent/skill
|
|
|
207
207
|
│ ├── 📁 brainstorming/ │
|
|
208
208
|
│ ├── 📁 stripe-integration/ │
|
|
209
209
|
│ ├── 📁 react-best-practices/ │
|
|
210
|
-
│ └── ... (2,
|
|
210
|
+
│ └── ... (2,107+ total) │
|
|
211
211
|
└─────────────────────────────────────────┘
|
|
212
212
|
```
|
|
213
213
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extract-document-data
|
|
3
|
+
description: Extract structured, grounded fields from documents — values cite their page, missing values abstain instead of hallucinating. Use for parsing invoices, payslips, statements, contracts.
|
|
4
|
+
category: document-verification
|
|
5
|
+
risk: critical
|
|
6
|
+
source: community
|
|
7
|
+
source_repo: Sketchjar/stipple-agent-skills
|
|
8
|
+
source_type: community
|
|
9
|
+
date_added: "2026-08-31"
|
|
10
|
+
author: Sketchjar
|
|
11
|
+
tags: [document-verification, fact-checking, stipple, authenticity]
|
|
12
|
+
tools: [claude, cursor, gemini, codex]
|
|
13
|
+
license: "Apache-2.0"
|
|
14
|
+
license_source: "https://github.com/Sketchjar/stipple-agent-skills/blob/main/LICENSE"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Extract Document Data
|
|
18
|
+
|
|
19
|
+
Extract structured JSON from documents with per-value grounding: every extracted value cites where it came from (page number, confidence), and values that aren't clearly present are reported in `not_found` rather than hallucinated. Uses the Stipple API (free anonymous tier).
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- Parsing payslips, invoices, bank statements, receipts, or contracts
|
|
24
|
+
- Converting unstructured documents to JSON for downstream systems
|
|
25
|
+
- Any extraction where hallucinated values are worse than missing values (lending, accounting, compliance)
|
|
26
|
+
|
|
27
|
+
## Instructions
|
|
28
|
+
|
|
29
|
+
1. **Get the document.** URL or local file path (PDF, PNG, JPEG, DOCX).
|
|
30
|
+
|
|
31
|
+
2. **Choose the extraction mode:**
|
|
32
|
+
- **Ad-hoc fields** — tell the API exactly which fields you want:
|
|
33
|
+
```bash
|
|
34
|
+
curl -X POST https://www.stipple.sh/v1/extract \
|
|
35
|
+
-F "file=@payslip.pdf" \
|
|
36
|
+
-F 'fields=[{"name":"employer_name"},{"name":"net_pay"},{"name":"pay_date"}]' \
|
|
37
|
+
-H "Authorization: Bearer $STIPPLE_API_KEY"
|
|
38
|
+
```
|
|
39
|
+
- **Template** — use a built-in schema: `payslip`, `tax_invoice`, `bank_statement`, `receipt`, `contract`
|
|
40
|
+
- **Schema-free** — omit `fields` and let the model extract what it finds
|
|
41
|
+
|
|
42
|
+
3. **Interpret the response.**
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mode": "schema_free",
|
|
47
|
+
"document_type": "payslip",
|
|
48
|
+
"pages_read": 1,
|
|
49
|
+
"fields": {
|
|
50
|
+
"employer_name": {"value": "Acme Cleaning Pty Ltd", "confidence": 0.95, "page": 1},
|
|
51
|
+
"net_pay": {"value": "2845.10", "confidence": 0.97, "page": 1}
|
|
52
|
+
},
|
|
53
|
+
"not_found": ["ytd_tax"]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- Every value carries `confidence` (the model's self-report) and `page` (grounding)
|
|
58
|
+
- `not_found[]` lists requested fields the model couldn't find — **absences are reported, never guessed**
|
|
59
|
+
- `pages_read` shows how many pages were processed (page limits apply per document)
|
|
60
|
+
|
|
61
|
+
4. **Report honestly.** This is *extraction, not verification* — values are what the document **shows**, not proof it's genuine:
|
|
62
|
+
- "Employer: Acme Cleaning Pty Ltd (confidence 0.95, page 1)"
|
|
63
|
+
- "ytd_tax: not found in document" — never "ytd_tax: 0" or a guess
|
|
64
|
+
- For "is this document genuine?", pair with the `verify-document` skill first
|
|
65
|
+
|
|
66
|
+
## Output format
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Payslip fields (grounded, not guessed):
|
|
70
|
+
|
|
71
|
+
Employer Acme Cleaning Pty Ltd (confidence 0.95, page 1)
|
|
72
|
+
Employee J. Citizen (confidence 0.98, page 1)
|
|
73
|
+
Net pay 2,845.10 (confidence 0.97, page 1)
|
|
74
|
+
Superannuation 268.20 (confidence 0.93, page 1)
|
|
75
|
+
|
|
76
|
+
not_found: ytd_tax
|
|
77
|
+
(absences are reported, never hallucinated)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Limitations and Safety
|
|
81
|
+
|
|
82
|
+
- Invoices, statements, payslips, and contracts often contain sensitive personal,
|
|
83
|
+
financial, or commercial data. Obtain explicit approval before uploading them to
|
|
84
|
+
a hosted third party, minimize the submitted content, and confirm current
|
|
85
|
+
retention, residency, access, and deletion terms.
|
|
86
|
+
- Confidence and page grounding do not prove that an extracted value is correct or
|
|
87
|
+
that the source document is authentic. Reconcile consequential values against the
|
|
88
|
+
original document and authoritative systems before payment, lending, accounting,
|
|
89
|
+
compliance, or legal action.
|
|
90
|
+
- Keep the original file and extraction response so a human reviewer can reproduce
|
|
91
|
+
and correct disputed fields.
|
|
92
|
+
|
|
93
|
+
## Notes
|
|
94
|
+
|
|
95
|
+
- Costs 1 credit per page read by the model (minimum 1); free weekly allowance applies
|
|
96
|
+
- Templates: `payslip`, `tax_invoice`, `bank_statement`, `receipt`, `contract` — pass as the `template` form field
|
|
97
|
+
- Tables are extracted with structure preserved; multi-page documents are processed page by page
|
|
98
|
+
- Pairs with `verify-document` (run first, for authenticity) — an extracted value from a tampered document is still wrong
|
|
99
|
+
- Free key at https://www.stipple.sh for metering beyond the anonymous allowance
|
|
@@ -35,6 +35,20 @@ NO VERDICT WITHOUT A FALSIFIABLE HYPOTHESIS.
|
|
|
35
35
|
If you cannot write down what would prove you wrong, you are not allowed to conclude. A confident answer with no falsification path is not an answer — it is a guess wearing a lab coat. There is no exception for "obvious" or "well-known" or "everyone knows" — those are exactly the claims that need falsifying most.
|
|
36
36
|
</EXTREMELY-IMPORTANT>
|
|
37
37
|
|
|
38
|
+
## MODE SELECTION — route BEFORE answering (mandatory)
|
|
39
|
+
|
|
40
|
+
First decide which mode this question is, then act accordingly. **Do not run the five stages unless you picked Depth.** The wrong mode is itself a protocol failure.
|
|
41
|
+
|
|
42
|
+
| If the ask is... | Mode | Do |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Live incident / production down / outage / "act now" / degrading | **Incident (OODA)** | **ACT first** at ~70% confidence with a known rollback and a time box. Do NOT run the five stages. Stabilize, then falsify the effect. Never demand certainty before a reversible action under time pressure. |
|
|
45
|
+
| Trivial / one-lookup fact / small talk / zero consequence | **Simple** | Answer briefly and directly. No protocol, no follow-up questions, no stage labels. |
|
|
46
|
+
| Rough estimate / ballpark / "about how much" / "大概" (low-stakes, reversible) | **Nudge** | Give the helpful estimate with its main assumption stated, then 2–3 targeted questions. No five-stage ledger. If being wrong costs time/money/trust, escalate to Depth. |
|
|
47
|
+
| Under-specified / unfalsifiable / missing key inputs | **Question** | Ask the whole open frontier in ONE round (numbered, with a recommended default each). Do not conclude, do not fabricate a default justification. |
|
|
48
|
+
| High-stakes / correctness gate / "why" about a failing system / will be acted on | **Depth** | Run the five stages below. |
|
|
49
|
+
|
|
50
|
+
In an incident, the Iron Law means "act reversibly, then falsify the effect" — never "analyze first, act later".
|
|
51
|
+
|
|
38
52
|
## When to Use This Skill
|
|
39
53
|
|
|
40
54
|
|
|
@@ -45,6 +59,9 @@ If you cannot write down what would prove you wrong, you are not allowed to conc
|
|
|
45
59
|
- Claims about what a user, market, or system "will" do
|
|
46
60
|
- Anything where being wrong costs time, money, or trust
|
|
47
61
|
|
|
62
|
+
|
|
63
|
+
**Default to Nudge (not depth) when the ask is a rough ballpark** — "rough estimate", "ballpark", "about how much", "大概", "粗略": give the helpful estimate directly with its main assumption stated, then 2–3 questions. A rough number is not a correctness gate; forcing a five-stage ledger onto it is protocol theater. **Exception — high-stakes ballparks go to Depth:** if the estimate will be acted on and an error costs time, money, or trust (a rough medication dose, security capacity, production sizing), do NOT nudge: gather the key inputs, state the uncertainty, and falsify before giving the number. The shortcut only pays when the error is cheap.
|
|
64
|
+
|
|
48
65
|
**Do NOT activate (answer simply)** for:
|
|
49
66
|
- Factual recall you can verify in one lookup
|
|
50
67
|
- Trivial questions where the answer is obvious and consequences are zero
|
|
@@ -181,7 +198,7 @@ For each detected bias, state the direction (pushes the estimate up or down) and
|
|
|
181
198
|
## The Nudge
|
|
182
199
|
|
|
183
200
|
|
|
184
|
-
When the question does not warrant full depth but the answer will still be acted on, do not run the five stages — append **at most 2–3 short questions**, once per conversation, each tied to something specific in the answer just given
|
|
201
|
+
When the question does not warrant full depth but the answer will still be acted on, do not run the five stages — append **at most 2–3 short questions**, once per conversation, each tied to something specific in the answer just given. **High-stakes ballparks are NOT nudge territory:** a rough medication dose, security capacity, or production sizing estimate is Depth — being wrong there costs more than the shortcut saves.
|
|
185
202
|
|
|
186
203
|
1. **Check a fact** — "which claim here would be worth verifying, and against what?"
|
|
187
204
|
2. **Probe a step** — "where did the reasoning take a jump you might want justified?"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: find-matching-tenders
|
|
3
|
+
description: Find open AU/NZ government tenders matching what a company does, ranked by fit with why and gap analysis. Use when the user asks to find tenders, bid opportunities, government contracts, or RFPs for their business (or a client's).
|
|
4
|
+
category: document-verification
|
|
5
|
+
risk: critical
|
|
6
|
+
source: community
|
|
7
|
+
source_repo: Sketchjar/stipple-agent-skills
|
|
8
|
+
source_type: community
|
|
9
|
+
date_added: "2026-08-31"
|
|
10
|
+
author: Sketchjar
|
|
11
|
+
tags: [document-verification, fact-checking, stipple, authenticity]
|
|
12
|
+
tools: [claude, cursor, gemini, codex]
|
|
13
|
+
license: "Apache-2.0"
|
|
14
|
+
license_source: "https://github.com/Sketchjar/stipple-agent-skills/blob/main/LICENSE"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Find Matching Tenders
|
|
18
|
+
|
|
19
|
+
Search live AU/NZ government tenders, rank them against what a company actually does (read from its website), and explain why each opportunity is relevant — including the capability gaps to prepare evidence for. Tender search is free forever on the Stipple API; no API key or signup needed.
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- Business development: "find tenders we could bid on"
|
|
24
|
+
- Market research: "what government work is out there for cybersecurity firms in NSW?"
|
|
25
|
+
- Bid pipeline maintenance: "check for new construction tenders this week"
|
|
26
|
+
|
|
27
|
+
## Instructions
|
|
28
|
+
|
|
29
|
+
1. **Get the company profile.** Ask for the company's website URL. If the user provides a description instead, skip resolution and use their description directly as capability context.
|
|
30
|
+
|
|
31
|
+
2. **Resolve the company** (optional but improves matching):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -X POST https://www.stipple.sh/v1/companies/resolve \
|
|
35
|
+
-H "Content-Type: application/json" \
|
|
36
|
+
-d '{"query": "https://your-company.com"}'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Returns the company's registered name, ABN, jurisdiction, and status.
|
|
40
|
+
|
|
41
|
+
3. **Search open tenders** (free, no key):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
curl "https://www.stipple.sh/v1/tenders?q=construction&jurisdiction=AU&limit=10"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Filters: `q` (keyword), `jurisdiction` (AU, AU-NSW, AU-VIC, NZ, ...), `limit`, `offset`.
|
|
48
|
+
|
|
49
|
+
4. **Rank against the company** (optional, uses free weekly allowance):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -X POST https://www.stipple.sh/v1/tenders/match \
|
|
53
|
+
-H "Content-Type: application/json" \
|
|
54
|
+
-d '{"url": "https://your-company.com", "jurisdiction": "AU", "limit": 5}'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Returns ranked matches with `why[]` (why it fits) and `gaps[]` (capability evidence to prepare).
|
|
58
|
+
|
|
59
|
+
5. **Check data provenance** if results seem thin (free):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl "https://www.stipple.sh/v1/tenders/sources"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Shows which government feeds are indexed and their freshness — explains why a search can be empty.
|
|
66
|
+
|
|
67
|
+
6. **Report honestly.** Match scores are *fit signals*, not win probabilities. Present `gaps[]` as "prepare evidence for this", not disqualification.
|
|
68
|
+
|
|
69
|
+
## Output format
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Found 403 open tenders matching "construction" (AU/NZ)
|
|
73
|
+
|
|
74
|
+
#1 [AU-NSW] Digital platform modernisation services
|
|
75
|
+
buyer: NSW Department of ...
|
|
76
|
+
why: matches 'cloud migration', 'API integration' from your services
|
|
77
|
+
gap: no evidence found for 'ICT security assessment'
|
|
78
|
+
closes: 2026-09-15
|
|
79
|
+
https://...
|
|
80
|
+
|
|
81
|
+
#2 [NZ] ...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Limitations and Safety
|
|
85
|
+
|
|
86
|
+
- The hosted service receives the search terms and, for matching, the supplied
|
|
87
|
+
company URL or description. Obtain approval before sending private capability or
|
|
88
|
+
client information and do not include secrets or non-public bid strategy.
|
|
89
|
+
- Feed coverage and freshness can be incomplete. Confirm eligibility, scope,
|
|
90
|
+
amendments, deadlines, and submission instructions on the issuing authority's
|
|
91
|
+
primary tender page before investing time or submitting a bid.
|
|
92
|
+
- Match scores and gap analysis are triage signals, not procurement advice, an
|
|
93
|
+
eligibility ruling, or a probability of winning.
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- Tender search and source listing are free forever — no key, no credits
|
|
98
|
+
- Matching costs a small number of credits on the free weekly allowance
|
|
99
|
+
- For ongoing monitoring, pair with a cron job that re-runs step 3 daily and diffs results
|
|
100
|
+
- Data sources: NZ GETS, NSW eTendering, VendorPanel, and other government feeds
|
|
@@ -97,20 +97,21 @@ async function drainAndExit(): Promise<void> {
|
|
|
97
97
|
|
|
98
98
|
### Step 4: Implement readiness and liveness probes
|
|
99
99
|
|
|
100
|
-
Orchestrators use these to decide whether to route traffic and whether to restart the container.
|
|
100
|
+
Orchestrators use these to decide whether to route traffic and whether to restart the container. Liveness proves the process is alive; readiness controls whether traffic is routed. While the listener is still available during a drain, keep liveness healthy and return 503 only from readiness. After the listener closes, new probes cannot connect, so do not promise that HTTP liveness remains reachable for the entire termination window.
|
|
101
101
|
|
|
102
102
|
```typescript
|
|
103
103
|
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
104
104
|
|
|
105
105
|
function handleHealthCheck(req: IncomingMessage, res: ServerResponse): void {
|
|
106
106
|
if (req.url === "/healthz") {
|
|
107
|
-
//
|
|
107
|
+
// Keep liveness distinct from readiness while the listener is available.
|
|
108
|
+
// Drain-rejection middleware must not turn this endpoint into a 503.
|
|
108
109
|
res.writeHead(200).end("ok");
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
if (req.url === "/readyz") {
|
|
113
|
-
// Readiness:
|
|
114
|
+
// Readiness: 503 during shutdown so the load balancer stops routing.
|
|
114
115
|
if (isShuttingDown) {
|
|
115
116
|
res.writeHead(503).end("shutting down");
|
|
116
117
|
} else {
|
|
@@ -123,21 +124,25 @@ function handleHealthCheck(req: IncomingMessage, res: ServerResponse): void {
|
|
|
123
124
|
|
|
124
125
|
### Step 5: Track active connections
|
|
125
126
|
|
|
126
|
-
Maintain a count of in-flight requests so you know when draining is complete.
|
|
127
|
+
Maintain a count of in-flight requests so you know when draining is complete. Use a once guard covering both `finish` and `close` events so that client disconnects (aborted requests) correctly decrement the counter.
|
|
127
128
|
|
|
128
129
|
```typescript
|
|
129
130
|
let activeConnections = 0;
|
|
130
131
|
let drainResolve: (() => void) | null = null;
|
|
131
132
|
|
|
132
|
-
function
|
|
133
|
+
function trackRequest(res: ServerResponse): void {
|
|
133
134
|
activeConnections++;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
drainResolve
|
|
135
|
+
let counted = true;
|
|
136
|
+
function release(): void {
|
|
137
|
+
if (!counted) return;
|
|
138
|
+
counted = false;
|
|
139
|
+
activeConnections--;
|
|
140
|
+
if (isShuttingDown && activeConnections === 0 && drainResolve) {
|
|
141
|
+
drainResolve();
|
|
142
|
+
}
|
|
140
143
|
}
|
|
144
|
+
res.on("finish", release);
|
|
145
|
+
res.on("close", release);
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
function waitForActiveConnections(): Promise<void> {
|
|
@@ -160,25 +165,41 @@ const app = express();
|
|
|
160
165
|
const server = createServer(app);
|
|
161
166
|
let isShuttingDown = false;
|
|
162
167
|
let activeRequests = 0;
|
|
168
|
+
let drainResolve: (() => void) | null = null;
|
|
169
|
+
|
|
170
|
+
// Health endpoints — registered BEFORE the drain-rejection middleware so it
|
|
171
|
+
// cannot turn liveness into a 503 while the listener is still available.
|
|
172
|
+
app.get("/healthz", (_, res) => res.send("ok"));
|
|
173
|
+
app.get("/readyz", (_, res) => {
|
|
174
|
+
res.status(isShuttingDown ? 503 : 200).send(isShuttingDown ? "draining" : "ready");
|
|
175
|
+
});
|
|
163
176
|
|
|
164
|
-
// Track in-flight requests
|
|
177
|
+
// Track in-flight requests and reject new application work during drain.
|
|
165
178
|
app.use((req, res, next) => {
|
|
166
179
|
if (isShuttingDown) {
|
|
167
180
|
res.setHeader("Connection", "close");
|
|
168
181
|
res.status(503).json({ error: "Server is shutting down" });
|
|
169
182
|
return;
|
|
170
183
|
}
|
|
184
|
+
|
|
171
185
|
activeRequests++;
|
|
172
|
-
|
|
186
|
+
let counted = true;
|
|
187
|
+
function release(): void {
|
|
188
|
+
if (!counted) return;
|
|
189
|
+
counted = false;
|
|
190
|
+
activeRequests--;
|
|
191
|
+
if (isShuttingDown && activeRequests === 0 && drainResolve) {
|
|
192
|
+
drainResolve();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Listen for both finish (normal) and close (client abort) so the
|
|
196
|
+
// counter always decrements. The once guard prevents double-decrement
|
|
197
|
+
// when both events fire.
|
|
198
|
+
res.on("finish", release);
|
|
199
|
+
res.on("close", release);
|
|
173
200
|
next();
|
|
174
201
|
});
|
|
175
202
|
|
|
176
|
-
// Health endpoints
|
|
177
|
-
app.get("/healthz", (_, res) => res.send("ok"));
|
|
178
|
-
app.get("/readyz", (_, res) => {
|
|
179
|
-
res.status(isShuttingDown ? 503 : 200).send(isShuttingDown ? "draining" : "ready");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
203
|
// Application routes
|
|
183
204
|
app.get("/api/data", async (req, res) => {
|
|
184
205
|
const data = await fetchData();
|
|
@@ -199,13 +220,15 @@ function shutdown(signal: string): void {
|
|
|
199
220
|
}, 25_000);
|
|
200
221
|
forceExit.unref();
|
|
201
222
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
if (activeRequests === 0) {
|
|
224
|
+
console.log("No active requests — exiting cleanly");
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
drainResolve = () => {
|
|
229
|
+
console.log("All requests drained — exiting cleanly");
|
|
230
|
+
process.exit(0);
|
|
231
|
+
};
|
|
209
232
|
}
|
|
210
233
|
|
|
211
234
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -214,60 +237,28 @@ process.on("SIGINT", () => shutdown("SIGINT"));
|
|
|
214
237
|
server.listen(3000, () => console.log("Server ready on :3000"));
|
|
215
238
|
```
|
|
216
239
|
|
|
217
|
-
### Example 2: Python FastAPI
|
|
240
|
+
### Example 2: Python FastAPI under Uvicorn
|
|
241
|
+
|
|
242
|
+
Uvicorn owns SIGTERM handling and request draining. It stops accepting new connections, asks existing connections to shut down, waits for connections and tasks up to `--timeout-graceful-shutdown`, and only then sends the ASGI lifespan shutdown event. Do not replace its signal handler or wait for requests again inside `lifespan`; use that hook to release application resources after Uvicorn's drain.
|
|
218
243
|
|
|
219
244
|
```python
|
|
220
|
-
import asyncio
|
|
221
|
-
import signal
|
|
222
245
|
from contextlib import asynccontextmanager
|
|
223
|
-
from fastapi import FastAPI
|
|
224
|
-
|
|
225
|
-
active_requests = 0
|
|
226
|
-
is_shutting_down = False
|
|
227
|
-
shutdown_event = asyncio.Event()
|
|
246
|
+
from fastapi import FastAPI
|
|
228
247
|
|
|
229
248
|
|
|
230
249
|
@asynccontextmanager
|
|
231
250
|
async def lifespan(app: FastAPI):
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
try:
|
|
239
|
-
await asyncio.wait_for(shutdown_event.wait(), timeout=25.0)
|
|
240
|
-
except asyncio.TimeoutError:
|
|
241
|
-
print(f"Drain timeout — {active_requests} requests abandoned")
|
|
242
|
-
print("Shutdown complete")
|
|
251
|
+
app.state.db_pool = await open_database_pool()
|
|
252
|
+
try:
|
|
253
|
+
yield
|
|
254
|
+
finally:
|
|
255
|
+
# Uvicorn has already completed or timed out its request drain.
|
|
256
|
+
await app.state.db_pool.close()
|
|
243
257
|
|
|
244
258
|
|
|
245
259
|
app = FastAPI(lifespan=lifespan)
|
|
246
260
|
|
|
247
261
|
|
|
248
|
-
def begin_shutdown():
|
|
249
|
-
global is_shutting_down
|
|
250
|
-
is_shutting_down = True
|
|
251
|
-
print(f"SIGTERM received — draining {active_requests} requests")
|
|
252
|
-
if active_requests == 0:
|
|
253
|
-
shutdown_event.set()
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
@app.middleware("http")
|
|
257
|
-
async def track_requests(request: Request, call_next):
|
|
258
|
-
global active_requests
|
|
259
|
-
if is_shutting_down:
|
|
260
|
-
return Response("Service shutting down", status_code=503)
|
|
261
|
-
active_requests += 1
|
|
262
|
-
try:
|
|
263
|
-
response = await call_next(request)
|
|
264
|
-
return response
|
|
265
|
-
finally:
|
|
266
|
-
active_requests -= 1
|
|
267
|
-
if is_shutting_down and active_requests == 0:
|
|
268
|
-
shutdown_event.set()
|
|
269
|
-
|
|
270
|
-
|
|
271
262
|
@app.get("/healthz")
|
|
272
263
|
async def healthz():
|
|
273
264
|
return {"status": "ok"}
|
|
@@ -275,11 +266,17 @@ async def healthz():
|
|
|
275
266
|
|
|
276
267
|
@app.get("/readyz")
|
|
277
268
|
async def readyz():
|
|
278
|
-
if is_shutting_down:
|
|
279
|
-
return Response("draining", status_code=503)
|
|
280
269
|
return {"status": "ready"}
|
|
281
270
|
```
|
|
282
271
|
|
|
272
|
+
Run Uvicorn with a deadline shorter than the orchestrator's kill timeout:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
uvicorn app:app --timeout-graceful-shutdown 25
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
If readiness must turn 503 before SIGTERM, coordinate a separately secured and tested pre-stop drain signal plus a propagation delay. FastAPI's lifespan shutdown hook is too late for that transition because Uvicorn invokes it after request draining.
|
|
279
|
+
|
|
283
280
|
### Example 3: Background worker with checkpoint
|
|
284
281
|
|
|
285
282
|
```typescript
|
|
@@ -325,6 +322,15 @@ async function processJobs(queue: JobQueue): Promise<void> {
|
|
|
325
322
|
|
|
326
323
|
## Common Pitfalls
|
|
327
324
|
|
|
325
|
+
- **Problem:** Drain-rejection middleware turns both readiness and liveness into 503 while the HTTP listener is still available.
|
|
326
|
+
**Solution:** Register health routes before that middleware and flip only readiness. Once the listener closes, new probes may no longer connect; the shutdown deadline, not a promise of HTTP liveness, bounds termination.
|
|
327
|
+
|
|
328
|
+
- **Problem:** Drain hangs until the force-exit timeout even though all clients have disconnected.
|
|
329
|
+
**Solution:** Track request completion with both `finish` and `close` events (Node.js) or equivalent. If a client aborts the connection, `finish` may never fire — `close` will. Use a once guard to prevent double-decrementing the counter.
|
|
330
|
+
|
|
331
|
+
- **Problem:** A FastAPI application replaces Uvicorn's SIGTERM handler or waits for in-flight requests inside lifespan shutdown.
|
|
332
|
+
**Solution:** Let Uvicorn own signal handling, connection/task draining, and `--timeout-graceful-shutdown`. Use lifespan shutdown for resource cleanup; it runs after Uvicorn's request-drain phase.
|
|
333
|
+
|
|
328
334
|
- **Problem:** Kubernetes kills the pod before connections drain because `terminationGracePeriodSeconds` is too short.
|
|
329
335
|
**Solution:** Set it to at least drain timeout + 5s buffer. If your longest request takes 60s, use `terminationGracePeriodSeconds: 70` and drain timeout of 65s.
|
|
330
336
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: grok-delegate
|
|
3
3
|
description: Delegate coding tasks to the Grok Build CLI only when the user explicitly
|
|
4
4
|
requests it, while the orchestrator retains review and landing responsibility.
|
|
5
|
-
risk:
|
|
5
|
+
risk: critical
|
|
6
6
|
category: agent-orchestration
|
|
7
7
|
source: https://github.com/amElnagdy/delegate-skills
|
|
8
8
|
source_repo: amElnagdy/delegate-skills
|
|
@@ -37,6 +37,7 @@ and durable execution without managing queues or workers.
|
|
|
37
37
|
## Scope
|
|
38
38
|
|
|
39
39
|
- redis-queues -> bullmq-specialist
|
|
40
|
+
- serverless-queues -> upstash-qstash
|
|
40
41
|
- workflow-orchestration -> temporal-craftsman
|
|
41
42
|
- message-streaming -> event-architect
|
|
42
43
|
- infrastructure -> infra-architect
|
|
@@ -388,6 +389,7 @@ Fix action: Add idempotency: 'event.data.orderId' to function config
|
|
|
388
389
|
### Delegation Triggers
|
|
389
390
|
|
|
390
391
|
- redis|queue infrastructure|bullmq -> bullmq-specialist (Need Redis-based queue with existing infrastructure)
|
|
392
|
+
- serverless queue|http queue|scheduled http -> upstash-qstash (Need plain HTTP delivery and cron without an event framework)
|
|
391
393
|
- saga|compensation|rollback|long-running workflow -> temporal-craftsman (Need complex workflow orchestration with compensation)
|
|
392
394
|
- event sourcing|event store|cqrs -> event-architect (Need event sourcing patterns)
|
|
393
395
|
- vercel|deploy|production -> vercel-deployment (Need deployment configuration)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: kimi-delegate
|
|
3
3
|
description: Delegate coding tasks to the Kimi Code CLI (`kimi`) only when the user
|
|
4
4
|
explicitly requests it, while the orchestrator retains review and landing responsibility.
|
|
5
|
-
risk:
|
|
5
|
+
risk: critical
|
|
6
6
|
category: agent-orchestration
|
|
7
7
|
source: https://github.com/amElnagdy/delegate-skills
|
|
8
8
|
source_repo: amElnagdy/delegate-skills
|
|
@@ -90,9 +90,10 @@ Maintain a serializable manifest with these top-level fields:
|
|
|
90
90
|
### 7. Validate and report
|
|
91
91
|
|
|
92
92
|
- Scaffold a clean starter with `scripts/scaffold_template.py` when a neutral implementation is needed.
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
- The bundled helpers parse only the restricted data-literal assignment used by
|
|
94
|
+
the starter. They reject expressions, function calls, duplicate keys,
|
|
95
|
+
unsupported syntax, trailing statements, and oversized manifests without
|
|
96
|
+
executing JavaScript. Keep runtime theme configs data-only as well.
|
|
96
97
|
- Run `scripts/validate_manifest.py` on each theme config before rendering.
|
|
97
98
|
- Capture desktop and mobile screenshots with a real browser. Inspect them directly if visual capability is available.
|
|
98
99
|
- Run `scripts/measure_preview.py` on the pure field screenshot and retain measured chromatic ratio, luminance statistics, per-color coverage, and effective share.
|
|
@@ -122,7 +123,8 @@ Use the bundled starter under `assets/starter/` as a neutral base. Copy only the
|
|
|
122
123
|
### scripts/
|
|
123
124
|
|
|
124
125
|
- `scaffold_template.py`: copy the neutral starter for `opal`, `obsidian`, or both.
|
|
125
|
-
- `
|
|
126
|
+
- `manifest_parser.py`: statically parse the restricted data-only manifest grammar without executing JavaScript.
|
|
127
|
+
- `validate_manifest.py`: validate required manifest fields and ranges.
|
|
126
128
|
- `measure_preview.py`: measure a rendered pure-field PNG against the configured OKLCH palette.
|
|
127
129
|
|
|
128
130
|
### references/
|