@slowcook-ai/cli 0.19.5 → 0.20.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 (99) hide show
  1. package/README.md +192 -28
  2. package/dist/cli.js +65 -73
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/brew/fidelity-loop.d.ts +71 -0
  5. package/dist/commands/brew/fidelity-loop.d.ts.map +1 -0
  6. package/dist/commands/brew/fidelity-loop.js +108 -0
  7. package/dist/commands/brew/fidelity-loop.js.map +1 -0
  8. package/dist/commands/brew/fidelity-phase.d.ts +49 -0
  9. package/dist/commands/brew/fidelity-phase.d.ts.map +1 -0
  10. package/dist/commands/brew/fidelity-phase.js +75 -0
  11. package/dist/commands/brew/fidelity-phase.js.map +1 -0
  12. package/dist/commands/eye/index.d.ts +2 -0
  13. package/dist/commands/eye/index.d.ts.map +1 -0
  14. package/dist/commands/eye/index.js +64 -0
  15. package/dist/commands/eye/index.js.map +1 -0
  16. package/dist/commands/eye/plan.d.ts +61 -0
  17. package/dist/commands/eye/plan.d.ts.map +1 -0
  18. package/dist/commands/eye/plan.js +81 -0
  19. package/dist/commands/eye/plan.js.map +1 -0
  20. package/dist/commands/eye/run.d.ts +19 -0
  21. package/dist/commands/eye/run.d.ts.map +1 -0
  22. package/dist/commands/eye/run.js +47 -0
  23. package/dist/commands/eye/run.js.map +1 -0
  24. package/dist/commands/eye/spec-modes.d.ts +5 -0
  25. package/dist/commands/eye/spec-modes.d.ts.map +1 -0
  26. package/dist/commands/eye/spec-modes.js +36 -0
  27. package/dist/commands/eye/spec-modes.js.map +1 -0
  28. package/dist/commands/gate/github.d.ts +27 -0
  29. package/dist/commands/gate/github.d.ts.map +1 -0
  30. package/dist/commands/gate/github.js +46 -0
  31. package/dist/commands/gate/github.js.map +1 -0
  32. package/dist/commands/gate/index.d.ts +2 -0
  33. package/dist/commands/gate/index.d.ts.map +1 -0
  34. package/dist/commands/gate/index.js +68 -0
  35. package/dist/commands/gate/index.js.map +1 -0
  36. package/dist/commands/gate/model.d.ts +55 -0
  37. package/dist/commands/gate/model.d.ts.map +1 -0
  38. package/dist/commands/gate/model.js +64 -0
  39. package/dist/commands/gate/model.js.map +1 -0
  40. package/dist/commands/gate/reviewers.d.ts +24 -0
  41. package/dist/commands/gate/reviewers.d.ts.map +1 -0
  42. package/dist/commands/gate/reviewers.js +69 -0
  43. package/dist/commands/gate/reviewers.js.map +1 -0
  44. package/dist/commands/recon/shape-preserve.d.ts +38 -0
  45. package/dist/commands/recon/shape-preserve.d.ts.map +1 -1
  46. package/dist/commands/recon/shape-preserve.js +112 -1
  47. package/dist/commands/recon/shape-preserve.js.map +1 -1
  48. package/dist/commands/refine/spec-yaml.d.ts +3 -0
  49. package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
  50. package/dist/commands/refine/spec-yaml.js +4 -0
  51. package/dist/commands/refine/spec-yaml.js.map +1 -1
  52. package/dist/commands/serve/config.d.ts +159 -0
  53. package/dist/commands/serve/config.d.ts.map +1 -0
  54. package/dist/commands/serve/config.js +216 -0
  55. package/dist/commands/serve/config.js.map +1 -0
  56. package/dist/commands/serve/detect.d.ts +31 -0
  57. package/dist/commands/serve/detect.d.ts.map +1 -0
  58. package/dist/commands/serve/detect.js +56 -0
  59. package/dist/commands/serve/detect.js.map +1 -0
  60. package/dist/commands/serve/dev.d.ts +46 -0
  61. package/dist/commands/serve/dev.d.ts.map +1 -0
  62. package/dist/commands/serve/dev.js +122 -0
  63. package/dist/commands/serve/dev.js.map +1 -0
  64. package/dist/commands/serve/index.d.ts +28 -0
  65. package/dist/commands/serve/index.d.ts.map +1 -0
  66. package/dist/commands/serve/index.js +224 -0
  67. package/dist/commands/serve/index.js.map +1 -0
  68. package/dist/commands/serve/mock.d.ts +24 -0
  69. package/dist/commands/serve/mock.d.ts.map +1 -0
  70. package/dist/commands/serve/mock.js +111 -0
  71. package/dist/commands/serve/mock.js.map +1 -0
  72. package/dist/commands/serve/runner.d.ts +52 -0
  73. package/dist/commands/serve/runner.d.ts.map +1 -0
  74. package/dist/commands/serve/runner.js +53 -0
  75. package/dist/commands/serve/runner.js.map +1 -0
  76. package/dist/commands/serve/staging.d.ts +28 -0
  77. package/dist/commands/serve/staging.d.ts.map +1 -0
  78. package/dist/commands/serve/staging.js +152 -0
  79. package/dist/commands/serve/staging.js.map +1 -0
  80. package/dist/commands/stories/index.d.ts +15 -0
  81. package/dist/commands/stories/index.d.ts.map +1 -0
  82. package/dist/commands/stories/index.js +280 -0
  83. package/dist/commands/stories/index.js.map +1 -0
  84. package/dist/commands/stories/status.d.ts +74 -0
  85. package/dist/commands/stories/status.d.ts.map +1 -0
  86. package/dist/commands/stories/status.js +176 -0
  87. package/dist/commands/stories/status.js.map +1 -0
  88. package/dist/commands/upsert-agent-docs.d.ts.map +1 -1
  89. package/dist/commands/upsert-agent-docs.js +12 -0
  90. package/dist/commands/upsert-agent-docs.js.map +1 -1
  91. package/dist/commands.manifest.d.ts +37 -0
  92. package/dist/commands.manifest.d.ts.map +1 -0
  93. package/dist/commands.manifest.js +301 -0
  94. package/dist/commands.manifest.js.map +1 -0
  95. package/dist/help.d.ts +43 -0
  96. package/dist/help.d.ts.map +1 -0
  97. package/dist/help.js +131 -0
  98. package/dist/help.js.map +1 -0
  99. package/package.json +8 -6
@@ -0,0 +1,108 @@
1
+ /**
2
+ * design #8 — eye-driven brew convergence loop.
3
+ *
4
+ * Bounded fidelity-correction controller: the "brain" that drives a front-end
5
+ * brew toward a mock by repeatedly measuring fidelity violations (the eye) and
6
+ * applying fixes (the brew agent), relying on a hot-reload stack to re-render
7
+ * between iterations so each pass is cheap.
8
+ *
9
+ * Generic + dependency-free. Parameterised over a violation type `V` so it
10
+ * never couples to the eye's concrete types (no import from
11
+ * @slowcook-ai/gates or any sibling package).
12
+ *
13
+ * MUST be bounded and detect non-progress. slowcook's brew agent has a
14
+ * documented "analysis-paralysis" failure mode (see
15
+ * `project_brew_agent_improvements`) where it loops without converging — so
16
+ * the controller escalates rather than spinning:
17
+ * - converged: zero violations measured.
18
+ * - stalled: violation COUNT failed to strictly decrease for `stallLimit`
19
+ * consecutive iterations (default 2). The stalling applyFix is
20
+ * NOT re-run — we bail before burning another fix.
21
+ * - exhausted: `maxIters` (default 5) measure() calls made while violations
22
+ * remain.
23
+ *
24
+ * Progress is measured by strictly-decreasing violation COUNT. `keyOf` is
25
+ * required in deps (and is the stable per-violation identity used by callers
26
+ * for logging/diffing `remaining`) but the convergence decision itself is
27
+ * count-based and documented as such for simplicity.
28
+ */
29
+ /**
30
+ * Drive a bounded measure → fix → re-measure loop until the front-end matches
31
+ * the mock (zero violations) or the controller escalates.
32
+ *
33
+ * Escalation semantics (return without calling applyFix again on bail):
34
+ * - First iteration always proceeds to applyFix if violations exist (there is
35
+ * no "previous" count to stall against).
36
+ * - After each measure: if `iterations >= maxIters` and violations remain →
37
+ * `exhausted`.
38
+ * - Stall: if this iteration's count did NOT strictly decrease vs the prior
39
+ * count (>= previous), increment the stall counter; otherwise reset it to
40
+ * 0. When the stall counter reaches `stallLimit` → `stalled` (applyFix is
41
+ * NOT invoked for this iteration).
42
+ * - `escalated === !converged`.
43
+ */
44
+ export async function runFidelityLoop(deps) {
45
+ const maxIters = deps.maxIters ?? 5;
46
+ const stallLimit = deps.stallLimit ?? 2;
47
+ const history = [];
48
+ let iterations = 0;
49
+ let stallCount = 0;
50
+ let prevCount = null;
51
+ let remaining = [];
52
+ // Loop guard: maxIters caps measure() calls, so this can never run forever.
53
+ for (;;) {
54
+ remaining = await deps.measure();
55
+ iterations += 1;
56
+ const count = remaining.length;
57
+ history.push(count);
58
+ const converged = count === 0;
59
+ deps.onIteration?.({ iteration: iterations, count, converged });
60
+ if (converged) {
61
+ return {
62
+ converged: true,
63
+ escalated: false,
64
+ iterations,
65
+ remaining,
66
+ history,
67
+ reason: 'converged',
68
+ };
69
+ }
70
+ // Out of budget: violations remain and we've used our measure() quota.
71
+ if (iterations >= maxIters) {
72
+ return {
73
+ converged: false,
74
+ escalated: true,
75
+ iterations,
76
+ remaining,
77
+ history,
78
+ reason: 'exhausted',
79
+ };
80
+ }
81
+ // Progress check (count-based). Skipped on the first iteration since there
82
+ // is no previous count to compare against.
83
+ if (prevCount !== null) {
84
+ if (count >= prevCount) {
85
+ stallCount += 1;
86
+ }
87
+ else {
88
+ stallCount = 0;
89
+ }
90
+ if (stallCount >= stallLimit) {
91
+ // Bail BEFORE applying another fix — the agent is spinning.
92
+ return {
93
+ converged: false,
94
+ escalated: true,
95
+ iterations,
96
+ remaining,
97
+ history,
98
+ reason: 'stalled',
99
+ };
100
+ }
101
+ }
102
+ prevCount = count;
103
+ // Hand the current violations to the brew agent; the caller's HMR stack
104
+ // re-renders before the next measure().
105
+ await deps.applyFix(remaining, iterations);
106
+ }
107
+ }
108
+ //# sourceMappingURL=fidelity-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-loop.js","sourceRoot":"","sources":["../../../src/commands/brew/fidelity-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA0BH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAyB;IAEzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;IAExC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,SAAS,GAAQ,EAAE,CAAC;IAExB,4EAA4E;IAC5E,SAAS,CAAC;QACR,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,UAAU,IAAI,CAAC,CAAC;QAChB,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEpB,MAAM,SAAS,GAAG,KAAK,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAEhE,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,KAAK;gBAChB,UAAU;gBACV,SAAS;gBACT,OAAO;gBACP,MAAM,EAAE,WAAW;aACpB,CAAC;QACJ,CAAC;QAED,uEAAuE;QACvE,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,SAAS,EAAE,IAAI;gBACf,UAAU;gBACV,SAAS;gBACT,OAAO;gBACP,MAAM,EAAE,WAAW;aACpB,CAAC;QACJ,CAAC;QAED,2EAA2E;QAC3E,2CAA2C;QAC3C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,UAAU,IAAI,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,CAAC,CAAC;YACjB,CAAC;YAED,IAAI,UAAU,IAAI,UAAU,EAAE,CAAC;gBAC7B,4DAA4D;gBAC5D,OAAO;oBACL,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,IAAI;oBACf,UAAU;oBACV,SAAS;oBACT,OAAO;oBACP,MAAM,EAAE,SAAS;iBAClB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,SAAS,GAAG,KAAK,CAAC;QAElB,wEAAwE;QACxE,wCAAwC;QACxC,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * design #8 — the post-green fidelity phase. After brew's behavioural/structural
3
+ * tests pass, render the brewed app vs the mock across the spec-declared modes
4
+ * and either (a) gate: drift → escalate to the #9 designer/QA gate, or (b) drive:
5
+ * feed the hard per-element violations back to the brew agent over a hot-reload
6
+ * candidate, bounded, until convergence or escalation.
7
+ *
8
+ * The eye measurement + the brew-fix application are INJECTED — the orchestration
9
+ * (matrix from spec, loop, escalation mapping) is pure-flow + unit-tested; the
10
+ * Playwright render (../eye/run.ts) and the live brew-agent fix supply the seams.
11
+ */
12
+ import type { FidelityViolation } from "@slowcook-ai/gates";
13
+ import { type EyeContext } from "../eye/plan.js";
14
+ /**
15
+ * Render violations into a concise, mock-faithful fix instruction for the brew
16
+ * agent. Grouped by viewport/scheme; scoped to presentation-only edits. Pure.
17
+ */
18
+ export declare function formatViolationsForBrew(violations: FidelityViolation[]): string;
19
+ export interface FidelityPhaseDeps {
20
+ /** Story id — used to resolve the spec's fidelity.modes. */
21
+ story?: string;
22
+ /** Repo root for the spec lookup. */
23
+ cwd: string;
24
+ /** Render + grade the matrix → violations (real: a runEyeMatrix wrapper). */
25
+ measure: (matrix: EyeContext[]) => Promise<FidelityViolation[]>;
26
+ /**
27
+ * Apply fixes via the brew agent against a hot-reload candidate, then the
28
+ * caller's HMR re-renders. OMIT for gate-only mode (drift → escalate, no fix).
29
+ */
30
+ applyFix?: (violations: FidelityViolation[], iteration: number) => Promise<void>;
31
+ maxIters?: number;
32
+ log?: (msg: string) => void;
33
+ }
34
+ export interface FidelityPhaseResult {
35
+ converged: boolean;
36
+ escalated: boolean;
37
+ iterations: number;
38
+ remaining: FidelityViolation[];
39
+ reason: string;
40
+ /** The #9 gate action: pass advances; blocked-on-designer halts for review. */
41
+ gateAction: "pass" | "blocked-on-designer";
42
+ matrixCells: number;
43
+ }
44
+ /**
45
+ * Run the fidelity phase. Matrix comes from the spec's fidelity.modes (the
46
+ * contract) or the full default. Gate-only when `applyFix` is omitted.
47
+ */
48
+ export declare function runFidelityPhase(deps: FidelityPhaseDeps): Promise<FidelityPhaseResult>;
49
+ //# sourceMappingURL=fidelity-phase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-phase.d.ts","sourceRoot":"","sources":["../../../src/commands/brew/fidelity-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG5D,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAElF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAuB/E;AAED,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAChE;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuC5F"}
@@ -0,0 +1,75 @@
1
+ import { runFidelityLoop } from "./fidelity-loop.js";
2
+ import { loadFidelityModes } from "../eye/spec-modes.js";
3
+ import { matrixFromModes, DEFAULT_MATRIX } from "../eye/plan.js";
4
+ /**
5
+ * Render violations into a concise, mock-faithful fix instruction for the brew
6
+ * agent. Grouped by viewport/scheme; scoped to presentation-only edits. Pure.
7
+ */
8
+ export function formatViolationsForBrew(violations) {
9
+ if (violations.length === 0)
10
+ return "No fidelity violations.";
11
+ const byCtx = new Map();
12
+ for (const v of violations) {
13
+ const k = `${v.context.viewport}/${v.context.scheme}`;
14
+ const arr = byCtx.get(k) ?? [];
15
+ arr.push(v);
16
+ byCtx.set(k, arr);
17
+ }
18
+ const lines = [
19
+ "Visual fidelity drift vs the approved mock. Edit ONLY the presentation of the",
20
+ "affected @slowcook-port-from components to restore the mock's rendering — do not",
21
+ "change behaviour, data-wiring, or markup structure, only the diverged styling:",
22
+ "",
23
+ ];
24
+ for (const [ctx, vs] of byCtx) {
25
+ lines.push(`## ${ctx}`);
26
+ for (const v of vs) {
27
+ lines.push(`- \`${v.selector}\`: ${v.property} should be ${v.expected} (currently ${v.actual}) [${v.axis}]`);
28
+ }
29
+ lines.push("");
30
+ }
31
+ return lines.join("\n").trimEnd() + "\n";
32
+ }
33
+ const keyOf = (v) => `${v.context.viewport}/${v.context.scheme}|${v.selector}|${v.property}`;
34
+ /**
35
+ * Run the fidelity phase. Matrix comes from the spec's fidelity.modes (the
36
+ * contract) or the full default. Gate-only when `applyFix` is omitted.
37
+ */
38
+ export async function runFidelityPhase(deps) {
39
+ const modes = deps.story ? loadFidelityModes(deps.cwd, deps.story) : null;
40
+ const matrix = modes && modes.length ? matrixFromModes(modes) : DEFAULT_MATRIX;
41
+ deps.log?.(`fidelity phase: ${matrix.length} cell(s)${deps.applyFix ? " (driving)" : " (gate-only)"}`);
42
+ const measure = () => deps.measure(matrix);
43
+ // Gate-only: one measurement; drift escalates to the designer/QA gate.
44
+ if (!deps.applyFix) {
45
+ const violations = await measure();
46
+ const converged = violations.length === 0;
47
+ return {
48
+ converged,
49
+ escalated: !converged,
50
+ iterations: 1,
51
+ remaining: violations,
52
+ reason: converged ? "converged" : "drift (gate-only — escalating)",
53
+ gateAction: converged ? "pass" : "blocked-on-designer",
54
+ matrixCells: matrix.length,
55
+ };
56
+ }
57
+ // Eye-driven correction loop (bounded; escalates on stall/exhaustion).
58
+ const loop = await runFidelityLoop({
59
+ measure,
60
+ applyFix: deps.applyFix,
61
+ keyOf,
62
+ maxIters: deps.maxIters,
63
+ onIteration: deps.log ? (i) => deps.log(` iter ${i.iteration}: ${i.count} violation(s)`) : undefined,
64
+ });
65
+ return {
66
+ converged: loop.converged,
67
+ escalated: loop.escalated,
68
+ iterations: loop.iterations,
69
+ remaining: loop.remaining,
70
+ reason: loop.reason,
71
+ gateAction: loop.converged ? "pass" : "blocked-on-designer",
72
+ matrixCells: matrix.length,
73
+ };
74
+ }
75
+ //# sourceMappingURL=fidelity-phase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-phase.js","sourceRoot":"","sources":["../../../src/commands/brew/fidelity-phase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function eye(args: string[], _version: string): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/eye/index.ts"],"names":[],"mappings":"AAmBA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CzE"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * design #8 — `slowcook eye`. Renders a reference (mock) URL and a candidate
3
+ * (brewed) URL across the (viewport × scheme) matrix in headless Chromium,
4
+ * grades candidate-vs-reference with the gates fidelity engine, writes
5
+ * screenshots + a JSON report, and exits 1 when the gate fails. This is the
6
+ * runnable eye behind the brew-loop + the #9 designer/QA gate; it consumes
7
+ * `references.visual` as the reference URL and the spec's fidelity.modes for
8
+ * the matrix.
9
+ *
10
+ * slowcook eye --reference http://localhost:33010 --candidate http://localhost:3001 \
11
+ * [--story 020] [--cwd .] [--out .brewing/eye] [--viewport mobile|desktop] \
12
+ * [--scheme light|dark] [--max-violations N] [--fail-on color,box,computed-style,missing]
13
+ */
14
+ import { writeFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { parseEyeArgs, matrixFromModes, narrowMatrix } from "./plan.js";
17
+ import { loadFidelityModes } from "./spec-modes.js";
18
+ import { runEyeMatrix } from "./run.js";
19
+ export async function eye(args, _version) {
20
+ if (args[0] === "--help" || args[0] === "-h") {
21
+ console.log("usage: slowcook eye --reference <url> --candidate <url> [--story <id>] [--cwd <dir>] [--out dir] [--viewport m] [--scheme s] [--max-violations N] [--fail-on a,b]");
22
+ return;
23
+ }
24
+ let opts;
25
+ try {
26
+ opts = parseEyeArgs(args);
27
+ }
28
+ catch (e) {
29
+ console.error(String(e instanceof Error ? e.message : e));
30
+ process.exit(64);
31
+ }
32
+ // Spec-declared modes (the contract) override the default matrix; explicit
33
+ // --viewport/--scheme flags still narrow on top.
34
+ if (opts.story) {
35
+ const modes = loadFidelityModes(opts.cwd, opts.story);
36
+ if (modes && modes.length) {
37
+ opts.matrix = narrowMatrix(matrixFromModes(modes), { viewport: opts.viewport, scheme: opts.scheme });
38
+ console.log(`eye: matrix from story-${opts.story} fidelity.modes [${modes.join(", ")}] → ${opts.matrix.length} cell(s)`);
39
+ }
40
+ else {
41
+ console.log(`eye: story-${opts.story} declares no fidelity.modes; using ${opts.matrix.length}-cell default matrix`);
42
+ }
43
+ }
44
+ const { result, screenshots } = await runEyeMatrix({
45
+ referenceUrl: opts.referenceUrl,
46
+ candidateUrl: opts.candidateUrl,
47
+ matrix: opts.matrix,
48
+ outDir: opts.outDir,
49
+ gate: opts.gate,
50
+ });
51
+ writeFileSync(join(opts.outDir, "eye-report.json"), JSON.stringify({ ...result, screenshots }, null, 2));
52
+ console.log(`\nslowcook eye — ${result.passed ? "PASS ✓" : "FAIL ✗"} (${result.violations.length} violation(s) across ${opts.matrix.length} render(s))`);
53
+ for (const ctx of result.byContext) {
54
+ if (ctx.violations.length === 0)
55
+ continue;
56
+ console.log(` [${ctx.context.viewport}/${ctx.context.scheme}] ${ctx.violations.length}: ${JSON.stringify(ctx.summary.byAxis)}`);
57
+ for (const v of ctx.violations.slice(0, 5))
58
+ console.log(` ${v.axis}: ${v.evidence}`);
59
+ }
60
+ console.log(` screenshots + report → ${opts.outDir}/`);
61
+ if (!result.passed)
62
+ process.exit(1);
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/eye/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * design #8 — `slowcook eye` planning (pure). Expands the CLI flags + the
3
+ * spec's declared fidelity modes into the (viewport × scheme) capture matrix +
4
+ * gate thresholds the runner executes. Kept pure + unit-tested; the Playwright
5
+ * execution + spec IO live in ./index.ts (matrix source) and ./spec-modes.ts.
6
+ *
7
+ * Which modes are in fidelity scope is a CONTRACT declared by refine in the
8
+ * spec (`fidelity.modes`), not chosen by brew. The eye reads + enforces it;
9
+ * brew is measured against it. CLI flags can only NARROW (never widen).
10
+ */
11
+ import type { FidelityGateOptions } from "@slowcook-ai/gates";
12
+ /** A capture context plus the pixel viewport to emulate. */
13
+ export interface EyeContext {
14
+ viewport: string;
15
+ scheme: "light" | "dark";
16
+ width: number;
17
+ height: number;
18
+ }
19
+ export interface EyeOptions {
20
+ referenceUrl: string;
21
+ candidateUrl: string;
22
+ outDir: string;
23
+ matrix: EyeContext[];
24
+ gate: FidelityGateOptions;
25
+ /** When set, the runner derives the matrix from this story's `fidelity.modes`. */
26
+ story?: string;
27
+ /** Repo root for spec lookup (default "."). */
28
+ cwd: string;
29
+ /** Raw narrowing flags, re-applied after a spec-derived matrix is built. */
30
+ viewport?: string;
31
+ scheme?: string;
32
+ }
33
+ export declare const VIEWPORTS: Record<string, {
34
+ width: number;
35
+ height: number;
36
+ }>;
37
+ /** Full default matrix: {mobile,desktop} × {light,dark}. */
38
+ export declare const DEFAULT_MATRIX: EyeContext[];
39
+ /**
40
+ * Build the capture matrix from a spec's declared `fidelity.modes` (pure).
41
+ * Tokens are dimension VALUES (`light`/`dark`/`mobile`/`desktop`), expanded to
42
+ * the product: a dimension with no declared value defaults to its full set
43
+ * (so `[dark]` → dark × {mobile,desktop}; `[mobile]` → {light,dark} × mobile).
44
+ * Unrecognised tokens are ignored; if NONE are recognised, the full default
45
+ * matrix is returned (fail-open — a typo never silently checks nothing).
46
+ */
47
+ export declare function matrixFromModes(modes: string[]): EyeContext[];
48
+ /** Narrow a matrix by explicit --viewport / --scheme flags (pure). Validates. */
49
+ export declare function narrowMatrix(base: EyeContext[], opts: {
50
+ viewport?: string;
51
+ scheme?: string;
52
+ }): EyeContext[];
53
+ /**
54
+ * Parse `slowcook eye` flags. Required: --reference <url>, --candidate <url>.
55
+ * Optional: --story <id> (derive the matrix from the spec's fidelity.modes),
56
+ * --cwd <dir>, --out <dir>, --viewport <name> + --scheme <light|dark> (narrow),
57
+ * --max-violations <n>, --fail-on <a,b>. The returned `matrix` is the flag-only
58
+ * default; when `story` is set the runner rebuilds it from the spec.
59
+ */
60
+ export declare function parseEyeArgs(args: string[]): EyeOptions;
61
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/eye/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAGvE,CAAC;AAIF,4DAA4D;AAC5D,eAAO,MAAM,cAAc,EAAE,UAAU,EAEtC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAQ7D;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,EAAE,CAa3G;AAOD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA4BvD"}
@@ -0,0 +1,81 @@
1
+ export const VIEWPORTS = {
2
+ mobile: { width: 390, height: 844 },
3
+ desktop: { width: 1280, height: 800 },
4
+ };
5
+ const SCHEMES = ["light", "dark"];
6
+ /** Full default matrix: {mobile,desktop} × {light,dark}. */
7
+ export const DEFAULT_MATRIX = Object.entries(VIEWPORTS).flatMap(([viewport, dim]) => SCHEMES.map((scheme) => ({ viewport, scheme, ...dim })));
8
+ /**
9
+ * Build the capture matrix from a spec's declared `fidelity.modes` (pure).
10
+ * Tokens are dimension VALUES (`light`/`dark`/`mobile`/`desktop`), expanded to
11
+ * the product: a dimension with no declared value defaults to its full set
12
+ * (so `[dark]` → dark × {mobile,desktop}; `[mobile]` → {light,dark} × mobile).
13
+ * Unrecognised tokens are ignored; if NONE are recognised, the full default
14
+ * matrix is returned (fail-open — a typo never silently checks nothing).
15
+ */
16
+ export function matrixFromModes(modes) {
17
+ const wanted = new Set(modes.map((m) => String(m).toLowerCase().trim()));
18
+ const viewports = Object.keys(VIEWPORTS).filter((v) => wanted.has(v));
19
+ const schemes = SCHEMES.filter((s) => wanted.has(s));
20
+ if (!viewports.length && !schemes.length)
21
+ return DEFAULT_MATRIX;
22
+ const vps = viewports.length ? viewports : Object.keys(VIEWPORTS);
23
+ const schs = schemes.length ? schemes : SCHEMES;
24
+ return vps.flatMap((viewport) => schs.map((scheme) => ({ viewport, scheme, ...VIEWPORTS[viewport] })));
25
+ }
26
+ /** Narrow a matrix by explicit --viewport / --scheme flags (pure). Validates. */
27
+ export function narrowMatrix(base, opts) {
28
+ let m = base;
29
+ if (opts.viewport) {
30
+ if (!VIEWPORTS[opts.viewport]) {
31
+ throw new Error(`eye: unknown --viewport '${opts.viewport}' (have: ${Object.keys(VIEWPORTS).join(", ")})`);
32
+ }
33
+ m = m.filter((c) => c.viewport === opts.viewport);
34
+ }
35
+ if (opts.scheme) {
36
+ if (opts.scheme !== "light" && opts.scheme !== "dark")
37
+ throw new Error("eye: --scheme must be light|dark");
38
+ m = m.filter((c) => c.scheme === opts.scheme);
39
+ }
40
+ return m;
41
+ }
42
+ function val(args, flag) {
43
+ const i = args.indexOf(flag);
44
+ return i >= 0 ? args[i + 1] : undefined;
45
+ }
46
+ /**
47
+ * Parse `slowcook eye` flags. Required: --reference <url>, --candidate <url>.
48
+ * Optional: --story <id> (derive the matrix from the spec's fidelity.modes),
49
+ * --cwd <dir>, --out <dir>, --viewport <name> + --scheme <light|dark> (narrow),
50
+ * --max-violations <n>, --fail-on <a,b>. The returned `matrix` is the flag-only
51
+ * default; when `story` is set the runner rebuilds it from the spec.
52
+ */
53
+ export function parseEyeArgs(args) {
54
+ const referenceUrl = val(args, "--reference");
55
+ const candidateUrl = val(args, "--candidate");
56
+ if (!referenceUrl || !candidateUrl) {
57
+ throw new Error("eye: --reference <url> and --candidate <url> are both required");
58
+ }
59
+ const viewport = val(args, "--viewport");
60
+ const scheme = val(args, "--scheme");
61
+ const matrix = narrowMatrix(DEFAULT_MATRIX, { viewport, scheme });
62
+ const maxV = val(args, "--max-violations");
63
+ const failOn = val(args, "--fail-on");
64
+ const gate = {};
65
+ if (maxV !== undefined)
66
+ gate.maxViolations = Number.parseInt(maxV, 10);
67
+ if (failOn)
68
+ gate.failOnAxes = failOn.split(",").map((s) => s.trim());
69
+ return {
70
+ referenceUrl,
71
+ candidateUrl,
72
+ outDir: val(args, "--out") ?? ".brewing/eye",
73
+ matrix,
74
+ gate,
75
+ story: val(args, "--story"),
76
+ cwd: val(args, "--cwd") ?? ".",
77
+ viewport,
78
+ scheme,
79
+ };
80
+ }
81
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/eye/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type FidelityGateOptions, type FidelityGateResult } from "@slowcook-ai/gates";
2
+ import type { EyeContext } from "./plan.js";
3
+ export interface RunEyeOptions {
4
+ referenceUrl: string;
5
+ candidateUrl: string;
6
+ matrix: EyeContext[];
7
+ /** Directory for screenshots. Files named `<label>-<viewport>-<scheme>.png`. */
8
+ outDir: string;
9
+ gate?: FidelityGateOptions;
10
+ /** Screenshot filename prefix (e.g. `eye-pr-123`); default none. */
11
+ shotPrefix?: string;
12
+ }
13
+ export interface RunEyeResult {
14
+ result: FidelityGateResult;
15
+ screenshots: string[];
16
+ }
17
+ /** Render + grade the full matrix. Launches one browser, a fresh context per cell. */
18
+ export declare function runEyeMatrix(opts: RunEyeOptions): Promise<RunEyeResult>;
19
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/eye/run.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAmC7E"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * design #8 — reusable eye runner. Renders a reference (mock) URL + a candidate
3
+ * (brewed) URL across a capture matrix in headless Chromium, screenshots each
4
+ * cell, and grades candidate-vs-reference with the gates fidelity engine.
5
+ * Shared by the `slowcook eye` command (./index.ts) and the eye-driven brew
6
+ * fidelity phase (../brew/fidelity-phase.ts) so both measure identically.
7
+ */
8
+ import { mkdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { chromium } from "@playwright/test";
11
+ import { captureSnapshot, gradeFidelity, } from "@slowcook-ai/gates";
12
+ /** Render + grade the full matrix. Launches one browser, a fresh context per cell. */
13
+ export async function runEyeMatrix(opts) {
14
+ mkdirSync(opts.outDir, { recursive: true });
15
+ const prefix = opts.shotPrefix ? `${opts.shotPrefix}-` : "";
16
+ const browser = await chromium.launch();
17
+ const screenshots = [];
18
+ const captureUrl = async (url, label, ctx) => {
19
+ const c = await browser.newContext({
20
+ colorScheme: ctx.scheme,
21
+ viewport: { width: ctx.width, height: ctx.height },
22
+ deviceScaleFactor: 2,
23
+ });
24
+ const page = await c.newPage();
25
+ await page.goto(url, { waitUntil: "networkidle" });
26
+ await page.waitForTimeout(400); // let lazy / client styles settle
27
+ const shot = join(opts.outDir, `${prefix}${label}-${ctx.viewport}-${ctx.scheme}.png`);
28
+ await page.screenshot({ path: shot, fullPage: true });
29
+ screenshots.push(shot);
30
+ const snap = await captureSnapshot(page, { viewport: ctx.viewport, scheme: ctx.scheme });
31
+ await c.close();
32
+ return snap;
33
+ };
34
+ const pairs = [];
35
+ try {
36
+ for (const ctx of opts.matrix) {
37
+ const reference = await captureUrl(opts.referenceUrl, "reference", ctx);
38
+ const candidate = await captureUrl(opts.candidateUrl, "candidate", ctx);
39
+ pairs.push({ reference, candidate });
40
+ }
41
+ }
42
+ finally {
43
+ await browser.close();
44
+ }
45
+ return { result: gradeFidelity(pairs, opts.gate), screenshots };
46
+ }
47
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/eye/run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EACL,eAAe,EACf,aAAa,GAId,MAAM,oBAAoB,CAAC;AAmB5B,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAmB;IACpD,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;IACxC,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,MAAM,UAAU,GAAG,KAAK,EAAE,GAAW,EAAE,KAAa,EAAE,GAAe,EAA0B,EAAE;QAC/F,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YACjC,WAAW,EAAE,GAAG,CAAC,MAAM;YACvB,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;YAClD,iBAAiB,EAAE,CAAC;SACrB,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;QACnD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,kCAAkC;QAClE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;QACtF,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACzF,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,KAAK,GAA6D,EAAE,CAAC;IAC3E,IAAI,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;YACxE,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;YACxE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Pure: extract `fidelity.modes` from a spec YAML string, or null if absent. */
2
+ export declare function extractFidelityModes(specYaml: string): string[] | null;
3
+ /** Load `fidelity.modes` for a story from `<repoRoot>/specs/story-<id>.yaml`. */
4
+ export declare function loadFidelityModes(repoRoot: string, story: string): string[] | null;
5
+ //# sourceMappingURL=spec-modes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-modes.d.ts","sourceRoot":"","sources":["../../../src/commands/eye/spec-modes.ts"],"names":[],"mappings":"AAeA,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAUtE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAIlF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * design #8 — read a story spec's declared fidelity modes. refine writes
3
+ * `fidelity.modes` on the spec (the contract for which viewport/scheme cells
4
+ * matter); the eye reads it here to build its matrix. Decoupled from the
5
+ * (not-yet-built) #7 `references` field — when that lands, `references.visual[].modes`
6
+ * becomes the per-source override and `fidelity.modes` the spec-level default.
7
+ *
8
+ * # specs/story-020.yaml
9
+ * fidelity:
10
+ * modes: [light, dark, mobile, desktop]
11
+ */
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import YAML from "yaml";
15
+ /** Pure: extract `fidelity.modes` from a spec YAML string, or null if absent. */
16
+ export function extractFidelityModes(specYaml) {
17
+ let doc;
18
+ try {
19
+ doc = YAML.parse(specYaml);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ const modes = doc?.fidelity?.modes;
25
+ if (Array.isArray(modes))
26
+ return modes.map((m) => String(m));
27
+ return null;
28
+ }
29
+ /** Load `fidelity.modes` for a story from `<repoRoot>/specs/story-<id>.yaml`. */
30
+ export function loadFidelityModes(repoRoot, story) {
31
+ const p = join(repoRoot, "specs", `story-${story}.yaml`);
32
+ if (!existsSync(p))
33
+ return null;
34
+ return extractFidelityModes(readFileSync(p, "utf8"));
35
+ }
36
+ //# sourceMappingURL=spec-modes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-modes.js","sourceRoot":"","sources":["../../../src/commands/eye/spec-modes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,iFAAiF;AACjF,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAI,GAAiD,EAAE,QAAQ,EAAE,KAAK,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,KAAa;IAC/D,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC;IACzD,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC"}