@dommaker/harness 1.5.0 → 1.6.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/CHANGELOG.md +11 -0
- package/README.md +0 -2
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -32
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/passes-gate.d.ts +0 -2
- package/dist/cli/commands/passes-gate.d.ts.map +1 -1
- package/dist/cli/commands/passes-gate.js +0 -1
- package/dist/cli/commands/passes-gate.js.map +1 -1
- package/dist/cli/commands/performance.d.ts +0 -4
- package/dist/cli/commands/performance.d.ts.map +1 -1
- package/dist/cli/commands/performance.js +10 -20
- package/dist/cli/commands/performance.js.map +1 -1
- package/dist/cli/commands/report.d.ts +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +2 -55
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +4 -8
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/context/index.d.ts +2 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -8
- package/dist/context/index.js.map +1 -1
- package/dist/context/token-budget.d.ts +0 -29
- package/dist/context/token-budget.d.ts.map +1 -1
- package/dist/context/token-budget.js +1 -65
- package/dist/context/token-budget.js.map +1 -1
- package/dist/context/types.d.ts +0 -1
- package/dist/context/types.d.ts.map +1 -1
- package/dist/context/types.js +0 -9
- package/dist/context/types.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +14 -0
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +22 -2
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/git-evidence.d.ts +6 -3
- package/dist/core/constraints/git-evidence.d.ts.map +1 -1
- package/dist/core/constraints/git-evidence.js +6 -3
- package/dist/core/constraints/git-evidence.js.map +1 -1
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/spec/validator.d.ts +2 -0
- package/dist/core/spec/validator.d.ts.map +1 -1
- package/dist/core/spec/validator.js +9 -11
- package/dist/core/spec/validator.js.map +1 -1
- package/dist/core/validators/index.d.ts +1 -1
- package/dist/core/validators/index.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +1 -15
- package/dist/core/validators/passes-gate.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.js +0 -125
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/failure/index.d.ts +0 -1
- package/dist/failure/index.d.ts.map +1 -1
- package/dist/failure/index.js +1 -7
- package/dist/failure/index.js.map +1 -1
- package/dist/gates/definitions.d.ts.map +1 -1
- package/dist/gates/definitions.js +0 -4
- package/dist/gates/definitions.js.map +1 -1
- package/dist/gates/performance.d.ts +7 -22
- package/dist/gates/performance.d.ts.map +1 -1
- package/dist/gates/performance.js +7 -89
- package/dist/gates/performance.js.map +1 -1
- package/dist/gates/types.d.ts +2 -8
- package/dist/gates/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +0 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -3
- package/dist/knowledge/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts +25 -10
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +147 -116
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/types/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +5 -99
- package/src/__tests__/performance-gate.test.ts +3 -11
- package/src/__tests__/public-exports.test.ts +46 -16
- package/src/__tests__/trace-analyzer.test.ts +52 -2
- package/src/cli/commands/CONTEXT.md +3 -2
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
- package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
- package/src/cli/commands/__tests__/performance.test.ts +5 -16
- package/src/cli/commands/__tests__/registry.test.ts +46 -3
- package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
- package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
- package/src/cli/commands/__tests__/report.test.ts +0 -20
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
- package/src/cli/commands/__tests__/status.test.ts +66 -148
- package/src/cli/commands/definitions.ts +1 -32
- package/src/cli/commands/passes-gate.ts +0 -3
- package/src/cli/commands/performance.ts +13 -27
- package/src/cli/commands/report.ts +3 -61
- package/src/cli/commands/status.ts +5 -10
- package/src/context/CONTEXT.md +2 -3
- package/src/context/__tests__/token-budget.test.ts +1 -81
- package/src/context/index.ts +2 -7
- package/src/context/token-budget.ts +0 -73
- package/src/context/types.ts +0 -9
- package/src/core/CONTEXT.md +5 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +33 -2
- package/src/core/constraints/git-evidence.ts +6 -3
- package/src/core/index.ts +0 -11
- package/src/core/spec/__tests__/validator.test.ts +24 -0
- package/src/core/spec/validator.ts +9 -10
- package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
- package/src/core/validators/index.ts +0 -1
- package/src/core/validators/passes-gate.ts +1 -149
- package/src/failure/CONTEXT.md +8 -6
- package/src/failure/index.ts +0 -10
- package/src/gates/CONTEXT.md +3 -2
- package/src/gates/__tests__/performance.test.ts +6 -74
- package/src/gates/definitions.ts +0 -4
- package/src/gates/performance.ts +9 -111
- package/src/gates/types.ts +2 -8
- package/src/index.ts +1 -36
- package/src/knowledge/CONTEXT.md +1 -2
- package/src/knowledge/index.ts +0 -1
- package/src/monitoring/CONTEXT.md +2 -2
- package/src/monitoring/trace-analyzer.ts +160 -126
- package/src/types/passes-gate.ts +0 -26
- package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
- package/dist/cli/commands/analyze-sessions.d.ts +0 -20
- package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
- package/dist/cli/commands/analyze-sessions.js +0 -290
- package/dist/cli/commands/analyze-sessions.js.map +0 -1
- package/dist/cli/commands/update-user-model.d.ts +0 -30
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -406
- package/dist/cli/commands/update-user-model.js.map +0 -1
- package/dist/cli/session-mining/corrections.d.ts +0 -17
- package/dist/cli/session-mining/corrections.d.ts.map +0 -1
- package/dist/cli/session-mining/corrections.js +0 -58
- package/dist/cli/session-mining/corrections.js.map +0 -1
- package/dist/cli/session-mining/index.d.ts +0 -13
- package/dist/cli/session-mining/index.d.ts.map +0 -1
- package/dist/cli/session-mining/index.js +0 -29
- package/dist/cli/session-mining/index.js.map +0 -1
- package/dist/cli/session-mining/text.d.ts +0 -31
- package/dist/cli/session-mining/text.d.ts.map +0 -1
- package/dist/cli/session-mining/text.js +0 -126
- package/dist/cli/session-mining/text.js.map +0 -1
- package/dist/cli/session-mining/transcript.d.ts +0 -34
- package/dist/cli/session-mining/transcript.d.ts.map +0 -1
- package/dist/cli/session-mining/transcript.js +0 -130
- package/dist/cli/session-mining/transcript.js.map +0 -1
- package/dist/context/compaction.d.ts +0 -57
- package/dist/context/compaction.d.ts.map +0 -1
- package/dist/context/compaction.js +0 -165
- package/dist/context/compaction.js.map +0 -1
- package/dist/core/session/clean-state.d.ts +0 -17
- package/dist/core/session/clean-state.d.ts.map +0 -1
- package/dist/core/session/clean-state.js +0 -192
- package/dist/core/session/clean-state.js.map +0 -1
- package/dist/core/session/index.d.ts +0 -6
- package/dist/core/session/index.d.ts.map +0 -1
- package/dist/core/session/index.js +0 -15
- package/dist/core/session/index.js.map +0 -1
- package/dist/core/session/startup.d.ts +0 -47
- package/dist/core/session/startup.d.ts.map +0 -1
- package/dist/core/session/startup.js +0 -263
- package/dist/core/session/startup.js.map +0 -1
- package/dist/failure/constraint-handler.d.ts +0 -83
- package/dist/failure/constraint-handler.d.ts.map +0 -1
- package/dist/failure/constraint-handler.js +0 -138
- package/dist/failure/constraint-handler.js.map +0 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
- package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
- package/dist/knowledge/lifecycle-hooks.js +0 -143
- package/dist/knowledge/lifecycle-hooks.js.map +0 -1
- package/dist/types/session.d.ts +0 -113
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -6
- package/dist/types/session.js.map +0 -1
- package/src/__tests__/clean-state-extra.test.ts +0 -301
- package/src/__tests__/clean-state.test.ts +0 -169
- package/src/__tests__/constraint-handler.test.ts +0 -132
- package/src/__tests__/passes-gate-extra.test.ts +0 -93
- package/src/__tests__/session-startup.test.ts +0 -334
- package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
- package/src/cli/commands/__tests__/update-user-model.test.ts +0 -268
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -476
- package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
- package/src/cli/session-mining/corrections.ts +0 -56
- package/src/cli/session-mining/index.ts +0 -13
- package/src/cli/session-mining/text.ts +0 -117
- package/src/cli/session-mining/transcript.ts +0 -125
- package/src/context/__tests__/compaction.test.ts +0 -169
- package/src/context/compaction.ts +0 -201
- package/src/core/session/__tests__/startup.test.ts +0 -281
- package/src/core/session/clean-state.ts +0 -178
- package/src/core/session/index.ts +0 -15
- package/src/core/session/startup.ts +0 -253
- package/src/failure/constraint-handler.ts +0 -171
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
- package/src/knowledge/lifecycle-hooks.ts +0 -202
- package/src/types/session.ts +0 -132
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* harness update-user-model — 用户模型持续演化引擎
|
|
3
|
-
*
|
|
4
|
-
* 不依赖固定规则快照。每天从最新数据中提取增量信号,
|
|
5
|
-
* 对比昨天状态,输出变化,更新模型。
|
|
6
|
-
*
|
|
7
|
-
* 数据源:
|
|
8
|
-
* ~/.claude/projects/-root--claude/*.jsonl (对话)
|
|
9
|
-
* /tmp/claude-knowledge-hooks.log (行为信号)
|
|
10
|
-
* studio/.harness/knowledge/ (知识新鲜度)
|
|
11
|
-
* ~/.claude/projects/-root-projects/memory/ (规则库)
|
|
12
|
-
*
|
|
13
|
-
* 模型状态: $HARNESS_UUM_STATE_FILE,默认 ~/.claude/user-model-state.json
|
|
14
|
-
* 画像输出: $HARNESS_UUM_PROFILE_FILE,默认 ~/.claude/projects/-root-projects/memory/user_profile.md
|
|
15
|
-
*
|
|
16
|
-
* 工单 19-C:transcript 解析/纠正模式/相似度收敛至 cli/session-mining/。
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import chalk from 'chalk';
|
|
20
|
-
import * as fs from 'fs';
|
|
21
|
-
import * as path from 'path';
|
|
22
|
-
import * as os from 'os';
|
|
23
|
-
import {
|
|
24
|
-
readTranscriptSessions,
|
|
25
|
-
type TranscriptFilter,
|
|
26
|
-
extractCorrectionMatches,
|
|
27
|
-
cleanCorrectionConcept,
|
|
28
|
-
jaccardChinese,
|
|
29
|
-
} from '../session-mining';
|
|
30
|
-
import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
|
|
31
|
-
|
|
32
|
-
export interface UpdateUserModelOptions {
|
|
33
|
-
days?: number; // 只处理最近 N 天(自然日,含今天)的会话;缺省不过滤(向后兼容)
|
|
34
|
-
json?: boolean;
|
|
35
|
-
dryRun?: boolean; // don't update state, just show what would change
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ModelState {
|
|
39
|
-
lastUpdated: string;
|
|
40
|
-
sessionsProcessed: string[]; // session IDs already counted
|
|
41
|
-
patterns: Record<string, { // pattern → running stats
|
|
42
|
-
firstSeen: string;
|
|
43
|
-
occurrences: number;
|
|
44
|
-
sessions: string[];
|
|
45
|
-
trend: 'rising' | 'stable' | 'falling' | 'new' | 'gone';
|
|
46
|
-
lastSeen: string;
|
|
47
|
-
}>;
|
|
48
|
-
lensWeights: Record<string, number>; // lens → activation count → normalized weight
|
|
49
|
-
principleWeights: Record<string, number>;
|
|
50
|
-
evolutionLog: Array<{ date: string; change: string }>;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 路径集中解析(harness#116):默认值保持原行为,env 可覆盖(多工作区/测试隔离)。
|
|
54
|
-
// 运行时解析而非模块加载期常量,保证 env 设置后生效。
|
|
55
|
-
export interface UserModelPaths {
|
|
56
|
-
stateFile: string;
|
|
57
|
-
profileFile: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function resolveUserModelPaths(env: NodeJS.ProcessEnv = process.env): UserModelPaths {
|
|
61
|
-
const home = os.homedir();
|
|
62
|
-
return {
|
|
63
|
-
stateFile: env.HARNESS_UUM_STATE_FILE
|
|
64
|
-
|| path.join(home, '.claude', 'user-model-state.json'),
|
|
65
|
-
profileFile: env.HARNESS_UUM_PROFILE_FILE
|
|
66
|
-
|| path.join(home, '.claude', 'projects', '-root-projects', 'memory', 'user_profile.md'),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function updateUserModel(options: UpdateUserModelOptions, io: CommandIO = processIO): Promise<CommandResult> {
|
|
71
|
-
const transcriptDir = process.env.CLAUDE_TRANSCRIPTS_DIR
|
|
72
|
-
|| path.join(os.homedir(), '.claude', 'projects', '-root--claude');
|
|
73
|
-
|
|
74
|
-
// 1. Load previous state
|
|
75
|
-
const paths = resolveUserModelPaths();
|
|
76
|
-
const state = loadState(paths);
|
|
77
|
-
|
|
78
|
-
// 2. Scan new data
|
|
79
|
-
const newSessions = findNewSessions(transcriptDir, state.sessionsProcessed, options.days);
|
|
80
|
-
if (newSessions.length === 0) {
|
|
81
|
-
log(io, chalk.gray('No new sessions to process'));
|
|
82
|
-
return { kind: 'skip', reason: '没有新会话可处理' };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 3. Extract signals from new data
|
|
86
|
-
const signals = extractSignals(newSessions);
|
|
87
|
-
|
|
88
|
-
// 4. Build concept clusters (semantic dedup)
|
|
89
|
-
const mergedConcepts = buildMergedConcepts(signals);
|
|
90
|
-
|
|
91
|
-
// 5. Diff against previous state → find changes
|
|
92
|
-
const changes = diffState(state, mergedConcepts, signals);
|
|
93
|
-
|
|
94
|
-
// 6. Update state
|
|
95
|
-
if (!options.dryRun) {
|
|
96
|
-
for (const sid of newSessions.map(s => s.id)) {
|
|
97
|
-
if (!state.sessionsProcessed.includes(sid)) {
|
|
98
|
-
state.sessionsProcessed.push(sid);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
applySignals(state, signals, mergedConcepts);
|
|
102
|
-
state.lastUpdated = new Date().toISOString();
|
|
103
|
-
|
|
104
|
-
if (state.sessionsProcessed.length > 200) {
|
|
105
|
-
state.sessionsProcessed = state.sessionsProcessed.slice(-200);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
saveState(state, paths);
|
|
109
|
-
updateProfile(state, paths);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 6. Output
|
|
113
|
-
if (options.json) {
|
|
114
|
-
log(io, JSON.stringify({ newSessions: newSessions.length, changes }, null, 2));
|
|
115
|
-
return { kind: 'ok' };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
log(io, chalk.blue(`📊 Processed ${newSessions.length} new sessions\n`));
|
|
119
|
-
printChanges(changes, signals, io);
|
|
120
|
-
return { kind: 'ok' };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── State I/O ──
|
|
124
|
-
|
|
125
|
-
function loadState(paths: UserModelPaths): ModelState {
|
|
126
|
-
try {
|
|
127
|
-
if (fs.existsSync(paths.stateFile)) {
|
|
128
|
-
return JSON.parse(fs.readFileSync(paths.stateFile, 'utf-8'));
|
|
129
|
-
}
|
|
130
|
-
} catch {}
|
|
131
|
-
return {
|
|
132
|
-
lastUpdated: new Date(0).toISOString(),
|
|
133
|
-
sessionsProcessed: [],
|
|
134
|
-
patterns: {},
|
|
135
|
-
lensWeights: {},
|
|
136
|
-
principleWeights: {},
|
|
137
|
-
evolutionLog: [],
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function saveState(state: ModelState, paths: UserModelPaths): void {
|
|
142
|
-
try {
|
|
143
|
-
const dir = path.dirname(paths.stateFile);
|
|
144
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
145
|
-
fs.writeFileSync(paths.stateFile, JSON.stringify(state, null, 2), 'utf-8');
|
|
146
|
-
} catch {}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── Session scanning ──
|
|
150
|
-
|
|
151
|
-
interface SimpleSession {
|
|
152
|
-
id: string;
|
|
153
|
-
date: string;
|
|
154
|
-
userText: string; // all user messages joined
|
|
155
|
-
assistantText: string; // all assistant messages joined
|
|
156
|
-
toolCalls: string[]; // tool names used
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function findNewSessions(dir: string, processed: string[], days?: number): SimpleSession[] {
|
|
160
|
-
// 过滤下推 seam(harness#112):excludeIds 走文件名、since 走 stat,未命中不 parse
|
|
161
|
-
const filter: TranscriptFilter = { excludeIds: processed };
|
|
162
|
-
|
|
163
|
-
// 「最近 N 天」:自然日窗口(含今天)。days<=0 视为不过滤,与缺省一致。
|
|
164
|
-
// date 是 mtime 的 UTC 日,date >= cutoff ⟺ mtimeMs >= cutoff 日 UTC 零点
|
|
165
|
-
if (days !== undefined && days > 0) {
|
|
166
|
-
const cutoff = new Date(Date.now() - (days - 1) * 86_400_000)
|
|
167
|
-
.toISOString()
|
|
168
|
-
.slice(0, 10);
|
|
169
|
-
filter.since = Date.parse(cutoff);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const sessions = readTranscriptSessions(dir, filter);
|
|
173
|
-
|
|
174
|
-
sessions.sort((a, b) => a.date.localeCompare(b.date));
|
|
175
|
-
|
|
176
|
-
return sessions.map(s => ({
|
|
177
|
-
id: s.id,
|
|
178
|
-
date: s.date,
|
|
179
|
-
userText: s.turns.filter(t => t.role === 'user').map(t => t.content).join('\n'),
|
|
180
|
-
assistantText: s.turns.filter(t => t.role === 'assistant').map(t => t.content).join('\n'),
|
|
181
|
-
toolCalls: s.toolCalls,
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ── Signal extraction ──
|
|
186
|
-
|
|
187
|
-
interface SessionSignals {
|
|
188
|
-
sessionId: string;
|
|
189
|
-
date: string;
|
|
190
|
-
correctionPhrases: string[]; // "你又..." etc.
|
|
191
|
-
concepts: Record<string, number>; // N-gram → frequency
|
|
192
|
-
toolPatterns: Record<string, number>; // tool → count
|
|
193
|
-
sessionDuration: number; // estimated from turn count
|
|
194
|
-
turnCount: number;
|
|
195
|
-
deepAnalysis: boolean; // EnterPlanMode or Agent(Explore)
|
|
196
|
-
knowledgeCaptured: boolean; // Write to knowledge-docs
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function extractCorrectionConcepts(userText: string): string[] {
|
|
200
|
-
const concepts: string[] = [];
|
|
201
|
-
for (const sentence of extractCorrectionMatches(userText)) {
|
|
202
|
-
const cleaned = cleanCorrectionConcept(sentence);
|
|
203
|
-
if (cleaned.length >= 4 && cleaned.length <= 60) concepts.push(cleaned);
|
|
204
|
-
}
|
|
205
|
-
return [...new Set(concepts)];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ── Semantic clustering ──
|
|
209
|
-
|
|
210
|
-
function clusterConcepts(
|
|
211
|
-
agg: Record<string, { count: number; sessions: string[] }>
|
|
212
|
-
): Record<string, { count: number; sessions: string[] }> {
|
|
213
|
-
const entries = Object.entries(agg).filter(([k]) => k.length >= 4);
|
|
214
|
-
if (entries.length <= 1) return agg;
|
|
215
|
-
|
|
216
|
-
const merged: Record<string, { count: number; sessions: string[] }> = {};
|
|
217
|
-
const used = new Set<number>();
|
|
218
|
-
|
|
219
|
-
for (let i = 0; i < entries.length; i++) {
|
|
220
|
-
if (used.has(i)) continue;
|
|
221
|
-
const [concept, data] = entries[i];
|
|
222
|
-
let clusterName = concept;
|
|
223
|
-
let clusterCount = data.count;
|
|
224
|
-
const clusterSessions = new Set(data.sessions);
|
|
225
|
-
used.add(i);
|
|
226
|
-
|
|
227
|
-
for (let j = i + 1; j < entries.length; j++) {
|
|
228
|
-
if (used.has(j)) continue;
|
|
229
|
-
const [other, otherData] = entries[j];
|
|
230
|
-
const sim = jaccardChinese(concept, other);
|
|
231
|
-
if (sim > 0.5) {
|
|
232
|
-
clusterCount += otherData.count;
|
|
233
|
-
for (const s of otherData.sessions) clusterSessions.add(s);
|
|
234
|
-
// Use the shorter name as cluster label
|
|
235
|
-
if (other.length < clusterName.length) clusterName = other;
|
|
236
|
-
used.add(j);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
merged[clusterName] = { count: clusterCount, sessions: [...clusterSessions] };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return merged;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function extractSignals(sessions: SimpleSession[]): SessionSignals[] {
|
|
247
|
-
return sessions.map(s => {
|
|
248
|
-
const correctionPhrases = extractCorrectionConcepts(s.userText);
|
|
249
|
-
|
|
250
|
-
// Concept extraction (Chinese char N-grams, etc.)
|
|
251
|
-
const concepts: Record<string, number> = {};
|
|
252
|
-
const cleanText = s.userText
|
|
253
|
-
.replace(/```[\s\S]*?```/g, '')
|
|
254
|
-
.replace(/\{[^{}]*\}/g, '')
|
|
255
|
-
.replace(/[{}[\]"':,\\]+/g, ' ');
|
|
256
|
-
let cnSeq = '';
|
|
257
|
-
for (const char of cleanText) {
|
|
258
|
-
if (/[\u4e00-\u9fff]/.test(char)) {
|
|
259
|
-
cnSeq += char;
|
|
260
|
-
} else {
|
|
261
|
-
if (cnSeq.length >= 4) concepts[cnSeq] = (concepts[cnSeq] || 0) + 1;
|
|
262
|
-
cnSeq = '';
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (cnSeq.length >= 4) concepts[cnSeq] = (concepts[cnSeq] || 0) + 1;
|
|
266
|
-
|
|
267
|
-
// Tool patterns
|
|
268
|
-
const toolPatterns: Record<string, number> = {};
|
|
269
|
-
for (const t of s.toolCalls) {
|
|
270
|
-
toolPatterns[t] = (toolPatterns[t] || 0) + 1;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
sessionId: s.id,
|
|
275
|
-
date: s.date,
|
|
276
|
-
correctionPhrases,
|
|
277
|
-
concepts,
|
|
278
|
-
toolPatterns,
|
|
279
|
-
sessionDuration: s.userText.length + s.assistantText.length, // proxy
|
|
280
|
-
turnCount: (s.userText.match(/\n/g) || []).length,
|
|
281
|
-
deepAnalysis: s.toolCalls.includes('EnterPlanMode') || s.toolCalls.includes('Agent'),
|
|
282
|
-
knowledgeCaptured: s.toolCalls.includes('Write') && (
|
|
283
|
-
s.assistantText.includes('.harness/knowledge-docs/') ||
|
|
284
|
-
s.assistantText.includes('.harness/knowledge/')
|
|
285
|
-
),
|
|
286
|
-
};
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ── Concept aggregation ──
|
|
291
|
-
|
|
292
|
-
type ConceptMap = Record<string, { count: number; sessions: string[] }>;
|
|
293
|
-
|
|
294
|
-
function buildMergedConcepts(signals: SessionSignals[]): ConceptMap {
|
|
295
|
-
const agg: ConceptMap = {};
|
|
296
|
-
for (const sig of signals) {
|
|
297
|
-
for (const [concept, count] of Object.entries(sig.concepts)) {
|
|
298
|
-
if (!agg[concept]) agg[concept] = { count: 0, sessions: [] };
|
|
299
|
-
agg[concept].count += count;
|
|
300
|
-
if (!agg[concept].sessions.includes(sig.sessionId)) {
|
|
301
|
-
agg[concept].sessions.push(sig.sessionId);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
for (const phrase of sig.correctionPhrases) {
|
|
305
|
-
if (phrase.length < 4) continue;
|
|
306
|
-
if (!agg[phrase]) agg[phrase] = { count: 0, sessions: [] };
|
|
307
|
-
agg[phrase].count += 3;
|
|
308
|
-
if (!agg[phrase].sessions.includes(sig.sessionId)) {
|
|
309
|
-
agg[phrase].sessions.push(sig.sessionId);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return clusterConcepts(agg);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ── Diff & Apply ──
|
|
317
|
-
|
|
318
|
-
interface Change {
|
|
319
|
-
type: 'new_pattern' | 'rising' | 'falling' | 'gone' | 'lens_shift';
|
|
320
|
-
key: string;
|
|
321
|
-
detail: string;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function diffState(state: ModelState, mergedConcepts: ConceptMap, signals: SessionSignals[]): Change[] {
|
|
325
|
-
const changes: Change[] = [];
|
|
326
|
-
|
|
327
|
-
// Detect new/rising/falling/gone concepts (from merged clusters)
|
|
328
|
-
for (const [concept, agg] of Object.entries(mergedConcepts)) {
|
|
329
|
-
const existing = state.patterns[concept];
|
|
330
|
-
if (!existing) {
|
|
331
|
-
if (agg.sessions.length >= 2) {
|
|
332
|
-
changes.push({ type: 'new_pattern', key: concept, detail: `Appeared in ${agg.sessions.length} sessions` });
|
|
333
|
-
}
|
|
334
|
-
} else if (existing.trend === 'gone' || existing.trend === 'falling') {
|
|
335
|
-
changes.push({ type: 'rising', key: concept, detail: `Re-emerged after being ${existing.trend}` });
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Detect lens weight shifts (from correction patterns)
|
|
340
|
-
const totalCorrections = signals.reduce((sum, s) => sum + s.correctionPhrases.length, 0);
|
|
341
|
-
if (totalCorrections >= 3) {
|
|
342
|
-
changes.push({ type: 'lens_shift', key: 'completeness',
|
|
343
|
-
detail: `${totalCorrections} corrections in these sessions — completeness lens may need weight increase` });
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return changes;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function applySignals(state: ModelState, signals: SessionSignals[], mergedConcepts: ConceptMap): void {
|
|
350
|
-
const now = new Date().toISOString().slice(0, 10);
|
|
351
|
-
|
|
352
|
-
// Update patterns from merged concept clusters
|
|
353
|
-
// occurrences 唯一来源:mergedConcepts 聚合值(含 correction phrase 的 ×3 加权,
|
|
354
|
-
// 见 buildMergedConcepts)。不再叠加 per-session 原始 count —— 那是 #113 裁决的双计 bug。
|
|
355
|
-
for (const [concept, agg] of Object.entries(mergedConcepts)) {
|
|
356
|
-
if (!state.patterns[concept]) {
|
|
357
|
-
state.patterns[concept] = {
|
|
358
|
-
firstSeen: now, occurrences: 0, sessions: [], trend: 'new', lastSeen: now,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
const p = state.patterns[concept];
|
|
362
|
-
p.occurrences += agg.count;
|
|
363
|
-
for (const sid of agg.sessions) {
|
|
364
|
-
if (!p.sessions.includes(sid)) p.sessions.push(sid);
|
|
365
|
-
}
|
|
366
|
-
p.lastSeen = now;
|
|
367
|
-
p.trend = p.occurrences >= 5 ? 'stable' : 'rising';
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Update lens weights from correction signals
|
|
371
|
-
for (const sig of signals) {
|
|
372
|
-
if (sig.correctionPhrases.length > 0) {
|
|
373
|
-
state.lensWeights.completeness = (state.lensWeights.completeness || 0) + 1;
|
|
374
|
-
state.lensWeights.automation = (state.lensWeights.automation || 0) + (sig.correctionPhrases.length > 2 ? 1 : 0);
|
|
375
|
-
}
|
|
376
|
-
if (sig.deepAnalysis) {
|
|
377
|
-
state.lensWeights.first_principles = (state.lensWeights.first_principles || 0) + 1;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Detect falling/stable shifts
|
|
382
|
-
for (const [, p] of Object.entries(state.patterns)) {
|
|
383
|
-
if (p.trend === 'stable' && p.lastSeen < new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10)) {
|
|
384
|
-
p.trend = 'falling';
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ── Profile update ──
|
|
390
|
-
|
|
391
|
-
function updateProfile(state: ModelState, paths: UserModelPaths): void {
|
|
392
|
-
try {
|
|
393
|
-
let content = fs.readFileSync(paths.profileFile, 'utf-8');
|
|
394
|
-
|
|
395
|
-
// Replace Derived Rules section
|
|
396
|
-
const derivedStart = '## Derived Rules';
|
|
397
|
-
const derivedEnd = '\n## Evolution';
|
|
398
|
-
const derivedIdx = content.indexOf(derivedStart);
|
|
399
|
-
|
|
400
|
-
const activePatterns = Object.entries(state.patterns)
|
|
401
|
-
.filter(([, p]) => (p.trend === 'rising' || p.trend === 'stable') && p.sessions.length >= 2)
|
|
402
|
-
.sort(([, a], [, b]) => b.occurrences - a.occurrences)
|
|
403
|
-
.slice(0, 10);
|
|
404
|
-
|
|
405
|
-
const derivedContent = [
|
|
406
|
-
'## Derived Rules (auto-generated by update-user-model)',
|
|
407
|
-
`Last scan: ${state.lastUpdated}. ${state.sessionsProcessed.length} sessions analyzed.`,
|
|
408
|
-
'',
|
|
409
|
-
'| Pattern | Trend | Occurrences | Sessions |',
|
|
410
|
-
'|---------|-------|-------------|----------|',
|
|
411
|
-
...activePatterns.map(([k, p]) =>
|
|
412
|
-
`| ${k} | ${p.trend} | ${p.occurrences} | ${p.sessions.length} |`),
|
|
413
|
-
'',
|
|
414
|
-
].join('\n');
|
|
415
|
-
|
|
416
|
-
if (derivedIdx >= 0) {
|
|
417
|
-
content = content.slice(0, derivedIdx) + derivedContent + '\n' + content.slice(content.indexOf(derivedEnd, derivedIdx));
|
|
418
|
-
} else {
|
|
419
|
-
// No derived section yet — append before Evolution
|
|
420
|
-
const evIdx = content.indexOf('## Evolution');
|
|
421
|
-
if (evIdx >= 0) {
|
|
422
|
-
content = content.slice(0, evIdx) + derivedContent + '\n' + content.slice(evIdx);
|
|
423
|
-
} else {
|
|
424
|
-
content += '\n' + derivedContent;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
fs.writeFileSync(paths.profileFile, content, 'utf-8');
|
|
429
|
-
} catch (e) {
|
|
430
|
-
// Profile file might not exist yet — skip
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// ── Output ──
|
|
435
|
-
|
|
436
|
-
function printChanges(changes: Change[], signals: SessionSignals[], io: CommandIO): void {
|
|
437
|
-
if (changes.length === 0) {
|
|
438
|
-
log(io, chalk.green('No significant changes detected'));
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const byType = {
|
|
443
|
-
new_pattern: changes.filter(c => c.type === 'new_pattern'),
|
|
444
|
-
rising: changes.filter(c => c.type === 'rising'),
|
|
445
|
-
lens_shift: changes.filter(c => c.type === 'lens_shift'),
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
if (byType.new_pattern.length > 0) {
|
|
449
|
-
log(io, chalk.yellow(`🌱 New patterns: ${byType.new_pattern.length}`));
|
|
450
|
-
for (const c of byType.new_pattern.slice(0, 5)) {
|
|
451
|
-
log(io, chalk.gray(` ${c.key}: ${c.detail}`));
|
|
452
|
-
}
|
|
453
|
-
log(io);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (byType.rising.length > 0) {
|
|
457
|
-
log(io, chalk.green(`📈 Re-emerging: ${byType.rising.length}`));
|
|
458
|
-
for (const c of byType.rising.slice(0, 5)) {
|
|
459
|
-
log(io, chalk.gray(` ${c.key}: ${c.detail}`));
|
|
460
|
-
}
|
|
461
|
-
log(io);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (byType.lens_shift.length > 0) {
|
|
465
|
-
log(io, chalk.cyan(`🎯 Lens shifts:`));
|
|
466
|
-
for (const c of byType.lens_shift) {
|
|
467
|
-
log(io, chalk.gray(` ${c.key}: ${c.detail}`));
|
|
468
|
-
}
|
|
469
|
-
log(io);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
log(io, chalk.bold(`Total signals processed:`));
|
|
473
|
-
log(io, ` Sessions: ${signals.length}`);
|
|
474
|
-
log(io, ` Correction phrases: ${signals.reduce((s, sig) => s + sig.correctionPhrases.length, 0)}`);
|
|
475
|
-
log(io, ` Concepts extracted: ${signals.reduce((s, sig) => s + Object.keys(sig.concepts).length, 0)}`);
|
|
476
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* readTranscriptSessions 坏行/null 记录容错测试(harness#82)
|
|
3
|
-
*
|
|
4
|
-
* transcript 行可 parse 但值为 null(或提取异常)时,
|
|
5
|
-
* 与原逐行 catch 实现一致:该条跳过,不影响其余记录。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
import * as os from 'os';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import { readTranscriptSessions } from '../transcript';
|
|
12
|
-
|
|
13
|
-
describe('readTranscriptSessions 坏行容错(harness#82)', () => {
|
|
14
|
-
let dir: string;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-transcript-'));
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('null 行与坏行只丢该行,合法 turn 照常提取', () => {
|
|
25
|
-
const lines = [
|
|
26
|
-
JSON.stringify({ message: { role: 'user', content: 'hello' } }),
|
|
27
|
-
'null', // parse 合法但值为 null,原实现经逐行 catch 跳过
|
|
28
|
-
'{"broken',
|
|
29
|
-
JSON.stringify({ message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }),
|
|
30
|
-
];
|
|
31
|
-
fs.writeFileSync(path.join(dir, 'session-a.jsonl'), lines.join('\n') + '\n', 'utf-8');
|
|
32
|
-
|
|
33
|
-
const sessions = readTranscriptSessions(dir);
|
|
34
|
-
expect(sessions).toHaveLength(1);
|
|
35
|
-
expect(sessions[0].turns.map(t => t.role)).toEqual(['user', 'assistant']);
|
|
36
|
-
expect(sessions[0].turns[1].content).toBe('hi');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('目录不可读返回空数组', () => {
|
|
40
|
-
expect(readTranscriptSessions(path.join(dir, 'missing'))).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('readTranscriptSessions 过滤(harness#112)', () => {
|
|
45
|
-
let dir: string;
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-transcript-filter-'));
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
function writeSession(name: string, text: string, mtime?: Date): void {
|
|
56
|
-
const p = path.join(dir, name);
|
|
57
|
-
fs.writeFileSync(
|
|
58
|
-
p,
|
|
59
|
-
JSON.stringify({ message: { role: 'user', content: text } }) + '\n',
|
|
60
|
-
'utf-8',
|
|
61
|
-
);
|
|
62
|
-
if (mtime) fs.utimesSync(p, mtime, mtime);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
it('excludeIds:命中的会话不进结果(文件名级过滤,不 parse)', () => {
|
|
66
|
-
writeSession('session-a.jsonl', '甲会话内容');
|
|
67
|
-
writeSession('session-b.jsonl', '乙会话内容');
|
|
68
|
-
|
|
69
|
-
const sessions = readTranscriptSessions(dir, { excludeIds: ['session-a'] });
|
|
70
|
-
expect(sessions.map(s => s.id)).toEqual(['session-b']);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('excludeIds 按截断 40 字符的会话 ID 匹配', () => {
|
|
74
|
-
const longName = 'a'.repeat(50);
|
|
75
|
-
writeSession(`${longName}.jsonl`, '长名会话内容');
|
|
76
|
-
writeSession('session-b.jsonl', '乙会话内容');
|
|
77
|
-
|
|
78
|
-
const sessions = readTranscriptSessions(dir, { excludeIds: [longName.slice(0, 40)] });
|
|
79
|
-
expect(sessions.map(s => s.id)).toEqual(['session-b']);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('since:mtimeMs 早于窗口的文件不进结果(stat 级过滤,不 parse)', () => {
|
|
83
|
-
writeSession('old.jsonl', '旧会话内容', new Date(Date.now() - 30 * 86_400_000));
|
|
84
|
-
writeSession('recent.jsonl', '新会话内容');
|
|
85
|
-
|
|
86
|
-
const since = Date.now() - 7 * 86_400_000;
|
|
87
|
-
const sessions = readTranscriptSessions(dir, { since });
|
|
88
|
-
expect(sessions.map(s => s.id)).toEqual(['recent']);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('since 边界含等于:mtimeMs == since 仍计入', () => {
|
|
92
|
-
const t = new Date('2026-08-01T12:00:00.000Z');
|
|
93
|
-
writeSession('edge.jsonl', '边界会话内容', t);
|
|
94
|
-
|
|
95
|
-
const sessions = readTranscriptSessions(dir, { since: t.getTime() });
|
|
96
|
-
expect(sessions.map(s => s.id)).toEqual(['edge']);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('since 与 excludeIds 可叠加', () => {
|
|
100
|
-
writeSession('old.jsonl', '旧会话内容', new Date(Date.now() - 30 * 86_400_000));
|
|
101
|
-
writeSession('excluded.jsonl', '被排除会话');
|
|
102
|
-
writeSession('kept.jsonl', '保留会话内容');
|
|
103
|
-
|
|
104
|
-
const sessions = readTranscriptSessions(dir, {
|
|
105
|
-
since: Date.now() - 7 * 86_400_000,
|
|
106
|
-
excludeIds: ['excluded'],
|
|
107
|
-
});
|
|
108
|
-
expect(sessions.map(s => s.id)).toEqual(['kept']);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('无 filter:全量读取(向后兼容)', () => {
|
|
112
|
-
writeSession('a.jsonl', '甲会话内容', new Date(Date.now() - 30 * 86_400_000));
|
|
113
|
-
writeSession('b.jsonl', '乙会话内容');
|
|
114
|
-
|
|
115
|
-
const sessions = readTranscriptSessions(dir);
|
|
116
|
-
expect(sessions).toHaveLength(2);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 纠正信号模式表与概念清洗(工单 19-C)
|
|
3
|
-
*
|
|
4
|
-
* 合并 analyze-sessions 与 update-user-model 两份几乎相同的模式表,
|
|
5
|
-
* 采用较宽窗口(update-user-model 版本)作为唯一实现。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** 用户纠正/重复强调语句的匹配模式(两命令合并后的超集,取较宽窗口) */
|
|
9
|
-
export const CORRECTION_PATTERNS: RegExp[] = [
|
|
10
|
-
/你(?:又|还是)(?:在)?(?:犯|忘|没|不).{0,30}?[了]?/g,
|
|
11
|
-
/我不是(?:说|让|让做|讲)[了过]?.{0,50}?[了吗??]?/g,
|
|
12
|
-
/怎么(?:又|还|老)(?:是|在)?.{0,30}?[了]?/g,
|
|
13
|
-
/我(?:一直|反复|总是)(?:在|说|强调)?.{0,30}?[了]?/g,
|
|
14
|
-
/老(?:是|在|犯|忘)(?:了)?.{0,20}?[了]?/g,
|
|
15
|
-
/这(?:个|种)(?:问题|模式|错误).{0,20}?[.。!]?/g,
|
|
16
|
-
/(?:第[一二三四五六七八九十\d]+次|反复|重复)(?:说|提醒|强调).{0,20}?/g,
|
|
17
|
-
/不(?:要|能|想|愿意).{0,20}?老.{0,10}?[了]?/g,
|
|
18
|
-
/(?:补上|加上|记上|修复).{0,10}?[了吗??]?/g,
|
|
19
|
-
/(?:沉淀|监控|日志|记录).{0,5}?[了吗??]?/g,
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
/** 纠正语句前缀(提取概念时剥离) */
|
|
23
|
-
const CORRECTION_PREFIX_STRIP: RegExp[] = [
|
|
24
|
-
/你(?:又|还是)(?:在)?(?:犯|忘|没|不)/g,
|
|
25
|
-
/我不是(?:说|让|让做|讲)[了过]?/g,
|
|
26
|
-
/怎么(?:又|还|老)(?:是|在)?/g,
|
|
27
|
-
/我(?:一直|反复|总是)(?:在|说|强调)?/g,
|
|
28
|
-
/老(?:是|在|犯|忘)(?:了)?/g,
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 从纠正语句中提取概念:剥离纠正前缀与标点
|
|
33
|
-
*/
|
|
34
|
-
export function cleanCorrectionConcept(sentence: string): string {
|
|
35
|
-
let cleaned = sentence;
|
|
36
|
-
for (const re of CORRECTION_PREFIX_STRIP) {
|
|
37
|
-
re.lastIndex = 0;
|
|
38
|
-
cleaned = cleaned.replace(re, '');
|
|
39
|
-
}
|
|
40
|
-
return cleaned.replace(/[,。!?、;:""''()\s]+/g, '').trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 用模式表在文本中抽取纠正语句(去重)
|
|
45
|
-
*/
|
|
46
|
-
export function extractCorrectionMatches(text: string): string[] {
|
|
47
|
-
const matches: string[] = [];
|
|
48
|
-
for (const pattern of CORRECTION_PATTERNS) {
|
|
49
|
-
pattern.lastIndex = 0;
|
|
50
|
-
let match: RegExpExecArray | null;
|
|
51
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
52
|
-
matches.push(match[0].trim());
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return matches;
|
|
56
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 会话挖掘共享层(工单 19-C)
|
|
3
|
-
*
|
|
4
|
-
* update-user-model 与 analyze-sessions 两个命令此前各自维护一份
|
|
5
|
-
* transcript 解析 / 纠正模式 / 分词与相似度实现,收敛到本目录:
|
|
6
|
-
* - transcript.ts Claude Code transcript(.jsonl)读取
|
|
7
|
-
* - corrections.ts 纠正信号模式表与概念清洗
|
|
8
|
-
* - text.ts 分词 / 停用词 / Jaccard / 文本清洗
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export * from './transcript';
|
|
12
|
-
export * from './corrections';
|
|
13
|
-
export * from './text';
|