@slowcook-ai/cli 0.19.0-alpha.18 → 0.19.0-alpha.41

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 (92) hide show
  1. package/dist/cli.js +28 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/brand/index.d.ts +58 -0
  4. package/dist/commands/brand/index.d.ts.map +1 -0
  5. package/dist/commands/brand/index.js +257 -0
  6. package/dist/commands/brand/index.js.map +1 -0
  7. package/dist/commands/budget/index.d.ts +2 -0
  8. package/dist/commands/budget/index.d.ts.map +1 -0
  9. package/dist/commands/budget/index.js +252 -0
  10. package/dist/commands/budget/index.js.map +1 -0
  11. package/dist/commands/dev-env/config.d.ts +136 -0
  12. package/dist/commands/dev-env/config.d.ts.map +1 -0
  13. package/dist/commands/dev-env/config.js +96 -0
  14. package/dist/commands/dev-env/config.js.map +1 -0
  15. package/dist/commands/dev-env/index.d.ts +27 -0
  16. package/dist/commands/dev-env/index.d.ts.map +1 -0
  17. package/dist/commands/dev-env/index.js +226 -0
  18. package/dist/commands/dev-env/index.js.map +1 -0
  19. package/dist/commands/dev-env/init.d.ts +28 -0
  20. package/dist/commands/dev-env/init.d.ts.map +1 -0
  21. package/dist/commands/dev-env/init.js +135 -0
  22. package/dist/commands/dev-env/init.js.map +1 -0
  23. package/dist/commands/eval/index.d.ts.map +1 -1
  24. package/dist/commands/eval/index.js +15 -1
  25. package/dist/commands/eval/index.js.map +1 -1
  26. package/dist/commands/init/mock-vite.d.ts +54 -0
  27. package/dist/commands/init/mock-vite.d.ts.map +1 -0
  28. package/dist/commands/init/mock-vite.js +611 -0
  29. package/dist/commands/init/mock-vite.js.map +1 -0
  30. package/dist/commands/init/mock.d.ts +6 -0
  31. package/dist/commands/init/mock.d.ts.map +1 -1
  32. package/dist/commands/init/mock.js +20 -4
  33. package/dist/commands/init/mock.js.map +1 -1
  34. package/dist/commands/plate/agent.d.ts +7 -0
  35. package/dist/commands/plate/agent.d.ts.map +1 -1
  36. package/dist/commands/plate/agent.js +1 -1
  37. package/dist/commands/plate/agent.js.map +1 -1
  38. package/dist/commands/plate/index.d.ts.map +1 -1
  39. package/dist/commands/plate/index.js +6 -0
  40. package/dist/commands/plate/index.js.map +1 -1
  41. package/dist/commands/refine/agent.d.ts +12 -0
  42. package/dist/commands/refine/agent.d.ts.map +1 -1
  43. package/dist/commands/refine/agent.js +173 -13
  44. package/dist/commands/refine/agent.js.map +1 -1
  45. package/dist/commands/refine/brownfield-answer.d.ts +81 -0
  46. package/dist/commands/refine/brownfield-answer.d.ts.map +1 -0
  47. package/dist/commands/refine/brownfield-answer.js +231 -0
  48. package/dist/commands/refine/brownfield-answer.js.map +1 -0
  49. package/dist/commands/refine/context.d.ts.map +1 -1
  50. package/dist/commands/refine/context.js +18 -0
  51. package/dist/commands/refine/context.js.map +1 -1
  52. package/dist/commands/refine/history-index.d.ts +29 -0
  53. package/dist/commands/refine/history-index.d.ts.map +1 -1
  54. package/dist/commands/refine/history-index.js +63 -0
  55. package/dist/commands/refine/history-index.js.map +1 -1
  56. package/dist/commands/refine/index.d.ts.map +1 -1
  57. package/dist/commands/refine/index.js +32 -1
  58. package/dist/commands/refine/index.js.map +1 -1
  59. package/dist/commands/refine/proposals-synth.d.ts +50 -1
  60. package/dist/commands/refine/proposals-synth.d.ts.map +1 -1
  61. package/dist/commands/refine/proposals-synth.js +199 -35
  62. package/dist/commands/refine/proposals-synth.js.map +1 -1
  63. package/dist/commands/refine/spec-yaml.d.ts +274 -250
  64. package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
  65. package/dist/commands/refine/spec-yaml.js +10 -0
  66. package/dist/commands/refine/spec-yaml.js.map +1 -1
  67. package/dist/commands/run-mock/index.d.ts.map +1 -1
  68. package/dist/commands/run-mock/index.js +7 -1
  69. package/dist/commands/run-mock/index.js.map +1 -1
  70. package/dist/commands/testgen/agent.d.ts.map +1 -1
  71. package/dist/commands/testgen/agent.js +34 -9
  72. package/dist/commands/testgen/agent.js.map +1 -1
  73. package/dist/commands/vibe/agent.d.ts +7 -0
  74. package/dist/commands/vibe/agent.d.ts.map +1 -1
  75. package/dist/commands/vibe/agent.js +2 -2
  76. package/dist/commands/vibe/agent.js.map +1 -1
  77. package/dist/commands/vibe/index.d.ts.map +1 -1
  78. package/dist/commands/vibe/index.js +7 -1
  79. package/dist/commands/vibe/index.js.map +1 -1
  80. package/dist/cost-store.d.ts +52 -0
  81. package/dist/cost-store.d.ts.map +1 -0
  82. package/dist/cost-store.js +108 -0
  83. package/dist/cost-store.js.map +1 -0
  84. package/dist/lib/budget.d.ts +123 -0
  85. package/dist/lib/budget.d.ts.map +1 -0
  86. package/dist/lib/budget.js +225 -0
  87. package/dist/lib/budget.js.map +1 -0
  88. package/dist/lib/mock-shape.d.ts +44 -0
  89. package/dist/lib/mock-shape.d.ts.map +1 -0
  90. package/dist/lib/mock-shape.js +77 -0
  91. package/dist/lib/mock-shape.js.map +1 -0
  92. package/package.json +5 -5
@@ -0,0 +1,52 @@
1
+ export interface CostEntry {
2
+ agent: string;
3
+ usd: number;
4
+ model?: string;
5
+ round?: string;
6
+ /** ISO-8601. Caller supplies — keeps the module testable. */
7
+ at: string;
8
+ /** Where the cost was incurred (PR comment URL, run URL, etc.) */
9
+ source_url?: string;
10
+ /** Optional token breakdown for forensic audits. */
11
+ tokens_in?: number;
12
+ tokens_out?: number;
13
+ cache_read?: number;
14
+ cache_create?: number;
15
+ }
16
+ /** Sidecar path for a story's cost log. */
17
+ export declare function costSidecarPath(repoRoot: string, storyId: string): string;
18
+ /**
19
+ * Append one cost entry to the sidecar. Creates the file (and any
20
+ * missing parent dirs) if it doesn't exist. Does NOT touch the spec
21
+ * yaml — call `applyCostToSpec` separately so the caller controls
22
+ * when the spec changes (one write at end-of-round vs N writes per
23
+ * LLM call).
24
+ */
25
+ export declare function appendCostEntry(repoRoot: string, storyId: string, entry: CostEntry): void;
26
+ /**
27
+ * Read + sum all entries in a story's sidecar. Tolerant of:
28
+ * - missing sidecar (returns zero/empty).
29
+ * - blank lines.
30
+ * - malformed lines (logged via the optional callback; total uses parseable ones).
31
+ */
32
+ export declare function readCostTotal(repoRoot: string, storyId: string, onMalformed?: (lineNo: number, raw: string) => void): {
33
+ totalUsd: number;
34
+ entries: CostEntry[];
35
+ };
36
+ /**
37
+ * Recompute spec.cost.total_usd + spec.cost.last_updated from the
38
+ * sidecar and write it back. Returns true if the spec was actually
39
+ * modified (caller stages + commits). Safe to call when the spec
40
+ * doesn't exist yet — returns false.
41
+ *
42
+ * Uses a low-fidelity YAML round-trip (parse + stringify) so it
43
+ * preserves keys but doesn't preserve comments or key ordering. That's
44
+ * acceptable for refine-emitted specs which don't carry hand-authored
45
+ * comments. If we later want comment-preserving updates, switch to
46
+ * yaml's Document API.
47
+ */
48
+ export declare function applyCostToSpec(repoRoot: string, storyId: string, nowIso?: string): {
49
+ changed: boolean;
50
+ totalUsd: number;
51
+ };
52
+ //# sourceMappingURL=cost-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-store.d.ts","sourceRoot":"","sources":["../src/cost-store.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAOD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,GACf,IAAI,CAKN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,GAClD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,EAAE,CAAA;CAAE,CAsB5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAiC,GACxC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAoBxC"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * 0.19.0-α.34 (sc#67) — canonical cost storage.
3
+ *
4
+ * Today each agent emits an HTML cost marker into its own PR comment.
5
+ * Aggregating a story's total cost means walking every comment across
6
+ * issue + spec PR + mockup PR + tests PR + brew PR. Fragile and slow.
7
+ *
8
+ * This module provides:
9
+ * - `appendCostEntry(repoRoot, storyId, entry)` — append-only sidecar
10
+ * `specs/story-<id>.cost.jsonl`, one JSON line per LLM call.
11
+ * - `readCostTotal(repoRoot, storyId)` — sum + entries, for the fuel-
12
+ * gauge command (sc#66) and any reflection tooling.
13
+ * - `applyCostToSpec(repoRoot, storyId)` — recomputes spec.cost.total_usd
14
+ * and spec.cost.last_updated from the sidecar; callers stage + commit.
15
+ *
16
+ * The HTML cost markers + visible cost footer stay for now — they're
17
+ * how the PR-reader sees cost at a glance. Sidecar is for aggregation.
18
+ * Migration of all agents to call appendCostEntry is incremental: refine
19
+ * is wired in α.34; vibe / brew / chef are follow-ups.
20
+ */
21
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+ import YAML from "yaml";
24
+ const SPECS_DIR = "specs";
25
+ /** Sidecar path for a story's cost log. */
26
+ export function costSidecarPath(repoRoot, storyId) {
27
+ return join(repoRoot, SPECS_DIR, `story-${storyId}.cost.jsonl`);
28
+ }
29
+ /** Spec yaml path (mirrors spec-yaml.ts; duplicated to avoid cycle). */
30
+ function specYamlPath(repoRoot, storyId) {
31
+ return join(repoRoot, SPECS_DIR, `story-${storyId}.yaml`);
32
+ }
33
+ /**
34
+ * Append one cost entry to the sidecar. Creates the file (and any
35
+ * missing parent dirs) if it doesn't exist. Does NOT touch the spec
36
+ * yaml — call `applyCostToSpec` separately so the caller controls
37
+ * when the spec changes (one write at end-of-round vs N writes per
38
+ * LLM call).
39
+ */
40
+ export function appendCostEntry(repoRoot, storyId, entry) {
41
+ const p = costSidecarPath(repoRoot, storyId);
42
+ mkdirSync(dirname(p), { recursive: true });
43
+ // Trailing newline → safe append on partial-write recovery.
44
+ appendFileSync(p, JSON.stringify(entry) + "\n", "utf8");
45
+ }
46
+ /**
47
+ * Read + sum all entries in a story's sidecar. Tolerant of:
48
+ * - missing sidecar (returns zero/empty).
49
+ * - blank lines.
50
+ * - malformed lines (logged via the optional callback; total uses parseable ones).
51
+ */
52
+ export function readCostTotal(repoRoot, storyId, onMalformed) {
53
+ const p = costSidecarPath(repoRoot, storyId);
54
+ if (!existsSync(p))
55
+ return { totalUsd: 0, entries: [] };
56
+ const lines = readFileSync(p, "utf8").split("\n");
57
+ const entries = [];
58
+ let total = 0;
59
+ lines.forEach((raw, i) => {
60
+ const line = raw.trim();
61
+ if (!line)
62
+ return;
63
+ try {
64
+ const parsed = JSON.parse(line);
65
+ if (typeof parsed.usd !== "number" || typeof parsed.agent !== "string") {
66
+ onMalformed?.(i + 1, raw);
67
+ return;
68
+ }
69
+ entries.push(parsed);
70
+ total += parsed.usd;
71
+ }
72
+ catch {
73
+ onMalformed?.(i + 1, raw);
74
+ }
75
+ });
76
+ return { totalUsd: total, entries };
77
+ }
78
+ /**
79
+ * Recompute spec.cost.total_usd + spec.cost.last_updated from the
80
+ * sidecar and write it back. Returns true if the spec was actually
81
+ * modified (caller stages + commits). Safe to call when the spec
82
+ * doesn't exist yet — returns false.
83
+ *
84
+ * Uses a low-fidelity YAML round-trip (parse + stringify) so it
85
+ * preserves keys but doesn't preserve comments or key ordering. That's
86
+ * acceptable for refine-emitted specs which don't carry hand-authored
87
+ * comments. If we later want comment-preserving updates, switch to
88
+ * yaml's Document API.
89
+ */
90
+ export function applyCostToSpec(repoRoot, storyId, nowIso = new Date().toISOString()) {
91
+ const specPath = specYamlPath(repoRoot, storyId);
92
+ if (!existsSync(specPath))
93
+ return { changed: false, totalUsd: 0 };
94
+ const { totalUsd } = readCostTotal(repoRoot, storyId);
95
+ const raw = readFileSync(specPath, "utf8");
96
+ const parsed = (YAML.parse(raw) ?? {});
97
+ const existing = parsed.cost ?? {};
98
+ // Round to 4 dp so we don't churn the spec on sub-tenth-of-a-cent diffs.
99
+ const rounded = Math.round(totalUsd * 10000) / 10000;
100
+ if (existing.total_usd === rounded &&
101
+ typeof existing.last_updated === "string") {
102
+ return { changed: false, totalUsd: rounded };
103
+ }
104
+ parsed.cost = { total_usd: rounded, last_updated: nowIso };
105
+ writeFileSync(specPath, YAML.stringify(parsed, { lineWidth: 0 }), "utf8");
106
+ return { changed: true, totalUsd: rounded };
107
+ }
108
+ //# sourceMappingURL=cost-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-store.js","sourceRoot":"","sources":["../src/cost-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ import { z } from "zod";
2
+ declare const BudgetConfigSchema: z.ZodEffects<z.ZodObject<{
3
+ schema_version: z.ZodLiteral<1>;
4
+ monthly_budget_usd: z.ZodOptional<z.ZodNumber>;
5
+ /** 1-31. The day of the calendar month the budget resets. */
6
+ monthly_start_day: z.ZodDefault<z.ZodNumber>;
7
+ /** Optional per-story ceiling (informational). */
8
+ story_budget_usd: z.ZodOptional<z.ZodNumber>;
9
+ /**
10
+ * 0.19.0-α.35 — anthropic credit deposit tracking. Unlike
11
+ * `monthly_budget_usd` (which resets each period), this models a
12
+ * non-recurring credit deposit (e.g. you topped up $25). The gauge
13
+ * subtracts ALL-TIME spend from this number, so once the sidecar
14
+ * shows $24 of usage you're down to $1 remaining.
15
+ *
16
+ * Set via: slowcook budget set --credit 25
17
+ *
18
+ * Re-set whenever you top up: slowcook budget set --credit 50
19
+ * (the all-time spend baseline is captured at the time of set, so
20
+ * re-setting resets the gauge to "full" for the new deposit).
21
+ */
22
+ credit_balance_usd: z.ZodOptional<z.ZodNumber>;
23
+ /**
24
+ * ISO timestamp captured when `credit_balance_usd` was set / last
25
+ * topped up. The gauge sums sidecar entries with `at >=` this value;
26
+ * spend before this is presumed already paid for by an earlier
27
+ * deposit. Written by `slowcook budget set --credit`; consumers
28
+ * shouldn't author this by hand.
29
+ */
30
+ credit_baseline_at: z.ZodOptional<z.ZodString>;
31
+ }, "strip", z.ZodTypeAny, {
32
+ schema_version: 1;
33
+ monthly_start_day: number;
34
+ monthly_budget_usd?: number | undefined;
35
+ story_budget_usd?: number | undefined;
36
+ credit_balance_usd?: number | undefined;
37
+ credit_baseline_at?: string | undefined;
38
+ }, {
39
+ schema_version: 1;
40
+ monthly_budget_usd?: number | undefined;
41
+ monthly_start_day?: number | undefined;
42
+ story_budget_usd?: number | undefined;
43
+ credit_balance_usd?: number | undefined;
44
+ credit_baseline_at?: string | undefined;
45
+ }>, {
46
+ schema_version: 1;
47
+ monthly_start_day: number;
48
+ monthly_budget_usd?: number | undefined;
49
+ story_budget_usd?: number | undefined;
50
+ credit_balance_usd?: number | undefined;
51
+ credit_baseline_at?: string | undefined;
52
+ }, {
53
+ schema_version: 1;
54
+ monthly_budget_usd?: number | undefined;
55
+ monthly_start_day?: number | undefined;
56
+ story_budget_usd?: number | undefined;
57
+ credit_balance_usd?: number | undefined;
58
+ credit_baseline_at?: string | undefined;
59
+ }>;
60
+ export type BudgetConfig = z.infer<typeof BudgetConfigSchema>;
61
+ /**
62
+ * Load `.brewing/budget.yaml`. Returns null when the file is absent —
63
+ * gauge is opt-in. Throws on malformed YAML / schema violation so a
64
+ * mis-authored config surfaces loudly instead of silently disabling.
65
+ */
66
+ export declare function loadBudgetConfig(repoRoot: string): BudgetConfig | null;
67
+ /** ISO-8601 date of the current monthly budget period start, given config. */
68
+ export declare function currentPeriodStart(config: Pick<BudgetConfig, "monthly_start_day">, now?: Date): Date;
69
+ /**
70
+ * Sum cost entries across ALL story sidecars in `specs/` whose entry
71
+ * `at` is >= `since`. Story IDs are derived from filenames
72
+ * (`story-<id>.cost.jsonl`). Used by both:
73
+ * - monthly_budget_usd gauge (since = period start)
74
+ * - credit_balance_usd gauge (since = credit_baseline_at)
75
+ */
76
+ export declare function aggregateSpendSince(repoRoot: string, since: Date): {
77
+ usd: number;
78
+ entryCount: number;
79
+ storyCount: number;
80
+ };
81
+ /**
82
+ * Back-compat wrapper for the period-based aggregation.
83
+ */
84
+ export declare function aggregateMonthSpend(repoRoot: string, config: BudgetConfig, now?: Date): {
85
+ usd: number;
86
+ entryCount: number;
87
+ storyCount: number;
88
+ };
89
+ export interface FuelGaugeArgs {
90
+ currentUsd: number;
91
+ budgetUsd: number;
92
+ /** GIF rendered when status === "warn" or "halt". Defaults to fuel-empty. */
93
+ gifUrl?: string;
94
+ }
95
+ export type BudgetStatus = "ok" | "warn" | "halt";
96
+ export declare function classifyBudgetStatus(currentUsd: number, budgetUsd: number): BudgetStatus;
97
+ /**
98
+ * Render the fuel-gauge markdown block. Empty string when budget <= 0
99
+ * (defensive — the config schema rejects this). Always returns a
100
+ * trailing newline so callers can concatenate without thinking.
101
+ */
102
+ export declare function formatFuelGauge(args: FuelGaugeArgs): string;
103
+ /**
104
+ * Render the credit-balance gauge (sc#66 0.19.0-α.35). Reports remaining
105
+ * USD on a one-shot Anthropic deposit (no auto-recharge). Spends counted
106
+ * since `baselineAt`; remaining = `deposit - spent`.
107
+ */
108
+ export declare function formatCreditGauge(args: {
109
+ depositUsd: number;
110
+ spentUsd: number;
111
+ baselineAt: string;
112
+ gifUrl?: string;
113
+ }): string;
114
+ /**
115
+ * Convenience: read config + aggregate spend + format gauge(s) in one
116
+ * call. Returns empty string when no budget.yaml exists (gauge disabled).
117
+ * Renders BOTH gauges when both fields are set in the config.
118
+ * Best-effort: a parse failure or aggregation error logs to console.warn
119
+ * and returns "" rather than blocking the agent's comment.
120
+ */
121
+ export declare function fuelGaugeFromRepo(repoRoot: string, now?: Date): string;
122
+ export {};
123
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/lib/budget.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,QAAA,MAAM,kBAAkB;;;IAGtB,6DAA6D;;IAE7D,kDAAkD;;IAElD;;;;;;;;;;;;OAYG;;IAEH;;;;;;OAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKJ,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAWtE;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,mBAAmB,CAAC,EAC/C,GAAG,GAAE,IAAiB,GACrB,IAAI,CAUN;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,IAAI,GACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAuBzD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,GAAG,GAAE,IAAiB,GACrB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAGzD;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAKlD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAMxF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAqB3D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAyBT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CA6BlF"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * 0.19.0-α.34 (sc#66) — project-level fuel gauge.
3
+ *
4
+ * Consumer authors `.brewing/budget.yaml`:
5
+ *
6
+ * schema_version: 1
7
+ * monthly_budget_usd: 50
8
+ * monthly_start_day: 1
9
+ *
10
+ * Aggregates spend by reading `specs/story-*.cost.jsonl` sidecars (sc#67)
11
+ * — much cheaper than walking GH comments. Returns a markdown gauge line
12
+ * that agents append to their existing cost footer:
13
+ *
14
+ * ⛽ **Project this month:** $42.18 of $50.00 (84%)
15
+ * ⚠️ approaching budget — top up at https://console.anthropic.com/...
16
+ *
17
+ * Future work (NOT in MVP): halt-before-LLM-call at 95%, override label,
18
+ * weekly slices, per-scope budgets. Today the gauge is informational.
19
+ */
20
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import YAML from "yaml";
23
+ import { z } from "zod";
24
+ import { readCostTotal } from "../cost-store.js";
25
+ const BudgetConfigSchema = z.object({
26
+ schema_version: z.literal(1),
27
+ monthly_budget_usd: z.number().positive().optional(),
28
+ /** 1-31. The day of the calendar month the budget resets. */
29
+ monthly_start_day: z.number().int().min(1).max(31).default(1),
30
+ /** Optional per-story ceiling (informational). */
31
+ story_budget_usd: z.number().positive().optional(),
32
+ /**
33
+ * 0.19.0-α.35 — anthropic credit deposit tracking. Unlike
34
+ * `monthly_budget_usd` (which resets each period), this models a
35
+ * non-recurring credit deposit (e.g. you topped up $25). The gauge
36
+ * subtracts ALL-TIME spend from this number, so once the sidecar
37
+ * shows $24 of usage you're down to $1 remaining.
38
+ *
39
+ * Set via: slowcook budget set --credit 25
40
+ *
41
+ * Re-set whenever you top up: slowcook budget set --credit 50
42
+ * (the all-time spend baseline is captured at the time of set, so
43
+ * re-setting resets the gauge to "full" for the new deposit).
44
+ */
45
+ credit_balance_usd: z.number().positive().optional(),
46
+ /**
47
+ * ISO timestamp captured when `credit_balance_usd` was set / last
48
+ * topped up. The gauge sums sidecar entries with `at >=` this value;
49
+ * spend before this is presumed already paid for by an earlier
50
+ * deposit. Written by `slowcook budget set --credit`; consumers
51
+ * shouldn't author this by hand.
52
+ */
53
+ credit_baseline_at: z.string().optional(),
54
+ }).refine((data) => data.monthly_budget_usd !== undefined || data.credit_balance_usd !== undefined, { message: "must set monthly_budget_usd or credit_balance_usd (or both)" });
55
+ /**
56
+ * Load `.brewing/budget.yaml`. Returns null when the file is absent —
57
+ * gauge is opt-in. Throws on malformed YAML / schema violation so a
58
+ * mis-authored config surfaces loudly instead of silently disabling.
59
+ */
60
+ export function loadBudgetConfig(repoRoot) {
61
+ const path = join(repoRoot, ".brewing", "budget.yaml");
62
+ if (!existsSync(path))
63
+ return null;
64
+ const raw = YAML.parse(readFileSync(path, "utf8"));
65
+ const parsed = BudgetConfigSchema.safeParse(raw);
66
+ if (!parsed.success) {
67
+ throw new Error(`Invalid .brewing/budget.yaml: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
68
+ }
69
+ return parsed.data;
70
+ }
71
+ /** ISO-8601 date of the current monthly budget period start, given config. */
72
+ export function currentPeriodStart(config, now = new Date()) {
73
+ const day = config.monthly_start_day;
74
+ const y = now.getUTCFullYear();
75
+ const m = now.getUTCMonth();
76
+ const candidate = new Date(Date.UTC(y, m, day));
77
+ // If today is before this month's reset day, the period started LAST month.
78
+ if (now.getTime() < candidate.getTime()) {
79
+ return new Date(Date.UTC(y, m - 1, day));
80
+ }
81
+ return candidate;
82
+ }
83
+ /**
84
+ * Sum cost entries across ALL story sidecars in `specs/` whose entry
85
+ * `at` is >= `since`. Story IDs are derived from filenames
86
+ * (`story-<id>.cost.jsonl`). Used by both:
87
+ * - monthly_budget_usd gauge (since = period start)
88
+ * - credit_balance_usd gauge (since = credit_baseline_at)
89
+ */
90
+ export function aggregateSpendSince(repoRoot, since) {
91
+ const specsDir = join(repoRoot, "specs");
92
+ if (!existsSync(specsDir))
93
+ return { usd: 0, entryCount: 0, storyCount: 0 };
94
+ let usd = 0;
95
+ let entryCount = 0;
96
+ let storyCount = 0;
97
+ for (const f of readdirSync(specsDir)) {
98
+ const m = /^story-(.+)\.cost\.jsonl$/.exec(f);
99
+ if (!m || !m[1])
100
+ continue;
101
+ const storyId = m[1];
102
+ const { entries } = readCostTotal(repoRoot, storyId);
103
+ let storyTouched = false;
104
+ for (const e of entries) {
105
+ const at = Date.parse(e.at);
106
+ if (Number.isFinite(at) && at >= since.getTime()) {
107
+ usd += e.usd;
108
+ entryCount++;
109
+ storyTouched = true;
110
+ }
111
+ }
112
+ if (storyTouched)
113
+ storyCount++;
114
+ }
115
+ return { usd, entryCount, storyCount };
116
+ }
117
+ /**
118
+ * Back-compat wrapper for the period-based aggregation.
119
+ */
120
+ export function aggregateMonthSpend(repoRoot, config, now = new Date()) {
121
+ const periodStart = currentPeriodStart(config, now);
122
+ return aggregateSpendSince(repoRoot, periodStart);
123
+ }
124
+ const FUEL_GIF = "https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMTVveDJ4bmNuY3dsa2hrNnVweTd6MWhpbXIxc3pvajhsN3Q5b21vdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/8CMmJ6F8hTxqX7Ts5k/giphy.gif";
125
+ export function classifyBudgetStatus(currentUsd, budgetUsd) {
126
+ if (budgetUsd <= 0)
127
+ return "ok";
128
+ const ratio = currentUsd / budgetUsd;
129
+ if (ratio >= 0.95)
130
+ return "halt";
131
+ if (ratio >= 0.8)
132
+ return "warn";
133
+ return "ok";
134
+ }
135
+ /**
136
+ * Render the fuel-gauge markdown block. Empty string when budget <= 0
137
+ * (defensive — the config schema rejects this). Always returns a
138
+ * trailing newline so callers can concatenate without thinking.
139
+ */
140
+ export function formatFuelGauge(args) {
141
+ const { currentUsd, budgetUsd } = args;
142
+ if (budgetUsd <= 0)
143
+ return "";
144
+ const pct = Math.round((currentUsd / budgetUsd) * 100);
145
+ const status = classifyBudgetStatus(currentUsd, budgetUsd);
146
+ const lines = [];
147
+ lines.push(`\n\n⛽ **Project this month:** $${currentUsd.toFixed(2)} of $${budgetUsd.toFixed(2)} (${pct}%)`);
148
+ if (status === "warn") {
149
+ lines.push(`⚠️ approaching monthly budget — consider topping up at https://console.anthropic.com/settings/billing`);
150
+ lines.push(`![fuel low](${args.gifUrl ?? FUEL_GIF})`);
151
+ }
152
+ else if (status === "halt") {
153
+ lines.push(`🛑 monthly budget exceeded — add the \`override-budget\` label to allow agents to continue, or top up at https://console.anthropic.com/settings/billing`);
154
+ lines.push(`![fuel empty](${args.gifUrl ?? FUEL_GIF})`);
155
+ }
156
+ return lines.join("\n") + "\n";
157
+ }
158
+ /**
159
+ * Render the credit-balance gauge (sc#66 0.19.0-α.35). Reports remaining
160
+ * USD on a one-shot Anthropic deposit (no auto-recharge). Spends counted
161
+ * since `baselineAt`; remaining = `deposit - spent`.
162
+ */
163
+ export function formatCreditGauge(args) {
164
+ const remaining = Math.max(0, args.depositUsd - args.spentUsd);
165
+ const ratio = args.depositUsd <= 0 ? 0 : args.spentUsd / args.depositUsd;
166
+ const pctSpent = Math.round(ratio * 100);
167
+ const pctLeft = Math.max(0, 100 - pctSpent);
168
+ let status = "ok";
169
+ if (ratio >= 0.95)
170
+ status = "halt";
171
+ else if (ratio >= 0.8)
172
+ status = "warn";
173
+ const lines = [];
174
+ lines.push(`\n\n🪙 **Anthropic credit:** $${remaining.toFixed(2)} of $${args.depositUsd.toFixed(2)} remaining (${pctLeft}%) — spent $${args.spentUsd.toFixed(2)} since ${args.baselineAt.slice(0, 10)}`);
175
+ if (status === "warn") {
176
+ lines.push(`⚠️ approaching empty — top up at https://console.anthropic.com/settings/billing before pipeline runs halt with 402.`);
177
+ lines.push(`![fuel low](${args.gifUrl ?? FUEL_GIF})`);
178
+ }
179
+ else if (status === "halt") {
180
+ lines.push(`🛑 credit nearly exhausted — pipeline will halt on 402 imminently. Top up at https://console.anthropic.com/settings/billing.`);
181
+ lines.push(`![fuel empty](${args.gifUrl ?? FUEL_GIF})`);
182
+ }
183
+ return lines.join("\n") + "\n";
184
+ }
185
+ /**
186
+ * Convenience: read config + aggregate spend + format gauge(s) in one
187
+ * call. Returns empty string when no budget.yaml exists (gauge disabled).
188
+ * Renders BOTH gauges when both fields are set in the config.
189
+ * Best-effort: a parse failure or aggregation error logs to console.warn
190
+ * and returns "" rather than blocking the agent's comment.
191
+ */
192
+ export function fuelGaugeFromRepo(repoRoot, now = new Date()) {
193
+ let config;
194
+ try {
195
+ config = loadBudgetConfig(repoRoot);
196
+ }
197
+ catch (e) {
198
+ console.warn(`[fuel-gauge] config load failed: ${e.message}`);
199
+ return "";
200
+ }
201
+ if (!config)
202
+ return "";
203
+ let out = "";
204
+ try {
205
+ if (config.monthly_budget_usd !== undefined) {
206
+ const { usd } = aggregateMonthSpend(repoRoot, config, now);
207
+ out += formatFuelGauge({ currentUsd: usd, budgetUsd: config.monthly_budget_usd });
208
+ }
209
+ if (config.credit_balance_usd !== undefined && config.credit_baseline_at) {
210
+ const baseline = new Date(config.credit_baseline_at);
211
+ const { usd } = aggregateSpendSince(repoRoot, baseline);
212
+ out += formatCreditGauge({
213
+ depositUsd: config.credit_balance_usd,
214
+ spentUsd: usd,
215
+ baselineAt: config.credit_baseline_at,
216
+ });
217
+ }
218
+ }
219
+ catch (e) {
220
+ console.warn(`[fuel-gauge] aggregation failed: ${e.message}`);
221
+ return "";
222
+ }
223
+ return out;
224
+ }
225
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/lib/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACpD,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,kDAAkD;IAClD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAClD;;;;;;;;;;;;OAYG;IACH,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACpD;;;;;;OAMG;IACH,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC,MAAM,CACP,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,KAAK,SAAS,IAAI,IAAI,CAAC,kBAAkB,KAAK,SAAS,EACxF,EAAE,OAAO,EAAE,6DAA6D,EAAE,CAC3E,CAAC;AAIF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,iCAAiC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACxF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAChC,MAA+C,EAC/C,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC;IACrC,MAAM,CAAC,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;IAC/B,MAAM,CAAC,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAChD,4EAA4E;IAC5E,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,KAAW;IAEX,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC3E,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS;QAC1B,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBACjD,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC;gBACb,UAAU,EAAE,CAAC;gBACb,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;QACH,CAAC;QACD,IAAI,YAAY;YAAE,UAAU,EAAE,CAAC;IACjC,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,MAAoB,EACpB,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,mBAAmB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AACpD,CAAC;AAWD,MAAM,QAAQ,GACZ,+KAA+K,CAAC;AAElL,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,SAAiB;IACxE,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;IACrC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,KAAK,IAAI,GAAG;QAAE,OAAO,MAAM,CAAC;IAChC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACvC,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,kCAAkC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAChG,CAAC;IACF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACR,uGAAuG,CACxG,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CACR,yJAAyJ,CAC1J,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAKjC;IACC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC;IAC5C,IAAI,MAAM,GAAiB,IAAI,CAAC;IAChC,IAAI,KAAK,IAAI,IAAI;QAAE,MAAM,GAAG,MAAM,CAAC;SAC9B,IAAI,KAAK,IAAI,GAAG;QAAE,MAAM,GAAG,MAAM,CAAC;IAEvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,iCAAiC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,OAAO,eAAe,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAC7L,CAAC;IACF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACR,qHAAqH,CACtH,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CACR,8HAA8H,CAC/H,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,MAAY,IAAI,IAAI,EAAE;IACxE,IAAI,MAA2B,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,oCAAqC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;YAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;YAC3D,GAAG,IAAI,eAAe,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,MAAM,CAAC,kBAAkB,KAAK,SAAS,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACzE,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;YACrD,MAAM,EAAE,GAAG,EAAE,GAAG,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACxD,GAAG,IAAI,iBAAiB,CAAC;gBACvB,UAAU,EAAE,MAAM,CAAC,kBAAkB;gBACrC,QAAQ,EAAE,GAAG;gBACb,UAAU,EAAE,MAAM,CAAC,kBAAkB;aACtC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,oCAAqC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ declare const MockShapeConfigSchema: z.ZodObject<{
3
+ schema_version: z.ZodLiteral<1>;
4
+ shape: z.ZodEnum<["vite", "nextjs"]>;
5
+ mock_root: z.ZodDefault<z.ZodString>;
6
+ screens_root: z.ZodDefault<z.ZodString>;
7
+ design_system_dir: z.ZodDefault<z.ZodString>;
8
+ router_file: z.ZodOptional<z.ZodString>;
9
+ scenarios_dir: z.ZodDefault<z.ZodString>;
10
+ scenario_registry_file: z.ZodDefault<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ schema_version: 1;
13
+ shape: "vite" | "nextjs";
14
+ mock_root: string;
15
+ screens_root: string;
16
+ design_system_dir: string;
17
+ scenarios_dir: string;
18
+ scenario_registry_file: string;
19
+ router_file?: string | undefined;
20
+ }, {
21
+ schema_version: 1;
22
+ shape: "vite" | "nextjs";
23
+ mock_root?: string | undefined;
24
+ screens_root?: string | undefined;
25
+ design_system_dir?: string | undefined;
26
+ router_file?: string | undefined;
27
+ scenarios_dir?: string | undefined;
28
+ scenario_registry_file?: string | undefined;
29
+ }>;
30
+ export type MockShapeConfig = z.infer<typeof MockShapeConfigSchema>;
31
+ /**
32
+ * Load `.brewing/mock.yaml`. Returns a default config when the file is
33
+ * absent (preserves backwards-compat with pre-sc#82 consumers). Throws
34
+ * on parse error / schema violation so a mis-authored config surfaces
35
+ * loudly rather than silently disabling features.
36
+ */
37
+ export declare function loadMockShapeConfig(repoRoot: string): MockShapeConfig;
38
+ /**
39
+ * Returns true when the consumer is on the Vite mock shape. Most
40
+ * agents only need this binary; full config is for path lookups.
41
+ */
42
+ export declare function isViteMock(repoRoot: string): boolean;
43
+ export {};
44
+ //# sourceMappingURL=mock-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-shape.d.ts","sourceRoot":"","sources":["../../src/lib/mock-shape.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAuBpE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAkBrE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpD"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * 0.19.0+ (sc#82) — mock-shape detection.
3
+ *
4
+ * Reads `.brewing/mock.yaml` to tell downstream agents (vibe, plate,
5
+ * recon, port, run-mock) which mock shape the consumer is on. Defaults
6
+ * to `nextjs` when the file is absent — preserves backwards-compat with
7
+ * pre-sc#82 consumers whose mock predates the config.
8
+ *
9
+ * Single source of truth so every agent reads + writes consistent
10
+ * conventions: paths to screens, design-system dir, router file,
11
+ * scenario registry. Nothing else should hard-code these locations.
12
+ */
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import YAML from "yaml";
16
+ import { z } from "zod";
17
+ const MockShapeConfigSchema = z.object({
18
+ schema_version: z.literal(1),
19
+ shape: z.enum(["vite", "nextjs"]),
20
+ mock_root: z.string().default("mock"),
21
+ screens_root: z.string().default("mock/src/apps"),
22
+ design_system_dir: z.string().default("mock/src/design-system"),
23
+ router_file: z.string().optional(),
24
+ scenarios_dir: z.string().default("mock/scenarios"),
25
+ scenario_registry_file: z.string().default("mock/src/lib/scenario-registry.ts"),
26
+ });
27
+ const NEXTJS_DEFAULT = {
28
+ schema_version: 1,
29
+ shape: "nextjs",
30
+ mock_root: "mock",
31
+ screens_root: "mock/src/app",
32
+ design_system_dir: "mock/src/design-system",
33
+ scenarios_dir: "mock/scenarios",
34
+ scenario_registry_file: "mock/src/lib/scenario-registry.ts",
35
+ };
36
+ const VITE_DEFAULT = {
37
+ schema_version: 1,
38
+ shape: "vite",
39
+ mock_root: "mock",
40
+ screens_root: "mock/src/apps",
41
+ design_system_dir: "mock/src/design-system",
42
+ router_file: "mock/src/App.tsx",
43
+ scenarios_dir: "mock/scenarios",
44
+ scenario_registry_file: "mock/src/lib/scenario-registry.ts",
45
+ };
46
+ /**
47
+ * Load `.brewing/mock.yaml`. Returns a default config when the file is
48
+ * absent (preserves backwards-compat with pre-sc#82 consumers). Throws
49
+ * on parse error / schema violation so a mis-authored config surfaces
50
+ * loudly rather than silently disabling features.
51
+ */
52
+ export function loadMockShapeConfig(repoRoot) {
53
+ const p = join(repoRoot, ".brewing", "mock.yaml");
54
+ if (!existsSync(p)) {
55
+ // Heuristic fallback: if mock/src/app/ exists (Next.js convention),
56
+ // treat as nextjs. If mock/src/App.tsx exists (Vite convention),
57
+ // treat as vite. Otherwise nextjs for legacy safety.
58
+ const viteAppFile = join(repoRoot, "mock", "src", "App.tsx");
59
+ if (existsSync(viteAppFile))
60
+ return VITE_DEFAULT;
61
+ return NEXTJS_DEFAULT;
62
+ }
63
+ const raw = YAML.parse(readFileSync(p, "utf8"));
64
+ const parsed = MockShapeConfigSchema.safeParse(raw);
65
+ if (!parsed.success) {
66
+ throw new Error(`Invalid .brewing/mock.yaml: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
67
+ }
68
+ return parsed.data;
69
+ }
70
+ /**
71
+ * Returns true when the consumer is on the Vite mock shape. Most
72
+ * agents only need this binary; full config is for path lookups.
73
+ */
74
+ export function isViteMock(repoRoot) {
75
+ return loadMockShapeConfig(repoRoot).shape === "vite";
76
+ }
77
+ //# sourceMappingURL=mock-shape.js.map