@exaudeus/workrail 3.11.2 → 3.12.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 (36) hide show
  1. package/dist/console/assets/index-CRgjJiMS.js +28 -0
  2. package/dist/console/assets/index-DW78t31j.css +1 -0
  3. package/dist/console/index.html +2 -2
  4. package/dist/manifest.json +65 -57
  5. package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +10 -2
  6. package/dist/mcp/handlers/shared/request-workflow-reader.js +27 -10
  7. package/dist/mcp/handlers/shared/workflow-source-visibility.d.ts +3 -1
  8. package/dist/mcp/handlers/shared/workflow-source-visibility.js +7 -3
  9. package/dist/mcp/handlers/v2-execution/replay.js +25 -1
  10. package/dist/mcp/handlers/v2-execution/start.js +23 -17
  11. package/dist/mcp/handlers/v2-workflow.js +123 -8
  12. package/dist/mcp/output-schemas.d.ts +393 -0
  13. package/dist/mcp/output-schemas.js +49 -1
  14. package/dist/mcp/v2/tools.d.ts +3 -0
  15. package/dist/mcp/v2/tools.js +1 -0
  16. package/dist/v2/durable-core/constants.d.ts +1 -0
  17. package/dist/v2/durable-core/constants.js +2 -1
  18. package/dist/v2/durable-core/domain/observation-builder.d.ts +4 -1
  19. package/dist/v2/durable-core/domain/observation-builder.js +9 -0
  20. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +76 -16
  21. package/dist/v2/durable-core/schemas/session/events.d.ts +26 -5
  22. package/dist/v2/durable-core/schemas/session/events.js +2 -1
  23. package/dist/v2/infra/local/session-summary-provider/index.js +2 -0
  24. package/dist/v2/infra/local/workspace-anchor/index.js +1 -0
  25. package/dist/v2/ports/workspace-anchor.port.d.ts +3 -0
  26. package/dist/v2/projections/resume-ranking.d.ts +1 -0
  27. package/dist/v2/usecases/console-routes.js +26 -0
  28. package/dist/v2/usecases/console-service.js +25 -6
  29. package/dist/v2/usecases/console-types.d.ts +22 -1
  30. package/dist/v2/usecases/worktree-service.d.ts +10 -0
  31. package/dist/v2/usecases/worktree-service.js +136 -0
  32. package/package.json +1 -1
  33. package/workflows/architecture-scalability-audit.json +317 -0
  34. package/workflows/routines/tension-driven-design.json +5 -5
  35. package/dist/console/assets/index-C5C4nDs4.css +0 -1
  36. package/dist/console/assets/index-CSUqsoQl.js +0 -28
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRepoRoot = resolveRepoRoot;
4
+ exports.buildActiveSessionCounts = buildActiveSessionCounts;
5
+ exports.getWorktreeList = getWorktreeList;
6
+ const child_process_1 = require("child_process");
7
+ const util_1 = require("util");
8
+ const path_1 = require("path");
9
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
10
+ const GIT_TIMEOUT_MS = 5000;
11
+ function isExecError(e) {
12
+ return e instanceof Error && 'killed' in e;
13
+ }
14
+ async function git(cwd, args) {
15
+ try {
16
+ const { stdout } = await execFileAsync('git', [...args], {
17
+ cwd,
18
+ encoding: 'utf-8',
19
+ timeout: GIT_TIMEOUT_MS,
20
+ });
21
+ return stdout.trim();
22
+ }
23
+ catch (e) {
24
+ if (isExecError(e))
25
+ return null;
26
+ throw e;
27
+ }
28
+ }
29
+ function parseWorktreePorcelain(raw) {
30
+ const entries = [];
31
+ for (const block of raw.split(/\n\n+/)) {
32
+ const lines = block.trim().split('\n');
33
+ const pathLine = lines.find(l => l.startsWith('worktree '));
34
+ const headLine = lines.find(l => l.startsWith('HEAD '));
35
+ const branchLine = lines.find(l => l.startsWith('branch '));
36
+ if (!pathLine || !headLine)
37
+ continue;
38
+ const path = pathLine.slice('worktree '.length).trim();
39
+ const head = headLine.slice('HEAD '.length).trim();
40
+ const branch = branchLine
41
+ ? branchLine.slice('branch refs/heads/'.length).trim()
42
+ : null;
43
+ entries.push({ path, head, branch });
44
+ }
45
+ return entries;
46
+ }
47
+ async function enrichWorktree(wt) {
48
+ const [logRaw, statusRaw, aheadRaw] = await Promise.all([
49
+ git(wt.path, ['log', '-1', '--format=%h%n%s%n%ct']),
50
+ git(wt.path, ['status', '--short']),
51
+ git(wt.path, ['rev-list', '--count', 'origin/main..HEAD']),
52
+ ]);
53
+ const [hashLine, messageLine, timestampLine] = logRaw?.split('\n') ?? [];
54
+ const headHash = hashLine?.trim() || wt.head.slice(0, 7);
55
+ const headMessage = messageLine?.trim() ?? '';
56
+ const headTimestampMs = timestampLine ? parseInt(timestampLine.trim(), 10) * 1000 : 0;
57
+ const changedCount = statusRaw !== null
58
+ ? statusRaw.split('\n').filter(l => l.trim()).length
59
+ : 0;
60
+ const parsedAhead = aheadRaw !== null ? parseInt(aheadRaw, 10) : NaN;
61
+ const aheadCount = isNaN(parsedAhead) ? 0 : parsedAhead;
62
+ return { headHash, headMessage, headTimestampMs, changedCount, aheadCount };
63
+ }
64
+ async function resolveRepoRoot(path) {
65
+ return git(path, ['rev-parse', '--show-toplevel']);
66
+ }
67
+ async function enrichRepo(repoRoot, activeSessions) {
68
+ const porcelain = await git(repoRoot, ['worktree', 'list', '--porcelain']);
69
+ if (porcelain === null)
70
+ return null;
71
+ const rawWorktrees = parseWorktreePorcelain(porcelain);
72
+ const results = await Promise.allSettled(rawWorktrees.map(wt => enrichWorktree(wt)));
73
+ const worktrees = rawWorktrees.flatMap((wt, i) => {
74
+ const result = results[i];
75
+ if (result.status === 'rejected') {
76
+ console.warn(`[WorktreeService] Failed to enrich worktree at ${wt.path}:`, result.reason);
77
+ return [];
78
+ }
79
+ const e = result.value;
80
+ return [{
81
+ path: wt.path,
82
+ name: (0, path_1.basename)(wt.path),
83
+ branch: wt.branch,
84
+ headHash: e.headHash,
85
+ headMessage: e.headMessage,
86
+ headTimestampMs: e.headTimestampMs,
87
+ changedCount: e.changedCount,
88
+ aheadCount: e.aheadCount,
89
+ activeSessionCount: wt.branch ? (activeSessions.counts.get(wt.branch) ?? 0) : 0,
90
+ }];
91
+ });
92
+ return [...worktrees].sort((a, b) => {
93
+ if (b.activeSessionCount !== a.activeSessionCount)
94
+ return b.activeSessionCount - a.activeSessionCount;
95
+ if (b.changedCount !== a.changedCount)
96
+ return b.changedCount - a.changedCount;
97
+ return b.headTimestampMs - a.headTimestampMs;
98
+ });
99
+ }
100
+ function buildActiveSessionCounts(sessions) {
101
+ const counts = new Map();
102
+ for (const s of sessions) {
103
+ if (s.gitBranch && s.status === 'in_progress') {
104
+ counts.set(s.gitBranch, (counts.get(s.gitBranch) ?? 0) + 1);
105
+ }
106
+ }
107
+ return { counts };
108
+ }
109
+ async function getWorktreeList(repoRoots, activeSessions) {
110
+ const repoResults = await Promise.allSettled(repoRoots.map(async (repoRoot) => {
111
+ const worktrees = await enrichRepo(repoRoot, activeSessions);
112
+ return { repoRoot, worktrees };
113
+ }));
114
+ const repos = repoResults.flatMap((result) => {
115
+ if (result.status === 'rejected') {
116
+ console.warn(`[WorktreeService] Failed to enrich repo:`, result.reason);
117
+ return [];
118
+ }
119
+ const { repoRoot, worktrees } = result.value;
120
+ if (!worktrees || worktrees.length === 0)
121
+ return [];
122
+ return [{
123
+ repoName: (0, path_1.basename)(repoRoot),
124
+ repoRoot,
125
+ worktrees,
126
+ }];
127
+ });
128
+ const sortedRepos = [...repos].sort((a, b) => {
129
+ const aActive = a.worktrees.some(w => w.activeSessionCount > 0) ? 0 : 1;
130
+ const bActive = b.worktrees.some(w => w.activeSessionCount > 0) ? 0 : 1;
131
+ if (aActive !== bActive)
132
+ return aActive - bActive;
133
+ return a.repoName.localeCompare(b.repoName);
134
+ });
135
+ return { repos: sortedRepos };
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.11.2",
3
+ "version": "3.12.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,317 @@
1
+ {
2
+ "id": "architecture-scalability-audit",
3
+ "name": "Architecture Scalability Audit (v1 • Evidence-Driven • Dimension-Scoped • rigorMode-Adaptive)",
4
+ "version": "0.1.0",
5
+ "description": "Audit a bounded codebase scope for architecture scalability. The user declares which scalability dimensions matter (load, data volume, team/org, feature extensibility, operational); the workflow audits only those dimensions and produces per-dimension verdicts grounded in actual code, not generic advice.",
6
+ "recommendedPreferences": {
7
+ "recommendedAutonomy": "guided",
8
+ "recommendedRiskPolicy": "conservative"
9
+ },
10
+ "features": [
11
+ "wr.features.subagent_guidance"
12
+ ],
13
+ "preconditions": [
14
+ "The user provides a bounded scope (project, feature, or explicitly named component boundary).",
15
+ "The user can identify which scalability dimensions they care about for this scope.",
16
+ "The agent can inspect the codebase, architecture patterns, and design decisions in the scope.",
17
+ "A human will consume the final per-dimension verdicts and prioritized concerns."
18
+ ],
19
+ "metaGuidance": [
20
+ "DEFAULT BEHAVIOR: self-execute with tools. Ask only for true scope or dimension decisions you cannot resolve yourself.",
21
+ "V2 DURABILITY: keep workflow truth in output.notesMarkdown and explicit context fields. Human-facing markdown artifacts are optional companions only.",
22
+ "OWNERSHIP: the main agent owns the fact packet, synthesis, verdict calibration, and final handoff. Delegated dimension audits are evidence, not authority.",
23
+ "DIMENSION DISCIPLINE: audit only the dimensions the user declared. Do not add dimensions the user did not select, even if they look relevant — surface them as advisory notes instead.",
24
+ "EVIDENCE FIRST: every risk or will_break finding must cite a specific file, class, method, or pattern in the codebase. Technology name alone is not evidence.",
25
+ "GROWTH SCENARIO: every concern must name a growth scenario (e.g. 10x traffic, 100x records, 3x team size). Generic 'won't scale' findings are not acceptable.",
26
+ "VERDICT TIERS: use will_break / risk / fine. Do not force a cleaner answer than the evidence supports.",
27
+ "BOUNDARY: this workflow audits and prioritizes. It must not drift into implementation planning or remediation design unless the user explicitly asks."
28
+ ],
29
+ "steps": [
30
+ {
31
+ "id": "phase-0-bound-scope-and-declare-dimensions",
32
+ "title": "Phase 0: Bound the Scope and Declare Dimensions",
33
+ "promptBlocks": {
34
+ "goal": "Establish a precise bounded scope and confirm which scalability dimensions this audit will cover.",
35
+ "constraints": [
36
+ [{ "kind": "ref", "refId": "wr.refs.notes_first_durability" }],
37
+ "Scope must be bounded before investigation begins. Unbounded scope produces generic findings.",
38
+ "Dimension selection is the user's decision. Explore the codebase to inform the conversation, but the user declares which dimensions matter."
39
+ ],
40
+ "procedure": [
41
+ "Read the codebase to understand the architecture: key components, entry points, data flows, and main patterns within the declared scope.",
42
+ "Present the five scalability dimensions and ask the user to select which apply: (1) load — handles more requests, users, or throughput; (2) data_volume — handles more records, storage, or query size; (3) team_org — more teams or developers working on this scope; (4) feature_extensibility — more features added without rearchitecting; (5) operational — more deployments, environments, or operational complexity.",
43
+ "Ask the user to confirm the scope boundary — what is explicitly in and explicitly out.",
44
+ "Classify audit complexity: Simple (1–2 dimensions, small scope), Medium (2–3 dimensions, moderate scope), Complex (4–5 dimensions or large scope).",
45
+ "Run a context-clarity check: score boundary_clarity, dimension_clarity, and codebase_familiarity 1–3. If any score is 1, gather more context before advancing."
46
+ ],
47
+ "outputRequired": {
48
+ "notesMarkdown": "Scope boundary (in and out), declared dimensions with rationale, audit complexity classification, and any open boundary questions.",
49
+ "context": "Capture scope, scopeDescription, declaredDimensions, auditComplexity, contextSummary, candidateFiles, and openQuestions."
50
+ },
51
+ "verify": [
52
+ "Declared dimensions are explicit and user-confirmed.",
53
+ "Scope boundary is concrete enough that an auditor who has never seen this codebase knows what to examine.",
54
+ "No dimensions were added by the agent without user confirmation."
55
+ ]
56
+ },
57
+ "requireConfirmation": true
58
+ },
59
+ {
60
+ "id": "phase-1-state-scalability-hypothesis",
61
+ "title": "Phase 1: State Scalability Hypothesis",
62
+ "promptBlocks": {
63
+ "goal": "State your current hypothesis about where scalability risks likely live before detailed investigation begins.",
64
+ "constraints": [
65
+ "Keep this short and falsifiable.",
66
+ "This is a reference point, not a position to defend."
67
+ ],
68
+ "procedure": [
69
+ "Based on your initial codebase scan, write your best guess about which dimension and which component is most likely to be the main scalability concern.",
70
+ "Name the specific architectural pattern or design decision you are most worried about.",
71
+ "Say what would most likely make your current hypothesis wrong."
72
+ ],
73
+ "outputRequired": {
74
+ "notesMarkdown": "Scalability hypothesis, most-worried-about pattern, and what could falsify it.",
75
+ "context": "Capture scalabilityHypothesis."
76
+ },
77
+ "verify": [
78
+ "The hypothesis names a specific dimension and component, not a generic concern.",
79
+ "The hypothesis is concrete enough that later synthesis can say what changed your mind."
80
+ ]
81
+ },
82
+ "requireConfirmation": false
83
+ },
84
+ {
85
+ "id": "phase-2-freeze-fact-packet-and-select-reviewers",
86
+ "title": "Phase 2: Freeze Fact Packet and Select Dimension Reviewer Families",
87
+ "promptBlocks": {
88
+ "goal": "Freeze a neutral scalability fact packet and assign one reviewer family per declared dimension.",
89
+ "constraints": [
90
+ [{ "kind": "ref", "refId": "wr.refs.notes_first_durability" }],
91
+ "The fact packet is the primary truth for all dimension reviewer families.",
92
+ "Keep the scalability hypothesis as a reference to challenge, not a frame to defend.",
93
+ "One reviewer family per declared dimension only. Do not add families for undeclared dimensions."
94
+ ],
95
+ "procedure": [
96
+ "Create a neutral `scalabilityFactPacket` containing: scope boundary (in and out), declared dimensions, key architectural patterns found, main components and their roles, data flow and storage patterns, concurrency and state management approach, dependency boundaries and coupling, deployment and runtime assumptions, and explicit open unknowns.",
97
+ "Include realism signals: code that looks scalable at a glance but may have hidden limits (e.g. in-memory state, synchronous choke points, missing pagination, tight coupling between components).",
98
+ "For each declared dimension, assign a reviewer family mission: load = examine request handling, concurrency, session/state management, caching, connection pools, and horizontal scaling readiness — check whether session state is in-memory or distributed, whether connection pools are bounded, whether synchronous bottlenecks exist in hot paths; data_volume = examine query patterns, pagination, indexing, result set bounds, storage growth, and data access layer scalability — check for unbounded queries (missing LIMIT/pagination), missing indexes on filtered columns, N+1 patterns in repository/service layers, and data structures that grow unboundedly; team_org = examine module coupling, shared state, and parallel development friction — specifically check import graphs for cross-module dependencies that would cause merge conflicts, identify shared mutable singletons or global state, look for test setup that requires spinning up adjacent modules, and check whether public interfaces change frequently or are stable; feature_extensibility = examine how much code changes when a new variant of a core concept is added — specifically look for switch/when/if-else chains on type discriminators that would need a new branch per feature, hardcoded business-rule constants, direct concrete dependencies instead of interfaces or abstractions, and files that are edited for every new feature; operational = examine deployment complexity, environment-specific behavior, observability, configuration surface, and operational runbook needs — specifically check for environment-specific code paths (if/switch on env vars that create different behavior per environment), configuration that must be updated in multiple places per deployment, whether logs and metrics cover the main operational failure modes, and whether a new deployment of this scope would require manual steps beyond a standard deploy.",
99
+ "Set selectedReviewerFamilies to the list of assigned families (one per declared dimension). Set contradictionCount and blindSpotCount to 0."
100
+ ],
101
+ "outputRequired": {
102
+ "notesMarkdown": "Neutral scalability fact packet, assigned reviewer families per dimension, and any realism signals surfaced.",
103
+ "context": "Capture scalabilityFactPacket, selectedReviewerFamilies, contradictionCount, blindSpotCount."
104
+ },
105
+ "verify": [
106
+ "The fact packet is concrete enough that a reviewer family can audit its dimension without regathering broad context.",
107
+ "Exactly one reviewer family is assigned per declared dimension."
108
+ ]
109
+ },
110
+ "promptFragments": [
111
+ {
112
+ "id": "phase-2-quick",
113
+ "when": { "var": "auditComplexity", "equals": "Simple" },
114
+ "text": "For a Simple audit, keep the fact packet compact — scope summary, key patterns, and declared dimensions only. Skip exhaustive realism signal enumeration."
115
+ }
116
+ ],
117
+ "requireConfirmation": false
118
+ },
119
+ {
120
+ "id": "phase-3-parallel-dimension-audit",
121
+ "title": "Phase 3: Parallel Dimension Audit Bundle",
122
+ "promptBlocks": {
123
+ "goal": "Run one reviewer family per declared dimension in parallel, then synthesize their findings as evidence rather than verdicts.",
124
+ "constraints": [
125
+ [{ "kind": "ref", "refId": "wr.refs.notes_first_durability" }],
126
+ [{ "kind": "ref", "refId": "wr.refs.synthesis_under_disagreement" }],
127
+ "Each reviewer family uses scalabilityFactPacket as primary truth.",
128
+ "Reviewer-family outputs are raw evidence. The main agent owns synthesis and verdict assignment.",
129
+ "Each reviewer family audits only its declared dimension — no cross-dimension scope creep."
130
+ ],
131
+ "procedure": [
132
+ "Before investigating, restate your scalabilityHypothesis and name which dimension is most likely to challenge it.",
133
+ "Run one investigation per declared dimension. For each dimension, the investigation must return: top findings, evidence for each finding (specific file, class, method, or pattern references — not just technology names), verdict tier per finding (will_break / risk / fine), growth scenario for each concern (e.g. 10x traffic, 100x records, 3x team size), biggest uncertainty, and likely false-confidence vector for this dimension.",
134
+ "After completing all dimension investigations, synthesize explicitly: what was confirmed, what was genuinely new, what looks weak or overstated, and what changed your current hypothesis.",
135
+ "Build dimensionFindings keyed by dimension containing: findings list, verdict summary, evidence quality assessment, and open questions.",
136
+ "Identify cross-cutting concerns: architectural patterns or components that appear in findings from multiple dimensions."
137
+ ],
138
+ "outputRequired": {
139
+ "notesMarkdown": "Per-dimension audit synthesis, hypothesis comparison, evidence quality notes, and cross-cutting concerns identified.",
140
+ "context": "Capture dimensionFindings, contradictionCount, blindSpotCount, crossCuttingConcerns."
141
+ },
142
+ "verify": [
143
+ "Every declared dimension has findings in dimensionFindings.",
144
+ "Reviewer-family outputs were synthesized by the main agent, not adopted verbatim.",
145
+ "Each will_break or risk finding in dimensionFindings has at least one specific code reference."
146
+ ]
147
+ },
148
+ "promptFragments": [
149
+ {
150
+ "id": "phase-3-quick",
151
+ "when": { "var": "auditComplexity", "equals": "Simple" },
152
+ "text": "For a Simple audit, self-execute each dimension investigation directly without spawning WorkRail Executors. One dimension at a time, using tools to inspect the codebase. This keeps the audit proportionate to the scope."
153
+ },
154
+ {
155
+ "id": "phase-3-thorough",
156
+ "when": { "var": "auditComplexity", "equals": "Complex" },
157
+ "text": "For a Complex audit, spawn all dimension executors simultaneously, then after synthesis run routine-hypothesis-challenge against any will_break finding before closing this phase. This adds an adversarial check on the most serious findings."
158
+ }
159
+ ],
160
+ "requireConfirmation": false
161
+ },
162
+ {
163
+ "id": "phase-4-synthesis-loop",
164
+ "type": "loop",
165
+ "title": "Phase 4: Contradiction and Synthesis Loop",
166
+ "loop": {
167
+ "type": "while",
168
+ "conditionSource": {
169
+ "kind": "artifact_contract",
170
+ "contractRef": "wr.contracts.loop_control",
171
+ "loopId": "scalability_synthesis_loop"
172
+ },
173
+ "maxIterations": 3
174
+ },
175
+ "body": [
176
+ {
177
+ "id": "phase-4a-resolve-contradictions",
178
+ "title": "Resolve Contradictions and Cross-Cutting Concerns",
179
+ "promptBlocks": {
180
+ "goal": "Resolve contradictions between dimension findings and sharpen cross-cutting concerns.",
181
+ "constraints": [
182
+ [{ "kind": "ref", "refId": "wr.refs.parallelize_cognition_serialize_synthesis" }],
183
+ "Contradiction resolution is main-agent work. Do not delegate synthesis.",
184
+ "A cross-cutting concern that spans multiple dimensions is its own finding."
185
+ ],
186
+ "procedure": [
187
+ "List any contradictions: cases where two dimension reviewers disagreed about the same component, or where one reviewer's finding undermines another's.",
188
+ "Adjudicate each contradiction explicitly: which evidence is stronger, and what is the resolved finding?",
189
+ "For any finding that lacks a specific code reference, either locate the evidence yourself or downgrade the verdict to risk and flag evidenceWeak = true.",
190
+ "Update dimensionFindings with resolved contradictions. Update crossCuttingConcerns with any newly identified cross-dimension patterns.",
191
+ "Update contradictionCount to 0 when all are resolved, or to the remaining unresolved count."
192
+ ],
193
+ "outputRequired": {
194
+ "notesMarkdown": "Contradictions resolved (or still open), cross-cutting concerns updated, evidence-weak findings flagged.",
195
+ "context": "Capture contradictionCount, crossCuttingConcerns, evidenceWeakCount, dimensionFindings."
196
+ },
197
+ "verify": [
198
+ "Each resolved contradiction names which evidence won and why.",
199
+ "Evidence-weak findings are explicitly flagged."
200
+ ]
201
+ },
202
+ "requireConfirmation": false
203
+ },
204
+ {
205
+ "id": "phase-4b-blind-spot-check",
206
+ "title": "Blind-Spot Check",
207
+ "promptBlocks": {
208
+ "goal": "Check for the four most dangerous blind spots before closing synthesis.",
209
+ "constraints": [
210
+ "This is a structured four-item check, not a free-form review."
211
+ ],
212
+ "procedure": [
213
+ "Check 1 — Technology-vs-usage: did any reviewer identify a scalable technology without checking actual usage patterns in the code? (e.g. Postgres was identified as the DB, but were N+1 queries, missing indexes, or unbounded result sets actually checked?) Fix any instances found.",
214
+ "Check 2 — Scope drift: did any reviewer audit components outside the declared scope boundary? Remove out-of-scope findings.",
215
+ "Check 3 — Undeclared relevant dimensions: does the codebase have patterns suggesting a declared-out dimension actually matters for this scope? If so, surface it as an advisory note without adding it to the audit verdict.",
216
+ "Check 4 — Growth scenario vagueness: does every concern name a specific growth scenario? If not, assign one now based on the most realistic growth pattern for this scope.",
217
+ "Set blindSpotCount to the number of blind spots found across all four checks."
218
+ ],
219
+ "outputRequired": {
220
+ "notesMarkdown": "Results for all four blind spot checks, fixes applied, and any advisory notes for undeclared dimensions.",
221
+ "context": "Capture blindSpotCount."
222
+ },
223
+ "verify": [
224
+ "All four blind spot categories were explicitly checked.",
225
+ "Every remaining concern has a growth scenario after this step."
226
+ ]
227
+ },
228
+ "requireConfirmation": false
229
+ },
230
+ {
231
+ "id": "phase-4c-synthesis-loop-decision",
232
+ "title": "Synthesis Loop Decision",
233
+ "promptBlocks": {
234
+ "goal": "Decide whether the synthesis loop needs another pass.",
235
+ "constraints": [
236
+ "Use the trigger rules, not vibes."
237
+ ],
238
+ "procedure": [
239
+ "Continue if contradictionCount > 0.",
240
+ "Otherwise continue if evidenceWeakCount > 0 for any will_break finding.",
241
+ "Otherwise continue if blindSpotCount > 0 and the blind spot affects a risk or will_break finding.",
242
+ "Otherwise stop."
243
+ ],
244
+ "outputRequired": {
245
+ "artifact": "Emit a `wr.loop_control` artifact for `scalability_synthesis_loop` with `decision` set to `continue` or `stop`."
246
+ },
247
+ "verify": [
248
+ "The loop does not stop while contradictions or evidence-weak will_break findings remain."
249
+ ]
250
+ },
251
+ "outputContract": {
252
+ "contractRef": "wr.contracts.loop_control"
253
+ },
254
+ "requireConfirmation": false
255
+ }
256
+ ]
257
+ },
258
+ {
259
+ "id": "phase-5-final-validation",
260
+ "title": "Phase 5: Final Validation",
261
+ "promptBlocks": {
262
+ "goal": "Verify all four hard gates before the final handoff.",
263
+ "constraints": [
264
+ "This is a structured checklist pass. If any hard gate fails, fix it before advancing.",
265
+ "Do not advance to handoff with known hard gate failures."
266
+ ],
267
+ "procedure": [
268
+ "Verdict aggregation — derive scalabilityVerdict from dimensionFindings using these explicit rules: (1) at_risk if any declared dimension has a will_break finding; (2) conditional if no will_break findings exist but at least one dimension has a risk finding; (3) ready_to_scale if all declared dimensions have only fine findings; (4) inconclusive if any dimension still has evidenceWeak = true after the synthesis loop, making a reliable verdict impossible. Capture verdictRationale naming the specific dimension and finding that drove the verdict.",
269
+ "Hard gate 1 — Evidence grounding: for every will_break and risk finding in dimensionFindings, confirm it cites a specific file, class, method, or code pattern. Technology name alone fails this gate. Fix by locating the code evidence or downgrading to risk with an evidence-needed note.",
270
+ "Hard gate 2 — Dimension coverage: confirm every declared dimension has at least one substantive finding. A verdict of fine with supporting evidence counts. A dimension with no findings at all fails this gate.",
271
+ "Hard gate 3 — Hypothesis revisited: confirm that scalabilityHypothesis from Phase 1 is either confirmed or explicitly revised in synthesis notes. If it was never addressed, address it now.",
272
+ "Hard gate 4 — Growth scenario specificity: confirm every concern in dimensionFindings names a growth scenario. If any do not, assign one now.",
273
+ "Set hardGatesPassed = true only when the verdict aggregation and all four gates pass. Set hardGateFailures to the list of any that needed fixing."
274
+ ],
275
+ "outputRequired": {
276
+ "notesMarkdown": "Verdict aggregation result, hard gate results: which passed, which failed, what was fixed.",
277
+ "context": "Capture hardGatesPassed, hardGateFailures, scalabilityVerdict, verdictRationale."
278
+ },
279
+ "verify": [
280
+ "All four hard gates were checked explicitly.",
281
+ "hardGatesPassed reflects the actual state after any fixes."
282
+ ]
283
+ },
284
+ "requireConfirmation": false
285
+ },
286
+ {
287
+ "id": "phase-6-scalability-handoff",
288
+ "title": "Phase 6: Scalability Handoff",
289
+ "promptBlocks": {
290
+ "goal": "Deliver the final scalability audit verdict for a human decision-maker.",
291
+ "constraints": [
292
+ "This workflow informs a decision. It does not make architectural changes by itself.",
293
+ "Do not drift into implementation planning or remediation design unless the user explicitly asks."
294
+ ],
295
+ "procedure": [
296
+ "Open with the overall scalability readiness verdict (ready_to_scale / conditional / at_risk / inconclusive) and the verdictRationale — name the specific dimension and finding that drove it.",
297
+ "For each declared dimension, give: dimension name, verdict tier (will_break / risk / fine), top finding with specific code reference, growth scenario, and severity.",
298
+ "List cross-cutting concerns: patterns that create scalability risk across multiple dimensions.",
299
+ "Revisit scalabilityHypothesis from Phase 1: was it confirmed or revised? What evidence changed your view?",
300
+ "Give a prioritized concern list ordered by: (1) will_break findings first, (2) risk findings by severity, (3) cross-cutting concerns, (4) fine findings worth noting as already solid.",
301
+ "Surface any advisory notes for undeclared dimensions that may be worth considering.",
302
+ "State what is already well-designed for scale — not everything should be a concern."
303
+ ],
304
+ "outputRequired": {
305
+ "notesMarkdown": "Decision-ready scalability handoff: overall verdict, per-dimension summary with code references, prioritized concerns, cross-cutting concerns, hypothesis outcome, and what is already solid."
306
+ },
307
+ "verify": [
308
+ "The handoff is verdict-first and evidence-grounded.",
309
+ "Every concern is tied to a specific code reference and growth scenario.",
310
+ "The hypothesis from Phase 1 is explicitly addressed.",
311
+ "What is already well-designed is stated — not just the concerns."
312
+ ]
313
+ },
314
+ "requireConfirmation": false
315
+ }
316
+ ]
317
+ }
@@ -34,30 +34,30 @@
34
34
  {
35
35
  "id": "step-understand-deeply",
36
36
  "title": "Step 2: Understand the Problem Deeply",
37
- "prompt": "Understand the problem before proposing anything.\n\nReason through:\n- What are the core tensions in this problem? (e.g., performance vs simplicity, flexibility vs type safety, backward compatibility vs clean design)\n- How does the codebase already solve similar problems? Study the most relevant existing patterns — analyze the architectural decisions and constraints they protect, not just list files.\n- What's the simplest naive solution? Why is it insufficient? (If it IS sufficient, note that — it may be the best candidate.)\n- What makes this problem hard? What would a junior developer miss?\n- Which of the dev's philosophy principles are under pressure from this problem's constraints?\n\nWorking notes:\n- Core tensions (2-4 real tradeoffs, not generic labels)\n- Existing patterns analysis (decisions, invariants they protect)\n- Naive solution and why it's insufficient (or sufficient)\n- What makes this hard\n- Philosophy principles under pressure",
37
+ "prompt": "Understand the problem before proposing anything.\n\nReason through:\n- What are the core tensions in this problem? (e.g., performance vs simplicity, flexibility vs type safety, backward compatibility vs clean design)\n- How does the codebase already solve similar problems? Study the most relevant existing patterns — analyze the architectural decisions and constraints they protect, not just list files.\n- Where does the problem most likely live? Is the requested location the real seam, or just where the symptom appears?\n- What nearby callers, consumers, sibling paths, or contracts must remain consistent if that boundary changes?\n- What's the simplest naive solution? Why is it insufficient? (If it IS sufficient, note that — it may be the best candidate.)\n- What makes this problem hard? What would a junior developer miss?\n- Which of the dev's philosophy principles are under pressure from this problem's constraints?\n\nWorking notes:\n- Core tensions (2-4 real tradeoffs, not generic labels)\n- Existing patterns analysis (decisions, invariants they protect)\n- Likely seam / plausible boundaries\n- Nearby impact surface that must stay consistent\n- Naive solution and why it's insufficient (or sufficient)\n- What makes this hard\n- Philosophy principles under pressure",
38
38
  "agentRole": "You are reasoning deeply about the problem space before generating any solutions.",
39
39
  "requireConfirmation": false
40
40
  },
41
41
  {
42
42
  "id": "step-generate-candidates",
43
43
  "title": "Step 3: Generate Candidates from Tensions",
44
- "prompt": "Generate design candidates that resolve the identified tensions differently.\n\nMANDATORY candidates:\n1. The simplest possible change that satisfies acceptance criteria. If the problem doesn't need an architectural solution, say so.\n2. Follow the existing repo pattern — adapt what the codebase already does for similar problems. Don't invent when you can adapt.\n\nAdditional candidates (1-2 more):\n- Each must resolve the identified tensions DIFFERENTLY, not just vary surface details\n- Each must be grounded in a real constraint or tradeoff, not an abstract perspective label\n- Consider philosophy conflicts: if the stated philosophy disagrees with repo patterns, one candidate could follow the stated philosophy and another could follow the established pattern\n\nFor each candidate, produce:\n- One-sentence summary of the approach\n- Which tensions it resolves and which it accepts\n- The specific failure mode you'd watch for\n- How it relates to existing repo patterns (follows / adapts / departs)\n- What you gain and what you give up\n- Which philosophy principles it honors and which it conflicts with (by name)\n\nRules:\n- candidates must be genuinely different in shape, not just wording\n- if all candidates converge on the same approach, that's signal — note it honestly rather than manufacturing fake diversity\n- cite specific files or patterns when they materially shape a candidate",
44
+ "prompt": "Generate design candidates that resolve the identified tensions differently.\n\nMANDATORY candidates:\n1. The simplest possible change that satisfies acceptance criteria. If the problem doesn't need an architectural solution, say so.\n2. Follow the existing repo pattern — adapt what the codebase already does for similar problems. Don't invent when you can adapt.\n\nAdditional candidates (1-2 more):\n- Each must resolve the identified tensions DIFFERENTLY, not just vary surface details\n- Each must be grounded in a real constraint or tradeoff, not an abstract perspective label\n- Consider philosophy conflicts: if the stated philosophy disagrees with repo patterns, one candidate could follow the stated philosophy and another could follow the established pattern\n\nFor each candidate, produce:\n- One-sentence summary of the approach\n- Which tensions it resolves and which it accepts\n- Boundary solved at, and why that boundary is the best fit\n- The specific failure mode you'd watch for\n- How it relates to existing repo patterns (follows / adapts / departs)\n- What you gain and what you give up\n- Impact surface beyond the immediate task\n- Scope judgment: too narrow / best-fit / too broad, with concrete evidence\n- Which philosophy principles it honors and which it conflicts with (by name)\n\nRules:\n- candidates must be genuinely different in shape, not just wording\n- if all candidates converge on the same approach, that's signal — note it honestly rather than manufacturing fake diversity\n- broader scope requires concrete evidence\n- cite specific files or patterns when they materially shape a candidate",
45
45
  "agentRole": "You are generating genuinely diverse design candidates grounded in real tensions.",
46
46
  "requireConfirmation": false
47
47
  },
48
48
  {
49
49
  "id": "step-compare-and-recommend",
50
50
  "title": "Step 4: Compare via Tradeoffs and Recommend",
51
- "prompt": "Compare candidates through tradeoff analysis, not checklists.\n\nFor the set of candidates, assess:\n- Which tensions does each resolve best?\n- Which has the most manageable failure mode?\n- Which best fits the dev's philosophy? Where are the philosophy conflicts?\n- Which is most consistent with existing repo patterns?\n- Which would be easiest to evolve or reverse if assumptions are wrong?\n\nProduce a clear recommendation with rationale tied back to tensions and philosophy. If two candidates are close, say so and explain what would tip the decision.\n\nSelf-critique your recommendation:\n- What's the strongest argument against your pick?\n- What would make you switch to a different candidate?\n- What assumption, if wrong, would invalidate this design?\n\nWorking notes:\n- Comparison matrix (tensions x candidates)\n- Recommendation and rationale\n- Strongest counter-argument\n- Pivot conditions",
51
+ "prompt": "Compare candidates through tradeoff analysis, not checklists.\n\nFor the set of candidates, assess:\n- Which tensions does each resolve best?\n- Which solves the problem at the best-fit boundary?\n- Which has the most manageable failure mode?\n- Which best fits the dev's philosophy? Where are the philosophy conflicts?\n- Which is most consistent with existing repo patterns?\n- Which would be easiest to evolve or reverse if assumptions are wrong?\n- Which is too narrow, best-fit, or too broad — and why?\n\nProduce a clear recommendation with rationale tied back to tensions, scope judgment, repo patterns, and philosophy. If two candidates are close, say so and explain what would tip the decision.\n\nSelf-critique your recommendation:\n- What's the strongest argument against your pick?\n- What narrower option might still work, and why did it lose?\n- What broader option might be justified, and what evidence would be required?\n- What assumption, if wrong, would invalidate this design?\n\nWorking notes:\n- Comparison matrix (tensions x candidates)\n- Recommendation and rationale\n- Strongest counter-argument\n- Pivot conditions",
52
52
  "agentRole": "You are comparing candidates honestly and recommending based on tradeoffs, not advocacy.",
53
53
  "requireConfirmation": false
54
54
  },
55
55
  {
56
56
  "id": "step-deliver",
57
57
  "title": "Step 5: Deliver the Design Candidates",
58
- "prompt": "Create `{deliverableName}`.\n\nRequired structure:\n- Problem Understanding (tensions, what makes it hard)\n- Philosophy Constraints (which principles matter, any conflicts)\n- Candidates (each with: summary, tensions resolved/accepted, failure mode, repo-pattern relationship, gains/losses, philosophy fit)\n- Comparison and Recommendation\n- Self-Critique (strongest counter-argument, pivot conditions)\n- Open Questions for the Main Agent\n\nThe main agent will interrogate this output — it is raw investigative material, not a final decision. Optimize for honest, useful analysis over polished presentation.",
58
+ "prompt": "Create `{deliverableName}`.\n\nRequired structure:\n- Problem Understanding (tensions, likely seam, what makes it hard)\n- Philosophy Constraints (which principles matter, any conflicts)\n- Impact Surface (what nearby paths, consumers, or contracts must stay consistent)\n- Candidates (each with: summary, tensions resolved/accepted, boundary solved at, why that boundary is the best fit, failure mode, repo-pattern relationship, gains/losses, scope judgment, philosophy fit)\n- Comparison and Recommendation\n- Self-Critique (strongest counter-argument, pivot conditions)\n- Open Questions for the Main Agent\n\nThe main agent will interrogate this output — it is raw investigative material, not a final decision. Optimize for honest, useful analysis over polished presentation.",
59
59
  "agentRole": "You are delivering design analysis for the main agent to interrogate and build on.",
60
60
  "requireConfirmation": false
61
61
  }
62
62
  ]
63
- }
63
+ }
@@ -1 +0,0 @@
1
- .react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.top-1\/2{top:50%}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.z-50{z-index:50}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-\[420px\]{margin-right:420px}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-\[500px\]{height:500px}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-screen{min-height:100vh}.w-\[420px\]{width:420px}.w-full{width:100%}.max-w-\[200px\]{max-width:200px}.max-w-\[360px\]{max-width:360px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[200px\]{min-width:200px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-\[var\(--border\)\]{border-color:var(--border)}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-card\)\]{background-color:var(--bg-card)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.py-20{padding-block:calc(var(--spacing) * 20)}.pt-2{padding-top:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-muted\)\]{color:var(--text-muted)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--warning\)\]{color:var(--warning)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.placeholder-\[var\(--text-muted\)\]::placeholder{color:var(--text-muted)}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-\[margin\]{transition-property:margin;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}@media(hover:hover){.group-hover\:text-\[var\(--accent\)\]:is(:where(.group):hover *){color:var(--accent)}.group-hover\:text-\[var\(--text-primary\)\]:is(:where(.group):hover *){color:var(--text-primary)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:border-\[var\(--accent\)\]:hover{border-color:var(--accent)}.hover\:bg-\[var\(--bg-card\)\]:hover{background-color:var(--bg-card)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}}:root{--bg-primary:#0a0a0a;--bg-secondary:#141414;--bg-card:#1a1a1a;--border:#2a2a2a;--text-primary:#fafafa;--text-secondary:#a0a0a0;--text-muted:#666;--accent:#3b82f6;--accent-hover:#2563eb;--success:#22c55e;--warning:#eab308;--error:#ef4444;--blocked:#f97316}body{background:var(--bg-primary);color:var(--text-primary);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}*{box-sizing:border-box}.markdown-view{color:var(--text-secondary);word-break:break-word;font-size:.8125rem;line-height:1.6}.markdown-view h1,.markdown-view h2,.markdown-view h3,.markdown-view h4,.markdown-view h5,.markdown-view h6{color:var(--text-primary);margin:1em 0 .4em;font-weight:600;line-height:1.3}.markdown-view h1{font-size:1.1em}.markdown-view h2{font-size:1em}.markdown-view h3{font-size:.95em}.markdown-view h4,.markdown-view h5,.markdown-view h6{font-size:.9em}.markdown-view>:first-child{margin-top:0}.markdown-view p{margin:.5em 0}.markdown-view strong{color:var(--text-primary);font-weight:600}.markdown-view em{font-style:italic}.markdown-view a{color:var(--accent);text-underline-offset:2px;text-decoration:underline}.markdown-view ul{margin:.4em 0;padding-left:1.4em;list-style-type:disc}.markdown-view ol{margin:.4em 0;padding-left:1.4em;list-style-type:decimal}.markdown-view li{color:var(--text-secondary);margin:.15em 0}.markdown-view li::marker{color:var(--text-muted)}.markdown-view li>ul,.markdown-view li>ol{margin:.1em 0}.markdown-view li>ul{list-style-type:circle}.markdown-view code{background:var(--bg-primary);color:var(--text-primary);border-radius:3px;padding:.15em .35em;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.88em}.markdown-view pre{background:var(--bg-primary);border-radius:6px;margin:.5em 0;padding:.75em;overflow-x:auto}.markdown-view pre code{background:0 0;padding:0;font-size:.85em;line-height:1.5}.markdown-view blockquote{border-left:3px solid var(--border);color:var(--text-muted);margin:.5em 0;padding:.25em .75em}.markdown-view hr{border:none;border-top:1px solid var(--border);margin:.75em 0}.markdown-view table{border-collapse:collapse;width:100%;margin:.5em 0;font-size:.9em}.markdown-view th,.markdown-view td{border:1px solid var(--border);text-align:left;padding:.35em .6em}.markdown-view th{background:var(--bg-primary);color:var(--text-primary);font-weight:600}.markdown-view img{border-radius:4px;max-width:100%}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}