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.
Files changed (200) hide show
  1. package/README.md +10 -10
  2. package/bin/skilldrop.js +15 -10
  3. package/catalogue.json +8 -4
  4. package/contracts/catalogue.schema.json +2 -1
  5. package/model-routing.json +70 -78
  6. package/package.json +1 -1
  7. package/packs/ai-engineering/skills/agent-adoption-stage/SKILL.md +0 -1
  8. package/packs/ai-engineering/skills/agent-adoption-stage/manifest.json +3 -4
  9. package/packs/ai-engineering/skills/ai-adoption-rollout/SKILL.md +0 -1
  10. package/packs/ai-engineering/skills/ai-adoption-rollout/manifest.json +3 -4
  11. package/packs/ai-engineering/skills/ai-readiness-assessment/SKILL.md +0 -1
  12. package/packs/ai-engineering/skills/ai-readiness-assessment/manifest.json +1 -2
  13. package/packs/ai-engineering/skills/ai-use-case-triage/manifest.json +3 -3
  14. package/packs/ai-engineering/skills/judge-audit/SKILL.md +29 -13
  15. package/packs/ai-engineering/skills/judge-audit/evals/evals.json +30 -0
  16. package/packs/ai-engineering/skills/judge-audit/evals/files/12/judge_runs.csv +241 -0
  17. package/packs/ai-engineering/skills/judge-audit/evals/files/12/rosa_labels.csv +33 -0
  18. package/packs/ai-engineering/skills/judge-audit/evals/files/13/moderation_eval.csv +601 -0
  19. package/packs/ai-engineering/skills/judge-audit/manifest.json +2 -3
  20. package/packs/ai-engineering/skills/llm-eval-harness/SKILL.md +1 -2
  21. package/packs/ai-engineering/skills/llm-eval-harness/manifest.json +3 -3
  22. package/packs/ai-engineering/skills/mcp-pairing/manifest.json +1 -1
  23. package/packs/ai-engineering/skills/mcp-pairing/reference.md +4 -3
  24. package/packs/ai-engineering/skills/subagent-design/SKILL.md +1 -0
  25. package/packs/ai-engineering/skills/subagent-design/examples/help-center-localization.md +146 -0
  26. package/packs/ai-engineering/skills/subagent-design/manifest.json +2 -2
  27. package/packs/api-builder/skills/prompt-caching-advisor/SKILL.md +4 -0
  28. package/packs/api-builder/skills/prompt-caching-advisor/examples/support-bot-block-order.md +120 -0
  29. package/packs/api-builder/skills/prompt-caching-advisor/manifest.json +2 -2
  30. package/packs/api-builder/skills/tool-use-schema-writer/manifest.json +2 -2
  31. package/packs/career/skills/application-form-answers/manifest.json +3 -3
  32. package/packs/career/skills/cover-letter/manifest.json +3 -3
  33. package/packs/career/skills/resume-tailor/manifest.json +3 -3
  34. package/packs/converters/skills/md-to-docx/manifest.json +3 -3
  35. package/packs/core/loops/ship-a-draft/evals/evals.json +28 -0
  36. package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-adr-from-thread/slack-thread.txt +47 -0
  37. package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-announcement-unsettled/ticket-PRD-412.md +44 -0
  38. package/packs/core/skills/output-hygiene/manifest.json +3 -3
  39. package/packs/data-analytics/loops/measure/evals/evals.json +30 -0
  40. package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/schema.md +43 -0
  41. package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/finance_active_customers.sql +7 -0
  42. package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/growth_active_customers.sql +9 -0
  43. package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/thread.md +30 -0
  44. package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/request.md +34 -0
  45. package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/schema.md +58 -0
  46. package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/active_subscribers.sql +17 -0
  47. package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/net_revenue_by_plan.sql +15 -0
  48. package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/weekly_churn_rate.sql +34 -0
  49. package/packs/data-analytics/skills/dashboard-spec/SKILL.md +0 -1
  50. package/packs/data-analytics/skills/dashboard-spec/manifest.json +1 -2
  51. package/packs/design/skills/slide-outliner/manifest.json +2 -2
  52. package/packs/dev-team/loops/build/evals/evals.json +28 -0
  53. package/packs/dev-team/loops/build/evals/files/build-promo-codes/.github/workflows/ci.yml.fixture +19 -0
  54. package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/__init__.py +4 -0
  55. package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/pricing.py +47 -0
  56. package/packs/dev-team/loops/build/evals/files/build-promo-codes/pyproject.toml.fixture +9 -0
  57. package/packs/dev-team/loops/build/evals/files/build-promo-codes/story.md +61 -0
  58. package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/__init__.py +0 -0
  59. package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/test_pricing.py +49 -0
  60. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/.github/workflows/ci.yml.fixture +19 -0
  61. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/bug-report.md +25 -0
  62. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pr-description.md +42 -0
  63. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pyproject.toml.fixture +9 -0
  64. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/__init__.py +4 -0
  65. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/bucket.py +49 -0
  66. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/__init__.py +0 -0
  67. package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/test_bucket.py +63 -0
  68. package/packs/dev-team/loops/modernize/evals/evals.json +30 -0
  69. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/.github/workflows/ci.yml.fixture +11 -0
  70. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/README.md +17 -0
  71. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/docs/adr/0004-late-fee-calculation.md +25 -0
  72. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/legacy/feecalc.py +74 -0
  73. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_2026-10.txt +10 -0
  74. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_edge.txt +11 -0
  75. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/tests/test_feecalc_smoke.py +21 -0
  76. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/.github/workflows/ci.yml.fixture +11 -0
  77. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/PR-412.md +24 -0
  78. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/README.md +17 -0
  79. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/docs/adr/0004-late-fee-calculation.md +25 -0
  80. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/__init__.py +1 -0
  81. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/cli.py +33 -0
  82. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/extract.py +33 -0
  83. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/rules.py +39 -0
  84. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/legacy/feecalc.py +74 -0
  85. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_2026-10.txt +10 -0
  86. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_edge.txt +11 -0
  87. package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/tests/test_feecalc_smoke.py +21 -0
  88. package/packs/dev-team/loops/release/LOOP.md +4 -4
  89. package/packs/dev-team/loops/release/evals/eval_queries.json +10 -0
  90. package/packs/dev-team/loops/release/evals/evals.json +34 -0
  91. package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/0061_switch_to_haldrin_tokens.sql +22 -0
  92. package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/eng-notes.md +40 -0
  93. package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/merged-prs.md +13 -0
  94. package/packs/dev-team/loops/release/evals/files/release-saved-searches/merged-prs.md +13 -0
  95. package/packs/dev-team/loops/release/evals/files/release-saved-searches/readiness-evidence.md +27 -0
  96. package/packs/dev-team/loops/release/evals/files/release-saved-searches/traffic-baseline.md +23 -0
  97. package/packs/dev-team/loops/release/loop.json +1 -1
  98. package/packs/dev-team/skills/bug-triage/SKILL.md +1 -0
  99. package/packs/dev-team/skills/bug-triage/examples/export-row-limit-escalation.md +126 -0
  100. package/packs/dev-team/skills/bug-triage/manifest.json +2 -2
  101. package/packs/dev-team/skills/feature-implement-loop/manifest.json +2 -2
  102. package/packs/dev-team/skills/pr-description-writer/manifest.json +3 -3
  103. package/packs/dev-team/skills/pre-merge-review/manifest.json +2 -2
  104. package/packs/dev-team/skills/release-notes/manifest.json +2 -2
  105. package/packs/dev-team/skills/test-plan-generator/manifest.json +2 -2
  106. package/packs/experience-design/skills/service-blueprint/manifest.json +3 -3
  107. package/packs/experience-design/skills/ux-writing/manifest.json +3 -3
  108. package/packs/grc/loops/comply/evals/evals.json +29 -0
  109. package/packs/grc/loops/comply/evals/files/comply-chat-vendor-launch/vendor-brief.md +72 -0
  110. package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/launch-note.md +31 -0
  111. package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/risk-register.csv +6 -0
  112. package/packs/infra-as-code/skills/terraform-module/manifest.json +3 -3
  113. package/packs/product-manager/skills/prd-draft/SKILL.md +1 -0
  114. package/packs/product-manager/skills/prd-draft/examples/approver-delay-not-slack.md +142 -0
  115. package/packs/product-manager/skills/prd-draft/manifest.json +2 -2
  116. package/packs/product-manager/skills/user-journey-map/manifest.json +2 -2
  117. package/packs/research/loops/research/evals/evals.json +28 -0
  118. package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t1-ticket-volume.md +15 -0
  119. package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t2-release-notes-4-2.md +11 -0
  120. package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t3-billing-migration.md +12 -0
  121. package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t4-agent-poll.md +11 -0
  122. package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t5-app-store-reviews.md +12 -0
  123. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s1-analytics-export.md +29 -0
  124. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s2-email-delivery-report.md +25 -0
  125. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s3-pricing-changelog.md +9 -0
  126. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s4-trial-interview-notes.md +21 -0
  127. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s5-competitor-note.md +9 -0
  128. package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s6-weekly-growth-recap.md +7 -0
  129. package/packs/research/skills/research-plan/manifest.json +3 -3
  130. package/packs/research/skills/source-synthesis/manifest.json +3 -3
  131. package/packs/skill-engineering/skills/skill-author/manifest.json +3 -3
  132. package/packs/solution-architect/loops/design/evals/evals.json +28 -0
  133. package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0001-postgres-system-of-record.md +23 -0
  134. package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0002-background-work-via-job-queue.md +23 -0
  135. package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0003-tenant-isolation-row-level.md +25 -0
  136. package/packs/solution-architect/loops/design/evals/files/design-bulk-export/prd-bulk-export.md +50 -0
  137. package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0002-background-work-via-job-queue.md +23 -0
  138. package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0004-new-stateful-infra-needs-platform-signoff.md +22 -0
  139. package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/prd-partner-webhooks.md +36 -0
  140. package/packs/solution-architect/skills/api-contract-draft/manifest.json +3 -3
  141. package/packs/solution-architect/skills/architecture-diagrams/manifest.json +3 -3
  142. package/packs/solution-architect/skills/design-doc/manifest.json +2 -2
  143. package/packs/solution-architect/skills/figma-diagrams/manifest.json +3 -3
  144. package/packs/sre-oncall/skills/cloud-cost-review/SKILL.md +0 -1
  145. package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -2
  146. package/packs/sre-oncall/skills/delivery-metrics-report/SKILL.md +0 -1
  147. package/packs/sre-oncall/skills/delivery-metrics-report/manifest.json +1 -2
  148. package/packs/sre-oncall/skills/incident-comms/manifest.json +2 -2
  149. package/packs/sre-oncall/skills/postmortem-generator/manifest.json +2 -2
  150. package/packs/stakeholder-comms/skills/decision-log/manifest.json +2 -2
  151. package/packs/trackers/skills/team-status-report/manifest.json +3 -3
  152. package/profiles.json +1 -1
  153. package/packs/ai-engineering/loops/adopt/LOOP.md +0 -120
  154. package/packs/ai-engineering/loops/adopt/evals/eval_queries.json +0 -10
  155. package/packs/ai-engineering/loops/adopt/evals/evals.json +0 -69
  156. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/ai-ideas.md +0 -15
  157. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/org-context.md +0 -27
  158. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/staff-survey.csv +0 -11
  159. package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/ai-ideas.md +0 -13
  160. package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/firm-context.md +0 -23
  161. package/packs/ai-engineering/loops/adopt/loop.json +0 -53
  162. package/packs/ai-engineering/skills/ai-usage-report/SKILL.md +0 -111
  163. package/packs/ai-engineering/skills/ai-usage-report/evals/eval_queries.json +0 -30
  164. package/packs/ai-engineering/skills/ai-usage-report/evals/evals.json +0 -46
  165. package/packs/ai-engineering/skills/ai-usage-report/evals/files/1/northwind-ai-events.csv +0 -4213
  166. package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/draftwell-events.csv +0 -249
  167. package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/export-note.md +0 -9
  168. package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/published-assets.csv +0 -44
  169. package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/team-roster.md +0 -10
  170. package/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +0 -133
  171. package/packs/ai-engineering/skills/ai-usage-report/manifest.json +0 -12
  172. package/packs/ai-engineering/skills/ai-usage-report/scripts/build_report.py +0 -453
  173. package/packs/ai-engineering/skills/ai-usage-report/templates/report-per-user.md +0 -42
  174. package/packs/ai-engineering/skills/ai-usage-report/templates/report-team-rollup.md +0 -49
  175. package/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +0 -76
  176. package/packs/career/loops/apply/LOOP.md +0 -97
  177. package/packs/career/loops/apply/evals/eval_queries.json +0 -9
  178. package/packs/career/loops/apply/evals/evals.json +0 -27
  179. package/packs/career/loops/apply/loop.json +0 -52
  180. package/packs/product-manager/loops/discover/LOOP.md +0 -82
  181. package/packs/product-manager/loops/discover/evals/eval_queries.json +0 -30
  182. package/packs/product-manager/loops/discover/evals/evals.json +0 -26
  183. package/packs/product-manager/loops/discover/loop.json +0 -41
  184. package/packs/product-manager/skills/lean-canvas/SKILL.md +0 -162
  185. package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +0 -42
  186. package/packs/product-manager/skills/lean-canvas/evals/evals.json +0 -82
  187. package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +0 -54
  188. package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +0 -30
  189. package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +0 -44
  190. package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +0 -5
  191. package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +0 -40
  192. package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +0 -18
  193. package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +0 -72
  194. package/packs/product-manager/skills/lean-canvas/manifest.json +0 -34
  195. package/packs/product-manager/skills/lean-canvas/reference.md +0 -102
  196. package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +0 -211
  197. package/packs/sre-oncall/loops/operate/LOOP.md +0 -84
  198. package/packs/sre-oncall/loops/operate/evals/eval_queries.json +0 -30
  199. package/packs/sre-oncall/loops/operate/evals/evals.json +0 -25
  200. 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
- }