skilldrop-cli 0.16.35 → 0.16.36
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/README.md +1 -0
- package/agents/README.md +4 -1
- package/agents/docs-reviewer.md +47 -0
- package/agents/finding-adjudicator.md +49 -0
- package/agents/test-reviewer.md +52 -0
- package/catalogue.json +17 -7
- package/model-routing.json +40 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
- package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
- package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
- package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
- package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
- package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
- package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
- package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
- package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
- package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
- package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
- package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
- package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
- package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
- package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
- package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
- package/packs/converters/skills/md-to-confluence/reference.md +92 -0
- package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
- package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
- package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
- package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
- package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
- package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
- package/packs/converters/skills/md-to-pptx/reference.md +139 -0
- package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
- package/packs/dev-team/loops/modernize/LOOP.md +124 -0
- package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
- package/packs/dev-team/loops/modernize/loop.json +60 -0
- package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
- package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
- package/packs/frontend/pack.json +55 -0
- package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
- package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
- package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
- package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
- package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
- package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
- package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
- package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
- package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
- package/packs/frontend/skills/web-performance-review/manifest.json +48 -0
- package/packs/frontend/skills/web-performance-review/reference.md +105 -0
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
- package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
- package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
- package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
- package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
- package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
- package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
- package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
- package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
- package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
- package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
- package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
- package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
- package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
- package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# lean-canvas reference
|
|
2
|
+
|
|
3
|
+
Long-form material for the steps in `SKILL.md`. The canvas is Ash Maurya's adaptation of the
|
|
4
|
+
Business Model Canvas (*Running Lean*); the interview rules follow Rob Fitzpatrick's *The Mom
|
|
5
|
+
Test*.
|
|
6
|
+
|
|
7
|
+
## The nine boxes, in the order to fill them
|
|
8
|
+
|
|
9
|
+
| Box | What goes in | Common wrong entry |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Problem | The top 1–3 problems, and how the early adopter copes today (existing alternatives) | A solution phrased as a problem: "no app for X" |
|
|
12
|
+
| Customer segments | Who has the problem; the **early adopter** named separately, narrow enough to find by name | "Small businesses", "everyone who…" |
|
|
13
|
+
| Unique value proposition | One sentence: why this, for the early adopter, instead of what they do today | A feature list |
|
|
14
|
+
| Solution | The smallest thing that addresses each problem | A roadmap |
|
|
15
|
+
| Channels | The paths to the early adopter, free and paid | Channels that reach the later segment |
|
|
16
|
+
| Revenue streams | Price, who pays, how often | "Freemium" with no paid tier defined |
|
|
17
|
+
| Cost structure | Cost to serve one customer, fixed costs, acquisition cost | Fixed costs only |
|
|
18
|
+
| Key metrics | The 1–3 numbers that show the model works (activation, retention, paid conversion) | Downloads, page views |
|
|
19
|
+
| Unfair advantage | What a funded competitor can't copy or buy within a year | A feature, "AI", "first mover", "passion" |
|
|
20
|
+
|
|
21
|
+
**Internal products.** Revenue streams = the budget line or measured saving that keeps it
|
|
22
|
+
funded. Customer = the team that has to change how it works; early adopter = the one team
|
|
23
|
+
with the worst version of the problem and a lead who asked for help. Channel = how that team
|
|
24
|
+
hears about and gets onto the tool (a mandate, a team lead, an existing workflow it plugs
|
|
25
|
+
into). Unfair advantage can be real here: access to internal data or a mandate no vendor
|
|
26
|
+
has. Still test whether the team will change its habits.
|
|
27
|
+
|
|
28
|
+
## Grading evidence
|
|
29
|
+
|
|
30
|
+
| Signal | Grade |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Paid, pre-ordered, signed a letter of intent, or switched from a current tool | Behaviour — strongest |
|
|
33
|
+
| Describes a recent, specific instance of the problem unprompted, and what it cost | Behaviour about the past — strong |
|
|
34
|
+
| Built a workaround (spreadsheet, hired someone) | Behaviour — strong |
|
|
35
|
+
| "I'd definitely use it", "great idea", survey interest | Opinion — weak, counts as assumption |
|
|
36
|
+
| Industry statistic or report | Sizes the market; says nothing about this segment's willingness to pay |
|
|
37
|
+
| Any of the above from a segment other than the early adopter | Counts as assumption for the early adopter |
|
|
38
|
+
|
|
39
|
+
## Scoring risk
|
|
40
|
+
|
|
41
|
+
| | Fatal (3) | Pivot (2) | Tune (1) |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| **High (3)** — no evidence, wrong segment, opinion only | 9 | 6 | 3 |
|
|
44
|
+
| **Medium (2)** — right segment, few people or indirect | 6 | 4 | 2 |
|
|
45
|
+
| **Low (1)** — observed behaviour, right segment | 3 | 2 | 1 |
|
|
46
|
+
|
|
47
|
+
Ties: rank the assumption whose test is cheaper first; it clears the way for the rest.
|
|
48
|
+
|
|
49
|
+
Why problem, customer and channel usually top the list: a wrong solution is fixed by
|
|
50
|
+
rebuilding it, which a team can do in weeks. A wrong problem or an unreachable customer means
|
|
51
|
+
the next build lands on nobody, and no amount of engineering moves it.
|
|
52
|
+
|
|
53
|
+
## Test menu, cheapest first
|
|
54
|
+
|
|
55
|
+
| Test | Proves wrong | Typical sample | Typical cost and time |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| Problem interview | The problem, the segment | 10–20 early adopters | $0–$200, 1–2 weeks |
|
|
58
|
+
| Channel smoke test | The channel and its cost | $100–$500 of spend or one week of outreach on one channel | the spend, 1 week |
|
|
59
|
+
| Landing page with a price | The UVP and the price | 100+ visitors from the stated channel | $100–$500, 1–2 weeks |
|
|
60
|
+
| Concierge run (deliver it by hand) | The solution and retention | 3–10 customers | founder time, 2–4 weeks |
|
|
61
|
+
| Pre-sale or letter of intent | Willingness to pay | 5–20 asks | $0, 2–4 weeks |
|
|
62
|
+
| Wizard of Oz (looks automated, done by hand) | The solution at small volume | 10–30 uses | founder time, 2–4 weeks |
|
|
63
|
+
|
|
64
|
+
A survey and "build an MVP and see" are not on the menu: the first measures opinion, the
|
|
65
|
+
second costs months to learn what an interview learns in a week.
|
|
66
|
+
|
|
67
|
+
## Setting the pass/fail line
|
|
68
|
+
|
|
69
|
+
- **Write it before the test runs**, as a count: "fewer than 4 of 10". A line set after the
|
|
70
|
+
results arrive moves to fit them.
|
|
71
|
+
- **Take it from the numbers when they exist.** Channel test fail line = the most a customer
|
|
72
|
+
can cost to acquire (lifetime margin ÷ 3 for a business that has to cover fixed costs;
|
|
73
|
+
lifetime margin at break-even). Landing-page fail line = the conversion that would
|
|
74
|
+
produce that cost per customer at the channel's cost per visitor.
|
|
75
|
+
- **No numbers yet:** a problem interview passes when at least 4 in 10 describe a recent
|
|
76
|
+
instance unprompted; a pre-sale passes when at least 1 in 5 pays or signs.
|
|
77
|
+
- **Say what failure changes:** which box is rewritten, and what the next test is.
|
|
78
|
+
|
|
79
|
+
## Problem-interview rules
|
|
80
|
+
|
|
81
|
+
1. Ask about the past, never the future. ✅ "When did that last happen? What did you do?"
|
|
82
|
+
❌ "Would you use an app that…?"
|
|
83
|
+
2. Don't pitch until the end, if at all. The moment the idea is described, people are polite.
|
|
84
|
+
3. Ask what it cost them: time, money, a lost customer.
|
|
85
|
+
4. Ask what they do about it today and what that costs. No workaround is a warning sign.
|
|
86
|
+
5. Count who raised the problem **before** it was named, separately from who agreed once asked.
|
|
87
|
+
|
|
88
|
+
Five-question script: "Walk me through your last [day/week/job]." / "What was the hardest
|
|
89
|
+
part?" / "When did [problem area] last go wrong? What happened?" / "What did you do about
|
|
90
|
+
it, and what did that cost?" / "What have you tried to fix it?"
|
|
91
|
+
|
|
92
|
+
## What counts as an unfair advantage
|
|
93
|
+
|
|
94
|
+
Counts: insider information, an existing audience or customer base, a network effect,
|
|
95
|
+
a community, exclusive contracts or access, regulatory permission others lack, a team
|
|
96
|
+
member whose name opens doors in this market, internal data or a mandate (internal products).
|
|
97
|
+
|
|
98
|
+
Doesn't count: a feature, an algorithm, "AI", a patent pending on a feature, first-mover,
|
|
99
|
+
better UX, lower price, hard work, passion. These go in Solution or UVP.
|
|
100
|
+
|
|
101
|
+
"None yet" is the most common correct answer at this stage. Name a candidate the idea
|
|
102
|
+
could grow into, if the input supports one.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Unit-economics sanity check for a lean canvas.
|
|
3
|
+
|
|
4
|
+
Stdlib only; runs on Python 3.9+. Computes, never estimates:
|
|
5
|
+
|
|
6
|
+
* contribution (gross margin) per customer per period, and as a % of price;
|
|
7
|
+
* customer lifetime in periods (given directly, or 1 / churn);
|
|
8
|
+
* lifetime gross margin (LTV) and LTV:CAC;
|
|
9
|
+
* CAC payback in periods, and whether it is longer than the lifetime;
|
|
10
|
+
* the most a customer can cost to acquire: at break-even (LTV) and at 3:1 (LTV / 3);
|
|
11
|
+
* break-even customers for the fixed costs, and the customers a stated
|
|
12
|
+
market can supply, when those are given.
|
|
13
|
+
|
|
14
|
+
All money figures share one currency and one period (default "month"). Convert
|
|
15
|
+
an annual price to monthly before passing it, or pass --period year with
|
|
16
|
+
annual figures throughout.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
python3 unit_economics.py --price 29 --variable-cost 6 --cac 400 \
|
|
20
|
+
--lifetime 9 --fixed 4300
|
|
21
|
+
python3 unit_economics.py --price 29 --variable-cost 6 --cac 400 --churn 0.11
|
|
22
|
+
python3 unit_economics.py --json inputs.json [-o out.md]
|
|
23
|
+
echo '{"price": 29, "variable_cost": 6, "cac": 400, "lifetime": 9}' | \
|
|
24
|
+
python3 unit_economics.py --json -
|
|
25
|
+
|
|
26
|
+
JSON keys mirror the flags: price, variable_cost, cac, lifetime, churn,
|
|
27
|
+
fixed, market_customers, period. Flags given on the command line override
|
|
28
|
+
the JSON.
|
|
29
|
+
|
|
30
|
+
Exit codes: 0 ok, 2 bad input.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import argparse
|
|
36
|
+
import json
|
|
37
|
+
import sys
|
|
38
|
+
|
|
39
|
+
KEYS = ("price", "variable_cost", "cac", "lifetime", "churn", "fixed", "market_customers")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fail(msg: str) -> None:
|
|
43
|
+
print(f"error: {msg}", file=sys.stderr)
|
|
44
|
+
sys.exit(2)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def load_json(path: str) -> dict:
|
|
48
|
+
try:
|
|
49
|
+
if path == "-":
|
|
50
|
+
data = json.load(sys.stdin)
|
|
51
|
+
else:
|
|
52
|
+
with open(path, encoding="utf-8") as fh:
|
|
53
|
+
data = json.load(fh)
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
fail(f"no such file: {path}")
|
|
56
|
+
except json.JSONDecodeError as exc:
|
|
57
|
+
fail(f"not valid JSON ({exc})")
|
|
58
|
+
if not isinstance(data, dict):
|
|
59
|
+
fail("JSON input must be one object")
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def number(name: str, value, minimum: float = 0.0, allow_equal: bool = True):
|
|
64
|
+
if value is None:
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
val = float(value)
|
|
68
|
+
except (TypeError, ValueError):
|
|
69
|
+
fail(f"{name} must be a number, got {value!r}")
|
|
70
|
+
if val < minimum or (not allow_equal and val == minimum):
|
|
71
|
+
op = ">=" if allow_equal else ">"
|
|
72
|
+
fail(f"{name} must be {op} {minimum:g}, got {val:g}")
|
|
73
|
+
return val
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def money(x: float) -> str:
|
|
77
|
+
return f"{x:,.2f}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def compute(inp: dict) -> tuple[list[str], list[str]]:
|
|
81
|
+
period = str(inp.get("period") or "month")
|
|
82
|
+
price = number("price", inp.get("price"), 0.0, allow_equal=False)
|
|
83
|
+
if price is None:
|
|
84
|
+
fail("price is required (per customer per period)")
|
|
85
|
+
vcost = number("variable_cost", inp.get("variable_cost")) or 0.0
|
|
86
|
+
cac = number("cac", inp.get("cac"))
|
|
87
|
+
lifetime = number("lifetime", inp.get("lifetime"), 0.0, allow_equal=False)
|
|
88
|
+
churn = inp.get("churn")
|
|
89
|
+
fixed = number("fixed", inp.get("fixed"))
|
|
90
|
+
market = number("market_customers", inp.get("market_customers"))
|
|
91
|
+
|
|
92
|
+
if churn is not None:
|
|
93
|
+
churn = number("churn", churn, 0.0, allow_equal=False)
|
|
94
|
+
if churn > 1:
|
|
95
|
+
if churn <= 100:
|
|
96
|
+
churn = churn / 100.0 # "11" meaning 11%
|
|
97
|
+
else:
|
|
98
|
+
fail("churn must be a fraction (0.11) or a percent (11)")
|
|
99
|
+
if lifetime is None:
|
|
100
|
+
lifetime = 1.0 / churn
|
|
101
|
+
|
|
102
|
+
lines: list[str] = []
|
|
103
|
+
flags: list[str] = []
|
|
104
|
+
|
|
105
|
+
margin = price - vcost
|
|
106
|
+
margin_pct = margin / price * 100
|
|
107
|
+
lines.append(f"Price per customer per {period}: {money(price)}")
|
|
108
|
+
if inp.get("variable_cost") is None:
|
|
109
|
+
lines.append(f"Variable cost per customer per {period}: not given, treated as 0")
|
|
110
|
+
flags.append("No cost to serve was given, so every margin figure here is an upper bound.")
|
|
111
|
+
else:
|
|
112
|
+
lines.append(f"Variable cost per customer per {period}: {money(vcost)}")
|
|
113
|
+
lines.append(f"Contribution per customer per {period}: {money(margin)} ({margin_pct:.1f}% gross margin)")
|
|
114
|
+
if margin <= 0:
|
|
115
|
+
flags.append(f"Each customer costs more to serve than they pay ({money(margin)} per {period}). No volume fixes this.")
|
|
116
|
+
|
|
117
|
+
ltv = None
|
|
118
|
+
if lifetime is not None:
|
|
119
|
+
src = f"1 / churn of {churn * 100:.1f}%" if churn is not None and inp.get("lifetime") is None else "as given"
|
|
120
|
+
lines.append(f"Customer lifetime: {lifetime:.1f} {period}s ({src})")
|
|
121
|
+
ltv = margin * lifetime
|
|
122
|
+
lines.append(f"Lifetime contribution (LTV): {money(ltv)}")
|
|
123
|
+
|
|
124
|
+
if cac is not None:
|
|
125
|
+
lines.append(f"Customer acquisition cost (CAC): {money(cac)}")
|
|
126
|
+
if margin > 0:
|
|
127
|
+
payback = cac / margin
|
|
128
|
+
lines.append(f"CAC payback: {payback:.1f} {period}s")
|
|
129
|
+
if lifetime is not None and payback > lifetime:
|
|
130
|
+
flags.append(
|
|
131
|
+
f"CAC payback ({payback:.1f} {period}s) is longer than the customer lifetime "
|
|
132
|
+
f"({lifetime:.1f} {period}s): every acquired customer loses {money(cac - ltv)}."
|
|
133
|
+
)
|
|
134
|
+
if ltv is not None:
|
|
135
|
+
ratio = ltv / cac if cac > 0 else float("inf")
|
|
136
|
+
lines.append(f"LTV:CAC: {ratio:.2f}" if cac > 0 else "LTV:CAC: no acquisition cost given as 0")
|
|
137
|
+
if cac > 0 and ratio < 1:
|
|
138
|
+
flags.append(f"LTV:CAC is {ratio:.2f}: acquisition destroys money before fixed costs.")
|
|
139
|
+
elif cac > 0 and ratio < 3:
|
|
140
|
+
flags.append(f"LTV:CAC is {ratio:.2f}, under the 3:1 most subscription businesses need to cover fixed costs.")
|
|
141
|
+
|
|
142
|
+
if ltv is not None and ltv > 0:
|
|
143
|
+
lines.append(f"Most a customer can cost to acquire: {money(ltv)} at break-even, {money(ltv / 3)} at 3:1")
|
|
144
|
+
|
|
145
|
+
if fixed is not None:
|
|
146
|
+
lines.append(f"Fixed costs per {period}: {money(fixed)}")
|
|
147
|
+
if margin > 0:
|
|
148
|
+
be = fixed / margin
|
|
149
|
+
be_whole = int(be) if be == int(be) else int(be) + 1
|
|
150
|
+
lines.append(f"Break-even customers (active at once): {be_whole:,}")
|
|
151
|
+
if market is not None:
|
|
152
|
+
lines.append(f"Customers the stated market holds: {int(market):,}")
|
|
153
|
+
if be_whole > market:
|
|
154
|
+
flags.append(
|
|
155
|
+
f"Break-even needs {be_whole:,} active customers but the stated market holds {int(market):,}."
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
share = be_whole / market * 100
|
|
159
|
+
lines.append(f"Break-even share of the stated market: {share:.1f}%")
|
|
160
|
+
if share >= 25:
|
|
161
|
+
flags.append(
|
|
162
|
+
f"Break-even needs {share:.0f}% of the stated market as paying customers at the same time."
|
|
163
|
+
)
|
|
164
|
+
if cac is not None and ltv is not None and ltv <= cac:
|
|
165
|
+
flags.append("Break-even customers cannot be reached through paid acquisition at this CAC: each one adds a loss.")
|
|
166
|
+
|
|
167
|
+
missing = [k for k in ("variable_cost", "cac", "lifetime/churn", "fixed")
|
|
168
|
+
if (k == "lifetime/churn" and lifetime is None)
|
|
169
|
+
or (k != "lifetime/churn" and inp.get(k) is None)]
|
|
170
|
+
if missing:
|
|
171
|
+
lines.append("Not computed (input missing): " + ", ".join(missing))
|
|
172
|
+
return lines, flags
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def main() -> None:
|
|
176
|
+
ap = argparse.ArgumentParser(description="Unit-economics sanity check for a lean canvas.")
|
|
177
|
+
ap.add_argument("--json", metavar="PATH", help="JSON object of inputs, or - for stdin")
|
|
178
|
+
ap.add_argument("--price", help="price per customer per period")
|
|
179
|
+
ap.add_argument("--variable-cost", dest="variable_cost", help="cost to serve one customer per period")
|
|
180
|
+
ap.add_argument("--cac", help="cost to acquire one paying customer")
|
|
181
|
+
ap.add_argument("--lifetime", help="average customer lifetime in periods")
|
|
182
|
+
ap.add_argument("--churn", help="churn per period, as 0.11 or 11")
|
|
183
|
+
ap.add_argument("--fixed", help="fixed costs per period")
|
|
184
|
+
ap.add_argument("--market-customers", dest="market_customers", help="customers the stated market holds")
|
|
185
|
+
ap.add_argument("--period", help='period label, default "month"')
|
|
186
|
+
ap.add_argument("-o", "--output", help="write the report to this path instead of stdout")
|
|
187
|
+
args = ap.parse_args()
|
|
188
|
+
|
|
189
|
+
inp: dict = load_json(args.json) if args.json else {}
|
|
190
|
+
unknown = set(inp) - set(KEYS) - {"period"}
|
|
191
|
+
if unknown:
|
|
192
|
+
fail("unknown JSON keys: " + ", ".join(sorted(unknown)))
|
|
193
|
+
for key in KEYS + ("period",):
|
|
194
|
+
val = getattr(args, key)
|
|
195
|
+
if val is not None:
|
|
196
|
+
inp[key] = val
|
|
197
|
+
|
|
198
|
+
lines, flags = compute(inp)
|
|
199
|
+
out = ["Unit economics", ""] + [f"- {ln}" for ln in lines]
|
|
200
|
+
out += ["", "Where the numbers don't close:" if flags else "Where the numbers don't close: none found."]
|
|
201
|
+
out += [f"- {f}" for f in flags]
|
|
202
|
+
text = "\n".join(out) + "\n"
|
|
203
|
+
if args.output:
|
|
204
|
+
with open(args.output, "w", encoding="utf-8") as fh:
|
|
205
|
+
fh.write(text)
|
|
206
|
+
else:
|
|
207
|
+
sys.stdout.write(text)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
main()
|
|
@@ -29,7 +29,7 @@ Produces the contract *before* the code — a valid OpenAPI 3.1 document two tea
|
|
|
29
29
|
3. **Open questions** — the unknowns that block freezing the contract, each with who can answer it
|
|
30
30
|
Start from [`templates/openapi-skeleton.yaml`](templates/openapi-skeleton.yaml).
|
|
31
31
|
|
|
32
|
-
6. **For event/queue contracts** (the brief says "publish", "consume", "webhook out"):
|
|
32
|
+
6. **For event/queue contracts** (the brief says "publish", "consume", "webhook out"): hand the event side to `event-contract-draft`, which owns event naming, the envelope, ordering, delivery and versioning. When the request mixes both, draft the HTTP side here and say which events it leaves to that contract.
|
|
33
33
|
|
|
34
34
|
## Useful references in this skill
|
|
35
35
|
|
|
@@ -50,12 +50,12 @@ Produces the contract *before* the code — a valid OpenAPI 3.1 document two tea
|
|
|
50
50
|
- ✅ Designing a new API or new resource before implementation
|
|
51
51
|
- ✅ Frontend and backend (or two teams) need a contract to build against in parallel
|
|
52
52
|
- ✅ A story from `user-story-splitter` whose slice is "expose X over the API"
|
|
53
|
-
- ✅ Drafting the webhook/event contract a partner will consume
|
|
54
53
|
|
|
55
54
|
## When NOT to use this skill
|
|
56
55
|
|
|
57
56
|
- ❌ Documenting an API that already exists — that's `guide-builder` (reference archetype)
|
|
58
57
|
- ❌ Choosing REST vs GraphQL vs gRPC — this skill starts after that choice is made
|
|
58
|
+
- ❌ An event, message or webhook contract — that's `event-contract-draft`
|
|
59
59
|
- ❌ Reviewing a contract someone else drafted — `doc-critique` or `council-review`
|
|
60
60
|
- ❌ Generating server/client code from the spec — hand the frozen contract to the codegen tool
|
|
61
61
|
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-contract-draft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Draft an OpenAPI 3.1 contract from a feature brief or story — resource-oriented paths, a full error catalog (RFC 9457), pagination/idempotency/versioning decided not deferred, examples on every operation, plus a decisions log so review argues choices instead of YAML. Use when the user wants to design an API, draft an OpenAPI/Swagger spec, define request/response shapes, or agree a contract between frontend/backend or two teams before code.",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
|
-
"deps": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"council-review",
|
|
16
|
+
"doc-critique",
|
|
17
|
+
"guide-builder",
|
|
18
|
+
"user-story-splitter",
|
|
19
|
+
"event-contract-draft"
|
|
20
|
+
],
|
|
21
|
+
"tags": [
|
|
22
|
+
"api-design",
|
|
23
|
+
"openapi",
|
|
24
|
+
"contract-first",
|
|
25
|
+
"rest",
|
|
26
|
+
"swagger",
|
|
27
|
+
"asyncapi"
|
|
28
|
+
],
|
|
29
|
+
"model": {
|
|
30
|
+
"tier": "standard",
|
|
31
|
+
"rationale": "Schema synthesis against fixed conventions. The resource modeling needs judgment; the conventions bound it. Escalate via ambiguous-input on contested multi-team contracts."
|
|
32
|
+
}
|
|
11
33
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event-contract-draft
|
|
3
|
+
description: Draft the contract for an event or message interface (Kafka topics, SNS/SQS, EventBridge, Pub/Sub, RabbitMQ, webhooks) as an AsyncAPI 3.0 document plus a short decisions page — past-tense event names, a required envelope with event id for deduplication, the partition key and the ordering it does and doesn't give, at-least-once delivery and how consumers stay idempotent, data minimisation with PII marked, additive-only versioning with dated deprecation, and retention, replay and dead-letter handling. Use when the user wants to define events, design a Kafka topic or message schema, write an AsyncAPI spec, agree what a producer publishes and what consumers must do, or version an event safely.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# event-contract-draft
|
|
7
|
+
|
|
8
|
+
Produces the contract for an asynchronous interface before producer and consumers build
|
|
9
|
+
against it: an AsyncAPI 3.0 document and a one-page decisions record. Request/response HTTP
|
|
10
|
+
APIs are `api-contract-draft`; a dataset's schema and quality SLAs for analytics readers are
|
|
11
|
+
`data-contract`; the system design that decides events are the right tool is `design-doc`.
|
|
12
|
+
|
|
13
|
+
## How to respond
|
|
14
|
+
|
|
15
|
+
**Four rules come before the steps and outrank them:**
|
|
16
|
+
|
|
17
|
+
- **Answer what was asked, first.** Open with the answer, the decision or the artifact, in plain words. Scores, matrices, frameworks and tags come after it, and anything that doesn't change the answer is cut.
|
|
18
|
+
- **Use only what you were given.** Don't add facts, names, numbers, incidents, history, steps or sections the input doesn't contain. What you need and don't have is left out of the artifact and listed once at the end under "To confirm".
|
|
19
|
+
- **Deliver from what you have.** When the request gives you something to work on, state your assumptions in a line and produce the result. When it gives you nothing to work on, ask for it in one or two plain sentences and say what you will do once you have it.
|
|
20
|
+
- **Write for someone who has never heard of this skill.** No skill names, no paths or scripts from this folder, no internal terms, and nothing about how the run was set up. A next step is one plain sentence at the end that describes the work.
|
|
21
|
+
- **For this skill:** When the request asks for something an event contract can't promise (global order, exactly-once, "change it in place", "include it just in case"), the first lines say no, why, and what the contract offers instead. Building the unsafe version quietly is the failure this skill exists to prevent.
|
|
22
|
+
|
|
23
|
+
1. **Size the reply to the ask.** A question about one rule ("is ordering guaranteed if we key
|
|
24
|
+
by paymentId?") gets a direct answer in a few lines: yes or no, why, the fix. No AsyncAPI
|
|
25
|
+
document. A request to define events gets the full contract (step 11).
|
|
26
|
+
|
|
27
|
+
2. **List the facts, the producer and the consumers.** For each consumer, write what it does
|
|
28
|
+
with each event; that line decides payload size and justifies every field. Ask at most 2
|
|
29
|
+
questions, spent on who consumes and what they do with it, and which broker. Missing broker
|
|
30
|
+
→ write broker-neutral with a Kafka binding, tagged `[assumption]`.
|
|
31
|
+
|
|
32
|
+
3. **Name events as facts, past tense, in the producer's domain.** One event type per message
|
|
33
|
+
schema.
|
|
34
|
+
- ✅ `OrderPaid` — ❌ `CreateShipment`. A command tells another team what to do and couples
|
|
35
|
+
the producer to the warehouse's process; the warehouse decides to pick when it sees
|
|
36
|
+
`OrderPaid`. If one named receiver must act, that is a command on the receiver's queue,
|
|
37
|
+
outside this contract; say so.
|
|
38
|
+
- ✅ `OrderPlaced`, `OrderCancelled` — ❌ `OrderEvent` with a `status` field. A generic
|
|
39
|
+
event makes every consumer parse a union and breaks them all when a status is added.
|
|
40
|
+
- ✅ `OrderDeliveryAddressChanged` — ❌ `OrderUpdated` when the business fact has a name.
|
|
41
|
+
|
|
42
|
+
4. **Decide notification or event-carried state per event, with the reason.** Notification =
|
|
43
|
+
ids plus a link; consumers call back. Event-carried state = the fields named consumers act
|
|
44
|
+
on, so they never call back. ✅ *"`OrderPaid` carries lines and address: fulfilment picks
|
|
45
|
+
without calling the order API."* ❌ The whole order aggregate on every event "for
|
|
46
|
+
flexibility". Table in [`reference.md`](reference.md).
|
|
47
|
+
|
|
48
|
+
5. **Put the envelope on every message, all required:** event id, event type, event version,
|
|
49
|
+
occurred-at (UTC, RFC 3339, `format: date-time`), producer, correlation id; causation id
|
|
50
|
+
too. Domain fields go under `data`, with a per-entity revision (`orderRevision`) that
|
|
51
|
+
increases on each change. ❌ `"placedAt": "2026-10-19 14:03:11"` — no zone, not RFC 3339.
|
|
52
|
+
Money is integer minor units plus currency: ❌ `"total": 42.50`.
|
|
53
|
+
|
|
54
|
+
6. **State delivery, ordering and duplicates per channel.**
|
|
55
|
+
- **Delivery is at-least-once** unless the broker and the consumer prove otherwise.
|
|
56
|
+
Consumers deduplicate on the event id or apply only a higher revision; name which, per
|
|
57
|
+
consumer. ✅ *"Loyalty awards points keyed on (orderId, reason); a second OrderPaid is a
|
|
58
|
+
no-op."* ❌ "Exactly-once delivery" in the description.
|
|
59
|
+
- **Ordering exists only per partition key.** Name the key, what it orders, and what it
|
|
60
|
+
doesn't. ✅ *"Keyed by orderId: one order's events arrive in order. No order across
|
|
61
|
+
orders, no global order."* Events whose relative order matters (`OrderPaid`,
|
|
62
|
+
`OrderCancelled`) share one channel and one key; on two topics they race.
|
|
63
|
+
- **A global-order request is declined.** One partition means one consumer at a time, and
|
|
64
|
+
retries and dead-letter redrives reorder it anyway. Offer what the reader needs:
|
|
65
|
+
per-entity order plus `occurredAt` and the revision, sorted at read time.
|
|
66
|
+
- **The producer publishes each entity's events in commit order** (an outbox written in
|
|
67
|
+
the same transaction), or the per-key promise is false.
|
|
68
|
+
- **Webhooks** have no ordering: receivers use the revision. Sign the raw body (HMAC) with
|
|
69
|
+
a timestamp inside the signature so receivers reject replays; state the retry schedule,
|
|
70
|
+
its cap, and where deliveries that keep failing go.
|
|
71
|
+
|
|
72
|
+
7. **Minimise the data.** Every field names the consumer that uses it; no consumer, no field.
|
|
73
|
+
❌ `customerEmail` "just in case": it sits in every consumer's log and replay for the whole
|
|
74
|
+
retention window and cannot be recalled. Personal data a consumer does need stays and gets
|
|
75
|
+
`x-pii` naming that consumer (✅ `deliveryAddress` for fulfilment). Card numbers never go on
|
|
76
|
+
an event, marked or not; send a payment reference or the last four digits.
|
|
77
|
+
|
|
78
|
+
8. **Version additively; break only with a new version and a date.** Within a version only
|
|
79
|
+
optional fields are added; consumers ignore unknown fields, so payload schemas stay open
|
|
80
|
+
(no `additionalProperties: false`). A rename, type or unit change, removal, new required
|
|
81
|
+
field or key change is breaking. ❌ Renaming `total` in place while a consumer reads it in
|
|
82
|
+
production. ✅ Add `totalMinor` beside `total`, publish both, mark `total` deprecated with a
|
|
83
|
+
sunset date; or publish `v2` on a new channel alongside `v1` for a dual-publish window. The
|
|
84
|
+
sunset date comes from the input or is "to agree with <consumer>"; never invent one. New
|
|
85
|
+
event types on a live topic whose consumers assume one type break those consumers: put
|
|
86
|
+
them on a new channel or confirm the consumers skip unknown types.
|
|
87
|
+
|
|
88
|
+
9. **State retention, replay and dead-letter on every channel.** How long messages are kept
|
|
89
|
+
(from the input or `[assumption]`), whether consumers can replay and from where, what goes
|
|
90
|
+
to the DLQ after how many attempts, and who watches it. An unnamed DLQ owner goes under "To
|
|
91
|
+
confirm", not a made-up team. Broker specifics in [`reference.md`](reference.md).
|
|
92
|
+
|
|
93
|
+
10. **Write the contract.** Start from
|
|
94
|
+
[`templates/asyncapi-skeleton.json`](templates/asyncapi-skeleton.json); emit JSON (the
|
|
95
|
+
check below reads JSON only). Channel facts go in `x-partition-key`, `x-ordering`,
|
|
96
|
+
`x-delivery`, `x-retention`, `x-replay`, `x-dead-letter`; consumer duties in
|
|
97
|
+
`info.description`. Then run the check and fix every FAIL:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Claude Code
|
|
101
|
+
python3 "${CLAUDE_SKILL_DIR}/scripts/check_asyncapi.py" asyncapi.json
|
|
102
|
+
# Other IDEs (from the skill folder)
|
|
103
|
+
python3 scripts/check_asyncapi.py asyncapi.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It checks AsyncAPI 3.x structure, `$ref`s, past-tense names, the envelope in every message,
|
|
107
|
+
one type per schema, unmarked personal data, closed schemas, deprecation dates, and each
|
|
108
|
+
channel's key, ordering, delivery, retention, replay and DLQ notes. When it can't run, say
|
|
109
|
+
the document is unchecked. Never claim a check that didn't run.
|
|
110
|
+
|
|
111
|
+
11. **Reply in this order:**
|
|
112
|
+
1. **What the contract decides**, 3–6 lines, leading with every pushback from steps 3–9.
|
|
113
|
+
2. **Decisions**: a table of decision, choice, why (shape in [`reference.md`](reference.md)).
|
|
114
|
+
3. **What consumers must do**: numbered, at most 6: dedupe, ignore unknown fields, revision
|
|
115
|
+
check, no cross-key order, DLQ duty, the migration they owe and by when.
|
|
116
|
+
4. **The AsyncAPI document**, saved as `asyncapi.json` when files can be written, inline
|
|
117
|
+
otherwise; then one line on the check result.
|
|
118
|
+
5. **To confirm**: each open item with who can answer it.
|
|
119
|
+
|
|
120
|
+
**Non-interactive runs** (subagent, CI, headless): a missing broker, retention, consumer
|
|
121
|
+
detail or sunset date becomes a tagged `[assumption]` or a "To confirm" line, and the contract
|
|
122
|
+
ships. No producer or no event at all emits `BLOCKED: need the producing service and what
|
|
123
|
+
happens in it that others react to`.
|
|
124
|
+
|
|
125
|
+
## Useful references in this skill
|
|
126
|
+
|
|
127
|
+
- [`reference.md`](reference.md) — envelope fields and CloudEvents mapping, notification vs state, safe vs breaking changes, broker ordering/delivery/retention facts, consumer idempotency patterns, the `x-` extensions, decisions page shape
|
|
128
|
+
- [`templates/asyncapi-skeleton.json`](templates/asyncapi-skeleton.json) — valid AsyncAPI 3.0 starting point with envelope, two messages and every channel extension
|
|
129
|
+
- [`scripts/check_asyncapi.py`](scripts/check_asyncapi.py) — the structural and rule check (stdlib only; JSON input; exit 1 on failures)
|
|
130
|
+
- [`examples/order-service.md`](examples/order-service.md) — worked example: an order service brief with four traps, and the contract that answers it
|
|
131
|
+
|
|
132
|
+
## Quality bar
|
|
133
|
+
|
|
134
|
+
- **Every event name is a past-tense fact** in the producer's domain, one type per schema.
|
|
135
|
+
- **Every message has the full envelope, required**, with a stable event id for deduplication.
|
|
136
|
+
- **Every channel names its key, the order it gives and the order it doesn't.** No global order.
|
|
137
|
+
- **Each consumer's idempotency is stated**: dedupe on id, revision check, or a natural key.
|
|
138
|
+
- **Every field has a consumer**; personal data that stays is marked; nothing "just in case".
|
|
139
|
+
- **Breaking changes go through a new field or version** with a deprecation date that came from the input or is listed as open.
|
|
140
|
+
- **Retention, replay and dead-letter are stated per channel**, with the DLQ owner named or open.
|
|
141
|
+
- **The check result is reported only if it ran**, and the document has no FAIL.
|
|
142
|
+
- **Nothing invented**: no team names, dates, volumes or retention the input didn't give, except as `[assumption]`.
|
|
143
|
+
|
|
144
|
+
## When to use this skill
|
|
145
|
+
|
|
146
|
+
- ✅ Defining the events a service publishes, before consumers build against them
|
|
147
|
+
- ✅ Designing a Kafka topic, SNS/SQS, EventBridge, Pub/Sub or RabbitMQ message contract
|
|
148
|
+
- ✅ Changing a live event's schema without breaking its consumers
|
|
149
|
+
- ✅ A webhook contract partners will receive
|
|
150
|
+
- ✅ "Is ordering / exactly-once / dedup guaranteed if we do X?" about a message interface
|
|
151
|
+
|
|
152
|
+
## When NOT to use this skill
|
|
153
|
+
|
|
154
|
+
- ❌ A request/response HTTP API — `api-contract-draft`
|
|
155
|
+
- ❌ A table, dataset or analytics stream's schema, freshness and quality SLAs — `data-contract`
|
|
156
|
+
- ❌ Deciding whether the system should be event-driven at all, or its components — `design-doc`
|
|
157
|
+
|
|
158
|
+
## Anti-patterns to avoid
|
|
159
|
+
|
|
160
|
+
- ❌ **Commands dressed as events.** `CreateShipment` on a topic makes the order service own the warehouse's workflow.
|
|
161
|
+
- ❌ **Promising global order** or exactly-once because someone asked. Brokers give per-key order and at-least-once.
|
|
162
|
+
- ❌ **No event id**, so consumers dedupe on a hash of the payload and double-count the legitimate repeat.
|
|
163
|
+
- ❌ **Related events on separate topics** whose order then races: `OrderCancelled` overtakes `OrderPaid`.
|
|
164
|
+
- ❌ **The "just in case" field.** Personal data with no consumer, kept in every log for the retention window.
|
|
165
|
+
- ❌ **In-place breaking change** to a live event, "so there's one version to maintain". Fulfilment breaks at deploy.
|
|
166
|
+
- ❌ **`additionalProperties: false`** on payloads, which turns every additive change into a breaking one.
|
|
167
|
+
- ❌ **A DLQ nobody owns.** Messages land there and the business notices weeks later.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Write the AsyncAPI spec for the order events our service publishes to Kafka",
|
|
4
|
+
"should_trigger": true
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"query": "Define the message schema, envelope and partition key for our payment events topic",
|
|
8
|
+
"should_trigger": true
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "We need to rename a field on a live SNS/SQS event without breaking the consumers. How do we version it?",
|
|
12
|
+
"should_trigger": true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"query": "Draft the EventBridge event contract for InvoicePaid, including what consumers have to do about duplicates",
|
|
16
|
+
"should_trigger": true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"query": "If we key our Kafka topic by customerId, is ordering guaranteed for each order?",
|
|
20
|
+
"should_trigger": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"query": "Draft the OpenAPI spec for the orders REST API: create, fetch and list orders",
|
|
24
|
+
"should_trigger": false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"query": "Write a data contract for the orders table the analysts query in the warehouse, with freshness and completeness SLAs",
|
|
28
|
+
"should_trigger": false
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"query": "Define the data contract for the clickstream topic that feeds the lakehouse, with null-rate and volume checks",
|
|
32
|
+
"should_trigger": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"query": "Write a design doc for moving the order service from polling to an event-driven architecture",
|
|
36
|
+
"should_trigger": false
|
|
37
|
+
}
|
|
38
|
+
]
|