@exaudeus/workrail 3.18.0 → 3.18.1

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveRepoRoot = resolveRepoRoot;
4
4
  exports.buildActiveSessionCounts = buildActiveSessionCounts;
5
+ exports.setEnrichmentCompleteCallback = setEnrichmentCompleteCallback;
5
6
  exports.getWorktreeList = getWorktreeList;
6
7
  const child_process_1 = require("child_process");
7
8
  const util_1 = require("util");
@@ -9,7 +10,12 @@ const path_1 = require("path");
9
10
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
10
11
  const GIT_TIMEOUT_MS = 5000;
11
12
  function isExecError(e) {
12
- return e instanceof Error && 'killed' in e;
13
+ if (!(e instanceof Error))
14
+ return false;
15
+ if ('killed' in e)
16
+ return true;
17
+ const sys = e.syscall ?? '';
18
+ return sys.startsWith('spawn');
13
19
  }
14
20
  async function git(cwd, args) {
15
21
  try {
@@ -54,7 +60,7 @@ function acquireEnrichmentSlot() {
54
60
  resolve();
55
61
  }
56
62
  else {
57
- enrichmentQueue.push(() => { activeEnrichments++; resolve(); });
63
+ enrichmentQueue.push(resolve);
58
64
  }
59
65
  });
60
66
  }
@@ -67,6 +73,29 @@ function releaseEnrichmentSlot() {
67
73
  activeEnrichments--;
68
74
  }
69
75
  }
76
+ const MAX_BACKGROUND_ENRICHMENTS = 16;
77
+ let activeBackgroundEnrichments = 0;
78
+ const backgroundEnrichmentQueue = [];
79
+ function acquireBackgroundSlot() {
80
+ return new Promise((resolve) => {
81
+ if (activeBackgroundEnrichments < MAX_BACKGROUND_ENRICHMENTS) {
82
+ activeBackgroundEnrichments++;
83
+ resolve();
84
+ }
85
+ else {
86
+ backgroundEnrichmentQueue.push(resolve);
87
+ }
88
+ });
89
+ }
90
+ function releaseBackgroundSlot() {
91
+ const next = backgroundEnrichmentQueue.shift();
92
+ if (next) {
93
+ next();
94
+ }
95
+ else {
96
+ activeBackgroundEnrichments--;
97
+ }
98
+ }
70
99
  function parseFileStatus(xy) {
71
100
  if (xy === '??')
72
101
  return 'untracked';
@@ -122,8 +151,8 @@ async function enrichWorktree(wt) {
122
151
  wt.branch !== 'main' &&
123
152
  mergedBranchesRaw !== null &&
124
153
  mergedBranchesRaw.split('\n').some(line => line.trim() === wt.branch);
125
- const branchDescription = descriptionRaw?.trim() ?? '';
126
- return { headHash, headMessage, headTimestampMs, changedCount, changedFiles, aheadCount, unpushedCommits, isMerged, branchDescription };
154
+ const description = descriptionRaw?.trim() ?? '';
155
+ return { headHash, headMessage, headTimestampMs, changedCount, changedFiles, aheadCount, unpushedCommits, isMerged, description };
127
156
  }
128
157
  async function resolveRepoRoot(path) {
129
158
  const commonDir = await git(path, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
@@ -131,18 +160,39 @@ async function resolveRepoRoot(path) {
131
160
  return null;
132
161
  return commonDir.replace(/\/\.git\/?$/, '') || null;
133
162
  }
134
- async function enrichRepo(repoRoot, activeSessions) {
163
+ async function buildFastWorktrees(repoRoot) {
164
+ const porcelain = await git(repoRoot, ['worktree', 'list', '--porcelain']);
165
+ if (porcelain === null)
166
+ return null;
167
+ const rawWorktrees = parseWorktreePorcelain(porcelain);
168
+ return rawWorktrees.map((wt) => ({
169
+ path: wt.path,
170
+ name: (0, path_1.basename)(wt.path),
171
+ branch: wt.branch,
172
+ headHash: wt.head.slice(0, 7),
173
+ headMessage: '',
174
+ headTimestampMs: 0,
175
+ changedCount: 0,
176
+ changedFiles: [],
177
+ aheadCount: 0,
178
+ unpushedCommits: [],
179
+ isMerged: false,
180
+ activeSessionCount: 0,
181
+ enrichment: null,
182
+ }));
183
+ }
184
+ async function enrichRepo(repoRoot) {
135
185
  const porcelain = await git(repoRoot, ['worktree', 'list', '--porcelain']);
136
186
  if (porcelain === null)
137
187
  return null;
138
188
  const rawWorktrees = parseWorktreePorcelain(porcelain);
139
189
  const results = await Promise.allSettled(rawWorktrees.map(async (wt) => {
140
- await acquireEnrichmentSlot();
190
+ await acquireBackgroundSlot();
141
191
  try {
142
192
  return await enrichWorktree(wt);
143
193
  }
144
194
  finally {
145
- releaseEnrichmentSlot();
195
+ releaseBackgroundSlot();
146
196
  }
147
197
  }));
148
198
  const worktrees = rawWorktrees.flatMap((wt, i) => {
@@ -152,6 +202,17 @@ async function enrichRepo(repoRoot, activeSessions) {
152
202
  return [];
153
203
  }
154
204
  const e = result.value;
205
+ const enrichment = {
206
+ headHash: e.headHash,
207
+ headMessage: e.headMessage,
208
+ headTimestampMs: e.headTimestampMs,
209
+ changedCount: e.changedCount,
210
+ changedFiles: e.changedFiles,
211
+ aheadCount: e.aheadCount,
212
+ unpushedCommits: e.unpushedCommits,
213
+ isMerged: e.isMerged,
214
+ description: e.description,
215
+ };
155
216
  return [{
156
217
  path: wt.path,
157
218
  name: (0, path_1.basename)(wt.path),
@@ -164,13 +225,12 @@ async function enrichRepo(repoRoot, activeSessions) {
164
225
  aheadCount: e.aheadCount,
165
226
  unpushedCommits: e.unpushedCommits,
166
227
  isMerged: e.isMerged,
167
- activeSessionCount: wt.branch ? (activeSessions.counts.get(wt.branch) ?? 0) : 0,
168
- ...(e.branchDescription ? { description: e.branchDescription } : {}),
228
+ activeSessionCount: 0,
229
+ ...(e.description ? { description: e.description } : {}),
230
+ enrichment,
169
231
  }];
170
232
  });
171
233
  return [...worktrees].sort((a, b) => {
172
- if (b.activeSessionCount !== a.activeSessionCount)
173
- return b.activeSessionCount - a.activeSessionCount;
174
234
  if (b.changedCount !== a.changedCount)
175
235
  return b.changedCount - a.changedCount;
176
236
  return b.headTimestampMs - a.headTimestampMs;
@@ -185,12 +245,20 @@ function buildActiveSessionCounts(sessions) {
185
245
  }
186
246
  return { counts };
187
247
  }
188
- async function getWorktreeList(repoRoots, activeSessions) {
248
+ const WORKTREE_CACHE_TTL_MS = 45000;
249
+ let worktreeCache = null;
250
+ let backgroundEnrichmentInFlight = false;
251
+ const BACKGROUND_ENRICHMENT_TIMEOUT_MS = 120000;
252
+ let onEnrichmentComplete = null;
253
+ function setEnrichmentCompleteCallback(cb) {
254
+ onEnrichmentComplete = cb;
255
+ }
256
+ async function scanRepos(repoRoots) {
189
257
  const repoResults = await Promise.allSettled(repoRoots.map(async (repoRoot) => {
190
- const worktrees = await enrichRepo(repoRoot, activeSessions);
258
+ const worktrees = await enrichRepo(repoRoot);
191
259
  return { repoRoot, worktrees };
192
260
  }));
193
- const repos = repoResults.flatMap((result) => {
261
+ return repoResults.flatMap((result) => {
194
262
  if (result.status === 'rejected') {
195
263
  console.warn(`[WorktreeService] Failed to enrich repo:`, result.reason);
196
264
  return [];
@@ -204,7 +272,33 @@ async function getWorktreeList(repoRoots, activeSessions) {
204
272
  worktrees,
205
273
  }];
206
274
  });
207
- const sortedRepos = [...repos].sort((a, b) => {
275
+ }
276
+ async function runBackgroundEnrichment(repoRoots, repoRootsKey) {
277
+ try {
278
+ const enriched = await Promise.race([
279
+ scanRepos(repoRoots),
280
+ new Promise((_, reject) => setTimeout(() => reject(new Error('background enrichment timeout')), BACKGROUND_ENRICHMENT_TIMEOUT_MS)),
281
+ ]);
282
+ if (worktreeCache?.repoRootsKey === repoRootsKey) {
283
+ worktreeCache = { ...worktreeCache, enrichedRepos: enriched };
284
+ onEnrichmentComplete?.();
285
+ }
286
+ }
287
+ catch {
288
+ }
289
+ finally {
290
+ backgroundEnrichmentInFlight = false;
291
+ }
292
+ }
293
+ function applyActiveSessionsAndSort(repos, activeSessions) {
294
+ const reposWithActiveSessions = repos.map((repo) => ({
295
+ ...repo,
296
+ worktrees: repo.worktrees.map((wt) => ({
297
+ ...wt,
298
+ activeSessionCount: wt.branch ? (activeSessions.counts.get(wt.branch) ?? 0) : 0,
299
+ })),
300
+ }));
301
+ const sortedRepos = [...reposWithActiveSessions].sort((a, b) => {
208
302
  const aActive = a.worktrees.some(w => w.activeSessionCount > 0) ? 0 : 1;
209
303
  const bActive = b.worktrees.some(w => w.activeSessionCount > 0) ? 0 : 1;
210
304
  if (aActive !== bActive)
@@ -213,3 +307,37 @@ async function getWorktreeList(repoRoots, activeSessions) {
213
307
  });
214
308
  return { repos: sortedRepos };
215
309
  }
310
+ async function getWorktreeList(repoRoots, activeSessions) {
311
+ const repoRootsKey = [...repoRoots].sort().join(',');
312
+ const nowMs = Date.now();
313
+ const isCacheValid = worktreeCache !== null &&
314
+ worktreeCache.repoRootsKey === repoRootsKey &&
315
+ nowMs - worktreeCache.cachedAtMs < WORKTREE_CACHE_TTL_MS;
316
+ if (!isCacheValid) {
317
+ const fastRepoResults = await Promise.allSettled(repoRoots.map(async (repoRoot) => {
318
+ const worktrees = await buildFastWorktrees(repoRoot);
319
+ return { repoRoot, worktrees };
320
+ }));
321
+ const fastRepos = fastRepoResults.flatMap((result) => {
322
+ if (result.status === 'rejected')
323
+ return [];
324
+ const { repoRoot, worktrees } = result.value;
325
+ if (!worktrees || worktrees.length === 0)
326
+ return [];
327
+ return [{ repoName: (0, path_1.basename)(repoRoot), repoRoot, worktrees }];
328
+ });
329
+ worktreeCache = {
330
+ unenrichedRepos: fastRepos,
331
+ enrichedRepos: null,
332
+ cachedAtMs: nowMs,
333
+ repoRootsKey,
334
+ };
335
+ if (!backgroundEnrichmentInFlight) {
336
+ backgroundEnrichmentInFlight = true;
337
+ void runBackgroundEnrichment(repoRoots, repoRootsKey);
338
+ }
339
+ }
340
+ const cache = worktreeCache;
341
+ const repos = cache.enrichedRepos ?? cache.unenrichedRepos;
342
+ return applyActiveSessionsAndSort(repos, activeSessions);
343
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.18.0",
3
+ "version": "3.18.1",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,8 @@
28
28
  "web"
29
29
  ],
30
30
  "scripts": {
31
- "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});\" && tsc -p tsconfig.build.json && npm run console:build",
31
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});\" && tsc -p tsconfig.build.json && npm run console:build && node -e \"require('fs').chmodSync('dist/mcp-server.js',0o755)\"",
32
+ "prepack": "node -e \"require('fs').chmodSync('dist/mcp-server.js',0o755)\"",
32
33
  "console:build": "cd console && npm install && npm run build",
33
34
  "console:dev": "cd console && npm run dev",
34
35
  "build:all": "npm run build",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "coding-task-workflow-agentic",
3
3
  "name": "Agentic Task Dev Workflow (Lean • Notes-First • WorkRail Executor)",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "description": "Use this to implement a software feature or task. Follows a plan-then-execute approach with architecture decisions, invariant tracking, and final verification.",
6
6
  "about": "## Agentic Coding Task Workflow\n\nThis workflow structures the full lifecycle of a software implementation task: from understanding and classifying the work, through architecture decisions and incremental implementation, to final verification and handoff.\n\n### What it does\n\nThe workflow guides an AI agent through a disciplined plan-then-execute process. It begins by analyzing the task to determine complexity, risk, and the right level of rigor (QUICK, STANDARD, or THOROUGH). For non-trivial tasks, it then gathers codebase context, surfaces invariants and non-goals, generates competing design candidates, and selects an approach before writing a single line of code. Implementation proceeds slice by slice, with built-in verification gates after each slice. A final integration verification pass confirms acceptance criteria are met before handoff.\n\n### When to use it\n\nUse this workflow whenever you are implementing a feature, fixing a non-trivial bug, or making an architectural change in a real codebase. It is especially valuable when:\n- The task touches multiple files or systems\n- There is meaningful risk of regressions or invariant violations\n- You want the agent to surface trade-offs and commit to a reasoned design decision rather than guessing\n- You need a resumable, auditable record of what was decided and why\n\nFor quick one-liner fixes or very small changes, the workflow includes a fast path that skips heavyweight planning.\n\n### What it produces\n\n- An `implementation_plan.md` artifact covering the selected approach, vertical slices, test design, and philosophy alignment\n- A `spec.md` for large or high-risk tasks, capturing observable behavior and acceptance criteria\n- Step-level notes in WorkRail that serve as a durable execution log\n- A PR-ready handoff summary with acceptance criteria status, invariant proofs, and follow-up tickets\n\n### How to get good results\n\n- Provide a clear task description and at least partial acceptance criteria before starting\n- If you have coding philosophy or project conventions configured in session rules or Memory MCP, the workflow will apply them automatically as a design lens\n- Let the workflow classify complexity and rigor itself; override only if the classification is clearly wrong\n- For large or high-risk tasks, review the architecture decision step before implementation begins",
7
7
  "examples": [
@@ -14,6 +14,96 @@
14
14
  "recommendedAutonomy": "guided",
15
15
  "recommendedRiskPolicy": "conservative"
16
16
  },
17
+ "assessments": [
18
+ {
19
+ "id": "design-soundness-gate",
20
+ "purpose": "The selected design approach is committed with rationale. No unresolved ambiguity remains about what to build.",
21
+ "dimensions": [
22
+ {
23
+ "id": "design_soundness",
24
+ "purpose": "Design decision is made, tradeoffs are recorded, and there is no remaining ambiguity about the chosen approach.",
25
+ "levels": ["low", "high"]
26
+ }
27
+ ]
28
+ },
29
+ {
30
+ "id": "design-gaps-gate",
31
+ "purpose": "A deliberate scan for unconsidered alternatives, unhandled edge cases, or untracked risks has been completed.",
32
+ "dimensions": [
33
+ {
34
+ "id": "design_gaps",
35
+ "purpose": "Active scan completed: either no material gaps were found, or any found were addressed or explicitly filed.",
36
+ "levels": ["low", "high"]
37
+ }
38
+ ]
39
+ },
40
+ {
41
+ "id": "plan-completeness-gate",
42
+ "purpose": "Every slice has a defined scope and verifiable acceptance criterion. No slice is vague or open-ended.",
43
+ "dimensions": [
44
+ {
45
+ "id": "plan_completeness",
46
+ "purpose": "Slices have clear boundaries and acceptance criteria. The agent knows what done looks like for each.",
47
+ "levels": ["low", "high"]
48
+ }
49
+ ]
50
+ },
51
+ {
52
+ "id": "invariant-clarity-gate",
53
+ "purpose": "Invariants and non-goals are explicit enough to verify against during and after implementation.",
54
+ "dimensions": [
55
+ {
56
+ "id": "invariant_clarity",
57
+ "purpose": "Named invariants are checkable in the implementation. Non-goals are stated and will prevent scope creep.",
58
+ "levels": ["low", "high"]
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "id": "plan-gaps-gate",
64
+ "purpose": "A deliberate scan for missing slices, untracked risks, or acceptance criteria mismatches has been completed.",
65
+ "dimensions": [
66
+ {
67
+ "id": "plan_gaps",
68
+ "purpose": "Active scan completed: either no material gaps were found, or any found were addressed or explicitly filed.",
69
+ "levels": ["low", "high"]
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "id": "build-correctness-gate",
75
+ "purpose": "The implementation compiles and passes all relevant tests.",
76
+ "dimensions": [
77
+ {
78
+ "id": "build_correctness",
79
+ "purpose": "Build succeeds and tests pass. No compilation errors or failing assertions.",
80
+ "levels": ["low", "high"]
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "id": "invariant-preservation-gate",
86
+ "purpose": "Invariants identified during planning still hold in the implemented code.",
87
+ "dimensions": [
88
+ {
89
+ "id": "invariant_preservation",
90
+ "purpose": "Each named invariant from the plan has been verified in the implementation.",
91
+ "levels": ["low", "high"]
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ "id": "implementation-gaps-gate",
97
+ "purpose": "A deliberate scan for gaps, issues, or improvements surfaced during implementation has been completed.",
98
+ "dimensions": [
99
+ {
100
+ "id": "implementation_gaps",
101
+ "purpose": "Active scan completed: gaps found are either fixed inline, filed as follow-up tickets, or explicitly deferred with rationale.",
102
+ "levels": ["low", "high"]
103
+ }
104
+ ]
105
+ }
106
+ ],
17
107
  "preconditions": [
18
108
  "User provides a task description or equivalent objective.",
19
109
  "Agent has codebase read access and can run the tools needed for analysis and validation.",
@@ -164,6 +254,19 @@
164
254
  "text": "Also run `routine-execution-simulation` on the three most likely failure paths before you decide."
165
255
  }
166
256
  ],
257
+ "assessmentRefs": [
258
+ "design-soundness-gate",
259
+ "design-gaps-gate"
260
+ ],
261
+ "assessmentConsequences": [
262
+ {
263
+ "when": { "anyEqualsLevel": "low" },
264
+ "effect": {
265
+ "kind": "require_followup",
266
+ "guidance": "Address whichever gate scored low: design_soundness low -- the design decision is still ambiguous; commit to an approach and record the rationale before proceeding. design_gaps low -- the gap scan was not completed or found unaddressed gaps; either resolve them or explicitly file them before proceeding."
267
+ }
268
+ }
269
+ ],
167
270
  "requireConfirmation": {
168
271
  "or": [
169
272
  {
@@ -251,6 +354,20 @@
251
354
  "not_equals": "Small"
252
355
  },
253
356
  "prompt": "Turn the decision into a plan someone else could execute without guessing.\n\nUpdate `implementation_plan.md`.\n\nIt should cover:\n1. Problem statement\n2. Acceptance criteria (mirror `spec.md` if it exists; `spec.md` owns observable behavior)\n3. Non-goals\n4. Philosophy-driven constraints\n5. Invariants\n6. Selected approach + rationale + runner-up\n7. Vertical slices\n8. Work packages only if they actually help\n9. Test design\n10. Risk register\n11. PR packaging strategy\n12. Philosophy alignment per slice:\n - [principle] -> [satisfied / tension / violated + 1-line why]\n\nCapture:\n- `implementationPlan`\n- `slices`\n- `testDesign`\n- `estimatedPRCount`\n- `followUpTickets` (initialize if needed)\n- `unresolvedUnknownCount` — count of open issues that would materially affect implementation quality\n- `planConfidenceBand` — Low / Medium / High\n\nThe plan is the deliverable for this step. Do not implement anything -- not a \"quick win\", not a file read that bleeds into edits, nothing. Execution begins in Phase 6, one slice at a time. If you find yourself writing code or editing source files right now, stop immediately.",
357
+ "assessmentRefs": [
358
+ "plan-completeness-gate",
359
+ "invariant-clarity-gate",
360
+ "plan-gaps-gate"
361
+ ],
362
+ "assessmentConsequences": [
363
+ {
364
+ "when": { "anyEqualsLevel": "low" },
365
+ "effect": {
366
+ "kind": "require_followup",
367
+ "guidance": "Address whichever gate scored low: plan_completeness low -- one or more slices lack clear boundaries or verifiable acceptance criteria; sharpen them before implementation begins. invariant_clarity low -- invariants or non-goals are too vague to verify against; make them concrete. plan_gaps low -- the gap scan was not completed or found unaddressed gaps; resolve or file them before proceeding."
368
+ }
369
+ }
370
+ ],
254
371
  "requireConfirmation": false
255
372
  },
256
373
  {
@@ -470,6 +587,20 @@
470
587
  "id": "phase-7b-fix-and-summarize",
471
588
  "title": "Synthesize Findings, Fix, and Re-Verify",
472
589
  "prompt": "Read `final-verification-findings.md` and decide what actually needs fixing.\n\nDon't rubber-stamp it. The verifier is evidence, not the decision.\n\nIf `spec.md` exists, use it as the verification anchor and make sure every acceptance criterion is actually met.\n\nThis loop is verify, fix, then re-verify. If you fix anything here, the next pass exists to prove the fixes worked.\n\nSynthesize the verification output explicitly:\n- what the verifier found\n- what you agree with\n- what you reject and why\n- what changed because of the fixes\n\nFor any finding that changes final acceptance, classify it as:\n- `Confirmed`: you checked it against primary evidence (code, spec, tests/build, or direct workflow context)\n- `Plausible`: interesting, but not verified enough to accept or block final signoff yet\n- `Rejected`: contradicted by fuller context or direct evidence\n\nSubagent agreement alone is not enough for `Confirmed`.\n\nFix what has to be fixed now, rerun the affected verification, and update:\n- `implementation_plan.md` if the execution shape changed\n- `spec.md` if acceptance criteria, observable behavior, or external contracts changed\n\nCapture:\n- `integrationFindings`\n- `integrationPassed`\n- `regressionDetected`",
590
+ "assessmentRefs": [
591
+ "build-correctness-gate",
592
+ "invariant-preservation-gate",
593
+ "implementation-gaps-gate"
594
+ ],
595
+ "assessmentConsequences": [
596
+ {
597
+ "when": { "anyEqualsLevel": "low" },
598
+ "effect": {
599
+ "kind": "require_followup",
600
+ "guidance": "Address whichever gate scored low: build_correctness low -- the build or tests are still failing; fix them before this step can complete. invariant_preservation low -- one or more invariants from the plan are violated; fix the implementation. implementation_gaps low -- the gap scan was not completed or found unaddressed gaps; fix them inline, file as follow-up tickets, or explicitly defer with rationale."
601
+ }
602
+ }
603
+ ],
473
604
  "requireConfirmation": false
474
605
  },
475
606
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "mr-review-workflow-agentic",
3
3
  "name": "MR Review Workflow (Lean v2 \u2022 Notes-First \u2022 Evidence-Driven Reviewer Families)",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "description": "Lean v2 MR review workflow. Merges intake, missing-input gating, context gathering, and re-triage into one structured front phase, then drives review through a shared fact packet, parallel reviewer families, contradiction-driven synthesis, and evidence-first final validation.",
6
6
  "about": "## MR Review Workflow\n\nThis workflow conducts a structured, evidence-driven code review of a merge request or pull request. It is designed for cases where you want a thorough, audit-quality review rather than a quick glance -- particularly when the change touches critical surfaces, spans many files, or carries real production risk.\n\n**What it does:**\nThe workflow locates and bounds the review target, enriches it with PR context and ticket intent, classifies the change by risk and shape, then runs parallel \"reviewer family\" agents (covering correctness, architecture, runtime risk, tests/docs, and more) from a shared neutral fact packet. It reconciles contradictions between reviewer families, stress-tests the recommendation with adversarial validators, and produces a final handoff with severity-classified findings and ready-to-post MR comments.\n\n**When to use it:**\n- Before merging a PR that touches auth, data models, APIs, or critical paths\n- When you want independent perspectives on a change without the noise of an unstructured review\n- When the change is large or the reviewer is unfamiliar with the surrounding code\n- When you need a reproducible audit trail for compliance or team review processes\n\n**What it produces:**\nA final review recommendation (approve / request changes / needs discussion) with a confidence band, severity-graded findings (Critical / Major / Minor / Nit), ready-to-post MR comments, a coverage ledger showing which review domains were checked, and an honest disclosure of any context that could not be recovered.\n\n**How to get good results:**\nProvide the PR URL, branch name, or diff. The workflow can recover most context on its own -- ticket links, repo patterns, policy docs -- but if the change has non-obvious intent, a one-sentence description of the goal helps calibrate review sensitivity. The workflow will not post comments or approve/reject without explicit instruction.",
7
7
  "examples": [
@@ -19,22 +19,34 @@
19
19
  ],
20
20
  "assessments": [
21
21
  {
22
- "id": "review_readiness_gate",
23
- "purpose": "Assess whether the review is ready to hand off across three orthogonal dimensions. Each must be high independently -- strength in one cannot compensate for weakness in another.",
22
+ "id": "evidence-quality-gate",
23
+ "purpose": "Key findings are backed by specific code references, line numbers, or concrete observations -- not intuition or pattern-matching alone.",
24
24
  "dimensions": [
25
25
  {
26
26
  "id": "evidence_quality",
27
- "purpose": "Key findings are backed by specific code references, line numbers, or concrete observations -- not intuition or pattern-matching alone.",
27
+ "purpose": "Each finding cites a specific file, function, or line. No finding relies on intuition or pattern-matching without concrete grounding.",
28
28
  "levels": ["low", "high"]
29
- },
29
+ }
30
+ ]
31
+ },
32
+ {
33
+ "id": "coverage-completeness-gate",
34
+ "purpose": "All relevant review domains have been adequately checked for this change. No material blind spots remain unacknowledged.",
35
+ "dimensions": [
30
36
  {
31
37
  "id": "coverage_completeness",
32
- "purpose": "All relevant review domains have been adequately checked for this change. No material blind spots remain unacknowledged.",
38
+ "purpose": "All material review domains are checked or explicitly acknowledged as gaps in the coverage ledger.",
33
39
  "levels": ["low", "high"]
34
- },
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "id": "contradiction-resolution-gate",
45
+ "purpose": "Material contradictions and competing interpretations are resolved or explicitly acknowledged with a clear rationale for the chosen position.",
46
+ "dimensions": [
35
47
  {
36
48
  "id": "contradiction_resolution",
37
- "purpose": "Material contradictions and competing interpretations are resolved or explicitly acknowledged with a clear rationale for the chosen position.",
49
+ "purpose": "Every material contradiction is resolved by evidence or explicitly acknowledged with a stated position and rationale.",
38
50
  "levels": ["low", "high"]
39
51
  }
40
52
  ]
@@ -260,7 +272,9 @@
260
272
  ]
261
273
  },
262
274
  "assessmentRefs": [
263
- "review_readiness_gate"
275
+ "evidence-quality-gate",
276
+ "coverage-completeness-gate",
277
+ "contradiction-resolution-gate"
264
278
  ],
265
279
  "assessmentConsequences": [
266
280
  {
@@ -269,7 +283,7 @@
269
283
  },
270
284
  "effect": {
271
285
  "kind": "require_followup",
272
- "guidance": "Address whichever dimensions scored low: evidence_quality low -- anchor each finding to a specific file, function, or line; remove findings without concrete grounding. coverage_completeness low -- investigate uncovered domains or explicitly acknowledge gaps in the ledger. contradiction_resolution low -- resolve each contradiction or explicitly state your position with rationale."
286
+ "guidance": "Address whichever gate scored low: evidence_quality low -- anchor each finding to a specific file, function, or line; remove findings without concrete grounding. coverage_completeness low -- investigate uncovered domains or explicitly acknowledge gaps in the coverage ledger. contradiction_resolution low -- resolve each contradiction or explicitly state your position with rationale."
273
287
  }
274
288
  }
275
289
  ],
@@ -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-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--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;--tw-ease: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-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-3xl:48rem;--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);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--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{.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing) * 3)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.z-20{z-index:20}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-1{margin-inline:calc(var(--spacing) * 1)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-\[3px\]{margin-top:3px}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-\[506px\]{height:506px}.h-full{height:100%}.h-px{height:1px}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-screen{min-height:100vh}.w-0\.5{width:calc(var(--spacing) * .5)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-1\/2{width:50%}.w-2\/3{width:66.6667%}.w-3{width:calc(var(--spacing) * 3)}.w-4\/6{width:66.6667%}.w-5\/6{width:83.3333%}.w-6{width:calc(var(--spacing) * 6)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-64{width:calc(var(--spacing) * 64)}.w-\[560px\]{width:560px}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[360px\]{max-width:360px}.max-w-\[calc\(92vw-12px\)\]{max-width:calc(92vw - 12px)}.max-w-lg{max-width:var(--container-lg)}.max-w-prose{max-width:65ch}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[44px\]{min-width:44px}.min-w-\[120px\]{min-width:120px}.min-w-\[200px\]{min-width:200px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-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,)}.animate-pulse{animation:var(--animate-pulse)}.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)}.gap-8{gap:calc(var(--spacing) * 8)}: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-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * 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-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-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)))}:where(.space-y-px>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(1px * var(--tw-space-y-reverse));margin-block-end:calc(1px * calc(1 - var(--tw-space-y-reverse)))}.gap-x-8{column-gap:calc(var(--spacing) * 8)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-center{align-self:center}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.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)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-\[rgba\(255\,255\,255\,0\.06\)\]{border-color:#ffffff0f}.border-\[var\(--blocked\)\]{border-color:var(--blocked)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/20{border-color:#3080ff33}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/20{border-color:color-mix(in oklab,var(--color-blue-500) 20%,transparent)}}.border-green-500\/20{border-color:#00c75833}@supports (color:color-mix(in lab,red,red)){.border-green-500\/20{border-color:color-mix(in oklab,var(--color-green-500) 20%,transparent)}}.border-orange-500\/20{border-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.border-orange-500\/20{border-color:color-mix(in oklab,var(--color-orange-500) 20%,transparent)}}.bg-\[rgba\(0\,0\,0\,0\.18\)\]{background-color:#0000002e}.bg-\[rgba\(10\,10\,10\,0\.38\)\]{background-color:#0a0a0a61}.bg-\[rgba\(255\,255\,255\,0\.02\)\]{background-color:#ffffff05}.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)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--border\)\]{background-color:var(--border)}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/10{background-color:color-mix(in oklab,var(--color-blue-500) 10%,transparent)}}.bg-green-500\/10{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/10{background-color:color-mix(in oklab,var(--color-green-500) 10%,transparent)}}.bg-orange-500\/10{background-color:#fe6e001a}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/10{background-color:color-mix(in oklab,var(--color-orange-500) 10%,transparent)}}.bg-transparent{background-color:#0000}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.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-5{padding-inline:calc(var(--spacing) * 5)}.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-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.py-20{padding-block:calc(var(--spacing) * 20)}.py-32{padding-block:calc(var(--spacing) * 32)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom: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-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.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-\[0\.16em\]{--tw-tracking:.16em;letter-spacing:.16em}.tracking-\[0\.18em\]{--tw-tracking:.18em;letter-spacing:.18em}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.text-ellipsis{text-overflow:ellipsis}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[var\(--accent\)\]{color:var(--accent)}.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-blue-400{color:var(--color-blue-400)}.text-green-400{color:var(--color-green-400)}.text-orange-400{color:var(--color-orange-400)}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.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}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-\[var\(--accent\)\]{--tw-ring-color:var(--accent)}.ring-blue-500\/40{--tw-ring-color:#3080ff66}@supports (color:color-mix(in lab,red,red)){.ring-blue-500\/40{--tw-ring-color:color-mix(in oklab, var(--color-blue-500) 40%, transparent)}}.ring-orange-500\/40{--tw-ring-color:#fe6e0066}@supports (color:color-mix(in lab,red,red)){.ring-orange-500\/40{--tw-ring-color:color-mix(in oklab, var(--color-orange-500) 40%, transparent)}}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);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,)}.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{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;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}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}@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\:border-\[var\(--accent-strong\)\]:hover{border-color:var(--accent-strong)}.hover\:border-\[var\(--blocked\)\]:hover{border-color:var(--blocked)}.hover\:bg-\[var\(--bg-card\)\]:hover{background-color:var(--bg-card)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-blue-500\/20:hover{background-color:#3080ff33}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-500\/20:hover{background-color:color-mix(in oklab,var(--color-blue-500) 20%,transparent)}}.hover\:bg-orange-500\/20:hover{background-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.hover\:bg-orange-500\/20:hover{background-color:color-mix(in oklab,var(--color-orange-500) 20%,transparent)}}.hover\:text-\[var\(--accent-hover\)\]:hover{color:var(--accent-hover)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:text-\[var\(--text-secondary\)\]:hover{color:var(--text-secondary)}}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-\[var\(--accent\)\]:focus-visible{--tw-ring-color:var(--accent)}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:block{display:block}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.xl\:grid-cols-\[minmax\(0\,1\.15fr\)_minmax\(0\,0\.85fr\)\]{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr)}}}: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;--accent-strong:#00f0ff;--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}.workrail-minimap-node{opacity:1}*{box-sizing:border-box}.console-blueprint-grid{background-image:linear-gradient(#41577624 1px,#0000 1px),linear-gradient(90deg,#41577624 1px,#0000 1px);background-size:24px 24px}.lineage-scroll-surface{background:var(--bg-primary)}.lineage-scroll-surface .react-flow,.lineage-scroll-surface .react-flow__renderer,.lineage-scroll-surface .react-flow__viewport,.lineage-scroll-surface .react-flow__pane{background:0 0!important}.workrail-current-lineage-node{isolation:isolate;animation:3.2s ease-in-out infinite workrail-current-node-pulse;position:relative;box-shadow:inset 0 0 0 1px #00f0ff38,inset 0 0 22px #00f0ff12}.workrail-current-lineage-node:after{content:"";pointer-events:none;border:1px solid #00f0ff47;animation:3.2s ease-in-out infinite workrail-current-node-outline;position:absolute;inset:-1px}.workrail-current-lineage-edge .react-flow__edge-path{stroke-dasharray:7 9;animation:1.35s linear infinite workrail-current-edge-flow}.workrail-selected-lineage-node{isolation:isolate;position:relative;box-shadow:inset 0 0 0 1px #f4b34161,0 0 0 1px #f4b34147,0 0 18px #f4b3411a}@keyframes workrail-current-node-pulse{0%,to{filter:brightness();transform:translateZ(0)}50%{filter:brightness(1.1);transform:translateZ(0)}}@keyframes workrail-current-node-outline{0%,to{opacity:.34;box-shadow:0 0 #00f0ff00,inset 0 0 #00f0ff00}50%{opacity:.82;box-shadow:0 0 20px #00f0ff2e,inset 0 0 18px #00f0ff14}}@keyframes workrail-current-edge-flow{to{stroke-dashoffset:-32px}}.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-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@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}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}