skilldrop-cli 1.3.0 → 1.5.0
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 +10 -10
- package/bin/skilldrop.js +15 -10
- package/catalogue.json +8 -4
- package/contracts/catalogue.schema.json +2 -1
- package/model-routing.json +70 -78
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-adoption-stage/SKILL.md +0 -1
- package/packs/ai-engineering/skills/agent-adoption-stage/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-adoption-rollout/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-adoption-rollout/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-readiness-assessment/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-readiness-assessment/manifest.json +1 -2
- package/packs/ai-engineering/skills/ai-use-case-triage/manifest.json +3 -3
- package/packs/ai-engineering/skills/judge-audit/SKILL.md +29 -13
- package/packs/ai-engineering/skills/judge-audit/evals/evals.json +30 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/12/judge_runs.csv +241 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/12/rosa_labels.csv +33 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/13/moderation_eval.csv +601 -0
- package/packs/ai-engineering/skills/judge-audit/manifest.json +2 -3
- package/packs/ai-engineering/skills/llm-eval-harness/SKILL.md +1 -2
- package/packs/ai-engineering/skills/llm-eval-harness/manifest.json +3 -3
- package/packs/ai-engineering/skills/mcp-pairing/manifest.json +1 -1
- package/packs/ai-engineering/skills/mcp-pairing/reference.md +4 -3
- package/packs/ai-engineering/skills/subagent-design/SKILL.md +1 -0
- package/packs/ai-engineering/skills/subagent-design/examples/help-center-localization.md +146 -0
- package/packs/ai-engineering/skills/subagent-design/manifest.json +2 -2
- package/packs/api-builder/skills/prompt-caching-advisor/SKILL.md +4 -0
- package/packs/api-builder/skills/prompt-caching-advisor/examples/support-bot-block-order.md +120 -0
- package/packs/api-builder/skills/prompt-caching-advisor/manifest.json +2 -2
- package/packs/api-builder/skills/tool-use-schema-writer/manifest.json +2 -2
- package/packs/career/skills/application-form-answers/manifest.json +3 -3
- package/packs/career/skills/cover-letter/manifest.json +3 -3
- package/packs/career/skills/resume-tailor/manifest.json +3 -3
- package/packs/converters/skills/md-to-docx/manifest.json +3 -3
- package/packs/core/loops/ship-a-draft/evals/evals.json +28 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-adr-from-thread/slack-thread.txt +47 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-announcement-unsettled/ticket-PRD-412.md +44 -0
- package/packs/core/skills/output-hygiene/manifest.json +3 -3
- package/packs/data-analytics/loops/measure/evals/evals.json +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/schema.md +43 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/finance_active_customers.sql +7 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/growth_active_customers.sql +9 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/thread.md +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/request.md +34 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/schema.md +58 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/active_subscribers.sql +17 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/net_revenue_by_plan.sql +15 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/weekly_churn_rate.sql +34 -0
- package/packs/data-analytics/skills/dashboard-spec/SKILL.md +0 -1
- package/packs/data-analytics/skills/dashboard-spec/manifest.json +1 -2
- package/packs/design/skills/slide-outliner/manifest.json +2 -2
- package/packs/dev-team/loops/build/evals/evals.json +28 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/pricing.py +47 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/story.md +61 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/test_pricing.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/bug-report.md +25 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pr-description.md +42 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/bucket.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/test_bucket.py +63 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +30 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/PR-412.md +24 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/__init__.py +1 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/cli.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/extract.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/rules.py +39 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/release/LOOP.md +4 -4
- package/packs/dev-team/loops/release/evals/eval_queries.json +10 -0
- package/packs/dev-team/loops/release/evals/evals.json +34 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/0061_switch_to_haldrin_tokens.sql +22 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/eng-notes.md +40 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/readiness-evidence.md +27 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/traffic-baseline.md +23 -0
- package/packs/dev-team/loops/release/loop.json +1 -1
- package/packs/dev-team/skills/bug-triage/SKILL.md +1 -0
- package/packs/dev-team/skills/bug-triage/examples/export-row-limit-escalation.md +126 -0
- package/packs/dev-team/skills/bug-triage/manifest.json +2 -2
- package/packs/dev-team/skills/feature-implement-loop/manifest.json +2 -2
- package/packs/dev-team/skills/pr-description-writer/manifest.json +3 -3
- package/packs/dev-team/skills/pre-merge-review/manifest.json +2 -2
- package/packs/dev-team/skills/release-notes/manifest.json +2 -2
- package/packs/dev-team/skills/test-plan-generator/manifest.json +2 -2
- package/packs/experience-design/skills/service-blueprint/manifest.json +3 -3
- package/packs/experience-design/skills/ux-writing/manifest.json +3 -3
- package/packs/grc/loops/comply/evals/evals.json +29 -0
- package/packs/grc/loops/comply/evals/files/comply-chat-vendor-launch/vendor-brief.md +72 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/launch-note.md +31 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/risk-register.csv +6 -0
- package/packs/infra-as-code/skills/terraform-module/manifest.json +3 -3
- package/packs/product-manager/skills/prd-draft/SKILL.md +1 -0
- package/packs/product-manager/skills/prd-draft/examples/approver-delay-not-slack.md +142 -0
- package/packs/product-manager/skills/prd-draft/manifest.json +2 -2
- package/packs/product-manager/skills/user-journey-map/manifest.json +2 -2
- package/packs/research/loops/research/evals/evals.json +28 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t1-ticket-volume.md +15 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t2-release-notes-4-2.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t3-billing-migration.md +12 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t4-agent-poll.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t5-app-store-reviews.md +12 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s1-analytics-export.md +29 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s2-email-delivery-report.md +25 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s3-pricing-changelog.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s4-trial-interview-notes.md +21 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s5-competitor-note.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s6-weekly-growth-recap.md +7 -0
- package/packs/research/skills/research-plan/manifest.json +3 -3
- package/packs/research/skills/source-synthesis/manifest.json +3 -3
- package/packs/skill-engineering/skills/skill-author/manifest.json +3 -3
- package/packs/solution-architect/loops/design/evals/evals.json +28 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0001-postgres-system-of-record.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0003-tenant-isolation-row-level.md +25 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/prd-bulk-export.md +50 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0004-new-stateful-infra-needs-platform-signoff.md +22 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/prd-partner-webhooks.md +36 -0
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +3 -3
- package/packs/solution-architect/skills/architecture-diagrams/manifest.json +3 -3
- package/packs/solution-architect/skills/design-doc/manifest.json +2 -2
- package/packs/solution-architect/skills/figma-diagrams/manifest.json +3 -3
- package/packs/sre-oncall/skills/cloud-cost-review/SKILL.md +0 -1
- package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -2
- package/packs/sre-oncall/skills/delivery-metrics-report/SKILL.md +0 -1
- package/packs/sre-oncall/skills/delivery-metrics-report/manifest.json +1 -2
- package/packs/sre-oncall/skills/incident-comms/manifest.json +2 -2
- package/packs/sre-oncall/skills/postmortem-generator/manifest.json +2 -2
- package/packs/stakeholder-comms/skills/decision-log/manifest.json +2 -2
- package/packs/trackers/skills/team-status-report/manifest.json +3 -3
- package/profiles.json +1 -1
- package/packs/ai-engineering/loops/adopt/LOOP.md +0 -120
- package/packs/ai-engineering/loops/adopt/evals/eval_queries.json +0 -10
- package/packs/ai-engineering/loops/adopt/evals/evals.json +0 -69
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/ai-ideas.md +0 -15
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/org-context.md +0 -27
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/staff-survey.csv +0 -11
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/ai-ideas.md +0 -13
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/firm-context.md +0 -23
- package/packs/ai-engineering/loops/adopt/loop.json +0 -53
- package/packs/ai-engineering/skills/ai-usage-report/SKILL.md +0 -111
- package/packs/ai-engineering/skills/ai-usage-report/evals/eval_queries.json +0 -30
- package/packs/ai-engineering/skills/ai-usage-report/evals/evals.json +0 -46
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/1/northwind-ai-events.csv +0 -4213
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/draftwell-events.csv +0 -249
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/export-note.md +0 -9
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/published-assets.csv +0 -44
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/team-roster.md +0 -10
- package/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +0 -133
- package/packs/ai-engineering/skills/ai-usage-report/manifest.json +0 -12
- package/packs/ai-engineering/skills/ai-usage-report/scripts/build_report.py +0 -453
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-per-user.md +0 -42
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-team-rollup.md +0 -49
- package/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +0 -76
- package/packs/career/loops/apply/LOOP.md +0 -97
- package/packs/career/loops/apply/evals/eval_queries.json +0 -9
- package/packs/career/loops/apply/evals/evals.json +0 -27
- package/packs/career/loops/apply/loop.json +0 -52
- package/packs/product-manager/loops/discover/LOOP.md +0 -82
- package/packs/product-manager/loops/discover/evals/eval_queries.json +0 -30
- package/packs/product-manager/loops/discover/evals/evals.json +0 -26
- package/packs/product-manager/loops/discover/loop.json +0 -41
- package/packs/product-manager/skills/lean-canvas/SKILL.md +0 -162
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +0 -42
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +0 -82
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +0 -54
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +0 -30
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +0 -44
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +0 -5
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +0 -40
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +0 -18
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +0 -72
- package/packs/product-manager/skills/lean-canvas/manifest.json +0 -34
- package/packs/product-manager/skills/lean-canvas/reference.md +0 -102
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +0 -211
- package/packs/sre-oncall/loops/operate/LOOP.md +0 -84
- package/packs/sre-oncall/loops/operate/evals/eval_queries.json +0 -30
- package/packs/sre-oncall/loops/operate/evals/evals.json +0 -25
- package/packs/sre-oncall/loops/operate/loop.json +0 -41
|
@@ -1,211 +0,0 @@
|
|
|
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 if market else 0.0 # 0 needed of a 0 market
|
|
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()
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: operate
|
|
3
|
-
description: Take a shipped service through detection, response, and learning — instrument it, write the runbook, communicate the incident, then feed the postmortem's deltas back into the runbook. Use when the user is preparing to run a service, is in an incident, or is writing a postmortem.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# operate
|
|
7
|
-
|
|
8
|
-
The only loop whose failures are **irreversible and live**. The other four can be re-run at
|
|
9
|
-
the cost of time; this one runs while real users are affected. Its feedback edge is the point:
|
|
10
|
-
`postmortem-generator` emits runbook deltas phrased as edits, and those go straight back into
|
|
11
|
-
`document`. An incident that changes no procedure has not been learned from.
|
|
12
|
-
|
|
13
|
-
## Stages
|
|
14
|
-
|
|
15
|
-
| # | Stage | Type | Skills | Gate |
|
|
16
|
-
|---|---|---|---|---|
|
|
17
|
-
| 1 | `instrument` | generate | [`observability-plan`](../../skills/observability-plan/SKILL.md) | — |
|
|
18
|
-
| 2 | `document` | generate | [`runbook-generator`](../../skills/runbook-generator/SKILL.md) | — |
|
|
19
|
-
| 3 | `respond` | generate | [`incident-comms`](../../skills/incident-comms/SKILL.md) | — |
|
|
20
|
-
| 4 | `learn` | verify | [`postmortem-generator`](../../skills/postmortem-generator/SKILL.md) | **G4** human |
|
|
21
|
-
|
|
22
|
-
## How to run this loop
|
|
23
|
-
|
|
24
|
-
1. **Instrument first.** `observability-plan` decides what is measured and what pages a human.
|
|
25
|
-
You cannot respond to what you never detected, and you cannot write a runbook entry for an
|
|
26
|
-
alert that does not exist.
|
|
27
|
-
2. **Write the runbook per alert.** Each alert the previous stage defined maps to a
|
|
28
|
-
`runbook-generator` entry. An alert with no procedure wakes someone who then has to think.
|
|
29
|
-
3. **Respond to the audience the incident actually has.** `incident-comms` is staged —
|
|
30
|
-
detect, mitigate, resolve — and the stage determines the message. Writing a resolution
|
|
31
|
-
update during mitigation is the most common error here.
|
|
32
|
-
4. **Learn at G4.** `postmortem-generator` analyses blamelessly and ends with runbook deltas.
|
|
33
|
-
A human signs off that the incident is closed. `REVISE` returns to `document`: the deltas
|
|
34
|
-
are the loop's output, and applying them is what closes it.
|
|
35
|
-
|
|
36
|
-
Entering mid-loop is normal and expected. An incident starts at `respond`; a new service
|
|
37
|
-
starts at `instrument`. Do not replay earlier stages for their own sake.
|
|
38
|
-
|
|
39
|
-
## Verdicts
|
|
40
|
-
|
|
41
|
-
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G4 emits `PROCEED`,
|
|
42
|
-
`PROCEED WITH CONDITIONS`, `REVISE`, or `BLOCKED`. `PROCEED WITH CONDITIONS` means the
|
|
43
|
-
incident is closed but named follow-ups are outstanding — they travel with the postmortem.
|
|
44
|
-
|
|
45
|
-
## Non-interactive runs
|
|
46
|
-
|
|
47
|
-
Never fabricate incident facts — timings, impact, customer counts, root cause. When the
|
|
48
|
-
timeline is incomplete, emit `BLOCKED: need <the missing facts>` and name them. A confident
|
|
49
|
-
postmortem built on guessed impact is worse than no postmortem.
|
|
50
|
-
|
|
51
|
-
## When a stage's skill is not installed
|
|
52
|
-
|
|
53
|
-
Name the missing skill and skilldrop as its source, do the minimal inline version, and record
|
|
54
|
-
the stage as degraded. During a live incident, prefer the degraded inline version over
|
|
55
|
-
stopping to install anything.
|
|
56
|
-
|
|
57
|
-
## Run log (opt-in)
|
|
58
|
-
|
|
59
|
-
Only when the environment variable `SKILLDROP_LOOP_LOG` is set to a file path: after each
|
|
60
|
-
gate's verdict, append one JSON line to that file. Nothing else is recorded, and nothing is
|
|
61
|
-
sent anywhere. When the variable isn't set, or you can't read it, skip this section.
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"operate","stage":"learn","gate":"G4","verdict":"PROCEED","round":1}' >> "$SKILLDROP_LOOP_LOG"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
|
|
68
|
-
gate's exact word), and `round` (1 for the first attempt, counting returns under the cap).
|
|
69
|
-
`skilldrop loop-stats` summarises the file: which gates pass first time, which loop back, and
|
|
70
|
-
which end in `BLOCKED`.
|
|
71
|
-
|
|
72
|
-
## Quality bar
|
|
73
|
-
|
|
74
|
-
- **Every alert has a runbook entry.** An alert that pages someone with no procedure is an interruption, not a signal.
|
|
75
|
-
- **The postmortem ends in runbook deltas.** Analysis with no procedural change is a story about an outage.
|
|
76
|
-
- **Comms match the incident's actual stage.** Detect, mitigate, and resolve have different audiences and different truths.
|
|
77
|
-
- **Blameless means blameless.** Naming a person is a defect in the postmortem; naming a missing guardrail is the point.
|
|
78
|
-
|
|
79
|
-
## Anti-patterns to avoid
|
|
80
|
-
|
|
81
|
-
- ❌ **Writing the runbook before the observability plan.** Procedures for alerts that do not exist, and none for the alerts that do.
|
|
82
|
-
- ❌ **Skipping `learn` because the incident was small.** Small incidents are where the cheap runbook deltas are.
|
|
83
|
-
- ❌ **Guessing impact numbers to finish the postmortem.** State the gap; a postmortem's value is entirely in its accuracy.
|
|
84
|
-
- ❌ **Replaying `instrument` during a live incident.** Enter at `respond`; the loop is not a checklist to complete in order.
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"query": "write the postmortem for last night's incident",
|
|
4
|
-
"should_trigger": true
|
|
5
|
-
},
|
|
6
|
-
{
|
|
7
|
-
"query": "run the operate loop on this service",
|
|
8
|
-
"should_trigger": true
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"query": "what should we alert on and what's the runbook?",
|
|
12
|
-
"should_trigger": true
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"query": "we're in an incident — what do I tell customers?",
|
|
16
|
-
"should_trigger": true
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"query": "implement the fix for this bug",
|
|
20
|
-
"should_trigger": false
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"query": "design the retry architecture",
|
|
24
|
-
"should_trigger": false
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"query": "draft the PRD for this feature",
|
|
28
|
-
"should_trigger": false
|
|
29
|
-
}
|
|
30
|
-
]
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"loop_name": "operate",
|
|
3
|
-
"evals": [
|
|
4
|
-
{
|
|
5
|
-
"id": "postmortem-must-produce-runbook-deltas",
|
|
6
|
-
"prompt": "Run the operate loop. Our rate limiter alert fired last night — a customer's retry loop hammered the API for 40 minutes.",
|
|
7
|
-
"assertions": [
|
|
8
|
-
"Enters at `respond`, not at `instrument` — an incident does not replay earlier stages",
|
|
9
|
-
"Stages the communication (detect / mitigate / resolve) and matches the message to the stage the incident is in",
|
|
10
|
-
"Produces a blameless postmortem that names missing guardrails, never people",
|
|
11
|
-
"Ends in concrete runbook deltas phrased as before/after edits",
|
|
12
|
-
"Routes those deltas back to `document` via G4's revise edge rather than treating the postmortem as terminal"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": "refuses-to-guess-incident-facts",
|
|
17
|
-
"prompt": "Write the postmortem for last night's outage. I don't have the exact timeline or customer impact numbers yet.",
|
|
18
|
-
"assertions": [
|
|
19
|
-
"Emits BLOCKED naming the missing facts rather than producing a confident postmortem",
|
|
20
|
-
"Does not fabricate timings, customer counts, or a root cause",
|
|
21
|
-
"States exactly what to re-run with once the facts exist"
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$comment": "RFC-0028. The only loop whose failures are irreversible and live. Its feedback edge is real and already documented in the repo: postmortem-generator emits runbook deltas phrased as edits for runbook-generator.",
|
|
3
|
-
"name": "operate",
|
|
4
|
-
"version": "0.3.0",
|
|
5
|
-
"description": "Take a shipped service through detection, response, and learning — instrument it, write the runbook, communicate the incident, then feed the postmortem's deltas back into the runbook. Use when the user is preparing to run a service, is in an incident, or is writing a postmortem.",
|
|
6
|
-
"entrypoint": "LOOP.md",
|
|
7
|
-
"kind": "loop",
|
|
8
|
-
"cap": 3,
|
|
9
|
-
"stages": [
|
|
10
|
-
{
|
|
11
|
-
"id": "instrument",
|
|
12
|
-
"type": "generate",
|
|
13
|
-
"intent": "Decide what is measured and what pages a human. You cannot respond to what you never detected.",
|
|
14
|
-
"skills": ["observability-plan"]
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"id": "document",
|
|
18
|
-
"type": "generate",
|
|
19
|
-
"intent": "Write the procedure the on-call follows at 3am, per alert the previous stage defined.",
|
|
20
|
-
"skills": ["runbook-generator"]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": "respond",
|
|
24
|
-
"type": "generate",
|
|
25
|
-
"intent": "Communicate during the incident, to the audience that needs it, at the stage the incident is actually in.",
|
|
26
|
-
"skills": ["incident-comms"]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"id": "learn",
|
|
30
|
-
"type": "verify",
|
|
31
|
-
"intent": "Analyse blamelessly and emit runbook deltas. An incident that changes no procedure has not been learned from.",
|
|
32
|
-
"skills": ["postmortem-generator"],
|
|
33
|
-
"gate": {
|
|
34
|
-
"id": "G4",
|
|
35
|
-
"kind": "human",
|
|
36
|
-
"verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "REVISE", "BLOCKED"],
|
|
37
|
-
"revise_to": "document"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
}
|