dsh-omni-router 1.2.0 → 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.
package/README.md CHANGED
@@ -55,6 +55,8 @@
55
55
  - **状态持久化**:分类/思维模式/任务类型写入会话事件,resume 可恢复。
56
56
  - **降级保护**:Plan Mode 不可用时自动限制为只读工具。
57
57
  - **Hybrid 分类**:启发式置信度 + 低置信度时 LLM 结构化分类。
58
+ - **Policy Engine**:`buildPolicyDecision` 统一输出完整决策对象(taskType/complexity/risk/executionMode/approval/verification/gitPolicy)。
59
+ - **Repository Snapshot**:`buildRepositorySnapshot` 识别包管理器、测试框架、框架、入口点。
58
60
  - **Policy/State Orchestration**:用 `workflowPolicy` 状态机代替大量重复 prompt。
59
61
  - **Agent 选择**:`selectAgentForTask` 推荐 frontend/backend/db/browser/security/review agent。
60
62
  - **Router Benchmark**:内置 `benchmark/run.mjs`(399 个任务),可评估 accuracy / false-direct rate。
package/README.zh-CN.md CHANGED
@@ -55,6 +55,8 @@
55
55
  - **状态持久化**:分类/思维模式/任务类型写入会话事件,resume 可恢复。
56
56
  - **降级保护**:Plan Mode 不可用时自动限制为只读工具。
57
57
  - **Hybrid 分类**:启发式置信度 + 低置信度时 LLM 结构化分类。
58
+ - **Policy Engine**:`buildPolicyDecision` 统一输出完整决策对象(taskType/complexity/risk/executionMode/approval/verification/gitPolicy)。
59
+ - **Repository Snapshot**:`buildRepositorySnapshot` 识别包管理器、测试框架、框架、入口点。
58
60
  - **Policy/State Orchestration**:用 `workflowPolicy` 状态机代替大量重复 prompt。
59
61
  - **Agent 选择**:`selectAgentForTask` 推荐 frontend/backend/db/browser/security/review agent。
60
62
  - **Router Benchmark**:内置 `benchmark/run.mjs`(399 个任务),可评估 accuracy / false-direct rate。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-omni-router",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Omni Router - auto-routing DeepSeek Harness preset: simple tasks run directly, complex tasks plan first with approval gate, coding tasks get TDD/delivery-gate guidance.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -368,7 +368,7 @@ export function estimateRisk(text) {
368
368
  if (/(生产环境|production|prod|密钥|secret|token|drop database|drop table|rm -rf)/.test(normalized)) {
369
369
  reasons.push('production/secret/destructive')
370
370
  }
371
- if (/(schema|migration|drop table|drop database|连接池|auth|权限|deploy|ci\/cd|配置|config|Webhook|验签|签名|对账|网关|删除.*(数据库|字段|表|生产|配置|auth|用户)|删掉.*(数据库|字段|表|生产|配置|auth|用户))/.test(normalized)) {
371
+ if (/(schema|migration|drop table|drop database|连接池|auth|权限|deploy|ci\/cd|配置|config|Webhook|验签|签名|对账|网关|退款|扣款|删除.*(数据库|字段|表|生产|配置|auth|用户)|删掉.*(数据库|字段|表|生产|配置|auth|用户))/.test(normalized)) {
372
372
  reasons.push('schema/auth/delete/deploy')
373
373
  }
374
374
  if (/(数据库|db|redis|登录|login|session|token|订单|order|支付|payment|业务逻辑|重构|refactor|api|接口|核心)/.test(normalized)) {
@@ -426,6 +426,66 @@ export function workflowPolicy(taskType, complexity, risk) {
426
426
  }
427
427
  }
428
428
 
429
+ /**
430
+ * Policy Engine: unify all routing dimensions into one decision object.
431
+ * This is the "brain first layer" described in the Omni roadmap.
432
+ */
433
+ export function buildPolicyDecision(taskText, config = {}) {
434
+ const complexity = classifyComplexity(taskText, config)
435
+ const taskType = classifyTaskType(taskText)
436
+ const risk = estimateRisk(taskText).level
437
+ const highRisk = ['high', 'critical'].includes(risk)
438
+ const executionMode = complexity === 'plan' || highRisk ? 'plan' : 'direct'
439
+ const coding = ['bugfix', 'feature', 'refactor', 'test'].includes(taskType)
440
+ return {
441
+ taskType,
442
+ complexity,
443
+ risk,
444
+ reasoningMode: complexity === 'plan' || highRisk ? 'max' : 'balanced',
445
+ contextStrategy: 'dependency-aware',
446
+ executionMode,
447
+ approvalRequired: executionMode === 'plan' || highRisk,
448
+ confidence: heuristicComplexity(taskText, config).confidence,
449
+ verification: coding ? ['unit', 'integration', 'regression'] : [],
450
+ gitPolicy: {
451
+ requireBranch: coding,
452
+ },
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Project Brain first step: build a lightweight repository snapshot from root
458
+ * entries. Later this will be backed by git/ripgrep/tree-sitter.
459
+ */
460
+ export function buildRepositorySnapshot(entries) {
461
+ const names = new Set((Array.isArray(entries) ? entries : []).map((entry) => entry.name))
462
+ const packageManager = names.has('pnpm-lock.yaml') ? 'pnpm'
463
+ : names.has('package-lock.json') ? 'npm'
464
+ : names.has('yarn.lock') ? 'yarn'
465
+ : names.has('bun.lockb') ? 'bun'
466
+ : null
467
+ const testFramework = names.has('vitest.config.ts') || names.has('vitest.config.js') ? 'vitest'
468
+ : names.has('jest.config.js') || names.has('jest.config.ts') ? 'jest'
469
+ : names.has('cypress.config.ts') || names.has('cypress.config.js') ? 'cypress'
470
+ : names.has('mocha.opts') ? 'mocha'
471
+ : null
472
+ const framework = names.has('next.config.js') || names.has('next.config.mjs') ? 'next'
473
+ : names.has('vite.config.ts') || names.has('vite.config.js') ? 'vite'
474
+ : names.has('angular.json') ? 'angular'
475
+ : names.has('nuxt.config.ts') ? 'nuxt'
476
+ : null
477
+ const entryPoints = ['src', 'lib', 'app', 'api', 'server', 'client', 'test', 'tests']
478
+ .filter((name) => names.has(name))
479
+ return {
480
+ packageManager,
481
+ testFramework,
482
+ framework,
483
+ entryPoints,
484
+ hasReadme: names.has('README.md'),
485
+ hasPackageJson: names.has('package.json'),
486
+ }
487
+ }
488
+
429
489
  /**
430
490
  * Build a compact project-context summary from root entries and key file
431
491
  * contents. This is injected into plan mode so the model starts with context
@@ -808,6 +868,12 @@ export function apply(ctx, config = {}) {
808
868
  text: `Risk level: ${state.riskLevel}. High or critical risk requires plan approval before mutation.`,
809
869
  })
810
870
  }
871
+ const policyDecision = buildPolicyDecision(state.firstText || '', config)
872
+ sections.push({
873
+ name: 'omni-router:policy-decision',
874
+ order: 37,
875
+ text: `Policy decision: ${JSON.stringify(policyDecision)}`,
876
+ })
811
877
  const policy = workflowPolicy(taskType, state.kind || 'direct', state.riskLevel || 'low')
812
878
  sections.push({
813
879
  name: 'omni-router:policy',
@@ -6,6 +6,8 @@ import {
6
6
  buildContextGraph,
7
7
  buildContextSummary,
8
8
  buildDependencyHints,
9
+ buildPolicyDecision,
10
+ buildRepositorySnapshot,
9
11
  classifyComplexity,
10
12
  classifyTaskType,
11
13
  classifyThinkingMode,
@@ -356,3 +358,28 @@ test('extractSymbolsFromText finds function/class/const names', () => {
356
358
  assert.ok(symbols.includes('TOKEN_KEY'))
357
359
  assert.ok(symbols.includes('refreshSession'))
358
360
  })
361
+
362
+ test('buildPolicyDecision returns a unified policy object', () => {
363
+ const policy = buildPolicyDecision('修复退款偶发重复扣款', {})
364
+ assert.equal(policy.taskType, 'bugfix')
365
+ assert.equal(policy.complexity, 'plan')
366
+ assert.equal(policy.risk, 'high')
367
+ assert.equal(policy.executionMode, 'plan')
368
+ assert.equal(policy.approvalRequired, true)
369
+ assert.ok(Array.isArray(policy.verification))
370
+ })
371
+
372
+ test('buildRepositorySnapshot detects project metadata from root entries', () => {
373
+ const entries = [
374
+ { name: 'package.json', type: 'file' },
375
+ { name: 'pnpm-lock.yaml', type: 'file' },
376
+ { name: 'src', type: 'directory' },
377
+ { name: 'tests', type: 'directory' },
378
+ { name: 'vitest.config.ts', type: 'file' },
379
+ { name: 'README.md', type: 'file' },
380
+ ]
381
+ const snapshot = buildRepositorySnapshot(entries)
382
+ assert.equal(snapshot.packageManager, 'pnpm')
383
+ assert.equal(snapshot.testFramework, 'vitest')
384
+ assert.ok(snapshot.entryPoints.includes('src'))
385
+ })