@seanyao/roll 3.609.2 → 3.610.2

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 (103) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +5 -6
  3. package/dist/roll.mjs +16146 -16186
  4. package/package.json +3 -2
  5. package/skills/README.md +14 -1
  6. package/skills/docs/skill-authoring.md +66 -0
  7. package/skills/reports/skill-audit-summary.md +53 -0
  8. package/skills/roll-.changelog/SKILL.md +25 -443
  9. package/skills/roll-.changelog/references/full-contract.md +462 -0
  10. package/skills/roll-.clarify/SKILL.md +6 -4
  11. package/skills/roll-.dream/SKILL.md +26 -353
  12. package/skills/roll-.dream/references/full-contract.md +365 -0
  13. package/skills/roll-.echo/SKILL.md +6 -4
  14. package/skills/roll-.qa/SKILL.md +25 -236
  15. package/skills/roll-.qa/references/full-contract.md +256 -0
  16. package/skills/roll-.review/SKILL.md +6 -2
  17. package/skills/roll-brief/SKILL.md +6 -8
  18. package/skills/roll-build/SKILL.md +28 -864
  19. package/skills/roll-build/references/full-contract.md +883 -0
  20. package/skills/roll-debug/SKILL.md +26 -585
  21. package/skills/roll-debug/references/full-contract.md +607 -0
  22. package/skills/roll-design/SKILL.md +28 -903
  23. package/skills/roll-design/references/full-contract.md +923 -0
  24. package/skills/roll-doc/SKILL.md +25 -574
  25. package/skills/roll-doc/references/full-contract.md +594 -0
  26. package/skills/roll-doctor/SKILL.md +21 -2
  27. package/skills/roll-fix/SKILL.md +28 -621
  28. package/skills/roll-fix/references/full-contract.md +640 -0
  29. package/skills/roll-idea/SKILL.md +6 -2
  30. package/skills/roll-loop/SKILL.md +27 -543
  31. package/skills/roll-loop/references/full-contract.md +555 -0
  32. package/skills/roll-notes/SKILL.md +6 -2
  33. package/skills/roll-onboard/SKILL.md +6 -2
  34. package/skills/roll-peer/SKILL.md +27 -316
  35. package/skills/roll-peer/references/full-contract.md +329 -0
  36. package/skills/roll-propose/SKILL.md +6 -8
  37. package/skills/roll-review-pr/SKILL.md +6 -2
  38. package/skills/roll-sentinel/SKILL.md +26 -344
  39. package/skills/roll-sentinel/references/full-contract.md +363 -0
  40. package/skills/roll-spar/SKILL.md +27 -269
  41. package/skills/roll-spar/references/full-contract.md +288 -0
  42. package/skills/route-cases/skills.json +235 -0
  43. package/skills/scripts/audit-skills.mjs +272 -0
  44. package/skills/scripts/test-audit-skills.mjs +39 -0
  45. package/skills/tests/fixtures/skill-audit/block-skill/SKILL.md +12 -0
  46. package/skills/tests/fixtures/skill-audit/minimal-skill/SKILL.md +8 -0
  47. package/skills/tests/fixtures/skill-audit/quoted-skill/SKILL.md +10 -0
  48. package/skills/tests/fixtures/skill-audit/route-cases.json +21 -0
  49. package/skills/tests/fixtures/skill-audit/spoke-skill/SKILL.md +12 -0
  50. package/skills/tests/fixtures/skill-audit/spoke-skill/references/runbook.md +3 -0
  51. package/bin/roll +0 -15361
  52. package/lib/backfill-pi-usage.py +0 -243
  53. package/lib/changelog_audit.py +0 -149
  54. package/lib/changelog_generate.py +0 -470
  55. package/lib/consistency_check.py +0 -409
  56. package/lib/context_feed_budget.sh +0 -194
  57. package/lib/github_sync.py +0 -876
  58. package/lib/i18n/slides.sh +0 -3
  59. package/lib/i18n/slides_build.sh +0 -38
  60. package/lib/i18n/slides_delete.sh +0 -19
  61. package/lib/i18n/slides_list.sh +0 -14
  62. package/lib/i18n/slides_logs.sh +0 -12
  63. package/lib/i18n/slides_new.sh +0 -15
  64. package/lib/i18n/slides_preview.sh +0 -14
  65. package/lib/i18n/slides_templates.sh +0 -7
  66. package/lib/i18n.sh +0 -211
  67. package/lib/loop-exit-summary.py +0 -393
  68. package/lib/loop-fmt.py +0 -589
  69. package/lib/loop_pick_agent.py +0 -316
  70. package/lib/loop_result_eval.py +0 -469
  71. package/lib/loop_unstick.py +0 -180
  72. package/lib/model_prices.py +0 -194
  73. package/lib/prices_fetcher.py +0 -534
  74. package/lib/roll-backlog.py +0 -225
  75. package/lib/roll-brief.py +0 -286
  76. package/lib/roll-help.py +0 -158
  77. package/lib/roll-home.py +0 -556
  78. package/lib/roll-init.py +0 -156
  79. package/lib/roll-loop-status.py +0 -1691
  80. package/lib/roll-loop-story.py +0 -191
  81. package/lib/roll-peer.py +0 -252
  82. package/lib/roll-setup.py +0 -102
  83. package/lib/roll-status.py +0 -367
  84. package/lib/roll_git.py +0 -41
  85. package/lib/roll_render.py +0 -414
  86. package/lib/slides/components/README.md +0 -123
  87. package/lib/slides/components/cards-2.html +0 -9
  88. package/lib/slides/components/cards-3.html +0 -9
  89. package/lib/slides/components/cards-4.html +0 -9
  90. package/lib/slides/components/compare.html +0 -22
  91. package/lib/slides/components/highlight.html +0 -9
  92. package/lib/slides/components/pipeline.html +0 -12
  93. package/lib/slides/components/plain.html +0 -7
  94. package/lib/slides/components/quote.html +0 -4
  95. package/lib/slides/components/timeline.html +0 -9
  96. package/lib/slides/templates/introduction-v3.html +0 -571
  97. package/lib/slides/templates/pitch.html +0 -0
  98. package/lib/slides-render.py +0 -778
  99. package/lib/slides-validate.py +0 -357
  100. package/lib/test_quality_gate.py +0 -143
  101. package/skills/roll-deck/SKILL.md +0 -296
  102. /package/skills/roll-debug/{injectable-bb.js → assets/injectable-bb.js} +0 -0
  103. /package/skills/roll-design/{ENGINEERING_CHECKLIST.md → references/engineering-checklist.md} +0 -0
@@ -1,194 +0,0 @@
1
- """
2
- model_prices — list-price table for AI model API pricing.
3
-
4
- Pricing is per million tokens (MTok), in the vendor's native currency.
5
- These are the public list rates; discounts (Pro subscription, prepay
6
- credits, etc.) are intentionally not modeled — IDEA-025 is about
7
- cross-account / cross-project comparable cost.
8
-
9
- US-VIEW-013: prices are no longer hardcoded here. They live in versioned
10
- snapshot files under ``lib/prices/snapshot-YYYY-MM-DD.json`` and are loaded
11
- at module import time. ``roll prices refresh`` produces new snapshots; this
12
- module never writes — it only loads all snapshots and merges them.
13
-
14
- FIX-116: multi-vendor support — snapshots carry ``vendor`` and ``currency``
15
- fields. All snapshots are loaded and merged into a single PRICES map, with
16
- each model entry carrying its native ``currency``. Vendor-prefixed model
17
- names (``deepseek/deepseek-chat``) are resolved by stripping the vendor
18
- segment when no exact match exists.
19
-
20
- Unknown models fall back to the snapshot's ``default_model`` with a stderr
21
- warning so dashboards don't blank out.
22
- """
23
-
24
- import json
25
- import os
26
- import sys
27
- from typing import Any, Dict, List, Optional, Tuple
28
-
29
- _LIB_DIR = os.path.dirname(os.path.abspath(__file__))
30
- SNAPSHOT_DIR = os.path.join(_LIB_DIR, "prices")
31
-
32
-
33
- def list_snapshots(snapshot_dir: str = SNAPSHOT_DIR) -> List[str]:
34
- """Return absolute paths of all snapshot files, sorted oldest → newest by filename."""
35
- if not os.path.isdir(snapshot_dir):
36
- return []
37
- entries = [
38
- os.path.join(snapshot_dir, name)
39
- for name in os.listdir(snapshot_dir)
40
- if name.startswith("snapshot-") and name.endswith(".json")
41
- ]
42
- return sorted(entries)
43
-
44
-
45
- def load_snapshot(path: str) -> Dict[str, Any]:
46
- """Load a snapshot file and validate its shape."""
47
- with open(path, "r", encoding="utf-8") as f:
48
- data = json.load(f)
49
- for key in ("version", "effective_at", "source_url", "prices"):
50
- if key not in data:
51
- raise ValueError(f"snapshot {path!r} missing required key {key!r}")
52
- if not isinstance(data["prices"], dict) or not data["prices"]:
53
- raise ValueError(f"snapshot {path!r} has empty or invalid prices map")
54
- data.setdefault("default_model", next(iter(data["prices"])))
55
- data.setdefault("vendor", "anthropic")
56
- data.setdefault("currency", "USD")
57
- return data
58
-
59
-
60
- def load_all_snapshots(snapshot_dir: str = SNAPSHOT_DIR) -> List[Dict[str, Any]]:
61
- """Load all snapshots, sorted oldest → newest. Raises FileNotFoundError if none."""
62
- snaps = list_snapshots(snapshot_dir)
63
- if not snaps:
64
- raise FileNotFoundError(
65
- f"no price snapshots found in {snapshot_dir}; run `roll prices refresh`"
66
- )
67
- return [load_snapshot(p) for p in snaps]
68
-
69
-
70
- _SNAPSHOTS: List[Dict[str, Any]] = load_all_snapshots()
71
- _DEFAULT_SNAP: Dict[str, Any] = _SNAPSHOTS[-1]
72
-
73
- # Merge PRICES from all snapshots, injecting currency per model.
74
- # Later snapshots override earlier ones for the same model name.
75
- PRICES: Dict[str, Dict[str, float]] = {}
76
- _CURRENCY: Dict[str, str] = {}
77
- for _snap in _SNAPSHOTS:
78
- _snap_currency = _snap.get("currency", "USD")
79
- for _model, _rates in _snap["prices"].items():
80
- PRICES[_model] = dict(_rates)
81
- PRICES[_model]["currency"] = _snap_currency
82
- _CURRENCY[_model] = _snap_currency
83
-
84
- DEFAULT: str = _DEFAULT_SNAP["default_model"]
85
- VERSION: str = _DEFAULT_SNAP["version"]
86
- EFFECTIVE_AT: str = _DEFAULT_SNAP["effective_at"]
87
- SOURCE_URL: str = _DEFAULT_SNAP["source_url"]
88
-
89
- _warned: set = set()
90
-
91
-
92
- def snapshot_meta() -> Tuple[str, str, str]:
93
- """Return (version, effective_at, source_url) of the active snapshot."""
94
- return VERSION, EFFECTIVE_AT, SOURCE_URL
95
-
96
-
97
- def _resolve(model: Optional[str], prices: Optional[Dict[str, Dict[str, float]]] = None,
98
- default: Optional[str] = None) -> Dict[str, float]:
99
- table = prices if prices is not None else PRICES
100
- fallback = default if default is not None else DEFAULT
101
- if not model:
102
- return table[fallback]
103
- base = model.split("[")[0].rstrip("0123456789-")
104
-
105
- # Direct match: model starts with a known key
106
- candidates = [k for k in table if model.startswith(k) or base.startswith(k)]
107
- if candidates:
108
- return table[max(candidates, key=len)]
109
-
110
- # Vendor prefix: try stripping "vendor/" segment for proxy tools (pi, etc.)
111
- if "/" in model:
112
- inner = model.split("/", 1)[1]
113
- inner_base = inner.split("[")[0].rstrip("0123456789-")
114
- for k in table:
115
- if inner == k or inner_base == k or inner.startswith(k) or inner_base.startswith(k):
116
- return table[k]
117
-
118
- if model not in _warned:
119
- _warned.add(model)
120
- print(f"[model_prices] warn: unknown model {model!r}, falling back to {fallback}",
121
- file=sys.stderr)
122
- return table[fallback]
123
-
124
-
125
- def _resolve_name(model: Optional[str],
126
- prices: Optional[Dict[str, Dict[str, float]]] = None,
127
- default: Optional[str] = None) -> str:
128
- """Return the canonical model name (key in PRICES) for a given model string.
129
-
130
- Same resolution logic as _resolve, but returns the matched key name
131
- instead of the rate dict. Used by currency_for() to find the currency.
132
- """
133
- table = prices if prices is not None else PRICES
134
- fallback = default if default is not None else DEFAULT
135
- if not model:
136
- return fallback
137
- base = model.split("[")[0].rstrip("0123456789-")
138
-
139
- # Direct match: model starts with a known key
140
- candidates = [k for k in table if model.startswith(k) or base.startswith(k)]
141
- if candidates:
142
- return max(candidates, key=len)
143
-
144
- # Vendor prefix: try stripping "vendor/" segment
145
- if "/" in model:
146
- inner = model.split("/", 1)[1]
147
- inner_base = inner.split("[")[0].rstrip("0123456789-")
148
- for k in table:
149
- if inner == k or inner_base == k or inner.startswith(k) or inner_base.startswith(k):
150
- return k
151
-
152
- return fallback
153
-
154
-
155
- _NO_CURRENCY_MATCH = "\x00__no_currency_match__\x00"
156
-
157
-
158
- def currency_for(model: Optional[str]) -> str:
159
- """Return the native currency code (USD/CNY) for a model.
160
-
161
- Falls back to 'USD' when the model isn't in any snapshot. FIX-162: resolve
162
- with a sentinel default so a *genuinely unknown* model returns USD instead
163
- of inheriting the global DEFAULT model's currency (which is a CNY kimi
164
- entry — that would mislabel unrelated unknown models as CNY).
165
- """
166
- name = _resolve_name(model, default=_NO_CURRENCY_MATCH)
167
- if name == _NO_CURRENCY_MATCH:
168
- return "USD"
169
- return _CURRENCY.get(name, "USD")
170
-
171
-
172
- def compute_list_cost(model: Optional[str],
173
- *,
174
- input_tokens: int = 0,
175
- output_tokens: int = 0,
176
- cache_creation_tokens: int = 0,
177
- cache_read_tokens: int = 0,
178
- prices: Optional[Dict[str, Dict[str, float]]] = None,
179
- default: Optional[str] = None) -> float:
180
- """Return cost (in native currency) at list price for one cycle's token usage."""
181
- p = _resolve(model, prices=prices, default=default)
182
- total = (input_tokens * p["in"]
183
- + output_tokens * p["out"]
184
- + cache_creation_tokens * p["cache_create"]
185
- + cache_read_tokens * p["cache_read"]) / 1_000_000
186
- return round(total, 4)
187
-
188
-
189
- def total_tokens(*,
190
- input_tokens: int = 0,
191
- output_tokens: int = 0,
192
- cache_creation_tokens: int = 0,
193
- cache_read_tokens: int = 0) -> int:
194
- return int(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens)