@pjmendonca/devflow 1.9.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/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Currency Converter - Multi-currency support for cost display.
|
|
4
|
+
|
|
5
|
+
Converts USD amounts to other currencies and formats them appropriately.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from lib.currency_converter import CurrencyConverter
|
|
9
|
+
|
|
10
|
+
converter = CurrencyConverter()
|
|
11
|
+
print(converter.format(1.77, "EUR")) # €1.63
|
|
12
|
+
print(converter.format_all(1.77)) # $1.77 | €1.63 | £1.40 | R$10.80
|
|
13
|
+
|
|
14
|
+
Exchange Rate Notice:
|
|
15
|
+
Default exchange rates are STATIC approximations and will become stale.
|
|
16
|
+
Last updated: December 2025
|
|
17
|
+
|
|
18
|
+
For accurate conversions, you should:
|
|
19
|
+
1. Use a config file with current rates:
|
|
20
|
+
converter = CurrencyConverter(config_path=Path("currency_config.json"))
|
|
21
|
+
|
|
22
|
+
2. Set custom rates directly:
|
|
23
|
+
converter.set_rates({"EUR": 0.95, "GBP": 0.82})
|
|
24
|
+
|
|
25
|
+
3. Use environment variables:
|
|
26
|
+
export CURRENCY_RATE_EUR=0.95
|
|
27
|
+
export CURRENCY_RATE_GBP=0.82
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import threading
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CurrencyConverter:
|
|
37
|
+
"""
|
|
38
|
+
Convert and format USD amounts to multiple currencies.
|
|
39
|
+
|
|
40
|
+
Supports customizable exchange rates via config file or direct setting.
|
|
41
|
+
|
|
42
|
+
Warning:
|
|
43
|
+
Default exchange rates are static approximations from December 2025.
|
|
44
|
+
For production use, provide current rates via config file or set_rates().
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Default exchange rates (USD to target currency)
|
|
48
|
+
# WARNING: These are approximate rates from December 2025
|
|
49
|
+
# Update via config file or set_rates() for accurate values
|
|
50
|
+
# Source: Approximate market rates - NOT suitable for financial calculations
|
|
51
|
+
DEFAULT_RATES = {
|
|
52
|
+
"USD": 1.0,
|
|
53
|
+
"EUR": 0.92,
|
|
54
|
+
"GBP": 0.79,
|
|
55
|
+
"BRL": 6.10,
|
|
56
|
+
"CAD": 1.36,
|
|
57
|
+
"AUD": 1.53,
|
|
58
|
+
"JPY": 149.50,
|
|
59
|
+
"CNY": 7.24,
|
|
60
|
+
"INR": 83.50,
|
|
61
|
+
"MXN": 17.20,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Currency symbols
|
|
65
|
+
SYMBOLS = {
|
|
66
|
+
"USD": "$",
|
|
67
|
+
"EUR": "€",
|
|
68
|
+
"GBP": "£",
|
|
69
|
+
"BRL": "R$",
|
|
70
|
+
"CAD": "C$",
|
|
71
|
+
"AUD": "A$",
|
|
72
|
+
"JPY": "¥",
|
|
73
|
+
"CNY": "¥",
|
|
74
|
+
"INR": "₹",
|
|
75
|
+
"MXN": "$",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Currency names
|
|
79
|
+
NAMES = {
|
|
80
|
+
"USD": "US Dollar",
|
|
81
|
+
"EUR": "Euro",
|
|
82
|
+
"GBP": "British Pound",
|
|
83
|
+
"BRL": "Brazilian Real",
|
|
84
|
+
"CAD": "Canadian Dollar",
|
|
85
|
+
"AUD": "Australian Dollar",
|
|
86
|
+
"JPY": "Japanese Yen",
|
|
87
|
+
"CNY": "Chinese Yuan",
|
|
88
|
+
"INR": "Indian Rupee",
|
|
89
|
+
"MXN": "Mexican Peso",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
rates: Optional[dict[str, float]] = None,
|
|
95
|
+
display_currencies: Optional[list[str]] = None,
|
|
96
|
+
config_path: Optional[Path] = None,
|
|
97
|
+
):
|
|
98
|
+
"""
|
|
99
|
+
Initialize converter.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
rates: Custom exchange rates (USD to target)
|
|
103
|
+
display_currencies: List of currencies to show in format_all()
|
|
104
|
+
config_path: Path to config file with rates
|
|
105
|
+
"""
|
|
106
|
+
self.rates = self.DEFAULT_RATES.copy()
|
|
107
|
+
|
|
108
|
+
# Load from config file if provided
|
|
109
|
+
if config_path and config_path.exists():
|
|
110
|
+
self._load_config(config_path)
|
|
111
|
+
|
|
112
|
+
# Override with provided rates
|
|
113
|
+
if rates:
|
|
114
|
+
self.rates.update(rates)
|
|
115
|
+
|
|
116
|
+
# Currencies to display by default
|
|
117
|
+
self.display_currencies = display_currencies or ["USD", "EUR", "GBP", "BRL"]
|
|
118
|
+
|
|
119
|
+
def _load_config(self, config_path: Path):
|
|
120
|
+
"""Load exchange rates from config file."""
|
|
121
|
+
try:
|
|
122
|
+
with open(config_path) as f:
|
|
123
|
+
config = json.load(f)
|
|
124
|
+
|
|
125
|
+
if "currency_rates" in config:
|
|
126
|
+
self.rates.update(config["currency_rates"])
|
|
127
|
+
|
|
128
|
+
if "display_currencies" in config:
|
|
129
|
+
self.display_currencies = config["display_currencies"]
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"Warning: Could not load currency config: {e}")
|
|
133
|
+
|
|
134
|
+
def convert(self, amount_usd: float, currency: str) -> float:
|
|
135
|
+
"""
|
|
136
|
+
Convert USD to target currency.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
amount_usd: Amount in USD
|
|
140
|
+
currency: Target currency code (e.g., "EUR")
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Amount in target currency
|
|
144
|
+
"""
|
|
145
|
+
currency = currency.upper()
|
|
146
|
+
rate = self.rates.get(currency, 1.0)
|
|
147
|
+
return amount_usd * rate
|
|
148
|
+
|
|
149
|
+
def format(
|
|
150
|
+
self, amount_usd: float, currency: str, include_symbol: bool = True, decimal_places: int = 2
|
|
151
|
+
) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Format amount in target currency.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
amount_usd: Amount in USD
|
|
157
|
+
currency: Target currency code
|
|
158
|
+
include_symbol: Include currency symbol
|
|
159
|
+
decimal_places: Number of decimal places
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Formatted string (e.g., "€1.63")
|
|
163
|
+
"""
|
|
164
|
+
currency = currency.upper()
|
|
165
|
+
converted = self.convert(amount_usd, currency)
|
|
166
|
+
|
|
167
|
+
# Special case for JPY (no decimals typically)
|
|
168
|
+
if currency == "JPY":
|
|
169
|
+
decimal_places = 0
|
|
170
|
+
|
|
171
|
+
formatted = f"{converted:,.{decimal_places}f}"
|
|
172
|
+
|
|
173
|
+
if include_symbol:
|
|
174
|
+
symbol = self.SYMBOLS.get(currency, currency)
|
|
175
|
+
return f"{symbol}{formatted}"
|
|
176
|
+
|
|
177
|
+
return formatted
|
|
178
|
+
|
|
179
|
+
def format_all(
|
|
180
|
+
self, amount_usd: float, separator: str = " | ", currencies: Optional[list[str]] = None
|
|
181
|
+
) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Format amount in all display currencies.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
amount_usd: Amount in USD
|
|
187
|
+
separator: Separator between currencies
|
|
188
|
+
currencies: Override list of currencies to display
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Formatted string (e.g., "$1.77 | €1.63 | £1.40")
|
|
192
|
+
"""
|
|
193
|
+
currencies = currencies or self.display_currencies
|
|
194
|
+
parts = [self.format(amount_usd, c) for c in currencies]
|
|
195
|
+
return separator.join(parts)
|
|
196
|
+
|
|
197
|
+
def format_compact(self, amount_usd: float) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Format amount in compact style for terminal display.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Compact format (e.g., "$1.77/€1.63/£1.40")
|
|
203
|
+
"""
|
|
204
|
+
return self.format_all(amount_usd, separator="/")
|
|
205
|
+
|
|
206
|
+
def format_table_row(self, amount_usd: float) -> dict[str, str]:
|
|
207
|
+
"""
|
|
208
|
+
Get formatted amounts as dictionary for table display.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Dictionary of currency code -> formatted amount
|
|
212
|
+
"""
|
|
213
|
+
return {currency: self.format(amount_usd, currency) for currency in self.display_currencies}
|
|
214
|
+
|
|
215
|
+
def set_rates(self, rates: dict[str, float]):
|
|
216
|
+
"""Update exchange rates."""
|
|
217
|
+
self.rates.update(rates)
|
|
218
|
+
|
|
219
|
+
def set_display_currencies(self, currencies: list[str]):
|
|
220
|
+
"""Set which currencies to display."""
|
|
221
|
+
self.display_currencies = [c.upper() for c in currencies]
|
|
222
|
+
|
|
223
|
+
def get_rate(self, currency: str) -> float:
|
|
224
|
+
"""Get exchange rate for a currency."""
|
|
225
|
+
return self.rates.get(currency.upper(), 1.0)
|
|
226
|
+
|
|
227
|
+
def list_currencies(self) -> list[dict]:
|
|
228
|
+
"""List all supported currencies with their rates."""
|
|
229
|
+
return [
|
|
230
|
+
{
|
|
231
|
+
"code": code,
|
|
232
|
+
"name": self.NAMES.get(code, code),
|
|
233
|
+
"symbol": self.SYMBOLS.get(code, code),
|
|
234
|
+
"rate": rate,
|
|
235
|
+
}
|
|
236
|
+
for code, rate in sorted(self.rates.items())
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
def save_config(self, config_path: Path):
|
|
240
|
+
"""Save current rates to config file."""
|
|
241
|
+
config = {
|
|
242
|
+
"currency_rates": self.rates,
|
|
243
|
+
"display_currencies": self.display_currencies,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
with open(config_path, "w") as f:
|
|
247
|
+
json.dump(config, f, indent=2)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Thread-safe global converter storage
|
|
251
|
+
_converter_lock = threading.Lock()
|
|
252
|
+
_converter: Optional[CurrencyConverter] = None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_converter() -> CurrencyConverter:
|
|
256
|
+
"""
|
|
257
|
+
Get or create the global converter instance (thread-safe).
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
A shared CurrencyConverter instance.
|
|
261
|
+
|
|
262
|
+
Note:
|
|
263
|
+
The converter is shared across threads since exchange rates
|
|
264
|
+
are read-only after initialization. If you need to modify
|
|
265
|
+
rates for a specific thread, create a new instance instead.
|
|
266
|
+
"""
|
|
267
|
+
global _converter
|
|
268
|
+
if _converter is None:
|
|
269
|
+
with _converter_lock:
|
|
270
|
+
# Double-check pattern for thread safety
|
|
271
|
+
if _converter is None:
|
|
272
|
+
_converter = CurrencyConverter()
|
|
273
|
+
return _converter
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def set_converter(converter: CurrencyConverter):
|
|
277
|
+
"""
|
|
278
|
+
Set the global converter instance (thread-safe).
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
converter: The CurrencyConverter instance to use globally.
|
|
282
|
+
"""
|
|
283
|
+
global _converter
|
|
284
|
+
with _converter_lock:
|
|
285
|
+
_converter = converter
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# Convenience functions
|
|
289
|
+
def convert(amount_usd: float, currency: str) -> float:
|
|
290
|
+
"""Convert USD to target currency using global converter."""
|
|
291
|
+
return get_converter().convert(amount_usd, currency)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def format_currency(amount_usd: float, currency: str = "USD") -> str:
|
|
295
|
+
"""Format amount using global converter."""
|
|
296
|
+
return get_converter().format(amount_usd, currency)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def format_all_currencies(amount_usd: float) -> str:
|
|
300
|
+
"""Format amount in all display currencies using global converter."""
|
|
301
|
+
return get_converter().format_all(amount_usd)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
# Demo/test
|
|
306
|
+
converter = CurrencyConverter()
|
|
307
|
+
|
|
308
|
+
amount = 15.77
|
|
309
|
+
|
|
310
|
+
print(f"Amount: ${amount:.2f} USD\n")
|
|
311
|
+
|
|
312
|
+
print("Individual currencies:")
|
|
313
|
+
for currency in ["USD", "EUR", "GBP", "BRL", "JPY"]:
|
|
314
|
+
print(f" {currency}: {converter.format(amount, currency)}")
|
|
315
|
+
|
|
316
|
+
print("\nAll display currencies:")
|
|
317
|
+
print(f" {converter.format_all(amount)}")
|
|
318
|
+
|
|
319
|
+
print("\nCompact format:")
|
|
320
|
+
print(f" {converter.format_compact(amount)}")
|
|
321
|
+
|
|
322
|
+
print("\nTable row:")
|
|
323
|
+
for code, formatted in converter.format_table_row(amount).items():
|
|
324
|
+
print(f" {code}: {formatted}")
|
|
325
|
+
|
|
326
|
+
print("\nSupported currencies:")
|
|
327
|
+
for curr in converter.list_currencies():
|
|
328
|
+
print(f" {curr['code']}: {curr['name']} ({curr['symbol']}) - Rate: {curr['rate']}")
|