dsh-continual-evolve 0.11.0 → 0.12.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 +35 -115
- package/README.zh.md +26 -93
- package/cordis.patch.yml +2 -2
- package/lib/index.d.ts +44 -225
- package/lib/index.js +30 -239
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +32 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/package.json +2 -17
- package/lib/apply.d.ts +0 -22
- package/lib/apply.js +0 -157
- package/lib/approval.d.ts +0 -73
- package/lib/approval.js +0 -127
- package/lib/audit-log.d.ts +0 -19
- package/lib/audit-log.js +0 -52
- package/lib/auto.d.ts +0 -236
- package/lib/auto.js +0 -773
- package/lib/autocase.d.ts +0 -38
- package/lib/autocase.js +0 -76
- package/lib/benchmark-command.d.ts +0 -9
- package/lib/benchmark-command.js +0 -357
- package/lib/benchmark.d.ts +0 -151
- package/lib/benchmark.js +0 -250
- package/lib/command-util.d.ts +0 -70
- package/lib/command-util.js +0 -123
- package/lib/command.d.ts +0 -13
- package/lib/command.js +0 -134
- package/lib/consolidate.d.ts +0 -90
- package/lib/consolidate.js +0 -180
- package/lib/copy.d.ts +0 -37
- package/lib/copy.js +0 -58
- package/lib/declines.d.ts +0 -103
- package/lib/declines.js +0 -179
- package/lib/evaluate.d.ts +0 -74
- package/lib/evaluate.js +0 -271
- package/lib/evolve-event.d.ts +0 -40
- package/lib/evolve-event.js +0 -60
- package/lib/extraction-phase.d.ts +0 -132
- package/lib/extraction-phase.js +0 -236
- package/lib/failures.d.ts +0 -42
- package/lib/failures.js +0 -171
- package/lib/fate.d.ts +0 -130
- package/lib/fate.js +0 -350
- package/lib/goal-command.d.ts +0 -7
- package/lib/goal-command.js +0 -37
- package/lib/goal.d.ts +0 -72
- package/lib/goal.js +0 -72
- package/lib/inject.d.ts +0 -90
- package/lib/inject.js +0 -164
- package/lib/injection-caps.d.ts +0 -4
- package/lib/injection-caps.js +0 -4
- package/lib/injection-directory.d.ts +0 -29
- package/lib/injection-directory.js +0 -78
- package/lib/injection-rank.d.ts +0 -85
- package/lib/injection-rank.js +0 -161
- package/lib/listener.d.ts +0 -45
- package/lib/listener.js +0 -199
- package/lib/llm-text.d.ts +0 -104
- package/lib/llm-text.js +0 -140
- package/lib/logfile.d.ts +0 -71
- package/lib/logfile.js +0 -159
- package/lib/maintenance-commands.d.ts +0 -23
- package/lib/maintenance-commands.js +0 -136
- package/lib/memory-agent.d.ts +0 -93
- package/lib/memory-agent.js +0 -300
- package/lib/memory-apply.d.ts +0 -44
- package/lib/memory-apply.js +0 -202
- package/lib/memory-benchmark.d.ts +0 -76
- package/lib/memory-benchmark.js +0 -170
- package/lib/memory-index.d.ts +0 -114
- package/lib/memory-index.js +0 -197
- package/lib/memory-manifest.d.ts +0 -16
- package/lib/memory-manifest.js +0 -70
- package/lib/message-source.d.ts +0 -23
- package/lib/message-source.js +0 -12
- package/lib/mount-command.d.ts +0 -10
- package/lib/mount-command.js +0 -48
- package/lib/mount.d.ts +0 -42
- package/lib/mount.js +0 -212
- package/lib/notify.d.ts +0 -48
- package/lib/notify.js +0 -57
- package/lib/plan-command.d.ts +0 -12
- package/lib/plan-command.js +0 -38
- package/lib/plan.d.ts +0 -16
- package/lib/plan.js +0 -126
- package/lib/planner.d.ts +0 -61
- package/lib/planner.js +0 -144
- package/lib/pool.d.ts +0 -7
- package/lib/pool.js +0 -25
- package/lib/prefix-cache.d.ts +0 -79
- package/lib/prefix-cache.js +0 -168
- package/lib/project.d.ts +0 -28
- package/lib/project.js +0 -66
- package/lib/projection.d.ts +0 -22
- package/lib/projection.js +0 -112
- package/lib/promotion.d.ts +0 -94
- package/lib/promotion.js +0 -171
- package/lib/recall.d.ts +0 -85
- package/lib/recall.js +0 -142
- package/lib/record-language.d.ts +0 -85
- package/lib/record-language.js +0 -152
- package/lib/render.d.ts +0 -13
- package/lib/render.js +0 -80
- package/lib/review-scheduler.d.ts +0 -38
- package/lib/review-scheduler.js +0 -183
- package/lib/review.d.ts +0 -60
- package/lib/review.js +0 -145
- package/lib/rollback.d.ts +0 -9
- package/lib/rollback.js +0 -68
- package/lib/rubric.d.ts +0 -29
- package/lib/rubric.js +0 -119
- package/lib/runtime-commands.d.ts +0 -14
- package/lib/runtime-commands.js +0 -115
- package/lib/runtime.d.ts +0 -24
- package/lib/runtime.js +0 -87
- package/lib/score.d.ts +0 -64
- package/lib/score.js +0 -191
- package/lib/search.d.ts +0 -83
- package/lib/search.js +0 -136
- package/lib/service.d.ts +0 -63
- package/lib/service.js +0 -253
- package/lib/skill-render.d.ts +0 -23
- package/lib/skill-render.js +0 -69
- package/lib/skill.d.ts +0 -15
- package/lib/skill.js +0 -80
- package/lib/skillquality.d.ts +0 -85
- package/lib/skillquality.js +0 -320
- package/lib/source.d.ts +0 -30
- package/lib/source.js +0 -43
- package/lib/state.d.ts +0 -26
- package/lib/state.js +0 -151
- package/lib/store.d.ts +0 -61
- package/lib/store.js +0 -164
- package/lib/token-usage.d.ts +0 -68
- package/lib/token-usage.js +0 -211
- package/lib/tool.d.ts +0 -34
- package/lib/tool.js +0 -320
- package/lib/turn-snapshot.d.ts +0 -78
- package/lib/turn-snapshot.js +0 -252
- package/lib/types.d.ts +0 -218
- package/lib/types.js +0 -133
- package/lib/usage.d.ts +0 -36
- package/lib/usage.js +0 -98
- package/lib/validate.d.ts +0 -28
- package/lib/validate.js +0 -181
- package/lib/wrapup-candidates.d.ts +0 -127
- package/lib/wrapup-candidates.js +0 -161
- package/lib/wrapup-command.d.ts +0 -10
- package/lib/wrapup-command.js +0 -243
- package/lib/wrapup-proposals.d.ts +0 -101
- package/lib/wrapup-proposals.js +0 -281
- package/lib/wrapup.d.ts +0 -56
- package/lib/wrapup.js +0 -105
package/lib/memory-apply.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { MEMORY_TYPE_KEY } from "./types.js";
|
|
2
|
-
import { requestScopeApproval } from "./approval.js";
|
|
3
|
-
import { fingerprintMemoryBatch, isDeclinedRepeat, loadDeclinedMemory, recordDeclinedMemory, } from "./declines.js";
|
|
4
|
-
import { compactText } from "./render.js";
|
|
5
|
-
import { buildConflictNotice, CONFLICT_WARN_SCORE, mostSimilarEntry } from "./promotion.js";
|
|
6
|
-
import { EvolutionApplyPostCommitError } from "./service.js";
|
|
7
|
-
/**
|
|
8
|
-
* Render the authoritative persistent-scope diff shown to the human. Model
|
|
9
|
-
* prose is retained only as an explicitly untrusted summary; actions, targets,
|
|
10
|
-
* content previews, memory types, and conflict warnings come from code.
|
|
11
|
-
*/
|
|
12
|
-
export function renderMemoryApprovalDetails(proposal, scope, state, maxChars = 6000) {
|
|
13
|
-
const edits = proposal.edits.filter((candidate) => candidate.targetScope === scope);
|
|
14
|
-
const details = edits.map((edit) => {
|
|
15
|
-
const before = edit.id ? state.entries.memory[edit.id] : undefined;
|
|
16
|
-
const title = edit.title ?? before?.title ?? "(existing title unchanged)";
|
|
17
|
-
const path = edit.path ?? before?.path ?? "(path unchanged)";
|
|
18
|
-
const content = edit.content ?? (edit.action === "update" ? "(content unchanged)" : "(no content field)");
|
|
19
|
-
const type = edit.metadata?.[MEMORY_TYPE_KEY] ?? before?.metadata[MEMORY_TYPE_KEY] ?? "untyped";
|
|
20
|
-
const candidates = Object.values(state.entries.memory).filter((entry) => entry.id !== edit.id);
|
|
21
|
-
const hit = edit.action === "create" || edit.action === "update"
|
|
22
|
-
? mostSimilarEntry(candidates, edit.title ?? before?.title ?? "", edit.content ?? before?.content ?? "", CONFLICT_WARN_SCORE)
|
|
23
|
-
: undefined;
|
|
24
|
-
const conflict = hit ? `; conflict: ${compactText(buildConflictNotice(hit), 120)}` : "";
|
|
25
|
-
const targetId = edit.id === undefined ? "(new)" : JSON.stringify(edit.id);
|
|
26
|
-
return {
|
|
27
|
-
target: `- ${edit.action} ${scope}:${targetId} — ${compactText(title, 80)}`,
|
|
28
|
-
detail: ` path=${compactText(path, 60)}; memoryType=${String(type)}; content=${compactText(content, 100)}${conflict}`,
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
let titleLimit = 80;
|
|
32
|
-
let targets = details.map((detail) => detail.target.replace(/ — .*$/, ` — ${compactText(detail.target.split(" — ")[1] ?? "", titleLimit)}`));
|
|
33
|
-
let base = ["edits to approve:", ...targets].join("\n");
|
|
34
|
-
while (base.length > maxChars && titleLimit > 20) {
|
|
35
|
-
titleLimit = Math.floor(titleLimit / 2);
|
|
36
|
-
targets = details.map((detail) => `- ${detail.target.split(" — ")[0]} — ${compactText(detail.target.split(" — ")[1] ?? "", titleLimit)}`);
|
|
37
|
-
base = ["edits to approve:", ...targets].join("\n");
|
|
38
|
-
}
|
|
39
|
-
if (base.length > maxChars)
|
|
40
|
-
throw new Error(`memory approval diff exceeds ${maxChars} characters`);
|
|
41
|
-
const output = [base];
|
|
42
|
-
const summary = `model summary (untrusted): ${compactText(proposal.summary, 180)}`;
|
|
43
|
-
if ([...output, summary].join("\n").length <= maxChars)
|
|
44
|
-
output.push(summary);
|
|
45
|
-
for (const detail of details) {
|
|
46
|
-
const line = detail.detail;
|
|
47
|
-
if ([...output, line].join("\n").length <= maxChars)
|
|
48
|
-
output.push(line);
|
|
49
|
-
}
|
|
50
|
-
if ([...output, " (additional previews omitted; every target is listed above)"].join("\n").length <= maxChars) {
|
|
51
|
-
output.push(" (additional previews omitted; every target is listed above)");
|
|
52
|
-
}
|
|
53
|
-
return output.join("\n");
|
|
54
|
-
}
|
|
55
|
-
function memoryScopeDecisionKey(cursor, scope, edits) {
|
|
56
|
-
return `${cursor}:${scope}:${JSON.stringify(edits.map(stripTargetScope))}`;
|
|
57
|
-
}
|
|
58
|
-
class MemoryApplyBatchError extends Error {
|
|
59
|
-
result;
|
|
60
|
-
scope;
|
|
61
|
-
storeId;
|
|
62
|
-
constructor(message, result, scope, storeId) {
|
|
63
|
-
super(message);
|
|
64
|
-
this.result = result;
|
|
65
|
-
this.scope = scope;
|
|
66
|
-
this.storeId = storeId;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Apply one accepted proposal through the only mutation engine. All persistent
|
|
71
|
-
* approvals are resolved before the first write, so an unavailable approval
|
|
72
|
-
* boundary cannot leave a partially applied batch. Explicit rejection is a
|
|
73
|
-
* no-op for that scope; service/question failures remain retryable errors.
|
|
74
|
-
*/
|
|
75
|
-
export async function applyMemoryExtractionProposal(ctx, engine, proposal, options) {
|
|
76
|
-
const scopeOrder = ["local", "project", "global"];
|
|
77
|
-
const editsByScope = new Map();
|
|
78
|
-
for (const scope of scopeOrder) {
|
|
79
|
-
const edits = proposal.edits.filter((edit) => edit.targetScope === scope);
|
|
80
|
-
if (edits.length > 0)
|
|
81
|
-
editsByScope.set(scope, edits);
|
|
82
|
-
}
|
|
83
|
-
if (editsByScope.has("project") && !options.projectKey) {
|
|
84
|
-
throw new Error("memory agent cannot target project scope without a resolved project key");
|
|
85
|
-
}
|
|
86
|
-
const approvedScopes = new Set();
|
|
87
|
-
const declinedScopes = [];
|
|
88
|
-
for (const scope of ["project", "global"]) {
|
|
89
|
-
if (!editsByScope.has(scope))
|
|
90
|
-
continue;
|
|
91
|
-
if (!options.requireApproval) {
|
|
92
|
-
approvedScopes.add(scope);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const baseline = options.baselines[scope];
|
|
96
|
-
if (!baseline)
|
|
97
|
-
throw new Error(`memory agent has no ${scope} baseline`);
|
|
98
|
-
const scopeEdits = proposal.edits.filter((edit) => edit.targetScope === scope);
|
|
99
|
-
const fingerprint = fingerprintMemoryBatch(scope, scopeEdits);
|
|
100
|
-
if (isDeclinedRepeat(loadDeclinedMemory(engine.baseDir), scope, fingerprint)) {
|
|
101
|
-
ctx.logger("continual-evolve").info(`memory ${scope} proposal repeat-suppressed (declined before, no popup)`);
|
|
102
|
-
declinedScopes.push(scope);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const decisionKey = options.decisionCursor === undefined
|
|
106
|
-
? undefined
|
|
107
|
-
: memoryScopeDecisionKey(options.decisionCursor, scope, proposal.edits.filter((edit) => edit.targetScope === scope));
|
|
108
|
-
const previous = decisionKey === undefined ? undefined : options.scopeDecisions?.[decisionKey];
|
|
109
|
-
if (previous === "approved") {
|
|
110
|
-
approvedScopes.add(scope);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (previous === "declined") {
|
|
114
|
-
declinedScopes.push(scope);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline), options.recordLanguage);
|
|
118
|
-
if (decisionKey !== undefined)
|
|
119
|
-
options.onScopeDecision?.(decisionKey, approved);
|
|
120
|
-
if (approved === "approved")
|
|
121
|
-
approvedScopes.add(scope);
|
|
122
|
-
else {
|
|
123
|
-
declinedScopes.push(scope);
|
|
124
|
-
try {
|
|
125
|
-
recordDeclinedMemory(engine.baseDir, scope, scopeEdits, compactText(scopeEdits[0]?.title ?? proposal.summary, 120));
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
ctx.logger("continual-evolve").warn(`declined-memory ledger write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const results = [];
|
|
133
|
-
const applied = [];
|
|
134
|
-
try {
|
|
135
|
-
for (const scope of scopeOrder) {
|
|
136
|
-
const edits = editsByScope.get(scope);
|
|
137
|
-
if (!edits)
|
|
138
|
-
continue;
|
|
139
|
-
if (scope !== "local" && !approvedScopes.has(scope))
|
|
140
|
-
continue;
|
|
141
|
-
options.signal?.throwIfAborted();
|
|
142
|
-
const storeId = scope === "local" ? options.agent.id : scope === "project" ? options.projectKey : undefined;
|
|
143
|
-
const baseline = options.baselines[scope];
|
|
144
|
-
if (!baseline)
|
|
145
|
-
throw new Error(`memory agent has no ${scope} baseline`);
|
|
146
|
-
const result = engine.apply(scope, storeId, {
|
|
147
|
-
summary: proposal.summary,
|
|
148
|
-
rationale: proposal.rationale,
|
|
149
|
-
expectedOutcome: proposal.expectedOutcome,
|
|
150
|
-
edits: prepareMemoryEdits(edits, baseline),
|
|
151
|
-
}, {
|
|
152
|
-
scope,
|
|
153
|
-
baselineState: baseline,
|
|
154
|
-
...(options.source ? { source: options.source } : {}),
|
|
155
|
-
});
|
|
156
|
-
const failed = result.appliedEdits.filter((edit) => !edit.applied);
|
|
157
|
-
if (failed.length > 0) {
|
|
158
|
-
throw new MemoryApplyBatchError(`${scope} batch ${result.id} left ${failed.length} edit(s) unapplied: ${failed.map((edit) => edit.error ?? "unknown").join("; ")}`, result, scope, storeId);
|
|
159
|
-
}
|
|
160
|
-
results.push(result);
|
|
161
|
-
applied.push({ scope, storeId, result });
|
|
162
|
-
}
|
|
163
|
-
return { results, declinedScopes };
|
|
164
|
-
}
|
|
165
|
-
catch (cause) {
|
|
166
|
-
const attempted = [...applied];
|
|
167
|
-
if (cause instanceof MemoryApplyBatchError && cause.result && cause.scope) {
|
|
168
|
-
attempted.push({ scope: cause.scope, storeId: cause.storeId, result: cause.result });
|
|
169
|
-
}
|
|
170
|
-
else if (cause instanceof EvolutionApplyPostCommitError) {
|
|
171
|
-
attempted.push({ scope: cause.scope, storeId: cause.storeId, result: cause.result });
|
|
172
|
-
}
|
|
173
|
-
const rollbackErrors = [];
|
|
174
|
-
for (const item of attempted.reverse()) {
|
|
175
|
-
try {
|
|
176
|
-
const rollback = engine.rollbackResult(item.scope, item.storeId, item.result);
|
|
177
|
-
const failed = rollback.appliedEdits.filter((edit) => !edit.applied);
|
|
178
|
-
if (failed.length > 0)
|
|
179
|
-
rollbackErrors.push(`${item.scope}:${item.result.id} left ${failed.length} rollback edit(s) unapplied`);
|
|
180
|
-
}
|
|
181
|
-
catch (rollbackCause) {
|
|
182
|
-
rollbackErrors.push(`${item.scope}:${item.result.id} rollback failed: ${rollbackCause instanceof Error ? rollbackCause.message : String(rollbackCause)}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const suffix = rollbackErrors.length > 0 ? `; compensation errors: ${rollbackErrors.join("; ")}` : "; prior memory writes were rolled back";
|
|
186
|
-
throw new Error(`memory agent apply failed: ${cause instanceof Error ? cause.message : String(cause)}${suffix}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function prepareMemoryEdits(edits, baseline) {
|
|
190
|
-
return edits.map((edit) => {
|
|
191
|
-
const plain = stripTargetScope(edit);
|
|
192
|
-
if (plain.action !== "update" || plain.metadata === undefined || plain.id === undefined)
|
|
193
|
-
return plain;
|
|
194
|
-
const before = baseline.entries.memory[plain.id];
|
|
195
|
-
return before ? { ...plain, metadata: { ...before.metadata, ...plain.metadata } } : plain;
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
function stripTargetScope(edit) {
|
|
199
|
-
const { targetScope: _targetScope, ...plain } = edit;
|
|
200
|
-
return plain;
|
|
201
|
-
}
|
|
202
|
-
//# sourceMappingURL=memory-apply.js.map
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { HarnessEntry } from "./types.js";
|
|
2
|
-
/** One labeled fact the extractor should (or, when outdated, should not) produce. */
|
|
3
|
-
export interface MemoryBenchmarkFact {
|
|
4
|
-
id: string;
|
|
5
|
-
memoryType: string;
|
|
6
|
-
/** Key phrases; an edit matches when its title+content carry every phrase. */
|
|
7
|
-
mustContain: string[];
|
|
8
|
-
/** Outdated facts must be avoided (create/update) or removed (archive/delete). */
|
|
9
|
-
outdated?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface MemoryBenchmarkReference {
|
|
12
|
-
facts: MemoryBenchmarkFact[];
|
|
13
|
-
}
|
|
14
|
-
/** One proposed edit in the shape the memory loop emits. */
|
|
15
|
-
export interface MemoryBenchmarkEdit {
|
|
16
|
-
action: "create" | "update" | "delete" | "archive";
|
|
17
|
-
title?: string;
|
|
18
|
-
content?: string;
|
|
19
|
-
/** Target entry id for update/delete/archive. */
|
|
20
|
-
id?: string;
|
|
21
|
-
}
|
|
22
|
-
export interface ScoredMemoryBenchmarkEdit {
|
|
23
|
-
action: MemoryBenchmarkEdit["action"];
|
|
24
|
-
matchedFactIds: string[];
|
|
25
|
-
duplicateOf?: string;
|
|
26
|
-
staleReason?: string;
|
|
27
|
-
noisyReason?: string;
|
|
28
|
-
}
|
|
29
|
-
export interface MemoryBenchmarkScore {
|
|
30
|
-
totalEdits: number;
|
|
31
|
-
matchedFacts: number;
|
|
32
|
-
precision: number;
|
|
33
|
-
recall: number;
|
|
34
|
-
duplicate: number;
|
|
35
|
-
stale: number;
|
|
36
|
-
noise: number;
|
|
37
|
-
uncoveredFactIds: string[];
|
|
38
|
-
edits: ScoredMemoryBenchmarkEdit[];
|
|
39
|
-
}
|
|
40
|
-
export interface MemoryGradeThresholds {
|
|
41
|
-
minPrecision: number;
|
|
42
|
-
minRecall: number;
|
|
43
|
-
maxDuplicate: number;
|
|
44
|
-
maxStale: number;
|
|
45
|
-
maxNoise: number;
|
|
46
|
-
}
|
|
47
|
-
/** Strict-by-default gate: duplicates and stale operations never pass unnoticed. */
|
|
48
|
-
export declare const DEFAULT_MEMORY_GRADE_THRESHOLDS: MemoryGradeThresholds;
|
|
49
|
-
/**
|
|
50
|
-
* v1 exclusion heuristics for noise ("不该记什么" in mechanical form):
|
|
51
|
-
* git history, repo paths re-readable from disk, stack traces, and
|
|
52
|
-
* explicitly temporary session state. Keep this list short and documented —
|
|
53
|
-
* every pattern here is a precision/recall tradeoff, not ground truth.
|
|
54
|
-
*/
|
|
55
|
-
export declare const MEMORY_NOISE_PATTERNS: readonly RegExp[];
|
|
56
|
-
/** Content shorter than this is too thin to be a durable fact. */
|
|
57
|
-
export declare const MEMORY_MIN_FACT_CHARS = 30;
|
|
58
|
-
/**
|
|
59
|
-
* Score one proposal against the reference and the pre-extraction manifest.
|
|
60
|
-
*
|
|
61
|
-
* @param reference Labeled facts (live + outdated).
|
|
62
|
-
* @param edits The proposal's memory edits.
|
|
63
|
-
* @param manifest Memory entries visible before extraction (duplicate source).
|
|
64
|
-
* @returns Per-dimension counts plus a per-edit breakdown.
|
|
65
|
-
*/
|
|
66
|
-
export declare function scoreMemoryExtraction(reference: MemoryBenchmarkReference, edits: readonly MemoryBenchmarkEdit[], manifest: readonly HarnessEntry[]): MemoryBenchmarkScore;
|
|
67
|
-
/**
|
|
68
|
-
* Grade a score against explicit thresholds.
|
|
69
|
-
*
|
|
70
|
-
* @returns pass plus one failure line per violated dimension (empty when passing).
|
|
71
|
-
*/
|
|
72
|
-
export declare function gradeMemoryScore(score: MemoryBenchmarkScore, thresholds?: MemoryGradeThresholds): {
|
|
73
|
-
pass: boolean;
|
|
74
|
-
failures: string[];
|
|
75
|
-
};
|
|
76
|
-
//# sourceMappingURL=memory-benchmark.d.ts.map
|
package/lib/memory-benchmark.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory-specific extraction benchmark (P1): deterministic scoring of one
|
|
3
|
-
* memory proposal against a labeled reference.
|
|
4
|
-
*
|
|
5
|
-
* Unlike the harness benchmark (which runs subagents against rubrics), this
|
|
6
|
-
* module scores a proposal's SHAPE without any model call: precision and
|
|
7
|
-
* recall over reference facts, duplicate creates (update-first violations),
|
|
8
|
-
* stale operations (re-proposing outdated facts, or removing live ones), and
|
|
9
|
-
* noise (unmatched or exclusion-list material). Suite authors hand-build
|
|
10
|
-
* small references plus transcripts; the extraction loop under test produces
|
|
11
|
-
* the proposal, this module grades it.
|
|
12
|
-
*
|
|
13
|
-
* Matching is phrase-substring based (case-insensitive): a reference fact
|
|
14
|
-
* lists must-contain phrases, and a proposed edit matches when its title
|
|
15
|
-
* plus content carry every phrase. Deliberately mechanical — the benchmark
|
|
16
|
-
* measures extraction coverage, not paraphrase quality.
|
|
17
|
-
*/
|
|
18
|
-
import { CONFLICT_BLOCK_SCORE, mostSimilarEntry } from "./promotion.js";
|
|
19
|
-
import { isArchived } from "./types.js";
|
|
20
|
-
/** Strict-by-default gate: duplicates and stale operations never pass unnoticed. */
|
|
21
|
-
export const DEFAULT_MEMORY_GRADE_THRESHOLDS = {
|
|
22
|
-
minPrecision: 0.7,
|
|
23
|
-
minRecall: 0.7,
|
|
24
|
-
maxDuplicate: 0,
|
|
25
|
-
maxStale: 0,
|
|
26
|
-
maxNoise: 0,
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* v1 exclusion heuristics for noise ("不该记什么" in mechanical form):
|
|
30
|
-
* git history, repo paths re-readable from disk, stack traces, and
|
|
31
|
-
* explicitly temporary session state. Keep this list short and documented —
|
|
32
|
-
* every pattern here is a precision/recall tradeoff, not ground truth.
|
|
33
|
-
*/
|
|
34
|
-
export const MEMORY_NOISE_PATTERNS = [
|
|
35
|
-
/\bgit\s+(log|show|commit|diff)\b/i,
|
|
36
|
-
/\bcommit\s+[0-9a-f]{5,40}\b/i,
|
|
37
|
-
/\b[0-9a-f]{40}\b/,
|
|
38
|
-
/(?:^|[\s("'])((?:src|lib|test|tests|docs|scripts)\/[A-Za-z0-9_./-]+\.[a-z]+)/,
|
|
39
|
-
/\bat\s+[\w$.]+\s*\(.*:\d+:\d+\)/,
|
|
40
|
-
/\b(temporary|one-off debugging|current task progress|in this session only)\b/i,
|
|
41
|
-
];
|
|
42
|
-
/** Content shorter than this is too thin to be a durable fact. */
|
|
43
|
-
export const MEMORY_MIN_FACT_CHARS = 30;
|
|
44
|
-
/**
|
|
45
|
-
* Score one proposal against the reference and the pre-extraction manifest.
|
|
46
|
-
*
|
|
47
|
-
* @param reference Labeled facts (live + outdated).
|
|
48
|
-
* @param edits The proposal's memory edits.
|
|
49
|
-
* @param manifest Memory entries visible before extraction (duplicate source).
|
|
50
|
-
* @returns Per-dimension counts plus a per-edit breakdown.
|
|
51
|
-
*/
|
|
52
|
-
export function scoreMemoryExtraction(reference, edits, manifest) {
|
|
53
|
-
const live = reference.facts.filter((fact) => !fact.outdated);
|
|
54
|
-
const outdated = reference.facts.filter((fact) => fact.outdated);
|
|
55
|
-
const liveMatched = new Set();
|
|
56
|
-
const outdatedHandled = new Set();
|
|
57
|
-
const scored = [];
|
|
58
|
-
let duplicate = 0;
|
|
59
|
-
let stale = 0;
|
|
60
|
-
let noise = 0;
|
|
61
|
-
for (const edit of edits) {
|
|
62
|
-
const text = `${edit.title ?? ""}\n${edit.content ?? ""}`;
|
|
63
|
-
const liveHits = live.filter((fact) => matchesFact(text, fact));
|
|
64
|
-
const outdatedHits = outdated.filter((fact) => matchesFact(text, fact));
|
|
65
|
-
const entry = { action: edit.action, matchedFactIds: [] };
|
|
66
|
-
if (edit.action === "create" || edit.action === "update") {
|
|
67
|
-
if (outdatedHits.length > 0) {
|
|
68
|
-
stale += 1;
|
|
69
|
-
entry.staleReason = `re-proposes outdated fact(s): ${outdatedHits.map((fact) => fact.id).join(",")}`;
|
|
70
|
-
}
|
|
71
|
-
else if (edit.action === "create") {
|
|
72
|
-
const hit = mostSimilarEntry(manifest.filter((candidate) => candidate.kind === "memory"), edit.title ?? "", edit.content ?? "", CONFLICT_BLOCK_SCORE);
|
|
73
|
-
if (hit) {
|
|
74
|
-
duplicate += 1;
|
|
75
|
-
entry.duplicateOf = hit.id;
|
|
76
|
-
}
|
|
77
|
-
else if (liveHits.length > 0) {
|
|
78
|
-
for (const fact of liveHits) {
|
|
79
|
-
liveMatched.add(fact.id);
|
|
80
|
-
entry.matchedFactIds.push(fact.id);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
noise += 1;
|
|
85
|
-
entry.noisyReason = noiseReason(text);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else if (liveHits.length > 0) {
|
|
89
|
-
for (const fact of liveHits) {
|
|
90
|
-
liveMatched.add(fact.id);
|
|
91
|
-
entry.matchedFactIds.push(fact.id);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
noise += 1;
|
|
96
|
-
entry.noisyReason = noiseReason(text);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
// delete/archive: correct only against outdated facts.
|
|
101
|
-
const targetHits = [...liveHits, ...outdatedHits];
|
|
102
|
-
const targetOutdated = outdatedHits.length > 0;
|
|
103
|
-
const targetEntry = edit.id !== undefined ? manifest.find((candidate) => candidate.id === edit.id) : undefined;
|
|
104
|
-
if (targetOutdated) {
|
|
105
|
-
for (const fact of outdatedHits) {
|
|
106
|
-
outdatedHandled.add(fact.id);
|
|
107
|
-
entry.matchedFactIds.push(fact.id);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else if (targetEntry !== undefined && isArchived(targetEntry)) {
|
|
111
|
-
// Re-archiving an already archived entry is harmless housekeeping.
|
|
112
|
-
entry.matchedFactIds.push(`archived:${targetEntry.id}`);
|
|
113
|
-
}
|
|
114
|
-
else if (targetHits.length > 0 || (targetEntry !== undefined && !isArchived(targetEntry))) {
|
|
115
|
-
stale += 1;
|
|
116
|
-
entry.staleReason = `removes live fact(s): ${targetHits.map((fact) => fact.id).join(",") || targetEntry?.id || "(unknown target)"}`;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
noise += 1;
|
|
120
|
-
entry.noisyReason = "removal matches no reference fact";
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
scored.push(entry);
|
|
124
|
-
}
|
|
125
|
-
const total = edits.length;
|
|
126
|
-
const preciseEdits = scored.filter((entry) => entry.matchedFactIds.length > 0).length;
|
|
127
|
-
const precision = total === 0 ? 1 : preciseEdits / total;
|
|
128
|
-
const recall = live.length === 0 ? 1 : liveMatched.size / live.length;
|
|
129
|
-
return {
|
|
130
|
-
totalEdits: total,
|
|
131
|
-
matchedFacts: liveMatched.size + outdatedHandled.size,
|
|
132
|
-
precision,
|
|
133
|
-
recall,
|
|
134
|
-
duplicate,
|
|
135
|
-
stale,
|
|
136
|
-
noise,
|
|
137
|
-
uncoveredFactIds: live.filter((fact) => !liveMatched.has(fact.id)).map((fact) => fact.id),
|
|
138
|
-
edits: scored,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Grade a score against explicit thresholds.
|
|
143
|
-
*
|
|
144
|
-
* @returns pass plus one failure line per violated dimension (empty when passing).
|
|
145
|
-
*/
|
|
146
|
-
export function gradeMemoryScore(score, thresholds = DEFAULT_MEMORY_GRADE_THRESHOLDS) {
|
|
147
|
-
const failures = [];
|
|
148
|
-
if (score.precision < thresholds.minPrecision)
|
|
149
|
-
failures.push(`precision ${score.precision.toFixed(2)} < ${thresholds.minPrecision}`);
|
|
150
|
-
if (score.recall < thresholds.minRecall)
|
|
151
|
-
failures.push(`recall ${score.recall.toFixed(2)} < ${thresholds.minRecall}`);
|
|
152
|
-
if (score.duplicate > thresholds.maxDuplicate)
|
|
153
|
-
failures.push(`duplicate ${score.duplicate} > ${thresholds.maxDuplicate}`);
|
|
154
|
-
if (score.stale > thresholds.maxStale)
|
|
155
|
-
failures.push(`stale ${score.stale} > ${thresholds.maxStale}`);
|
|
156
|
-
if (score.noise > thresholds.maxNoise)
|
|
157
|
-
failures.push(`noise ${score.noise} > ${thresholds.maxNoise}`);
|
|
158
|
-
return { pass: failures.length === 0, failures };
|
|
159
|
-
}
|
|
160
|
-
function matchesFact(text, fact) {
|
|
161
|
-
const lowered = text.toLowerCase();
|
|
162
|
-
return fact.mustContain.every((phrase) => lowered.includes(phrase.toLowerCase()));
|
|
163
|
-
}
|
|
164
|
-
function noiseReason(text) {
|
|
165
|
-
if (text.trim().length < MEMORY_MIN_FACT_CHARS)
|
|
166
|
-
return `thin content (<${MEMORY_MIN_FACT_CHARS} chars)`;
|
|
167
|
-
const hit = MEMORY_NOISE_PATTERNS.findIndex((pattern) => pattern.test(text));
|
|
168
|
-
return hit >= 0 ? `matches exclusion pattern #${hit + 1}` : "matches no reference fact";
|
|
169
|
-
}
|
|
170
|
-
//# sourceMappingURL=memory-benchmark.js.map
|
package/lib/memory-index.d.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* evolve v2 — the passive read path: the `evolve:memory-index` system-prompt
|
|
3
|
-
* section that puts memory CONTENT in front of the model at session start.
|
|
4
|
-
*
|
|
5
|
-
* Why not just hand out a path: the memory store lives in `<dshHome>/evolve/`,
|
|
6
|
-
* outside the workspace sandbox, so the model's read tool cannot open it.
|
|
7
|
-
* The section therefore carries the entries themselves — full body while the
|
|
8
|
-
* character budget allows, then one-line index rows, then a `evolve_recall`
|
|
9
|
-
* pointer for the rest. Budget is hard: a bounded prompt beats an unbounded one.
|
|
10
|
-
*
|
|
11
|
-
* Session-freeze contract (the prompt-cache guard): the section is computed
|
|
12
|
-
* once per session and reused byte-for-byte afterwards, so the system prompt
|
|
13
|
-
* stays stable within a session and cache reads keep hitting. A memory written
|
|
14
|
-
* mid-session becomes visible in the NEXT session; `evolve_recall` covers the
|
|
15
|
-
* immediate need. See `.agents/notes/proposed/feature/2026-10-01-evolve-v2-passive-read-write.md`.
|
|
16
|
-
*/
|
|
17
|
-
import type { HarnessEntry } from "./types.js";
|
|
18
|
-
import { type MemoryRecallType } from "./types.js";
|
|
19
|
-
import type { EvolutionEngine } from "./service.js";
|
|
20
|
-
import { type AgentLike } from "./inject.js";
|
|
21
|
-
/** Hard character budget for the `<memories>` block. */
|
|
22
|
-
export declare const DEFAULT_MEMORY_INDEX_MAX_CHARS = 6000;
|
|
23
|
-
/** Prompt-section order of the memory index (empty 1–499 slot, see ADR). */
|
|
24
|
-
export declare const DEFAULT_MEMORY_SECTION_ORDER = 400;
|
|
25
|
-
/** Sessions whose frozen section text is kept before LRU eviction. */
|
|
26
|
-
export declare const DEFAULT_FROZEN_SESSIONS = 32;
|
|
27
|
-
/** Name of the injected section (unique — duplicate names throw upstream). */
|
|
28
|
-
export declare const MEMORY_SECTION_NAME = "evolve:memory-index";
|
|
29
|
-
/**
|
|
30
|
-
* Recall-type priority for the budget: durable, behavior-shaping types first.
|
|
31
|
-
* `project` (current work context) > `feedback` (how to behave) > `user` (who
|
|
32
|
-
* the user is) > `reference` (external pointers). Entries without a valid
|
|
33
|
-
* memoryType sink below every typed entry.
|
|
34
|
-
*/
|
|
35
|
-
export declare const MEMORY_TYPE_PRIORITY: Record<MemoryRecallType, number>;
|
|
36
|
-
/** The entry's recall type, or undefined when the metadata key is absent/invalid. */
|
|
37
|
-
export declare function memoryTypeOf(entry: HarnessEntry): MemoryRecallType | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* Order memories for injection: recall-type priority first
|
|
40
|
-
* ({@link MEMORY_TYPE_PRIORITY}), then recency (newest first), then the stable
|
|
41
|
-
* dictionary order — deterministic for equal inputs, so a frozen section is
|
|
42
|
-
* reproducible across builds. The input is never mutated.
|
|
43
|
-
*/
|
|
44
|
-
export declare function rankMemories(entries: readonly HarnessEntry[], now?: number): HarnessEntry[];
|
|
45
|
-
/** One memory rendered in full, as a list item with the body indented. */
|
|
46
|
-
export declare function memoryFullLine(entry: HarnessEntry): string;
|
|
47
|
-
/** What a budget-bounded memory block shows. */
|
|
48
|
-
export interface MemorySelection {
|
|
49
|
-
/** Entries rendered with their full body, in display order. */
|
|
50
|
-
full: HarnessEntry[];
|
|
51
|
-
/** Entries degraded to a one-line index row (`id` + title hook). */
|
|
52
|
-
indexed: HarnessEntry[];
|
|
53
|
-
/** Entries that fit neither and are only counted. */
|
|
54
|
-
dropped: number;
|
|
55
|
-
/** Entries actually shown (full + indexed), in display order. */
|
|
56
|
-
shown: HarnessEntry[];
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Greedy budget fill over {@link rankMemories}: each entry takes the full body
|
|
60
|
-
* while it fits, otherwise degrades to an index row, otherwise is dropped —
|
|
61
|
-
* and because the ranking is fixed, the first entry that fits neither means
|
|
62
|
-
* every later one is dropped too (their rows are no smaller).
|
|
63
|
-
*/
|
|
64
|
-
export declare function selectMemoryInjection(entries: readonly HarnessEntry[], maxChars?: number): MemorySelection;
|
|
65
|
-
/** The `evolve_recall` pointer appended when rows had to be degraded or dropped. */
|
|
66
|
-
export declare const MEMORY_OVERFLOW_HINT = "- \u66F4\u591A\u8BB0\u5FC6\u53EF\u7528 evolve_recall \u6309\u9700\u8BFB\u53D6";
|
|
67
|
-
/**
|
|
68
|
-
* Render a selection as the `<memories>` block, or "" when nothing is shown.
|
|
69
|
-
* Full bodies come first in display order, then the degraded index rows, then
|
|
70
|
-
* the `evolve_recall` pointer with the degradation/drop counts.
|
|
71
|
-
*/
|
|
72
|
-
export declare function renderMemorySelection(selection: MemorySelection): string;
|
|
73
|
-
/** {@link renderMemorySelection} over a fresh selection (convenience). */
|
|
74
|
-
export declare function formatMemoriesBlock(entries: readonly HarnessEntry[], maxChars?: number): string;
|
|
75
|
-
/** Options for {@link memoryIndexSectionText}. */
|
|
76
|
-
export interface MemoryIndexOptions {
|
|
77
|
-
/** Hard budget for the memories block (default 6000). */
|
|
78
|
-
maxChars?: number;
|
|
79
|
-
/** Include the when_to_save guide (default true). */
|
|
80
|
-
guide?: boolean;
|
|
81
|
-
/** Pin the project layer explicitly (tests/tools); otherwise derived from cwd. */
|
|
82
|
-
projectKey?: string;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Compose the full `evolve:memory-index` section for one assembling agent:
|
|
86
|
-
* the persistent-memory guide (when to save / how to maintain) wrapped around
|
|
87
|
-
* the budget-bounded memories block. With the guide off and no memories the
|
|
88
|
-
* result is "" — the prompt renderer then drops the section, so an empty store
|
|
89
|
-
* keeps costing zero tokens.
|
|
90
|
-
*
|
|
91
|
-
* Injection usage is recorded for everything shown (best-effort; a failure
|
|
92
|
-
* never blocks injection), deduped per session by {@link recordInjection}.
|
|
93
|
-
*/
|
|
94
|
-
export declare function memoryIndexSectionText(engine: EvolutionEngine, agent: AgentLike | undefined, opts?: MemoryIndexOptions): string;
|
|
95
|
-
/** A per-session frozen view of the memory section text. */
|
|
96
|
-
export interface FrozenMemorySection {
|
|
97
|
-
/**
|
|
98
|
-
* The section text for this agent: built on first use per `agent.id` and
|
|
99
|
-
* reused byte-for-byte afterwards. `undefined` agent → "" (unbuilt).
|
|
100
|
-
*/
|
|
101
|
-
textFor(agent: AgentLike | undefined, build: (agent: AgentLike) => string): string;
|
|
102
|
-
/** Sessions currently frozen (test/observability hook). */
|
|
103
|
-
size(): number;
|
|
104
|
-
/** Drop every frozen session (tests; a restart rebuilds naturally). */
|
|
105
|
-
clear(): void;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Freeze the section text per session id. The frozen value is what keeps the
|
|
109
|
-
* system prompt stable across a session's assemblies (prompt-cache guard);
|
|
110
|
-
* bounded by `maxSessions` with insertion-order (LRU) eviction, so a long-lived
|
|
111
|
-
* process cannot grow without limit.
|
|
112
|
-
*/
|
|
113
|
-
export declare function createFrozenMemorySection(maxSessions?: number): FrozenMemorySection;
|
|
114
|
-
//# sourceMappingURL=memory-index.d.ts.map
|