feat: 整合功能恢复与技能学习闭环(含 ECC v2.1 parity + Opus 4.7 接入 + prompt 工程优化)

主要变更:
- Skill Learning 闭环系统 (9/9 AC)
- Opus 4.7 模型层接入 + adaptive thinking
- Prompt 工程优化 (64 审计测试)
- Agent Teams 简化门控 (默认启用)
- Windows Terminal 后端修复 (EncodedCommand/WT_SESSION)
- TF-IDF 技能搜索精准化 (字段加权/CJK 优化)
- Autonomy 系统 (/autonomy 命令)
- ACP 协议完整实现
- mock.module 泄漏修复 (CI 全绿)
- 152+ lint/type 修复
This commit is contained in:
unraid
2026-04-22 16:07:42 +08:00
parent 711927f01b
commit 95fece4b51
316 changed files with 39611 additions and 14298 deletions

View File

@@ -0,0 +1,152 @@
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { call } from '../skill-learning.js'
import {
recordSkillGap,
saveInstinct,
createInstinct,
resolveProjectContext,
} from '../../../services/skillLearning/index.js'
let root: string
const originalEnv = { ...process.env }
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'skill-learning-command-'))
process.env = { ...originalEnv }
process.env.CLAUDE_SKILL_LEARNING_HOME = root
process.env.CLAUDE_CONFIG_DIR = join(root, 'config')
process.env.SKILL_LEARNING_ENABLED = '1'
})
afterEach(() => {
process.env = { ...originalEnv }
rmSync(root, { recursive: true, force: true })
})
describe('skill-learning command', () => {
test('status reports observations and instincts', async () => {
const result = await call('status', {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('Skill Learning status')
expect(result.value).toContain('Observations: 0')
}
})
test('promote (no args) prints usage and candidate summary', async () => {
const result = await call('promote', {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('Promotion candidates')
expect(result.value).toContain('promote gap')
expect(result.value).toContain('promote instinct')
}
})
test('promote gap <key> promotes a pending gap to draft', async () => {
const project = resolveProjectContext(process.cwd())
const gap = await recordSkillGap({
prompt: 'refactor the api gateway',
cwd: process.cwd(),
project,
rootDir: root,
})
expect(gap.status).toBe('pending')
const result = await call(`promote gap ${gap.key}`, {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('Promoted gap')
expect(result.value).toContain('status=draft')
}
})
test('promote gap <unknown-key> reports not found', async () => {
const result = await call('promote gap does-not-exist', {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('No gap found')
}
})
test('promote instinct <id> copies a project instinct to global scope', async () => {
const project = resolveProjectContext(process.cwd())
const instinct = createInstinct({
trigger: 'when committing',
action: 'run tests first',
confidence: 0.85,
domain: 'testing',
source: 'session-observation',
scope: 'project',
projectId: project.projectId,
projectName: project.projectName,
evidence: ['observed twice'],
})
await saveInstinct(instinct, { project, rootDir: root })
const result = await call(`promote instinct ${instinct.id}`, {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('Promoted instinct')
expect(result.value).toContain('global scope')
}
})
test('projects lists known project scopes', async () => {
// Resolving once registers the current project in the registry.
resolveProjectContext(root)
const result = await call('projects', {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(
result.value.includes('Known project scopes') ||
result.value.includes('No known project scopes'),
).toBe(true)
}
})
test('default help mentions promote and projects, no write-fixture', async () => {
const result = await call('unknown-sub', {} as any)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('promote')
expect(result.value).toContain('projects')
expect(result.value).not.toContain('write-fixture')
}
})
test('ingest imports transcript observations and instincts', async () => {
const transcript = join(root, 'session.jsonl')
writeFileSync(
transcript,
JSON.stringify({
type: 'user',
sessionId: 's1',
cwd: root,
message: { role: 'user', content: '不要 mock用 testing-library' },
}) + '\n',
)
// Pass --min-session-length=0 so the 1-line test transcript is not skipped
// by the ECC-parity gate (default threshold: 10 observations).
const result = await call(
`ingest ${transcript} --min-session-length=0`,
{} as any,
)
expect(result.type).toBe('text')
if (result.type === 'text') {
expect(result.value).toContain('Ingested')
expect(result.value).toContain('saved 1 instincts')
}
})
})