uneven-ai 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/dist/application/orchestration/engine/ask/brain.d.ts +2 -8
  4. package/dist/application/orchestration/engine/ask/brain.d.ts.map +1 -1
  5. package/dist/application/orchestration/engine/ask/brain.js +16 -14
  6. package/dist/application/orchestration/engine/ask/chat-template.d.ts +9 -0
  7. package/dist/application/orchestration/engine/ask/chat-template.d.ts.map +1 -0
  8. package/dist/application/orchestration/engine/ask/chat-template.js +82 -0
  9. package/dist/application/orchestration/engine/ask/context-assembler.js +3 -3
  10. package/dist/application/orchestration/engine/ask/prompts.d.ts +2 -0
  11. package/dist/application/orchestration/engine/ask/prompts.d.ts.map +1 -1
  12. package/dist/application/orchestration/engine/ask/prompts.js +7 -12
  13. package/dist/application/orchestration/engine/ask.js +3 -13
  14. package/dist/cli/commands/ask.d.ts.map +1 -1
  15. package/dist/cli/commands/ask.js +1 -0
  16. package/dist/cli/commands/chat.d.ts.map +1 -1
  17. package/dist/cli/commands/chat.js +1 -0
  18. package/dist/cli/commands/docs.d.ts.map +1 -1
  19. package/dist/cli/commands/docs.js +1 -0
  20. package/dist/cli/commands/explain.d.ts.map +1 -1
  21. package/dist/cli/commands/explain.js +1 -0
  22. package/dist/cli/commands/init/config-builder.js +1 -1
  23. package/dist/cli/commands/init/constants.js +8 -8
  24. package/dist/domain/entities/session/constants.d.ts +1 -1
  25. package/dist/domain/entities/session/constants.js +1 -1
  26. package/dist/infrastructure/adapters/bridge.d.ts +2 -0
  27. package/dist/infrastructure/adapters/bridge.d.ts.map +1 -1
  28. package/dist/infrastructure/adapters/bridge.js +4 -2
  29. package/dist/infrastructure/utils/config-loader.js +2 -2
  30. package/dist/infrastructure/utils/llm-error.d.ts +2 -0
  31. package/dist/infrastructure/utils/llm-error.d.ts.map +1 -0
  32. package/dist/infrastructure/utils/llm-error.js +20 -0
  33. package/package.json +1 -1
  34. package/prebuilds/darwin-arm64/uneven_core.node +0 -0
  35. package/prebuilds/linux-arm64/uneven_core.node +0 -0
  36. package/prebuilds/linux-x64/uneven_core.node +0 -0
  37. package/prebuilds/win32-x64/uneven_core.node +0 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to Uneven AI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] - 2026-05-02
9
+
10
+ ### Added
11
+
12
+ - **Multi-architecture GGUF dispatcher** — The Rust inference loader now reads `general.architecture` from GGUF metadata and routes to the correct candle-transformers backend (`quantized_llama`, `quantized_qwen2`, `quantized_gemma3`, `quantized_phi3`). All seven catalog models are now supported out of the box.
13
+ - **Chat templates** — Each model family now receives prompts in its correct format: Llama 3 (`<|begin_of_text|>` header tokens), ChatML (`<|im_start|>`, used by Qwen and SmolLM2), DeepSeek-R1 (Unicode BOS/EOS tokens), Gemma 3 (`<start_of_turn>`), and Phi 3/4 (`<|system|>` role tokens). The `detectTemplate` helper selects the right template from the model ID automatically.
14
+ - **DeepSeek-R1 thinking-token stripping** — `<think>…</think>` blocks emitted by DeepSeek-R1 models are removed before the response reaches the user.
15
+ - **Adaptive inference timeout** — Local inference timeout is now `max(300 s, maxTokens × 1.2 s)` instead of a fixed 120 s, preventing spurious timeouts on slower hardware.
16
+ - **`llm-error` utility module** — `sanitizeLlmError` extracted to `src/infrastructure/utils/llm-error.ts` for isolated unit testing without the native binding mock.
17
+ - **Unit tests — chat templates (60 tests)** — Per-architecture token structure, content integrity, multi-turn history ordering, and per-model send/receive simulation for all seven catalog models.
18
+ - **Unit tests — error sanitisation (12 tests)** — Covers every error category mapped by `sanitizeLlmError`.
19
+
20
+ ### Fixed
21
+
22
+ - **Double BOS token** — `encode(prompt, true)` was adding a BOS token on top of the one already embedded in the chat template, corrupting Llama 3 prompts. Changed to `encode(prompt, false)`.
23
+ - **Default model ID** — Fallback model in the NAPI layer updated from `llama-3.2-1b-q8` to `llama-3.2-1b-q4` to match the catalog.
24
+
25
+ ---
26
+
8
27
  ## [1.2.2] - 2026-05-02
9
28
 
10
29
  ### Fixed
package/README.md CHANGED
@@ -157,7 +157,7 @@ uneven-ai ci
157
157
  Run `uneven-ai` with no arguments to open the interactive shell:
158
158
 
159
159
  ```
160
- ◈ Uneven AI v1.2.0
160
+ ◈ Uneven AI v1.3.0
161
161
  ────────────────────────────────────────────────────────────
162
162
  Olá! O que posso fazer por você hoje?
163
163
  (Escreva sua mensagem ou "sair" para encerrar)
@@ -1,14 +1,8 @@
1
1
  import type { EngineCtx } from '../context.js';
2
- /**
3
- * EngineBrain — Encapsulates the inference logic for the engine.
4
- * Handles switching between local and external providers, timeouts, and streaming.
5
- */
2
+ import type { ChatTurn } from './chat-template.js';
6
3
  export declare class EngineBrain {
7
4
  private ctx;
8
5
  constructor(ctx: EngineCtx);
9
- /**
10
- * Performs an inference (completion) with the configured provider.
11
- */
12
- infer(prompt: string, onToken?: (token: string) => void): Promise<string>;
6
+ infer(user: string, onToken?: (token: string) => void, maxTokensOverride?: number, system?: string, history?: ChatTurn[]): Promise<string>;
13
7
  }
14
8
  //# sourceMappingURL=brain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../../../../src/application/orchestration/engine/ask/brain.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C;;;GAGG;AACH,qBAAa,WAAW;IACV,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,SAAS;IAElC;;OAEG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;CAgChF"}
1
+ {"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../../../../src/application/orchestration/engine/ask/brain.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAElD,qBAAa,WAAW;IACV,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,SAAS;IAE5B,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE,QAAQ,EAAO,GACvB,OAAO,CAAC,MAAM,CAAC;CA2CnB"}
@@ -1,43 +1,45 @@
1
1
  import { llmInfer, llmInferStream, } from '../../../../infrastructure/adapters/bridge.js';
2
2
  import { ExternalProviders } from '../../../../infrastructure/adapters/providers/index.js';
3
- /**
4
- * EngineBrain — Encapsulates the inference logic for the engine.
5
- * Handles switching between local and external providers, timeouts, and streaming.
6
- */
3
+ import { detectTemplate, applyTemplate, stripThinkingTokens } from './chat-template.js';
7
4
  export class EngineBrain {
8
5
  ctx;
9
6
  constructor(ctx) {
10
7
  this.ctx = ctx;
11
8
  }
12
- /**
13
- * Performs an inference (completion) with the configured provider.
14
- */
15
- async infer(prompt, onToken) {
9
+ async infer(user, onToken, maxTokensOverride, system, history = []) {
16
10
  const brainConfig = this.ctx.config.brain;
17
11
  const provider = brainConfig?.provider || 'local';
18
- const model = brainConfig?.model || 'llama3.2';
19
- const maxTokens = brainConfig?.maxTokens || 1024;
12
+ const model = brainConfig?.model || 'llama-3.2-1b-q4';
13
+ const maxTokens = maxTokensOverride ?? brainConfig?.maxTokens ?? 1024;
20
14
  const temperature = brainConfig?.temperature ?? 0.0;
21
- const timeoutMs = brainConfig?.local?.inferTimeoutMs ?? 120_000;
15
+ const timeoutMs = brainConfig?.local?.inferTimeoutMs
16
+ ?? Math.max(300_000, maxTokens * 1_200);
22
17
  const withTimeout = (p) => Promise.race([
23
18
  p,
24
19
  new Promise((_, reject) => setTimeout(() => reject(new Error(`LLM inference timed out after ${timeoutMs}ms`)), timeoutMs)),
25
20
  ]);
26
21
  if (provider === 'local') {
27
22
  const threads = brainConfig?.local?.threads ?? 4;
23
+ const template = detectTemplate(model);
24
+ const prompt = system
25
+ ? applyTemplate(template, system, history, user)
26
+ : user;
27
+ const isDeepSeek = model.toLowerCase().startsWith('deepseek');
28
+ let content;
28
29
  if (onToken) {
29
30
  const result = await withTimeout(llmInferStream(prompt, onToken, maxTokens, threads, temperature));
30
- return result.content;
31
+ content = result.content;
31
32
  }
32
33
  else {
33
34
  const result = await withTimeout(llmInfer(prompt, maxTokens, threads, temperature));
34
- return result.content;
35
+ content = result.content;
35
36
  }
37
+ return isDeepSeek ? stripThinkingTokens(content) : content;
36
38
  }
37
39
  else {
38
40
  const extProviders = new ExternalProviders(this.ctx.logger);
39
41
  const apiKey = brainConfig?.apiKey;
40
- const result = await withTimeout(extProviders.infer(prompt, provider, model, maxTokens, onToken, apiKey));
42
+ const result = await withTimeout(extProviders.infer(user, provider, model, maxTokens, onToken, apiKey));
41
43
  return result.content;
42
44
  }
43
45
  }
@@ -0,0 +1,9 @@
1
+ export type ChatTemplate = 'llama3' | 'chatml' | 'deepseek-r1' | 'gemma3' | 'phi3';
2
+ export declare function detectTemplate(modelId: string): ChatTemplate;
3
+ export interface ChatTurn {
4
+ role: 'user' | 'assistant';
5
+ content: string;
6
+ }
7
+ export declare function applyTemplate(template: ChatTemplate, system: string, history: ChatTurn[], user: string): string;
8
+ export declare function stripThinkingTokens(text: string): string;
9
+ //# sourceMappingURL=chat-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-template.d.ts","sourceRoot":"","sources":["../../../../../src/application/orchestration/engine/ask/chat-template.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAA;AAElF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAQ5D;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,QAAQ,EAAE,EACnB,IAAI,EAAE,MAAM,GACX,MAAM,CAiER;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD"}
@@ -0,0 +1,82 @@
1
+ export function detectTemplate(modelId) {
2
+ const id = modelId.toLowerCase();
3
+ if (id.startsWith('deepseek'))
4
+ return 'deepseek-r1';
5
+ if (id.startsWith('llama') || id.startsWith('smollm'))
6
+ return 'llama3';
7
+ if (id.startsWith('qwen'))
8
+ return 'chatml';
9
+ if (id.startsWith('gemma'))
10
+ return 'gemma3';
11
+ if (id.startsWith('phi'))
12
+ return 'phi3';
13
+ return 'llama3';
14
+ }
15
+ export function applyTemplate(template, system, history, user) {
16
+ switch (template) {
17
+ case 'llama3': {
18
+ let prompt = `<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n${system}<|eot_id|>`;
19
+ for (const turn of history) {
20
+ prompt += `<|start_header_id|>${turn.role}<|end_header_id|>\n\n${turn.content}<|eot_id|>`;
21
+ }
22
+ prompt += `<|start_header_id|>user<|end_header_id|>\n\n${user}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n`;
23
+ return prompt;
24
+ }
25
+ case 'chatml': {
26
+ let prompt = `<|im_start|>system\n${system}<|im_end|>\n`;
27
+ for (const turn of history) {
28
+ prompt += `<|im_start|>${turn.role}\n${turn.content}<|im_end|>\n`;
29
+ }
30
+ prompt += `<|im_start|>user\n${user}<|im_end|>\n<|im_start|>assistant\n`;
31
+ return prompt;
32
+ }
33
+ case 'deepseek-r1': {
34
+ // DeepSeek-R1 uses its own special tokens (Unicode full-width vertical bars)
35
+ // System message goes before the first User marker (no dedicated system role)
36
+ let prompt = `<|begin▁of▁sentence|>${system}\n`;
37
+ for (const turn of history) {
38
+ if (turn.role === 'user') {
39
+ prompt += `<|User|>${turn.content}\n`;
40
+ }
41
+ else {
42
+ prompt += `<|Assistant|>${turn.content}<|end▁of▁sentence|>\n`;
43
+ }
44
+ }
45
+ prompt += `<|User|>${user}\n<|Assistant|>`;
46
+ return prompt;
47
+ }
48
+ case 'gemma3': {
49
+ // Gemma 3 has no system role — system message goes inside the first user turn
50
+ let prompt = `<start_of_turn>user\n${system}\n\n`;
51
+ // First user turn already opened; if there's history, close it and add alternating turns
52
+ if (history.length > 0) {
53
+ prompt += `<end_of_turn>\n`;
54
+ for (const turn of history) {
55
+ const role = turn.role === 'user' ? 'user' : 'model';
56
+ prompt += `<start_of_turn>${role}\n${turn.content}<end_of_turn>\n`;
57
+ }
58
+ prompt += `<start_of_turn>user\n${user}<end_of_turn>\n<start_of_turn>model\n`;
59
+ }
60
+ else {
61
+ prompt += `${user}<end_of_turn>\n<start_of_turn>model\n`;
62
+ }
63
+ return prompt;
64
+ }
65
+ case 'phi3': {
66
+ let prompt = `<|system|>\n${system}<|end|>\n`;
67
+ for (const turn of history) {
68
+ if (turn.role === 'user') {
69
+ prompt += `<|user|>\n${turn.content}<|end|>\n`;
70
+ }
71
+ else {
72
+ prompt += `<|assistant|>\n${turn.content}<|end|>\n`;
73
+ }
74
+ }
75
+ prompt += `<|user|>\n${user}<|end|>\n<|assistant|>\n`;
76
+ return prompt;
77
+ }
78
+ }
79
+ }
80
+ export function stripThinkingTokens(text) {
81
+ return text.replace(/<think>[\s\S]*?<\/think>\s*/g, '').trim();
82
+ }
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import { llmEmbed, retrievalSearch } from '../../../../infrastructure/adapters/bridge.js';
5
5
  import { KnowledgeRetriever } from '../../knowledge-retriever.js';
6
6
  import { FixEngine } from '../../../development/fix/index.js';
7
- import { ROUTER_PROMPT } from './prompts.js';
7
+ import { ROUTER_SYSTEM, ROUTER_USER } from './prompts.js';
8
8
  /**
9
9
  * ContextAssembler — Responsible for gathering relevant project context.
10
10
  * Uses Knowledge Map routing, Vector Search, and Proactive Auditing.
@@ -57,9 +57,9 @@ export class ContextAssembler {
57
57
  }
58
58
  }
59
59
  async detectRelevantPaths(filesMap, question, brain) {
60
- const routerPrompt = ROUTER_PROMPT(filesMap, question);
60
+ const routerUser = ROUTER_USER(filesMap, question);
61
61
  try {
62
- const result = await brain.infer(routerPrompt);
62
+ const result = await brain.infer(routerUser, undefined, 128, ROUTER_SYSTEM);
63
63
  // Use [\s\S] so the match works on both compact and pretty-printed arrays.
64
64
  const jsonMatch = result.match(/\[[\s\S]*?\]/);
65
65
  if (jsonMatch) {
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Prompts for the Ask Engine
3
3
  */
4
+ export declare const ROUTER_SYSTEM = "You are a context router for a coding assistant. Your only job is to identify which files (MAX 5) are relevant to the user's question and return them as a JSON array. Output ONLY the JSON array \u2014 no explanation, no markdown, no extra text.";
5
+ export declare const ROUTER_USER: (filesMap: string, question: string) => string;
4
6
  export declare const ROUTER_PROMPT: (filesMap: string, question: string) => string;
5
7
  export declare const SYSTEM_INSTRUCTIONS_HEADER: string;
6
8
  //# sourceMappingURL=prompts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../../src/application/orchestration/engine/ask/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,WAexD,CAAA;AAER,eAAO,MAAM,0BAA0B,QAI/B,CAAA"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../../src/application/orchestration/engine/ask/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,aAAa,yPACyN,CAAA;AAEnP,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,WAQtD,CAAA;AAER,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,WACN,CAAA;AAE1D,eAAO,MAAM,0BAA0B,QAI/B,CAAA"}
@@ -1,22 +1,17 @@
1
1
  /**
2
2
  * Prompts for the Ask Engine
3
3
  */
4
- export const ROUTER_PROMPT = (filesMap, question) => `
5
- You are a context router for a coding assistant.
6
- Given the following list of files in the project and the user's question, identify which files (MAX 5) are necessary to answer the question.
7
-
8
- ### PROJECT FILES:
4
+ export const ROUTER_SYSTEM = `You are a context router for a coding assistant. Your only job is to identify which files (MAX 5) are relevant to the user's question and return them as a JSON array. Output ONLY the JSON array — no explanation, no markdown, no extra text.`;
5
+ export const ROUTER_USER = (filesMap, question) => `
6
+ PROJECT FILES:
9
7
  ${filesMap}
10
8
 
11
- ### QUESTION:
12
- ${question}
9
+ QUESTION: ${question}
13
10
 
14
- ### INSTRUCTIONS:
15
- - Identify files that likely contain the answer.
16
- - Answer ONLY with a JSON array of relative paths.
17
- - If no files are relevant, return [].
18
- - Example: ["src/controllers/user.js", "src/models/user.ts"]
11
+ Answer with ONLY a JSON array of relative paths. If no files are relevant, return [].
12
+ Example: ["src/controllers/user.js", "src/models/user.ts"]
19
13
  `.trim();
14
+ export const ROUTER_PROMPT = (filesMap, question) => `${ROUTER_SYSTEM}\n\n${ROUTER_USER(filesMap, question)}`;
20
15
  export const SYSTEM_INSTRUCTIONS_HEADER = `
21
16
  You are Uneven AI (Snatchy), a senior software engineer assistant.
22
17
  Base your answers on the provided project context and conversation history.
@@ -30,12 +30,12 @@ export async function doAsk(ctx, question, onToken, history = [], fixEngineOverr
30
30
  // 3. Context Assembly (Data Diet)
31
31
  const context = await assembler.assemble(question, brain);
32
32
  // 4. Prompt Synthesis
33
- const historySection = formatHistory(history);
34
33
  const SafetyGuard = ctx.safetyGuard.constructor;
35
34
  const sysInstructions = SafetyGuard.SYSTEM_INSTRUCTIONS ?? SYSTEM_INSTRUCTIONS_HEADER;
36
- const fullPrompt = `${sysInstructions}\n\n${context}${historySection}\nQuestion: ${question}\n\nAnswer:`;
35
+ const userContent = `${context}\nQuestion: ${question}`;
36
+ const chatHistory = history.map(t => ({ role: t.role, content: t.content }));
37
37
  // 5. Brain Inference (Thought Process)
38
- let response = await brain.infer(fullPrompt, onToken);
38
+ let response = await brain.infer(userContent, onToken, undefined, sysInstructions, chatHistory);
39
39
  // 6. Sanitization & Finalization
40
40
  response = ctx.safetyGuard.sanitizeResponse(response);
41
41
  ctx.logger.info(`Ask: Generated answer (${response.length} chars)`);
@@ -52,13 +52,3 @@ export async function doAsk(ctx, question, onToken, history = [], fixEngineOverr
52
52
  throw error;
53
53
  }
54
54
  }
55
- /**
56
- * Formats conversation history for the prompt.
57
- */
58
- function formatHistory(history) {
59
- if (history.length === 0)
60
- return '';
61
- return '\n## CONVERSATION HISTORY\n\n' +
62
- history.map(t => t.role === 'user' ? `User: ${t.content}` : `Assistant: ${t.content}`).join('\n') +
63
- '\n';
64
- }
@@ -1 +1 @@
1
- {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ask.ts"],"names":[],"mappings":"AAQA,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,IAAI,CAAC,CAqEf"}
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ask.ts"],"names":[],"mappings":"AAQA,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,IAAI,CAAC,CAsEf"}
@@ -33,6 +33,7 @@ export async function askCommand(question, options = {}) {
33
33
  console.log(t.dim(' Action stopped to keep your machine responsive.'));
34
34
  }
35
35
  blank();
36
+ process.exit(0);
36
37
  });
37
38
  let streamingStarted = false;
38
39
  const answer = await uneven.ask(question, (token) => {
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/chat.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAQ5E;AAED,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,IAAI,CAAC,CAgHf"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/chat.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAQ5E;AAED,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,IAAI,CAAC,CAiHf"}
@@ -44,6 +44,7 @@ export async function chatCommand(options = {}) {
44
44
  blank();
45
45
  console.log(t.rust(` Safety interrupt: ${reason}`));
46
46
  blank();
47
+ process.exit(0);
47
48
  });
48
49
  const cleanup = () => {
49
50
  guardian.stop();
@@ -1 +1 @@
1
- {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/docs.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,OAAO,CAAA;AAEvC,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAA4B,GACrC,MAAM,CAMR;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,GACjB,MAAM,CAuCR;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAW,EACnB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Ef"}
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/docs.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,OAAO,CAAA;AAEvC,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAA4B,GACrC,MAAM,CAMR;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,GACjB,MAAM,CAuCR;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAW,EACnB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
@@ -86,6 +86,7 @@ export async function docsCommand(filePath, symbol = '', options = {}) {
86
86
  guardian = new ResourceGuardian(new Logger('./.uneven/logs/guardian.md'));
87
87
  guardian.start((reason) => {
88
88
  console.error(t.rust(`\n Safety interrupt: ${reason}`));
89
+ process.exit(0);
89
90
  });
90
91
  blank();
91
92
  const modeLabel = format === 'jsdoc' ? 'TSDoc' : 'Markdown';
@@ -1 +1 @@
1
- {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc,QAAS,CAAA;AAEpC,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAuB,GAChC,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAqBR;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAW,EAClB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAiEf"}
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc,QAAS,CAAA;AAEpC,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAuB,GAChC,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAqBR;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAW,EAClB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkEf"}
@@ -66,6 +66,7 @@ export async function explainCommand(filePath, focus = '', options = {}) {
66
66
  guardian = new ResourceGuardian(new Logger(logPath));
67
67
  guardian.start((reason) => {
68
68
  console.error(t.rust(`\n Safety interrupt: ${reason}`));
69
+ process.exit(0);
69
70
  });
70
71
  blank();
71
72
  console.log(t.dim(' ┌─ Explanation'));
@@ -2,7 +2,7 @@ export function defaultModelFor(provider, localModelId) {
2
2
  if (provider === 'local' && localModelId)
3
3
  return localModelId;
4
4
  const MAP = {
5
- local: 'llama-3.2-1b-q8',
5
+ local: 'llama-3.2-1b-q4',
6
6
  ollama: 'llama3.2',
7
7
  claude: 'claude-sonnet-4-6',
8
8
  openai: 'gpt-4o-mini',
@@ -18,19 +18,19 @@ export const EMBEDDING_FILES = [
18
18
  ];
19
19
  export const LOCAL_MODEL_CATALOG = [
20
20
  {
21
- id: 'llama-3.2-1b-q8',
22
- label: 'Llama 3.2 1B Q8',
23
- ram: '~2.5 GB',
24
- storage: '~1.4 GB',
21
+ id: 'llama-3.2-1b-q4',
22
+ label: 'Llama 3.2 1B Q4',
23
+ ram: '~1.5 GB',
24
+ storage: '~700 MB',
25
25
  files: [
26
26
  {
27
- url: 'https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q8_0.gguf',
28
- dest: '.uneven/models/llama-3.2-1b-q8.gguf',
29
- size: '~1.4 GB',
27
+ url: 'https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_K_M.gguf',
28
+ dest: '.uneven/models/llama-3.2-1b-q4.gguf',
29
+ size: '~700 MB',
30
30
  },
31
31
  {
32
32
  url: 'https://huggingface.co/unsloth/Llama-3.2-1B-Instruct/resolve/main/tokenizer.json',
33
- dest: '.uneven/models/llama-3.2-1b-q8-tokenizer.json',
33
+ dest: '.uneven/models/llama-3.2-1b-q4-tokenizer.json',
34
34
  size: '~1.7 MB',
35
35
  },
36
36
  ],
@@ -1,5 +1,5 @@
1
1
  export declare const SESSION_FILE = ".uneven/session.json";
2
- export declare const UNEVEN_VERSION = "1.2.2";
2
+ export declare const UNEVEN_VERSION = "1.3.0";
3
3
  export declare const STALE_THRESHOLD_MS: number;
4
4
  export declare const LOCK_TIMEOUT_MS = 30000;
5
5
  export declare const LOCK_DEBOUNCE_MS = 1500;
@@ -1,5 +1,5 @@
1
1
  export const SESSION_FILE = '.uneven/session.json';
2
- export const UNEVEN_VERSION = '1.2.2';
2
+ export const UNEVEN_VERSION = '1.3.0';
3
3
  export const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour
4
4
  export const LOCK_TIMEOUT_MS = 30_000; // 30 seconds
5
5
  export const LOCK_DEBOUNCE_MS = 1_500; // 1.5 seconds
@@ -1,3 +1,5 @@
1
+ import { sanitizeLlmError } from '../utils/llm-error.js';
2
+ export { sanitizeLlmError };
1
3
  export interface NativeEngine {
2
4
  initEngine(config: object, threads: number): Promise<string>;
3
5
  getVersion(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/adapters/bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/adapters/bridge.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,8 @@ import { createRequire } from 'module';
3
3
  import { resolve, dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { withTimeout, TimeoutError } from '../utils/timeout.js';
6
+ import { sanitizeLlmError } from '../utils/llm-error.js';
7
+ export { sanitizeLlmError };
6
8
  // Embedding: fast pass through the model — 2 min covers cold-start on CPU
7
9
  const LLM_EMBED_TIMEOUT_MS = 120_000;
8
10
  // Inference: generating 300-500 tokens on CPU — 3 min
@@ -210,7 +212,7 @@ repeatPenalty = 1.1) {
210
212
  catch (error) {
211
213
  if (error instanceof TimeoutError)
212
214
  nativeBinding.interruptLocalInference();
213
- throw new Error(`Failed to run inference: ${error instanceof Error ? error.message : error}`);
215
+ throw new Error(sanitizeLlmError(error));
214
216
  }
215
217
  }
216
218
  export async function llmInferStream(prompt, onToken, maxTokens, threads = 4, temperature = 0.0, repeatPenalty = 1.1) {
@@ -224,7 +226,7 @@ export async function llmInferStream(prompt, onToken, maxTokens, threads = 4, te
224
226
  catch (error) {
225
227
  if (error instanceof TimeoutError)
226
228
  nativeBinding.interruptLocalInference();
227
- throw new Error(`Failed to run streaming inference: ${error instanceof Error ? error.message : error}`);
229
+ throw new Error(sanitizeLlmError(error));
228
230
  }
229
231
  }
230
232
  export async function flushVectorStore() {
@@ -43,7 +43,7 @@ export async function findProjectRoot(startDir = process.cwd()) {
43
43
  const DEFAULTS = {
44
44
  brain: {
45
45
  provider: 'local',
46
- model: 'llama-3.2-1b-q8',
46
+ model: 'llama-3.2-1b-q4',
47
47
  temperature: 0.3,
48
48
  maxTokens: 2048,
49
49
  local: {
@@ -475,7 +475,7 @@ export async function saveConfig(config, projectRoot) {
475
475
  }
476
476
  // Display names for local model ids — keep in sync with init/constants LOCAL_MODEL_CATALOG
477
477
  const LOCAL_MODEL_LABELS = {
478
- 'llama-3.2-1b-q8': 'Llama 3.2 1B Q8',
478
+ 'llama-3.2-1b-q4': 'Llama 3.2 1B Q4',
479
479
  'qwen-2.5-1.5b-q8': 'Qwen 2.5 1.5B Q8',
480
480
  'deepseek-r1-1.5b-q8': 'DeepSeek-R1 1.5B Q8',
481
481
  'gemma-3-1b-q8': 'Gemma 3 1B Q8',
@@ -0,0 +1,2 @@
1
+ export declare function sanitizeLlmError(error: unknown): string;
2
+ //# sourceMappingURL=llm-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-error.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/utils/llm-error.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA2BvD"}
@@ -0,0 +1,20 @@
1
+ export function sanitizeLlmError(error) {
2
+ const raw = error instanceof Error ? error.message : String(error);
3
+ if (/model.*not found|cannot open.*models|no such file/i.test(raw))
4
+ return 'Local model not found. Run `uneven init` to download it.';
5
+ if (/tokenizer not found/i.test(raw))
6
+ return 'Model tokenizer missing. Run `uneven init` to reinstall.';
7
+ if (/model not loaded/i.test(raw))
8
+ return 'Local model is not loaded. Run `uneven init` to set up.';
9
+ if (/out of memory|insufficient memory|oom/i.test(raw))
10
+ return 'Not enough memory for local inference. Try a smaller model.';
11
+ if (/timed out|timeout/i.test(raw))
12
+ return 'Inference timed out. The model may be too slow for your hardware.';
13
+ if (/interrupted/i.test(raw))
14
+ return 'Inference was interrupted.';
15
+ if (/context window.*too small|unavailable/i.test(raw))
16
+ return raw;
17
+ if (process.env.UNEVEN_DEBUG)
18
+ return raw;
19
+ return 'Local inference failed. Set UNEVEN_DEBUG=1 for technical details.';
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uneven-ai",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"