uilint 0.2.146 → 0.2.148

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.
@@ -17,6 +17,7 @@ function createInitialState() {
17
17
  status: "checking"
18
18
  },
19
19
  backgroundTasks: /* @__PURE__ */ new Map(),
20
+ pluginStates: /* @__PURE__ */ new Map(),
20
21
  activities: [],
21
22
  maxActivities: 50,
22
23
  verbose: false,
@@ -60,6 +61,9 @@ function createDashboardStore() {
60
61
  get ollamaStatus() {
61
62
  return state.ollamaStatus;
62
63
  },
64
+ get pluginStates() {
65
+ return state.pluginStates;
66
+ },
63
67
  get activeFilter() {
64
68
  return state.activeFilter;
65
69
  },
@@ -199,6 +203,21 @@ function createDashboardStore() {
199
203
  state = { ...state, activeFilter: order[nextIndex] };
200
204
  notify();
201
205
  },
206
+ // Plugin states
207
+ registerPlugin(id, name, model) {
208
+ const newMap = new Map(state.pluginStates);
209
+ newMap.set(id, { id, name, model, status: "idle" });
210
+ state = { ...state, pluginStates: newMap };
211
+ notify();
212
+ },
213
+ setPluginState(id, update) {
214
+ const existing = state.pluginStates.get(id);
215
+ if (!existing) return;
216
+ const newMap = new Map(state.pluginStates);
217
+ newMap.set(id, { ...existing, ...update });
218
+ state = { ...state, pluginStates: newMap };
219
+ notify();
220
+ },
202
221
  // Ollama status
203
222
  setOllamaStatus(status) {
204
223
  state = { ...state, ollamaStatus: status };
@@ -226,4 +245,4 @@ function getDashboardStore() {
226
245
  export {
227
246
  getDashboardStore
228
247
  };
229
- //# sourceMappingURL=chunk-ZOLQHFVQ.js.map
248
+ //# sourceMappingURL=chunk-JEYSQ5KF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/serve/dashboard/store.ts"],"sourcesContent":["/**\n * Dashboard state store - simple event-based state management\n */\n\nimport type {\n DashboardState,\n DashboardStore,\n ActivityEntry,\n ActivityCategory,\n BackgroundTask,\n WorkspaceInfo,\n ServerStats,\n OllamaStatus,\n PluginId,\n PluginState,\n} from \"./types.js\";\n\ntype Listener = () => void;\n\nlet activityIdCounter = 0;\n\nfunction createInitialState(): DashboardState {\n return {\n isRunning: false,\n port: 9234,\n workspace: null,\n stats: {\n connectedClients: 0,\n subscriptions: 0,\n cacheEntries: 0,\n startTime: new Date(),\n },\n ollamaStatus: {\n status: \"checking\",\n },\n backgroundTasks: new Map(),\n pluginStates: new Map(),\n activities: [],\n maxActivities: 50,\n verbose: false,\n activeFilter: \"all\",\n };\n}\n\n/**\n * Create a dashboard store instance\n */\nexport function createDashboardStore(): DashboardStore & {\n subscribe: (listener: Listener) => () => void;\n getState: () => DashboardState;\n} {\n let state = createInitialState();\n const listeners = new Set<Listener>();\n\n function notify() {\n for (const listener of listeners) {\n listener();\n }\n }\n\n const store: DashboardStore & {\n subscribe: (listener: Listener) => () => void;\n getState: () => DashboardState;\n } = {\n // State getters\n get isRunning() {\n return state.isRunning;\n },\n get port() {\n return state.port;\n },\n get workspace() {\n return state.workspace;\n },\n get stats() {\n return state.stats;\n },\n get backgroundTasks() {\n return state.backgroundTasks;\n },\n get activities() {\n return state.activities;\n },\n get maxActivities() {\n return state.maxActivities;\n },\n get verbose() {\n return state.verbose;\n },\n get ollamaStatus() {\n return state.ollamaStatus;\n },\n get pluginStates() {\n return state.pluginStates;\n },\n get activeFilter() {\n return state.activeFilter;\n },\n\n // Server lifecycle\n setRunning(running: boolean) {\n state = { ...state, isRunning: running };\n if (running) {\n state.stats = { ...state.stats, startTime: new Date() };\n }\n notify();\n },\n\n setPort(port: number) {\n state = { ...state, port };\n notify();\n },\n\n setWorkspace(info: WorkspaceInfo) {\n state = { ...state, workspace: info };\n notify();\n },\n\n // Stats updates\n updateStats(partial: Partial<ServerStats>) {\n state = { ...state, stats: { ...state.stats, ...partial } };\n notify();\n },\n\n incrementClients() {\n state = {\n ...state,\n stats: {\n ...state.stats,\n connectedClients: state.stats.connectedClients + 1,\n },\n };\n notify();\n },\n\n decrementClients() {\n state = {\n ...state,\n stats: {\n ...state.stats,\n connectedClients: Math.max(0, state.stats.connectedClients - 1),\n },\n };\n notify();\n },\n\n incrementSubscriptions() {\n state = {\n ...state,\n stats: {\n ...state.stats,\n subscriptions: state.stats.subscriptions + 1,\n },\n };\n notify();\n },\n\n decrementSubscriptions() {\n state = {\n ...state,\n stats: {\n ...state.stats,\n subscriptions: Math.max(0, state.stats.subscriptions - 1),\n },\n };\n notify();\n },\n\n setCacheEntries(count: number) {\n state = {\n ...state,\n stats: { ...state.stats, cacheEntries: count },\n };\n notify();\n },\n\n // Background tasks\n setBackgroundTask(task: BackgroundTask) {\n const newTasks = new Map(state.backgroundTasks);\n newTasks.set(task.id, task);\n state = { ...state, backgroundTasks: newTasks };\n notify();\n },\n\n updateBackgroundTaskProgress(\n id: string,\n progress: number,\n current?: number,\n total?: number,\n message?: string\n ) {\n const task = state.backgroundTasks.get(id);\n if (task) {\n const newTasks = new Map(state.backgroundTasks);\n newTasks.set(id, {\n ...task,\n progress,\n current,\n total,\n message,\n status: \"running\",\n });\n state = { ...state, backgroundTasks: newTasks };\n notify();\n }\n },\n\n completeBackgroundTask(id: string, error?: string) {\n const task = state.backgroundTasks.get(id);\n if (task) {\n const newTasks = new Map(state.backgroundTasks);\n newTasks.set(id, {\n ...task,\n status: error ? \"error\" : \"complete\",\n progress: error ? task.progress : 100,\n error,\n });\n state = { ...state, backgroundTasks: newTasks };\n notify();\n }\n },\n\n // Activity logging\n addActivity(entry: Omit<ActivityEntry, \"id\" | \"timestamp\">) {\n const newEntry: ActivityEntry = {\n ...entry,\n id: `activity-${++activityIdCounter}`,\n timestamp: new Date(),\n };\n\n // Prepend new entry, trim to max\n const activities = [newEntry, ...state.activities].slice(\n 0,\n state.maxActivities\n );\n state = { ...state, activities };\n notify();\n },\n\n clearActivities() {\n state = { ...state, activities: [] };\n notify();\n },\n\n // Display options\n toggleVerbose() {\n state = { ...state, verbose: !state.verbose };\n notify();\n },\n\n // Activity filter\n cycleFilter() {\n const order: ActivityCategory[] = [\"all\", \"errors\", \"vision\", \"semantic\", \"lint\"];\n const currentIndex = order.indexOf(state.activeFilter);\n const nextIndex = (currentIndex + 1) % order.length;\n state = { ...state, activeFilter: order[nextIndex]! };\n notify();\n },\n\n // Plugin states\n registerPlugin(id: PluginId, name: string, model: string) {\n const newMap = new Map(state.pluginStates);\n newMap.set(id, { id, name, model, status: \"idle\" });\n state = { ...state, pluginStates: newMap };\n notify();\n },\n\n setPluginState(id: PluginId, update: Partial<PluginState>) {\n const existing = state.pluginStates.get(id);\n if (!existing) return;\n const newMap = new Map(state.pluginStates);\n newMap.set(id, { ...existing, ...update });\n state = { ...state, pluginStates: newMap };\n notify();\n },\n\n // Ollama status\n setOllamaStatus(status: OllamaStatus) {\n state = { ...state, ollamaStatus: status };\n notify();\n },\n\n // Subscription\n subscribe(listener: Listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n\n getState() {\n return state;\n },\n };\n\n return store;\n}\n\n// Singleton store instance for use across the application\nlet globalStore: ReturnType<typeof createDashboardStore> | null = null;\n\nexport function getDashboardStore(): ReturnType<typeof createDashboardStore> {\n if (!globalStore) {\n globalStore = createDashboardStore();\n }\n return globalStore;\n}\n\n// Reset store (useful for testing)\nexport function resetDashboardStore(): void {\n globalStore = null;\n}\n"],"mappings":";;;AAmBA,IAAI,oBAAoB;AAExB,SAAS,qBAAqC;AAC5C,SAAO;AAAA,IACL,WAAW;AAAA,IACX,MAAM;AAAA,IACN,WAAW;AAAA,IACX,OAAO;AAAA,MACL,kBAAkB;AAAA,MAClB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,WAAW,oBAAI,KAAK;AAAA,IACtB;AAAA,IACA,cAAc;AAAA,MACZ,QAAQ;AAAA,IACV;AAAA,IACA,iBAAiB,oBAAI,IAAI;AAAA,IACzB,cAAc,oBAAI,IAAI;AAAA,IACtB,YAAY,CAAC;AAAA,IACb,eAAe;AAAA,IACf,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AACF;AAKO,SAAS,uBAGd;AACA,MAAI,QAAQ,mBAAmB;AAC/B,QAAM,YAAY,oBAAI,IAAc;AAEpC,WAAS,SAAS;AAChB,eAAW,YAAY,WAAW;AAChC,eAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,QAGF;AAAA;AAAA,IAEF,IAAI,YAAY;AACd,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,OAAO;AACT,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,YAAY;AACd,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,QAAQ;AACV,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,kBAAkB;AACpB,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,aAAa;AACf,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,gBAAgB;AAClB,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,UAAU;AACZ,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,eAAe;AACjB,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,eAAe;AACjB,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,eAAe;AACjB,aAAO,MAAM;AAAA,IACf;AAAA;AAAA,IAGA,WAAW,SAAkB;AAC3B,cAAQ,EAAE,GAAG,OAAO,WAAW,QAAQ;AACvC,UAAI,SAAS;AACX,cAAM,QAAQ,EAAE,GAAG,MAAM,OAAO,WAAW,oBAAI,KAAK,EAAE;AAAA,MACxD;AACA,aAAO;AAAA,IACT;AAAA,IAEA,QAAQ,MAAc;AACpB,cAAQ,EAAE,GAAG,OAAO,KAAK;AACzB,aAAO;AAAA,IACT;AAAA,IAEA,aAAa,MAAqB;AAChC,cAAQ,EAAE,GAAG,OAAO,WAAW,KAAK;AACpC,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,YAAY,SAA+B;AACzC,cAAQ,EAAE,GAAG,OAAO,OAAO,EAAE,GAAG,MAAM,OAAO,GAAG,QAAQ,EAAE;AAC1D,aAAO;AAAA,IACT;AAAA,IAEA,mBAAmB;AACjB,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,OAAO;AAAA,UACL,GAAG,MAAM;AAAA,UACT,kBAAkB,MAAM,MAAM,mBAAmB;AAAA,QACnD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,mBAAmB;AACjB,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,OAAO;AAAA,UACL,GAAG,MAAM;AAAA,UACT,kBAAkB,KAAK,IAAI,GAAG,MAAM,MAAM,mBAAmB,CAAC;AAAA,QAChE;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,yBAAyB;AACvB,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,OAAO;AAAA,UACL,GAAG,MAAM;AAAA,UACT,eAAe,MAAM,MAAM,gBAAgB;AAAA,QAC7C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,yBAAyB;AACvB,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,OAAO;AAAA,UACL,GAAG,MAAM;AAAA,UACT,eAAe,KAAK,IAAI,GAAG,MAAM,MAAM,gBAAgB,CAAC;AAAA,QAC1D;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,gBAAgB,OAAe;AAC7B,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,OAAO,EAAE,GAAG,MAAM,OAAO,cAAc,MAAM;AAAA,MAC/C;AACA,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,kBAAkB,MAAsB;AACtC,YAAM,WAAW,IAAI,IAAI,MAAM,eAAe;AAC9C,eAAS,IAAI,KAAK,IAAI,IAAI;AAC1B,cAAQ,EAAE,GAAG,OAAO,iBAAiB,SAAS;AAC9C,aAAO;AAAA,IACT;AAAA,IAEA,6BACE,IACA,UACA,SACA,OACA,SACA;AACA,YAAM,OAAO,MAAM,gBAAgB,IAAI,EAAE;AACzC,UAAI,MAAM;AACR,cAAM,WAAW,IAAI,IAAI,MAAM,eAAe;AAC9C,iBAAS,IAAI,IAAI;AAAA,UACf,GAAG;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,QACV,CAAC;AACD,gBAAQ,EAAE,GAAG,OAAO,iBAAiB,SAAS;AAC9C,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,uBAAuB,IAAY,OAAgB;AACjD,YAAM,OAAO,MAAM,gBAAgB,IAAI,EAAE;AACzC,UAAI,MAAM;AACR,cAAM,WAAW,IAAI,IAAI,MAAM,eAAe;AAC9C,iBAAS,IAAI,IAAI;AAAA,UACf,GAAG;AAAA,UACH,QAAQ,QAAQ,UAAU;AAAA,UAC1B,UAAU,QAAQ,KAAK,WAAW;AAAA,UAClC;AAAA,QACF,CAAC;AACD,gBAAQ,EAAE,GAAG,OAAO,iBAAiB,SAAS;AAC9C,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,IAGA,YAAY,OAAgD;AAC1D,YAAM,WAA0B;AAAA,QAC9B,GAAG;AAAA,QACH,IAAI,YAAY,EAAE,iBAAiB;AAAA,QACnC,WAAW,oBAAI,KAAK;AAAA,MACtB;AAGA,YAAM,aAAa,CAAC,UAAU,GAAG,MAAM,UAAU,EAAE;AAAA,QACjD;AAAA,QACA,MAAM;AAAA,MACR;AACA,cAAQ,EAAE,GAAG,OAAO,WAAW;AAC/B,aAAO;AAAA,IACT;AAAA,IAEA,kBAAkB;AAChB,cAAQ,EAAE,GAAG,OAAO,YAAY,CAAC,EAAE;AACnC,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,gBAAgB;AACd,cAAQ,EAAE,GAAG,OAAO,SAAS,CAAC,MAAM,QAAQ;AAC5C,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,cAAc;AACZ,YAAM,QAA4B,CAAC,OAAO,UAAU,UAAU,YAAY,MAAM;AAChF,YAAM,eAAe,MAAM,QAAQ,MAAM,YAAY;AACrD,YAAM,aAAa,eAAe,KAAK,MAAM;AAC7C,cAAQ,EAAE,GAAG,OAAO,cAAc,MAAM,SAAS,EAAG;AACpD,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,eAAe,IAAc,MAAc,OAAe;AACxD,YAAM,SAAS,IAAI,IAAI,MAAM,YAAY;AACzC,aAAO,IAAI,IAAI,EAAE,IAAI,MAAM,OAAO,QAAQ,OAAO,CAAC;AAClD,cAAQ,EAAE,GAAG,OAAO,cAAc,OAAO;AACzC,aAAO;AAAA,IACT;AAAA,IAEA,eAAe,IAAc,QAA8B;AACzD,YAAM,WAAW,MAAM,aAAa,IAAI,EAAE;AAC1C,UAAI,CAAC,SAAU;AACf,YAAM,SAAS,IAAI,IAAI,MAAM,YAAY;AACzC,aAAO,IAAI,IAAI,EAAE,GAAG,UAAU,GAAG,OAAO,CAAC;AACzC,cAAQ,EAAE,GAAG,OAAO,cAAc,OAAO;AACzC,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,gBAAgB,QAAsB;AACpC,cAAQ,EAAE,GAAG,OAAO,cAAc,OAAO;AACzC,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,UAAU,UAAoB;AAC5B,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM,UAAU,OAAO,QAAQ;AAAA,IACxC;AAAA,IAEA,WAAW;AACT,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAGA,IAAI,cAA8D;AAE3D,SAAS,oBAA6D;AAC3E,MAAI,CAAC,aAAa;AAChB,kBAAc,qBAAqB;AAAA,EACrC;AACA,SAAO;AACT;","names":[]}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDashboardStore
4
- } from "./chunk-ZOLQHFVQ.js";
4
+ } from "./chunk-JEYSQ5KF.js";
5
5
  import {
6
6
  detectCoverageSetup,
7
7
  detectNextAppRouter,
@@ -1508,6 +1508,19 @@ function logVisionDone(route, issueCount, elapsedMs) {
1508
1508
  function logVisionCheck(requestId) {
1509
1509
  logActivity("vision:check", requestId ? `(req ${requestId})` : "");
1510
1510
  }
1511
+ function logSemanticAnalyze(filePath, requestId) {
1512
+ const msg = filePath + (requestId ? ` (req ${requestId})` : "");
1513
+ logActivity("semantic:analyze", msg);
1514
+ }
1515
+ function logSemanticDone(filePath, issueCount, elapsedMs) {
1516
+ logActivity(
1517
+ "semantic:done",
1518
+ `${filePath} \u2192 ${issueCount} issue(s) (${elapsedMs}ms)`
1519
+ );
1520
+ }
1521
+ function logSemanticSkipped(filePath, reason) {
1522
+ logActivity("semantic:skip", `${filePath} \u2014 ${reason}`);
1523
+ }
1511
1524
  function logConfigSet(key, value) {
1512
1525
  logActivity("config:set", `${key} = ${JSON.stringify(value)}`);
1513
1526
  }
@@ -1635,6 +1648,40 @@ function completeBackgroundTask(id, successMessage, error) {
1635
1648
  function logRuleInternalError(ruleId, filePath, detail) {
1636
1649
  logActivity("error", `Rule error [${ruleId}] ${filePath}`, detail, true);
1637
1650
  }
1651
+ function registerPlugin(id, name, model) {
1652
+ if (useDashboard) {
1653
+ const store = getDashboardStore();
1654
+ store.registerPlugin(id, name, model);
1655
+ } else {
1656
+ consoleInfo(`Plugin registered: ${name} (${model})`);
1657
+ }
1658
+ }
1659
+ function setPluginStatus(id, status, message) {
1660
+ if (useDashboard) {
1661
+ const store = getDashboardStore();
1662
+ store.setPluginState(id, {
1663
+ status,
1664
+ message,
1665
+ error: status === "error" ? message : void 0,
1666
+ // Clear progress on terminal states
1667
+ ...status === "idle" || status === "complete" || status === "error" ? { progress: void 0, current: void 0, total: void 0 } : {}
1668
+ });
1669
+ } else if (status === "error") {
1670
+ consoleError(`Plugin ${id}: ${message}`);
1671
+ }
1672
+ }
1673
+ function updatePluginProgress(id, progress, current, total, message) {
1674
+ if (useDashboard) {
1675
+ const store = getDashboardStore();
1676
+ store.setPluginState(id, { progress, current, total, message });
1677
+ }
1678
+ }
1679
+ function setPluginModel(id, model) {
1680
+ if (useDashboard) {
1681
+ const store = getDashboardStore();
1682
+ store.setPluginState(id, { model });
1683
+ }
1684
+ }
1638
1685
 
1639
1686
  // src/commands/serve.ts
1640
1687
  import { ruleRegistry } from "uilint-eslint";
@@ -2302,13 +2349,58 @@ async function getVisionAnalyzerInstance() {
2302
2349
  return visionAnalyzer;
2303
2350
  }
2304
2351
  var ollamaMutexPromise = Promise.resolve();
2305
- function acquireOllamaMutex() {
2352
+ var ollamaMutexHolder = null;
2353
+ var ollamaMutexQueue = [];
2354
+ function acquireOllamaMutex(pluginId) {
2306
2355
  let release;
2307
2356
  const prev = ollamaMutexPromise;
2308
2357
  ollamaMutexPromise = new Promise((resolve11) => {
2309
2358
  release = resolve11;
2310
2359
  });
2311
- return prev.then(() => release);
2360
+ ollamaMutexQueue.push(pluginId);
2361
+ setPluginStatus(pluginId, "waiting-for-ollama", "Queued for Ollama...");
2362
+ return prev.then(() => {
2363
+ const idx = ollamaMutexQueue.indexOf(pluginId);
2364
+ if (idx !== -1) ollamaMutexQueue.splice(idx, 1);
2365
+ ollamaMutexHolder = pluginId;
2366
+ setPluginStatus(pluginId, "using-ollama", "Using Ollama...");
2367
+ return () => {
2368
+ if (ollamaMutexHolder === pluginId) {
2369
+ ollamaMutexHolder = null;
2370
+ }
2371
+ release();
2372
+ };
2373
+ });
2374
+ }
2375
+ var semanticFilesRequested = 0;
2376
+ var semanticFilesCompleted = 0;
2377
+ var semanticIdleResetTimer = null;
2378
+ function updateSemanticBatchProgress(message) {
2379
+ const progress = semanticFilesRequested > 0 ? Math.round(semanticFilesCompleted / semanticFilesRequested * 100) : 0;
2380
+ updatePluginProgress(
2381
+ "semantic",
2382
+ progress,
2383
+ semanticFilesCompleted,
2384
+ semanticFilesRequested,
2385
+ message
2386
+ );
2387
+ }
2388
+ function completeSemanticFile(terminalStatus, message) {
2389
+ semanticFilesCompleted++;
2390
+ if (semanticFilesCompleted >= semanticFilesRequested) {
2391
+ const summary = terminalStatus === "complete" ? `Done: ${semanticFilesCompleted} file(s) analyzed` : message;
2392
+ setPluginStatus("semantic", terminalStatus, summary);
2393
+ semanticIdleResetTimer = setTimeout(() => {
2394
+ setPluginStatus("semantic", "idle");
2395
+ semanticFilesRequested = 0;
2396
+ semanticFilesCompleted = 0;
2397
+ semanticIdleResetTimer = null;
2398
+ }, 3e3);
2399
+ } else {
2400
+ updateSemanticBatchProgress(
2401
+ `${semanticFilesCompleted}/${semanticFilesRequested} files analyzed`
2402
+ );
2403
+ }
2312
2404
  }
2313
2405
  var serverAppRootForVision = process.cwd();
2314
2406
  function isValidScreenshotFilename(filename) {
@@ -2581,21 +2673,31 @@ async function lintFileFast(filePath, onProgress) {
2581
2673
  }
2582
2674
  }
2583
2675
  async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2676
+ const startTime = Date.now();
2677
+ logSemanticAnalyze(filePath, requestId);
2584
2678
  const absolutePath = resolveRequestedFilePath(filePath);
2585
- if (!existsSync5(absolutePath)) return;
2679
+ if (!existsSync5(absolutePath)) {
2680
+ logSemanticSkipped(filePath, "file not found");
2681
+ return;
2682
+ }
2586
2683
  const eslintConfigPath = findEslintConfigFile(serverAppRootForVision);
2587
2684
  const ruleConfigs = eslintConfigPath ? readRuleConfigsFromConfig(eslintConfigPath) : /* @__PURE__ */ new Map();
2588
2685
  const semanticConfig = ruleConfigs.get("semantic");
2589
2686
  const model = semanticConfig?.options?.model || "qwen3-vl:8b-instruct";
2590
2687
  const styleguidePath = semanticConfig?.options?.styleguidePath || void 0;
2688
+ setPluginModel("semantic", model);
2591
2689
  const { getStyleguide, hashContentSync, setCacheEntry, getCacheEntry } = await import("uilint-eslint");
2592
2690
  const fileDir = dirname5(absolutePath);
2593
2691
  const { content: styleguide } = getStyleguide(fileDir, styleguidePath);
2594
- if (!styleguide) return;
2692
+ if (!styleguide) {
2693
+ logSemanticSkipped(filePath, "no styleguide found");
2694
+ return;
2695
+ }
2595
2696
  let fileContent;
2596
2697
  try {
2597
2698
  fileContent = readFileSync2(absolutePath, "utf-8");
2598
2699
  } catch {
2700
+ logSemanticSkipped(filePath, "file read error");
2599
2701
  return;
2600
2702
  }
2601
2703
  const fileHash = hashContentSync(fileContent);
@@ -2603,7 +2705,18 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2603
2705
  const projectRoot = findWorkspaceRoot4(fileDir) || fileDir;
2604
2706
  const relativeFilePath = relative2(projectRoot, absolutePath);
2605
2707
  const cached = getCacheEntry(projectRoot, relativeFilePath, fileHash, styleguideHash);
2606
- if (cached) return;
2708
+ if (cached) {
2709
+ logSemanticSkipped(filePath, "cache fresh");
2710
+ return;
2711
+ }
2712
+ if (semanticIdleResetTimer) {
2713
+ clearTimeout(semanticIdleResetTimer);
2714
+ semanticIdleResetTimer = null;
2715
+ }
2716
+ semanticFilesRequested++;
2717
+ updateSemanticBatchProgress(
2718
+ `Queued ${relative2(serverAppRootForVision, absolutePath)}...`
2719
+ );
2607
2720
  sendMessage(ws, {
2608
2721
  type: "lint:progress",
2609
2722
  filePath,
@@ -2617,12 +2730,13 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2617
2730
  operationName: "analysis",
2618
2731
  message: `Analyzing ${relative2(serverAppRootForVision, absolutePath)}...`
2619
2732
  });
2620
- const release = await acquireOllamaMutex();
2733
+ const release = await acquireOllamaMutex("semantic");
2621
2734
  try {
2622
2735
  const { OllamaClient: OllamaClient2, buildSourceScanPrompt: buildSourceScanPrompt2 } = await import("uilint-core/node");
2623
2736
  const client = new OllamaClient2({ model });
2624
2737
  const ok = await client.isAvailable();
2625
2738
  if (!ok) {
2739
+ logServerWarning("Semantic analysis: Ollama not available");
2626
2740
  updateBackgroundTaskProgress("semantic-analysis", 0, 0, 0, "Ollama not available");
2627
2741
  completeBackgroundTask("semantic-analysis", void 0, "Ollama not available");
2628
2742
  broadcast({
@@ -2631,9 +2745,13 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2631
2745
  operationName: "analysis",
2632
2746
  error: "Ollama not available"
2633
2747
  });
2748
+ completeSemanticFile("error", "Ollama not available");
2634
2749
  return;
2635
2750
  }
2636
2751
  updateBackgroundTaskProgress("semantic-analysis", 50, 0, 0, "Waiting for LLM response...");
2752
+ updateSemanticBatchProgress(
2753
+ `Analyzing ${relative2(serverAppRootForVision, absolutePath)}...`
2754
+ );
2637
2755
  broadcast({
2638
2756
  type: "plugin:operation:progress",
2639
2757
  pluginId: "semantic",
@@ -2671,16 +2789,17 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2671
2789
  source: null,
2672
2790
  dataLoc: `${dataLocFile}:${issue.line}:${issue.column || 0}`
2673
2791
  }));
2674
- if (lintIssues.length > 0) {
2675
- sendMessage(ws, {
2676
- type: "lint:result",
2677
- filePath,
2678
- requestId,
2679
- issues: lintIssues
2680
- });
2681
- }
2792
+ sendMessage(ws, {
2793
+ type: "lint:result",
2794
+ filePath,
2795
+ requestId,
2796
+ issues: lintIssues
2797
+ });
2798
+ const elapsed = Date.now() - startTime;
2682
2799
  const msg = `${issues.length} issue(s) found`;
2800
+ logSemanticDone(filePath, issues.length, elapsed);
2683
2801
  completeBackgroundTask("semantic-analysis", msg);
2802
+ completeSemanticFile("complete", msg);
2684
2803
  broadcast({
2685
2804
  type: "plugin:operation:complete",
2686
2805
  pluginId: "semantic",
@@ -2697,6 +2816,7 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2697
2816
  const errorMessage = error instanceof Error ? error.message : String(error);
2698
2817
  logServerError("Async semantic analysis failed", errorMessage);
2699
2818
  completeBackgroundTask("semantic-analysis", void 0, errorMessage);
2819
+ completeSemanticFile("error", errorMessage);
2700
2820
  broadcast({
2701
2821
  type: "plugin:operation:error",
2702
2822
  pluginId: "semantic",
@@ -2709,6 +2829,7 @@ async function runSemanticAnalysisAsync(filePath, ws, requestId) {
2709
2829
  }
2710
2830
  async function runVisionAnalysisInBackground(ws, message) {
2711
2831
  const { route, timestamp, screenshot, screenshotFile, manifest, requestId } = message;
2832
+ setPluginStatus("vision", "processing", `Analyzing ${route}...`);
2712
2833
  startBackgroundTask("vision-analysis", "Vision Analysis", `Analyzing ${route}...`);
2713
2834
  broadcast({
2714
2835
  type: "plugin:operation:start",
@@ -2727,6 +2848,8 @@ async function runVisionAnalysisInBackground(ws, message) {
2727
2848
  requestId
2728
2849
  });
2729
2850
  completeBackgroundTask("vision-analysis", void 0, "uilint-vision not installed");
2851
+ setPluginStatus("vision", "error", "uilint-vision not installed");
2852
+ setTimeout(() => setPluginStatus("vision", "idle"), 3e3);
2730
2853
  broadcast({
2731
2854
  type: "plugin:operation:error",
2732
2855
  pluginId: "vision",
@@ -2744,11 +2867,14 @@ async function runVisionAnalysisInBackground(ws, message) {
2744
2867
  const startedAt = Date.now();
2745
2868
  const analyzer = await getVisionAnalyzerInstance();
2746
2869
  updateBackgroundTaskProgress("vision-analysis", 10, 0, 0, "Waiting for Ollama...");
2747
- const releaseOllama = await acquireOllamaMutex();
2870
+ const releaseOllama = await acquireOllamaMutex("vision");
2748
2871
  try {
2749
2872
  const analyzerObj = analyzer;
2750
2873
  const analyzerModel = typeof analyzerObj?.getModel === "function" ? analyzerObj.getModel() : void 0;
2751
2874
  const analyzerBaseUrl = typeof analyzerObj?.getBaseUrl === "function" ? analyzerObj.getBaseUrl() : void 0;
2875
+ if (analyzerModel) {
2876
+ setPluginModel("vision", analyzerModel);
2877
+ }
2752
2878
  if (!screenshot) {
2753
2879
  sendMessage(ws, {
2754
2880
  type: "vision:result",
@@ -2759,6 +2885,8 @@ async function runVisionAnalysisInBackground(ws, message) {
2759
2885
  requestId
2760
2886
  });
2761
2887
  completeBackgroundTask("vision-analysis", void 0, "No screenshot provided");
2888
+ setPluginStatus("vision", "error", "No screenshot provided");
2889
+ setTimeout(() => setPluginStatus("vision", "idle"), 3e3);
2762
2890
  broadcast({
2763
2891
  type: "plugin:operation:error",
2764
2892
  pluginId: "vision",
@@ -2836,6 +2964,8 @@ async function runVisionAnalysisInBackground(ws, message) {
2836
2964
  });
2837
2965
  const msg = `${resultIssues.length} issue(s) in ${(elapsed / 1e3).toFixed(1)}s`;
2838
2966
  completeBackgroundTask("vision-analysis", msg);
2967
+ setPluginStatus("vision", "complete", msg);
2968
+ setTimeout(() => setPluginStatus("vision", "idle"), 3e3);
2839
2969
  broadcast({
2840
2970
  type: "plugin:operation:complete",
2841
2971
  pluginId: "vision",
@@ -2855,6 +2985,8 @@ async function runVisionAnalysisInBackground(ws, message) {
2855
2985
  requestId
2856
2986
  });
2857
2987
  completeBackgroundTask("vision-analysis", void 0, errorMessage);
2988
+ setPluginStatus("vision", "error", errorMessage);
2989
+ setTimeout(() => setPluginStatus("vision", "idle"), 3e3);
2858
2990
  broadcast({
2859
2991
  type: "plugin:operation:error",
2860
2992
  pluginId: "vision",
@@ -2933,6 +3065,8 @@ async function handleMessage(ws, data) {
2933
3065
  runSemanticAnalysisAsync(filePath, ws, requestId).catch((err) => {
2934
3066
  logServerError("Async semantic analysis failed", err instanceof Error ? err.message : String(err));
2935
3067
  });
3068
+ } else {
3069
+ logSemanticSkipped(filePath, "rule not enabled");
2936
3070
  }
2937
3071
  break;
2938
3072
  }
@@ -2967,6 +3101,8 @@ async function handleMessage(ws, data) {
2967
3101
  runSemanticAnalysisAsync(filePath, ws, requestId).catch((err) => {
2968
3102
  logServerError("Async semantic analysis failed", err instanceof Error ? err.message : String(err));
2969
3103
  });
3104
+ } else {
3105
+ logSemanticSkipped(filePath, "rule not enabled");
2970
3106
  }
2971
3107
  break;
2972
3108
  }
@@ -3260,12 +3396,13 @@ async function buildDuplicatesIndex(appRoot) {
3260
3396
  return;
3261
3397
  }
3262
3398
  isIndexing = true;
3399
+ setPluginStatus("duplicates", "processing", "Preparing index...");
3263
3400
  startBackgroundTask(
3264
3401
  "duplicates-index",
3265
3402
  "Duplicates Index",
3266
3403
  "Waiting for Ollama..."
3267
3404
  );
3268
- const release = await acquireOllamaMutex();
3405
+ const release = await acquireOllamaMutex("duplicates");
3269
3406
  broadcast({ type: "duplicates:indexing:start" });
3270
3407
  broadcast({
3271
3408
  type: "plugin:operation:start",
@@ -3285,6 +3422,7 @@ async function buildDuplicatesIndex(appRoot) {
3285
3422
  total,
3286
3423
  message
3287
3424
  );
3425
+ updatePluginProgress("duplicates", progress, current, total, message);
3288
3426
  broadcast({
3289
3427
  type: "duplicates:indexing:progress",
3290
3428
  message,
@@ -3303,6 +3441,8 @@ async function buildDuplicatesIndex(appRoot) {
3303
3441
  });
3304
3442
  const successMsg = `${result.totalChunks} chunks (${result.added} added, ${result.modified} modified, ${result.deleted} deleted) in ${(result.duration / 1e3).toFixed(1)}s`;
3305
3443
  completeBackgroundTask("duplicates-index", `Index complete: ${successMsg}`);
3444
+ setPluginStatus("duplicates", "complete", successMsg);
3445
+ setTimeout(() => setPluginStatus("duplicates", "idle"), 3e3);
3306
3446
  broadcast({
3307
3447
  type: "duplicates:indexing:complete",
3308
3448
  added: result.added,
@@ -3320,6 +3460,8 @@ async function buildDuplicatesIndex(appRoot) {
3320
3460
  } catch (error) {
3321
3461
  const msg = error instanceof Error ? error.message : String(error);
3322
3462
  completeBackgroundTask("duplicates-index", void 0, msg);
3463
+ setPluginStatus("duplicates", "error", msg);
3464
+ setTimeout(() => setPluginStatus("duplicates", "idle"), 3e3);
3323
3465
  broadcast({ type: "duplicates:indexing:error", error: msg });
3324
3466
  broadcast({
3325
3467
  type: "plugin:operation:error",
@@ -3474,6 +3616,9 @@ async function serve(options) {
3474
3616
  const useDashboardUI = process.stdout.isTTY && !options.noDashboard;
3475
3617
  if (useDashboardUI) {
3476
3618
  enableDashboard();
3619
+ registerPlugin("semantic", "Semantic", "qwen3-vl:8b-instruct");
3620
+ registerPlugin("vision", "Vision", "gemma3:4b");
3621
+ registerPlugin("duplicates", "Duplicates", "nomic-embed-text");
3477
3622
  } else {
3478
3623
  disableDashboard();
3479
3624
  }
@@ -3643,7 +3788,7 @@ async function serve(options) {
3643
3788
  });
3644
3789
  setServerRunning(port);
3645
3790
  if (useDashboardUI) {
3646
- const { renderDashboard } = await import("./render-43OMCORR.js");
3791
+ const { renderDashboard } = await import("./render-2P4YWHXV.js");
3647
3792
  const { waitUntilExit } = renderDashboard({
3648
3793
  onQuit: () => {
3649
3794
  clearInterval(pingInterval);