@fenglimg/fabric-server 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,21 +2,26 @@ import {
2
2
  AGENTS_MD_RESOURCE_URI,
3
3
  FABRIC_DIR,
4
4
  appendEditIntentAuditEvents,
5
- appendGetRulesAuditEvent,
6
5
  appendLedgerEntry,
6
+ approveHumanLock,
7
7
  atomicWriteText,
8
8
  contextCache,
9
+ getRules,
10
+ loadGetRulesContext,
11
+ normalizeRulesPath,
9
12
  readAgentsMeta,
10
13
  readHumanLock,
14
+ readHumanLockEntry,
11
15
  resolveProjectRoot,
16
+ resolveRulesForPath,
12
17
  runDoctorAuditReport,
13
18
  runDoctorReport,
14
19
  sha256
15
- } from "./chunk-GU7AMRM3.js";
20
+ } from "./chunk-E3RZ276F.js";
16
21
 
17
22
  // src/index.ts
18
- import { readFile as readFile2 } from "fs/promises";
19
- import { join as join3, resolve } from "path";
23
+ import { readFile } from "fs/promises";
24
+ import { join as join2, resolve } from "path";
20
25
  import { fileURLToPath } from "url";
21
26
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
27
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -98,128 +103,13 @@ function registerAppendIntent(server) {
98
103
 
99
104
  // src/tools/get-rules.ts
100
105
  import { z as z2 } from "zod";
101
-
102
- // src/services/get-rules.ts
103
- import { readFile } from "fs/promises";
104
- import { join } from "path";
105
- import { minimatch } from "minimatch";
106
- var PRIORITY_ORDER = {
107
- high: 0,
108
- medium: 1,
109
- low: 2
110
- };
111
- async function getRules(projectRoot, input) {
112
- const context = await loadGetRulesContext(projectRoot);
113
- const stale = input.client_hash !== void 0 && input.client_hash !== context.meta.revision;
114
- const rules = await resolveRulesForPath(projectRoot, context, input.path);
115
- const result = {
116
- revision_hash: context.meta.revision,
117
- stale,
118
- rules
119
- };
120
- try {
121
- await appendGetRulesAuditEvent(projectRoot, {
122
- path: input.path,
123
- client_hash: input.client_hash
124
- });
125
- } catch {
126
- }
127
- return result;
128
- }
129
- async function loadGetRulesContext(projectRoot) {
130
- const cached = contextCache.get("context", projectRoot);
131
- if (cached !== void 0) {
132
- return cached;
133
- }
134
- const meta = await readAgentsMeta(projectRoot);
135
- const l0Content = await readFile(join(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
136
- const humanLockedNearby = (await readHumanLock(projectRoot)).map((entry) => ({
137
- file: entry.file,
138
- excerpt: JSON.stringify(entry)
139
- }));
140
- const context = {
141
- meta,
142
- l0Content,
143
- humanLockedNearby
144
- };
145
- contextCache.set("context", projectRoot, context);
146
- return context;
147
- }
148
- async function resolveRulesForPath(projectRoot, context, path, options = {}) {
149
- const loadedRules = await loadRulesForPath(projectRoot, context.meta, path);
150
- const { L1, L2 } = partitionRulesByLevel(loadedRules, options.dedupeByPath ?? false);
151
- return {
152
- L0: context.l0Content,
153
- L1,
154
- L2,
155
- human_locked_nearby: context.humanLockedNearby
156
- };
157
- }
158
- function normalizeRulesPath(value) {
159
- return value.replaceAll("\\", "/");
160
- }
161
- function classifyNode(nodeId) {
162
- if (nodeId.startsWith("L1/")) {
163
- return "L1";
164
- }
165
- if (nodeId.startsWith("L2/")) {
166
- return "L2";
167
- }
168
- return null;
169
- }
170
- async function loadRulesForPath(projectRoot, meta, path) {
171
- const requestedPath = normalizeRulesPath(path);
172
- const matchedNodes = Object.entries(meta.nodes).filter(([, node]) => minimatch(requestedPath, normalizeRulesPath(node.scope_glob), { dot: true })).sort((left, right) => {
173
- const [leftId, leftNode] = left;
174
- const [rightId, rightNode] = right;
175
- const priorityDelta = PRIORITY_ORDER[leftNode.priority] - PRIORITY_ORDER[rightNode.priority];
176
- return priorityDelta !== 0 ? priorityDelta : leftId.localeCompare(rightId);
177
- });
178
- return await Promise.all(
179
- matchedNodes.map(async ([nodeId, node]) => ({
180
- level: classifyNode(nodeId),
181
- entry: {
182
- path: node.file,
183
- content: await readFile(join(projectRoot, node.file), "utf8")
184
- }
185
- }))
186
- );
187
- }
188
- function partitionRulesByLevel(loadedRules, dedupeByPath) {
189
- const l1 = [];
190
- const l2 = [];
191
- for (const rule of loadedRules) {
192
- if (rule.level === "L1") {
193
- l1.push(rule.entry);
194
- continue;
195
- }
196
- if (rule.level === "L2") {
197
- l2.push(rule.entry);
198
- }
199
- }
200
- return {
201
- L1: dedupeByPath ? dedupeEntriesByPath(l1) : l1,
202
- L2: dedupeByPath ? dedupeEntriesByPath(l2) : l2
203
- };
204
- }
205
- function dedupeEntriesByPath(entries) {
206
- const seenPaths = /* @__PURE__ */ new Set();
207
- return entries.filter((entry) => {
208
- if (seenPaths.has(entry.path)) {
209
- return false;
210
- }
211
- seenPaths.add(entry.path);
212
- return true;
213
- });
214
- }
215
-
216
- // src/tools/get-rules.ts
217
106
  var inputSchema2 = {
218
107
  path: z2.string().describe("Target file path to query rules for"),
219
108
  client_hash: z2.string().optional().describe("Revision hash from prior fab_get_rules response; enables stale detection")
220
109
  };
221
110
  var rulesEntrySchema = z2.object({ path: z2.string(), content: z2.string() });
222
111
  var humanLockedSchema = z2.object({ file: z2.string(), excerpt: z2.string() });
112
+ var descriptionStubSchema = z2.object({ path: z2.string(), description: z2.string() });
223
113
  var outputSchema2 = z2.object({
224
114
  revision_hash: z2.string(),
225
115
  stale: z2.boolean(),
@@ -227,7 +117,8 @@ var outputSchema2 = z2.object({
227
117
  L0: z2.string(),
228
118
  L1: z2.array(rulesEntrySchema),
229
119
  L2: z2.array(rulesEntrySchema),
230
- human_locked_nearby: z2.array(humanLockedSchema)
120
+ human_locked_nearby: z2.array(humanLockedSchema),
121
+ description_stubs: z2.array(descriptionStubSchema).optional()
231
122
  })
232
123
  });
233
124
  function registerGetRules(server) {
@@ -331,9 +222,9 @@ import { z as z4 } from "zod";
331
222
 
332
223
  // src/services/update-registry.ts
333
224
  import { agentsMetaNodeSchema } from "@fenglimg/fabric-shared";
334
- import { join as join2 } from "path";
225
+ import { join } from "path";
335
226
  async function updateRegistry(projectRoot, input) {
336
- const metaPath = join2(projectRoot, FABRIC_DIR, "agents.meta.json");
227
+ const metaPath = join(projectRoot, FABRIC_DIR, "agents.meta.json");
337
228
  const currentMeta = await readAgentsMeta(projectRoot);
338
229
  const nextMeta = applyRegistryOperation(currentMeta, input.op, input.node_id, input.data);
339
230
  const newRevision = computeRevision(nextMeta);
@@ -448,7 +339,7 @@ function formatError(error) {
448
339
  function createFabricServer() {
449
340
  const server = new McpServer({
450
341
  name: "fabric-context-server",
451
- version: "1.4.0"
342
+ version: "1.5.0"
452
343
  });
453
344
  registerGetRules(server);
454
345
  registerPlanContext(server);
@@ -463,7 +354,7 @@ function createFabricServer() {
463
354
  },
464
355
  async (_uri) => {
465
356
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
466
- const content = await readFile2(join3(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
357
+ const content = await readFile(join2(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
467
358
  return {
468
359
  contents: [
469
360
  {
@@ -483,7 +374,7 @@ async function startStdioServer() {
483
374
  await server.connect(transport);
484
375
  }
485
376
  async function startHttpServer(options) {
486
- const { createFabricHttpApp } = await import("./http-7OHSKCPN.js");
377
+ const { createFabricHttpApp } = await import("./http-SK2HEFK4.js");
487
378
  const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
488
379
  const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
489
380
  return await new Promise((resolveServer, rejectServer) => {
@@ -510,7 +401,10 @@ if (isMainModule) {
510
401
  }
511
402
  export {
512
403
  AGENTS_MD_RESOURCE_URI,
404
+ approveHumanLock,
513
405
  createFabricServer,
406
+ readHumanLock,
407
+ readHumanLockEntry,
514
408
  runDoctorAuditReport,
515
409
  runDoctorReport,
516
410
  startHttpServer,
@@ -0,0 +1 @@
1
+ :root{--color-surface-canvas: #0b1016;--color-surface-panel: #0f172a;--color-surface-raised: #1e293b;--color-surface-overlay: #334155;--color-surface-code: #020617;--color-border-subtle: #1e293b;--color-border-default: #334155;--color-border-strong: #475569;--color-text-primary: #f8fafc;--color-text-secondary: #cbd5e1;--color-text-muted: #94a3b8;--color-text-dim: #64748b;--color-text-mono: #e2e8f0;--color-state-locked-bg: rgba(245, 158, 11, .08);--color-state-locked-border: #b45309;--color-state-locked-text: #fbbf24;--color-state-locked-accent: #f59e0b;--color-state-stale-bg: rgba(220, 38, 38, .1);--color-state-stale-border: #991b1b;--color-state-stale-text: #f87171;--color-state-stale-accent: #ef4444;--color-state-drift-bg: rgba(234, 88, 12, .1);--color-state-drift-border: #c2410c;--color-state-drift-text: #fb923c;--color-state-drift-accent: #ea580c;--color-state-approved-bg: rgba(34, 197, 94, .1);--color-state-approved-border: #166534;--color-state-approved-text: #4ade80;--color-state-approved-accent: #22c55e;--color-state-pending-bg: rgba(100, 116, 139, .1);--color-state-pending-border: #475569;--color-state-pending-text: #94a3b8;--color-state-pending-accent: #64748b;--color-source-ai-bg: rgba(99, 102, 241, .12);--color-source-ai-border: #4338ca;--color-source-ai-text: #a5b4fc;--color-source-ai-accent: #6366f1;--color-source-human-bg: rgba(20, 184, 166, .12);--color-source-human-border: #0f766e;--color-source-human-text: #5eead4;--color-source-human-accent: #14b8a6;--color-action-primary: #22c55e;--color-action-primary-hover: #16a34a;--color-action-primary-text: #052e16;--color-action-neutral: #334155;--color-action-neutral-hover: #475569;--color-action-danger: #dc2626;--font-family-mono: "Space Mono", "JetBrains Mono", "SF Mono", "Monaco", "Consolas", ui-monospace, monospace;--font-family-sans: "Inter", -apple-system, "Segoe UI", system-ui, sans-serif;--font-size-xs: 11px;--font-size-sm: 12px;--font-size-base: 13px;--font-size-md: 14px;--font-size-lg: 16px;--font-size-xl: 20px;--font-size-2xl: 24px;--font-weight-regular: 400;--font-weight-medium: 500;--font-weight-bold: 700;--line-height-tight: 1.25;--line-height-base: 1.5;--line-height-loose: 1.65;--letter-spacing-mono: 0;--letter-spacing-sans: -.01em;--letter-spacing-chip: .04em;--space-0: 0;--space-0-5: 2px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--space-16: 64px;--radius-none: 0;--radius-sm: 3px;--radius-md: 6px;--radius-lg: 8px;--radius-pill: 999px;--shadow-card: 0 1px 2px rgba(0, 0, 0, .3), 0 1px 1px rgba(0, 0, 0, .15);--shadow-raised: 0 4px 12px rgba(0, 0, 0, .4);--shadow-focus-ring: 0 0 0 2px #0f172a, 0 0 0 4px #a5b4fc;--motion-duration-fast: .12s;--motion-duration-base: .18s;--motion-duration-slow: .28s;--motion-easing-standard: cubic-bezier(.4, 0, .2, 1);--motion-easing-decel: cubic-bezier(0, 0, .2, 1);--z-base: 0;--z-sticky: 10;--z-dropdown: 20;--z-popover: 30;--z-modal: 50;--z-toast: 100;--layout-sidebar-width: 240px;--layout-sidebar-width-collapsed: 64px;--layout-header-height: 48px;--layout-container-max-width: 1440px;--layout-container-padding: 24px}*{box-sizing:border-box}html,body{height:100%;margin:0}body{background:radial-gradient(circle at top left,rgba(99,102,241,.12),transparent 32rem),linear-gradient(135deg,rgba(15,23,42,.9),var(--color-surface-canvas) 52%);color:var(--color-text-secondary);font-family:var(--font-family-sans);font-size:var(--font-size-base);line-height:var(--line-height-base);-webkit-font-smoothing:antialiased}button,input{font:inherit}button:focus-visible,a:focus-visible,input:focus-visible,[tabindex]:focus-visible{box-shadow:var(--shadow-focus-ring);outline:0}.app-shell{display:grid;grid-template-columns:var(--layout-sidebar-width) minmax(0,1fr);min-height:100vh}.sidebar{background:color-mix(in srgb,var(--color-surface-panel) 92%,black);border-right:1px solid var(--color-border-subtle);padding:var(--space-4)}.brand{align-items:center;color:var(--color-text-primary);display:flex;font-family:var(--font-family-mono);font-size:var(--font-size-md);font-weight:var(--font-weight-bold);gap:var(--space-2);margin-bottom:var(--space-4);padding:var(--space-2)}.brand-logo{align-items:center;border:1px solid var(--color-text-mono);border-radius:var(--radius-sm);color:var(--color-text-mono);display:inline-flex;font-size:10px;height:18px;justify-content:center;width:18px}.brand-version{color:var(--color-text-dim);font-size:10px;font-weight:var(--font-weight-regular);margin-left:auto}.nav-item{align-items:center;border-radius:var(--radius-md);color:var(--color-text-muted);display:grid;font-size:var(--font-size-sm);gap:var(--space-2);grid-template-columns:8px 1fr;margin-bottom:2px;min-height:44px;padding:var(--space-2) var(--space-3);text-decoration:none;transition:background var(--motion-duration-base),color var(--motion-duration-base)}.nav-item:hover,.nav-item.active{background:var(--color-surface-raised);color:var(--color-text-primary)}.nav-item small{color:var(--color-text-dim);font-family:var(--font-family-mono);font-size:10px;grid-column:2;margin-top:-6px}.nav-item .dot{background:var(--color-text-dim);border-radius:50%;height:6px;width:6px}.nav-item.active .dot{background:var(--color-action-primary)}.muted-nav{opacity:.7}.nav-section{color:var(--color-text-dim);font-size:10px;letter-spacing:.08em;padding:var(--space-4) var(--space-3) var(--space-2);text-transform:uppercase}.main{display:flex;flex-direction:column;min-width:0}.header{align-items:center;background:#0f172adb;border-bottom:1px solid var(--color-border-subtle);display:flex;height:var(--layout-header-height);justify-content:space-between;padding:0 var(--space-6)}.breadcrumb,.port-label{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-sm)}.breadcrumb .sep{color:var(--color-text-dim);margin:0 var(--space-1)}.breadcrumb strong{color:var(--color-text-primary);font-weight:var(--font-weight-regular)}.header-actions{align-items:center;display:flex;gap:var(--space-3)}.badge-live{align-items:center;border:1px solid rgba(34,197,94,.3);border-radius:var(--radius-pill);color:var(--color-state-approved-text);display:inline-flex;font-family:var(--font-family-mono);font-size:10px;gap:var(--space-1);padding:2px var(--space-2)}.badge-live.disconnected{border-color:var(--color-state-pending-border);color:var(--color-state-pending-text)}.pulse{background:currentColor;border-radius:50%;height:6px;width:6px}.view{flex:1;overflow:auto;padding:var(--space-6)}.view-header{align-items:flex-end;display:flex;justify-content:space-between;margin-bottom:var(--space-5);position:sticky;top:0;z-index:var(--z-sticky)}.view-title{color:var(--color-text-primary);font-size:var(--font-size-xl);margin:0}.view-subtitle,.muted{color:var(--color-text-muted);font-size:var(--font-size-sm);margin:var(--space-1) 0 0}.view-split{align-items:start;display:grid;gap:var(--space-5);grid-template-columns:minmax(0,2fr) minmax(280px,1fr)}.tree-panel,.detail-panel,.empty-card,.filter-bar{background:var(--color-surface-panel);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-card)}.tree-filter{border-bottom:1px solid var(--color-border-subtle);display:flex;gap:var(--space-3);padding:var(--space-4)}.tree-filter input,.annotate-input{background:var(--color-surface-code);border:1px solid var(--color-border-default);border-radius:var(--radius-md);color:var(--color-text-primary);font-family:var(--font-family-mono);min-height:44px;padding:var(--space-2) var(--space-3);width:100%}.status-line{color:var(--color-text-dim);display:flex;font-family:var(--font-family-mono);font-size:var(--font-size-xs);justify-content:space-between;padding:var(--space-3) var(--space-4)}.tree{font-family:var(--font-family-mono);padding:var(--space-2)}.tree-node{align-items:center;border-radius:var(--radius-md);color:var(--color-text-secondary);cursor:pointer;display:flex;gap:var(--space-2);min-height:40px;padding:var(--space-2) var(--space-3);position:relative;transition:background var(--motion-duration-fast),transform var(--motion-duration-fast)}.tree-node.is-readonly{cursor:default}.tree-node:hover:not(.is-readonly),.tree-node.is-selected{background:var(--color-surface-raised)}.tree-node.is-selected{box-shadow:inset 2px 0 0 var(--color-source-ai-accent)}.tree-node.is-locked{background:var(--color-state-locked-bg);box-shadow:inset 3px 0 0 var(--color-state-locked-border)}.tree-node.is-stale:before{background:var(--color-state-stale-text);border-radius:50%;content:"";height:6px;left:-2px;position:absolute;top:50%;transform:translateY(-50%);width:6px}.tree-caret{color:var(--color-text-dim);display:inline-flex;justify-content:center;transition:transform var(--motion-duration-base);width:14px}.tree-node.is-expanded .tree-caret{transform:rotate(90deg)}.tree-label{color:var(--color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-level-1 .tree-label{color:var(--color-text-mono)}.tree-level-2 .tree-label{color:var(--color-text-secondary);font-weight:var(--font-weight-regular)}.tree-meta{align-items:center;display:flex;gap:var(--space-2);margin-left:auto}.tree-hash{color:var(--color-text-dim);font-size:var(--font-size-xs)}.tree-children{border-left:1px dashed var(--color-border-subtle);margin-left:18px;padding-left:var(--space-2)}.badge,.drift-pill,.source-badge,.filter-chip{align-items:center;border:1px solid transparent;border-radius:var(--radius-pill);display:inline-flex;font-family:var(--font-family-mono);font-size:10px;gap:var(--space-1);letter-spacing:var(--letter-spacing-chip);line-height:1;padding:3px 7px;text-transform:uppercase}.badge-level{background:var(--color-surface-overlay);color:var(--color-text-muted)}.badge-locked{background:var(--color-state-locked-bg);border-color:var(--color-state-locked-border);color:var(--color-state-locked-text)}.drift-dot{border-radius:50%;display:inline-block;height:6px;width:6px}.drift-pill,.drift-banner{background:var(--color-state-pending-bg);border-color:var(--color-state-pending-border);color:var(--color-state-pending-text)}.drift-drift{background:var(--color-state-drift-bg);border-color:var(--color-state-drift-border);color:var(--color-state-drift-text)}.drift-stale{background:var(--color-state-stale-bg);border-color:var(--color-state-stale-border);color:var(--color-state-stale-text)}.drift-locked{background:var(--color-state-locked-bg);border-color:var(--color-state-locked-border);color:var(--color-state-locked-text)}.drift-ok{background:var(--color-state-approved-bg);border-color:var(--color-state-approved-border);color:var(--color-state-approved-text)}.drift-banner{border-radius:var(--radius-md);display:flex;gap:var(--space-2);margin-bottom:var(--space-4);padding:var(--space-3)}.detail-panel{padding:var(--space-5);position:sticky;top:var(--space-6)}.detail-panel h3{color:var(--color-text-muted);font-size:var(--font-size-sm);letter-spacing:.06em;margin:0 0 var(--space-3);text-transform:uppercase}.kv{font-family:var(--font-family-mono);font-size:var(--font-size-sm)}.kv-row{border-bottom:1px dashed var(--color-border-subtle);display:flex;gap:var(--space-3);justify-content:space-between;padding:var(--space-1) 0}.kv-key{color:var(--color-text-muted)}.kv-value{color:var(--color-text-primary);overflow-wrap:anywhere;text-align:right}.code,.preview-body{background:var(--color-surface-code);color:var(--color-text-mono);font-family:var(--font-family-mono)}.code{border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);margin-top:var(--space-3);overflow:auto;padding:var(--space-3)}.filter-bar{align-items:center;display:flex;gap:var(--space-2);margin-bottom:var(--space-4);padding:var(--space-3)}.filter-chip,.ghost-button{background:var(--color-surface-raised);border-color:var(--color-border-subtle);color:var(--color-text-muted);cursor:pointer;min-height:40px}.filter-chip.active,.filter-chip:hover,.ghost-button:hover{border-color:var(--color-border-default);color:var(--color-text-primary)}.filter-chip .count{background:var(--color-surface-code);border-radius:var(--radius-pill);padding:1px 5px}.filter-date{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-sm);margin-left:auto}.topology-toolbar,.topology-status{background:var(--color-surface-panel);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);margin-bottom:var(--space-4)}.topology-split{grid-template-columns:minmax(0,1.2fr) minmax(320px,1fr)}.topology-card{background:var(--color-surface-panel);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-card);min-height:280px;overflow:hidden}.topology-card-head{align-items:start;border-bottom:1px solid var(--color-border-subtle);display:flex;gap:var(--space-3);justify-content:space-between;padding:var(--space-4)}.topology-card-head h3{color:var(--color-text-primary);margin:0}.coverage-grid,.reason-list{display:grid;gap:var(--space-3);padding:var(--space-4)}.coverage-row,.reason-card,.module-placeholder{background:color-mix(in srgb,var(--color-surface-raised) 88%,transparent);border:1px solid var(--color-border-subtle);border-radius:var(--radius-md)}.coverage-row{padding:var(--space-3)}.coverage-row-main,.reason-card-head,.reason-card-meta{align-items:center;display:flex;gap:var(--space-2);justify-content:space-between}.coverage-row-meta,.reason-card-meta,.reason-description{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs)}.coverage-row-meta,.reason-card-meta{margin-top:var(--space-2)}.coverage-path,.reason-card strong{color:var(--color-text-primary);font-family:var(--font-family-mono);overflow-wrap:anywhere}.coverage-chip-full,.reason-tier-always{background:#22c55e1f;border-color:#22c55e4d;color:#86efac}.coverage-chip-partial,.reason-tier-path{background:#facc151f;border-color:#facc154d;color:#fde68a}.coverage-chip-none,.reason-tier-description{background:#94a3b81f;border-color:#94a3b84d;color:#cbd5e1}.coverage-full{box-shadow:inset 2px 0 #22c55e73}.coverage-partial{box-shadow:inset 2px 0 #facc1573}.coverage-none{box-shadow:inset 2px 0 #94a3b840}.reason-card{padding:var(--space-3)}.reason-description{line-height:1.6;margin:var(--space-2) 0 0}.module-placeholder{padding:var(--space-6)}.module-placeholder strong{color:var(--color-text-primary);display:block;font-family:var(--font-family-mono);margin-bottom:var(--space-2)}.module-placeholder p{color:var(--color-text-muted);margin:0}.filter-label{color:var(--color-text-dim);font-size:var(--font-size-xs);letter-spacing:.08em;text-transform:uppercase}.history-toolbar{margin-bottom:var(--space-5)}.history-slider{flex:1}.history-layout{grid-template-columns:minmax(340px,1.05fr) minmax(0,1.4fr)}.history-timeline-panel{min-width:0}.history-timeline-list{display:grid;gap:var(--space-3);max-height:calc(100vh - 280px);overflow:auto;padding:var(--space-4)}.history-timeline-item{background:transparent;border:0;cursor:pointer;padding:0;text-align:left}.history-timeline-item .timeline-entry{margin-bottom:0}.history-timeline-item.selected .timeline-entry{box-shadow:inset 0 0 0 1px var(--color-source-ai-accent)}.history-state-head{align-items:start}.history-state-title{color:var(--color-text-primary);font-family:var(--font-family-mono)}.ghost-button{border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);padding:var(--space-2) var(--space-3)}.empty-card{color:var(--color-text-muted);padding:var(--space-6);text-align:center}.lock-grid{display:grid;gap:var(--space-4);grid-template-columns:repeat(auto-fill,minmax(360px,1fr))}.lock-card,.timeline-entry{background:var(--color-surface-panel);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-card);overflow:hidden}.lock-card{display:flex;flex-direction:column}.lock-drift{border-left:3px solid var(--color-state-drift-border)}.lock-ok{border-left:3px solid var(--color-state-approved-border)}.lock-head,.lock-foot{align-items:center;display:flex;gap:var(--space-3);padding:var(--space-4)}.lock-head{border-bottom:1px solid var(--color-border-subtle)}.lock-foot{border-top:1px solid var(--color-border-subtle);justify-content:space-between}.lock-icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;font-family:var(--font-family-mono);height:28px;justify-content:center;width:28px}.lock-drift .lock-icon{background:var(--color-state-drift-bg);color:var(--color-state-drift-text)}.lock-ok .lock-icon{background:var(--color-state-approved-bg);color:var(--color-state-approved-text)}.lock-title{display:flex;flex:1;flex-direction:column;min-width:0}.lock-title strong{color:var(--color-text-primary);font-family:var(--font-family-mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lock-title span,.meta-line{color:var(--color-text-dim);font-family:var(--font-family-mono);font-size:var(--font-size-xs)}.lock-body{display:flex;flex:1;flex-direction:column;gap:var(--space-3);padding:var(--space-4)}.hash-block{font-family:var(--font-family-mono);font-size:var(--font-size-sm)}.hash-row{display:grid;gap:var(--space-2);grid-template-columns:110px minmax(0,1fr);padding:var(--space-1) 0}.hash-key{color:var(--color-text-muted);font-size:10px;letter-spacing:.06em;text-transform:uppercase}.hash-value{color:var(--color-text-primary);overflow-wrap:anywhere}.hash-value.is-stale,.hash-value.is-accent{color:var(--color-state-drift-text)}.hash-value.is-stale{text-decoration:line-through}.preview{border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);overflow:hidden}.preview-head{align-items:center;background:var(--color-surface-panel);border-bottom:1px solid var(--color-border-subtle);color:var(--color-text-dim);display:flex;font-family:var(--font-family-mono);font-size:10px;justify-content:space-between;letter-spacing:.06em;padding:var(--space-1) var(--space-3);text-transform:uppercase}.preview-body{display:block;line-height:1.55;margin:0;min-height:92px;overflow:auto;padding:var(--space-3);white-space:pre-wrap}.line-add,.line-del,.line-ctx{display:block}.line-add{background:#22c55e14;color:#86efac}.line-del{background:#dc262614;color:#fca5a5;text-decoration:line-through}.line-num{color:var(--color-text-dim);display:inline-block;padding-right:var(--space-2);text-align:right;-webkit-user-select:none;user-select:none;width:28px}.action-button{align-items:center;border:1px solid var(--color-border-default);border-radius:var(--radius-md);cursor:pointer;display:inline-flex;font-weight:var(--font-weight-medium);gap:var(--space-2);min-height:40px;padding:var(--space-2) var(--space-4);transition:transform var(--motion-duration-fast),background var(--motion-duration-base)}.action-button:active{transform:scale(.98)}.action-approve{background:var(--color-action-primary);border-color:transparent;color:var(--color-action-primary-text)}.action-annotate{background:var(--color-source-human-bg);border-color:var(--color-source-human-border);color:var(--color-source-human-text)}.action-success{background:var(--color-state-approved-bg);border-color:var(--color-state-approved-border);color:var(--color-state-approved-text);cursor:default}.action-busy{pointer-events:none}.action-error{border-color:var(--color-action-danger)}.spinner{animation:spin .7s linear infinite;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;height:14px;width:14px}.source-badge{background:var(--color-surface-raised);color:var(--color-text-muted);min-height:28px}button.source-badge{cursor:pointer}.source-badge-ai{background:var(--color-source-ai-bg);border-color:var(--color-source-ai-border);color:var(--color-source-ai-text)}.source-badge-human{background:var(--color-source-human-bg);border-color:var(--color-source-human-border);color:var(--color-source-human-text)}.source-badge-outline{background:transparent}.source-badge.is-selected{box-shadow:inset 0 0 0 1px currentColor}.source-badge-dot{background:currentColor;border-radius:50%;height:6px;width:6px}.col-headers{display:grid;gap:var(--space-4);grid-template-columns:1fr 1fr;margin-bottom:var(--space-3)}.col-head{align-items:center;border-radius:var(--radius-md);display:flex;font-family:var(--font-family-mono);justify-content:space-between;padding:var(--space-3)}.col-head.ai{background:var(--color-source-ai-bg);color:var(--color-source-ai-text)}.col-head.human{background:var(--color-source-human-bg);color:var(--color-source-human-text)}.timeline-grid{display:grid;gap:0;grid-template-columns:1fr 40px 1fr;position:relative}.axis{align-items:center;display:flex;grid-column:2;grid-row:1 / span 999;justify-content:center;pointer-events:none}.axis-line{align-self:stretch;background:linear-gradient(var(--color-border-subtle),var(--color-border-default),var(--color-border-subtle));width:2px}.timeline-entry{margin-bottom:var(--space-3);padding:var(--space-3) var(--space-4);position:relative}.timeline-ai{border-left:3px solid var(--color-source-ai-border);grid-column:1}.timeline-human{border-right:3px solid var(--color-source-human-border);grid-column:3}.dot-axis{border:2px solid var(--color-surface-canvas);border-radius:50%;height:10px;position:absolute;top:16px;width:10px}.dot-axis.ai{background:var(--color-source-ai-accent);right:-27px}.dot-axis.human{background:var(--color-source-human-accent);left:-27px}.timeline-head,.entry-meta,.entry-foot{align-items:center;display:flex;flex-wrap:wrap;gap:var(--space-2)}.entry-time{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs);margin-left:auto}.entry-title{color:var(--color-text-primary);font-size:var(--font-size-md);font-weight:var(--font-weight-medium);margin:var(--space-2) 0 0}.entry-meta{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs);margin-top:var(--space-2)}.meta-key{color:var(--color-text-dim)}.entry-body{color:var(--color-text-secondary);line-height:var(--line-height-loose);margin-top:var(--space-3)}.diff-badge,.commit-hash{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs)}.diff-badge{background:var(--color-surface-code);border:1px solid var(--color-border-subtle);border-radius:var(--radius-pill);padding:2px 6px}.annotate-form{display:grid;gap:var(--space-2);margin-top:var(--space-3)}.annotate-form label{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs);text-transform:uppercase}.timeline-empty{grid-column:1 / -1}.doctor-toolbar{margin-bottom:var(--space-5)}.doctor-layout{display:grid;gap:var(--space-5)}.doctor-summary-grid,.doctor-panels{display:grid;gap:var(--space-4);grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.doctor-card,.doctor-summary-card{background:var(--color-surface-panel);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-card)}.doctor-summary-card{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-4)}.doctor-summary-label,.doctor-summary-detail,.doctor-card-head span{color:var(--color-text-muted);font-family:var(--font-family-mono);font-size:var(--font-size-xs)}.doctor-summary-label{letter-spacing:.08em;text-transform:uppercase}.doctor-summary-value{color:var(--color-text-primary);font-size:var(--font-size-lg)}.doctor-card{overflow:hidden}.doctor-card-head{align-items:center;border-bottom:1px solid var(--color-border-subtle);display:flex;justify-content:space-between;padding:var(--space-4)}.doctor-card-head h3{color:var(--color-text-primary);font-size:var(--font-size-md);margin:0}.doctor-entry-list,.doctor-check-list{display:grid}.doctor-entry,.doctor-check{display:grid;gap:var(--space-2);padding:var(--space-4)}.doctor-entry+.doctor-entry,.doctor-check+.doctor-check{border-top:1px solid var(--color-border-subtle)}.doctor-entry strong,.doctor-check strong{color:var(--color-text-primary);font-family:var(--font-family-mono);font-size:var(--font-size-sm)}.doctor-entry span,.doctor-check p{color:var(--color-text-muted);margin:0}.doctor-check-head{align-items:center;display:flex;gap:var(--space-3);justify-content:space-between}.doctor-check-warn{background:linear-gradient(180deg,var(--color-state-locked-bg),transparent 90%)}.doctor-check-error{background:linear-gradient(180deg,var(--color-state-stale-bg),transparent 90%)}.doctor-check-ok{background:linear-gradient(180deg,var(--color-state-approved-bg),transparent 90%)}.doctor-empty{border:0;border-radius:0;box-shadow:none;margin:0}.live-region{clip:rect(0 0 0 0);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.45}}@media(prefers-reduced-motion:no-preference){.pulse,.drift-dot.drift-drift,.drift-dot.drift-stale{animation:pulse 2.4s infinite}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:0ms!important;scroll-behavior:auto!important;transition-duration:0ms!important}}@media(max-width:960px){.app-shell{grid-template-columns:var(--layout-sidebar-width-collapsed) minmax(0,1fr)}.brand span:not(.brand-logo),.nav-item span:not(.dot),.nav-item small,.nav-section,.muted-nav{display:none}.sidebar{padding:var(--space-3) var(--space-2)}.nav-item{grid-template-columns:1fr;justify-items:center}}@media(max-width:720px){.app-shell,.view-split,.topology-split,.timeline-grid,.col-headers,.doctor-panels,.doctor-summary-grid{grid-template-columns:1fr}.sidebar{border-bottom:1px solid var(--color-border-subtle);border-right:0;display:flex;overflow-x:auto}.main{min-height:0}.header{align-items:flex-start;flex-direction:column;height:auto;padding:var(--space-3)}.view{padding:var(--space-4)}.detail-panel{position:static}.axis,.dot-axis{display:none}.timeline-ai,.timeline-human{grid-column:1}}