dsh-continual-evolve 0.11.0 → 0.12.1
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 +42 -115
- package/README.zh.md +33 -93
- package/client/client.js +253 -0
- package/cordis.patch.yml +2 -2
- package/lib/card-routes.d.ts +32 -0
- package/lib/card-routes.js +92 -0
- package/lib/index.d.ts +65 -217
- package/lib/index.js +74 -234
- package/lib/known-workspaces.d.ts +22 -0
- package/lib/known-workspaces.js +42 -0
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +34 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/lib/memory-snapshot.d.ts +54 -0
- package/lib/memory-snapshot.js +125 -0
- package/package.json +8 -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/wrapup-command.js
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { questionServiceOf, requireGlobalApproval } from "./approval.js";
|
|
2
|
-
import { wrapupArchiveCopy } from "./copy.js";
|
|
3
|
-
import { assessLocalEntries, candidateKey, filterPromotable, listLocalCandidates, splitArchiveGuards, splitPromoteBlocked, splitPromoteProposals, valenceStampProposal, wholePromoteProposals } from "./wrapup.js";
|
|
4
|
-
import { DEFAULT_PROMOTION_POLICY } from "./promotion.js";
|
|
5
|
-
import { normalizeRecordLanguage, resolveRecordLanguage } from "./record-language.js";
|
|
6
|
-
import { recentUserText } from "./inject.js";
|
|
7
|
-
function success(text) {
|
|
8
|
-
return { kind: "success", text };
|
|
9
|
-
}
|
|
10
|
-
export async function executeWrapupCommand(ctx, engine, invocation, policy = DEFAULT_PROMOTION_POLICY, recordLanguage) {
|
|
11
|
-
const sessionId = invocation.agent.id;
|
|
12
|
-
const localState = engine.load("local", sessionId);
|
|
13
|
-
const globalState = engine.load("global", undefined);
|
|
14
|
-
const candidates = listLocalCandidates(localState, globalState, engine.baseDir);
|
|
15
|
-
if (candidates.length === 0) {
|
|
16
|
-
return success(`(nothing to wrap up: ${sessionId}'s local store has no active, un-promoted entries — use /evolve list to inspect it)`);
|
|
17
|
-
}
|
|
18
|
-
// 1. Classify: the model judges each audited candidate's fate.
|
|
19
|
-
const assessment = await assessLocalEntries(ctx, invocation.agent, candidates, {
|
|
20
|
-
signal: invocation.signal,
|
|
21
|
-
tokenUsage: {
|
|
22
|
-
baseDir: engine.baseDir,
|
|
23
|
-
sessionId,
|
|
24
|
-
retain: engine.retention.tokenUsage,
|
|
25
|
-
onError: (cause) => ctx.logger("continual-evolve").warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
26
|
-
},
|
|
27
|
-
tokenUsagePhase: "wrapup",
|
|
28
|
-
});
|
|
29
|
-
const byKey = new Map(candidates.map((candidate) => [candidateKey(candidate.kind, candidate.id), candidate]));
|
|
30
|
-
// 2. Partition by action. Deterministic guards re-check the LIVE global
|
|
31
|
-
// store right before anything lands (state may have changed mid-call).
|
|
32
|
-
const { promotable, skipped } = filterPromotable(assessment.items, globalState, candidates, policy);
|
|
33
|
-
const promoteItems = promotable.filter((item) => item.verdict === "promote");
|
|
34
|
-
const archiveItems = assessment.items.filter((item) => item.verdict === "archive");
|
|
35
|
-
// Split promotion (A-form): archive a mixed entry but promote ONLY the
|
|
36
|
-
// cleaned durable part the model extracted. Guarded the same way as whole
|
|
37
|
-
// promotes — a split that would duplicate a globally covered topic is
|
|
38
|
-
// dropped and the entry archives plain.
|
|
39
|
-
const splitItems = [];
|
|
40
|
-
const splitSkipped = [];
|
|
41
|
-
for (const item of archiveItems) {
|
|
42
|
-
if (!item.promote)
|
|
43
|
-
continue;
|
|
44
|
-
const candidate = byKey.get(item.key);
|
|
45
|
-
if (!candidate) {
|
|
46
|
-
splitSkipped.push({ key: item.key, reason: "not in the audited candidate list" });
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const blocked = splitPromoteBlocked(item, globalState, candidate.kind, policy, candidate.metadata);
|
|
50
|
-
if (blocked) {
|
|
51
|
-
splitSkipped.push({ key: item.key, reason: blocked });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
splitItems.push({ item, candidate });
|
|
55
|
-
}
|
|
56
|
-
// Plain archives (no split payload): the symmetric guard — an archive that
|
|
57
|
-
// is NOT globally covered AND was distilled from real user messages must
|
|
58
|
-
// not proceed silently.
|
|
59
|
-
const plainArchives = archiveItems.filter((item) => !item.promote);
|
|
60
|
-
const { silent: silentArchives, review: reviewArchives } = splitArchiveGuards(plainArchives, candidates);
|
|
61
|
-
const keepItems = assessment.items.filter((item) => item.verdict === "keep");
|
|
62
|
-
// 3. Report the assessment before touching anything.
|
|
63
|
-
const lines = [
|
|
64
|
-
`wrapup assessment (${sessionId}): ${candidates.length} candidates${candidates.some((c) => c.coveredGlobally) ? `, ${candidates.filter((c) => c.coveredGlobally).length} covered globally` : ""}`,
|
|
65
|
-
`${assessment.rationale}`,
|
|
66
|
-
];
|
|
67
|
-
for (const [heading, items] of [
|
|
68
|
-
["PROMOTE (to global)", promoteItems],
|
|
69
|
-
["SPLIT (archive + promote durable part)", splitItems.map((split) => split.item)],
|
|
70
|
-
["ARCHIVE", silentArchives],
|
|
71
|
-
["ARCHIVE (needs review)", reviewArchives],
|
|
72
|
-
["KEEP", keepItems],
|
|
73
|
-
]) {
|
|
74
|
-
lines.push(`${heading}: ${items.length}`);
|
|
75
|
-
for (const item of items) {
|
|
76
|
-
const candidate = byKey.get(item.key);
|
|
77
|
-
const title = candidate ? candidate.title : item.key;
|
|
78
|
-
const splitNote = item.promote ? ` → 拆出提升「${item.promote.title}」` : "";
|
|
79
|
-
lines.push(`- ${item.key} "${title}"${splitNote} — ${item.reason}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
for (const skip of skipped) {
|
|
83
|
-
lines.push(`- promote skipped: ${skip.key} — ${skip.reason}`);
|
|
84
|
-
}
|
|
85
|
-
for (const skip of splitSkipped) {
|
|
86
|
-
lines.push(`- split skipped: ${skip.key} — ${skip.reason}`);
|
|
87
|
-
}
|
|
88
|
-
lines.push("");
|
|
89
|
-
const applied = [];
|
|
90
|
-
// 4. Global writes: governed resource — ONE human approval gate covers
|
|
91
|
-
// every create (whole promotes AND split promotions). On approval:
|
|
92
|
-
// - whole promote → create global copy + stamp local promotedTo+archivedAt;
|
|
93
|
-
// - split → create the cleaned durable part + archive the original with
|
|
94
|
-
// promotedTo. On rejection: whole promotes are not written, and each
|
|
95
|
-
// split's original STILL archives plain (its snapshot half deserves
|
|
96
|
-
// the archive; the durable half is reported for manual handling).
|
|
97
|
-
const wholeCreates = promoteItems.map((item) => ({ item, candidate: byKey.get(item.key) }));
|
|
98
|
-
const splitCreates = splitItems;
|
|
99
|
-
const allCreates = new Set([...wholeCreates.map((c) => c.item.key), ...splitCreates.map((c) => c.item.key)]);
|
|
100
|
-
if (allCreates.size > 0) {
|
|
101
|
-
const what = `wrapup 将写入跨会话 global store(共 ${allCreates.size} 条:${promoteItems.length} 条整条提升 + ${splitItems.length} 条拆解提升):\n${[
|
|
102
|
-
...promoteItems.map((item) => `- 整条提升 ${item.key} "${byKey.get(item.key)?.title ?? item.key}"`),
|
|
103
|
-
...splitItems.map((split) => `- 拆解提升 ${split.item.key} → 清洗「${split.item.promote?.title}」(原条目随之归档)`),
|
|
104
|
-
].join("\n")}`;
|
|
105
|
-
let promoteAllowed = true;
|
|
106
|
-
try {
|
|
107
|
-
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, what, recordLanguage);
|
|
108
|
-
}
|
|
109
|
-
catch (cause) {
|
|
110
|
-
promoteAllowed = false;
|
|
111
|
-
const message = `global 写入未批准 — 整条提升与拆解提升均未写入 (${cause instanceof Error ? cause.message : String(cause)})`;
|
|
112
|
-
applied.push(message);
|
|
113
|
-
lines.push(message);
|
|
114
|
-
}
|
|
115
|
-
if (promoteAllowed) {
|
|
116
|
-
// Whole promotes: create global entry, retire the local copy.
|
|
117
|
-
// Shared proposal builders keep the wrap-up command and the gate's
|
|
118
|
-
// local-fate dimension writing IDENTICAL edits.
|
|
119
|
-
for (const { item, candidate } of wholeCreates) {
|
|
120
|
-
if (!candidate)
|
|
121
|
-
continue;
|
|
122
|
-
const proposals = wholePromoteProposals(item, candidate, sessionId);
|
|
123
|
-
const globalResult = engine.apply("global", undefined, proposals.global, { scope: "global" });
|
|
124
|
-
const createdId = globalResult.appliedEdits.find((edit) => edit.applied)?.id ?? candidate.id;
|
|
125
|
-
const localResult = engine.apply("local", sessionId, proposals.localStamp(createdId), {
|
|
126
|
-
scope: "local",
|
|
127
|
-
baselineState: localState,
|
|
128
|
-
});
|
|
129
|
-
applied.push(`promoted ${item.key} → global:${createdId} (${globalResult.id}; local stamped ${localResult.id})`);
|
|
130
|
-
}
|
|
131
|
-
// Split promotions: create the cleaned durable part, retire the
|
|
132
|
-
// original local entry (its snapshot half is archived along).
|
|
133
|
-
for (const { item, candidate } of splitCreates) {
|
|
134
|
-
if (!item.promote)
|
|
135
|
-
continue;
|
|
136
|
-
const proposals = splitPromoteProposals(item, candidate, sessionId);
|
|
137
|
-
const globalResult = engine.apply("global", undefined, proposals.global, { scope: "global" });
|
|
138
|
-
const createdId = globalResult.appliedEdits.find((edit) => edit.applied)?.id ?? candidate.id;
|
|
139
|
-
const localResult = engine.apply("local", sessionId, proposals.localStamp(createdId), {
|
|
140
|
-
scope: "local",
|
|
141
|
-
baselineState: localState,
|
|
142
|
-
});
|
|
143
|
-
applied.push(`split ${item.key}: promoted cleaned part → global:${createdId} (${globalResult.id}); original archived (${localResult.id})`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
// Rejected: whole promotes stay un-written; each split's original
|
|
148
|
-
// still archives plain (reported, data restorable).
|
|
149
|
-
for (const { item, candidate } of splitCreates) {
|
|
150
|
-
if (!candidate)
|
|
151
|
-
continue;
|
|
152
|
-
const result = engine.apply("local", sessionId, {
|
|
153
|
-
summary: `wrapup: split promotion not approved — archive original ${item.key} plain`,
|
|
154
|
-
rationale: item.reason,
|
|
155
|
-
expectedOutcome: `The original leaves injection; the cleaned part was NOT written (reported for manual handling).`,
|
|
156
|
-
edits: [{ action: "archive", kind: candidate.kind, id: candidate.id }],
|
|
157
|
-
}, { scope: "local", baselineState: localState });
|
|
158
|
-
applied.push(`split ${item.key}: promotion not approved — original archived plain (${result.id})`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// 5. Silent archives: deterministic local action (hidden from injection,
|
|
163
|
-
// data kept restorable) — covered topics and operational entries need no
|
|
164
|
-
// confirmation, matching the original behavior.
|
|
165
|
-
for (const item of silentArchives) {
|
|
166
|
-
const candidate = byKey.get(item.key);
|
|
167
|
-
if (!candidate)
|
|
168
|
-
continue;
|
|
169
|
-
const result = engine.apply("local", sessionId, {
|
|
170
|
-
summary: `wrapup: archive local ${item.key} — ${item.reason}`,
|
|
171
|
-
rationale: item.reason,
|
|
172
|
-
expectedOutcome: `The entry stops being injected but stays restorable.`,
|
|
173
|
-
edits: [{ action: "archive", kind: candidate.kind, id: candidate.id }],
|
|
174
|
-
}, { scope: "local", baselineState: localState });
|
|
175
|
-
applied.push(`archived ${item.key} (${result.id})`);
|
|
176
|
-
}
|
|
177
|
-
// 6. Review archives (symmetric guard): not covered globally + distilled
|
|
178
|
-
// from real user messages — the user decides before this content is
|
|
179
|
-
// hidden from future sessions. No question service → conservative keep.
|
|
180
|
-
const userQuestions = questionServiceOf(ctx);
|
|
181
|
-
for (const item of reviewArchives) {
|
|
182
|
-
const candidate = byKey.get(item.key);
|
|
183
|
-
if (!candidate)
|
|
184
|
-
continue;
|
|
185
|
-
if (!userQuestions) {
|
|
186
|
-
applied.push(`kept ${item.key} — archive pending user confirmation (no question service)`);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const questionId = "evolve-wrapup-archive-review";
|
|
190
|
-
let archiveConfirmed = false;
|
|
191
|
-
try {
|
|
192
|
-
const copy = wrapupArchiveCopy(candidate.title, normalizeRecordLanguage(recordLanguage) ??
|
|
193
|
-
resolveRecordLanguage({ ctx, trajectoryText: recentUserText(invocation.agent) }));
|
|
194
|
-
const answer = await userQuestions.ask({
|
|
195
|
-
questions: [
|
|
196
|
-
{
|
|
197
|
-
id: questionId,
|
|
198
|
-
question: copy.question,
|
|
199
|
-
options: copy.options,
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
agent: invocation.agent,
|
|
203
|
-
signal: invocation.signal,
|
|
204
|
-
});
|
|
205
|
-
const selected = answer.answers?.find((entry) => entry.id === questionId)?.selected ?? [];
|
|
206
|
-
archiveConfirmed = selected.includes("归档") || selected.includes("Archive");
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
archiveConfirmed = false;
|
|
210
|
-
}
|
|
211
|
-
if (archiveConfirmed) {
|
|
212
|
-
const result = engine.apply("local", sessionId, {
|
|
213
|
-
summary: `wrapup: archive local ${item.key} (user-confirmed) — ${item.reason}`,
|
|
214
|
-
rationale: item.reason,
|
|
215
|
-
expectedOutcome: `The entry stops being injected but stays restorable.`,
|
|
216
|
-
edits: [{ action: "archive", kind: candidate.kind, id: candidate.id }],
|
|
217
|
-
}, { scope: "local", baselineState: localState });
|
|
218
|
-
applied.push(`archived ${item.key} (user-confirmed, ${result.id})`);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
applied.push(`kept ${item.key} — user declined the archive`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// 7. Valence stamps (P1 效价反馈): keep-verdict entries the assessor marked
|
|
225
|
-
// contradicted get their negative counter bumped — the entry stays live
|
|
226
|
-
// but sinks in injection ranking and the next assessment prefers
|
|
227
|
-
// archiving it. Deterministic local action, no approval needed.
|
|
228
|
-
for (const item of keepItems) {
|
|
229
|
-
if (!item.contradicted)
|
|
230
|
-
continue;
|
|
231
|
-
const candidate = byKey.get(item.key);
|
|
232
|
-
if (!candidate)
|
|
233
|
-
continue;
|
|
234
|
-
const proposal = valenceStampProposal(item, candidate);
|
|
235
|
-
if (!proposal)
|
|
236
|
-
continue;
|
|
237
|
-
const result = engine.apply("local", sessionId, proposal, { scope: "local", baselineState: localState });
|
|
238
|
-
applied.push(`contradicted stamp ${item.key} → valenceNegative=${candidate.negativeCount + 1} (${result.id})`);
|
|
239
|
-
}
|
|
240
|
-
lines.push(...(applied.length > 0 ? applied : ["(no changes applied — all entries kept)"]));
|
|
241
|
-
return success(lines.join("\n"));
|
|
242
|
-
}
|
|
243
|
-
//# sourceMappingURL=wrapup-command.js.map
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wrap-up proposal builders: turn assessed verdicts into deterministic,
|
|
3
|
-
* evidence-carrying refinement proposals — whole promotes, split promotes,
|
|
4
|
-
* archive reviews, and valence stamps. Pure logic over the candidate types
|
|
5
|
-
* in `wrapup-candidates.ts`; the user approves, the engine applies.
|
|
6
|
-
*/
|
|
7
|
-
import type { HarnessState, RefinementKind, RefinementProposal } from "./types.js";
|
|
8
|
-
import { type PromotionPolicy } from "./promotion.js";
|
|
9
|
-
import { type WrapupCandidate, type WrapupItem } from "./wrapup-candidates.js";
|
|
10
|
-
export interface PromotableSplit {
|
|
11
|
-
/** Items that may be promoted: classified promote AND not covered globally. */
|
|
12
|
-
promotable: WrapupItem[];
|
|
13
|
-
/** Items classified promote but blocked by the deterministic guard, with why. */
|
|
14
|
-
skipped: {
|
|
15
|
-
key: string;
|
|
16
|
-
reason: string;
|
|
17
|
-
}[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Apply-time deterministic guard: re-check every promote verdict against the
|
|
21
|
-
* global store right before it lands. The LLM classification may be stale
|
|
22
|
-
* (a gate ran while assessing) or wrong; this ensures a promote never writes
|
|
23
|
-
* a duplicate, project-scoped, or too-thin global entry. Pure and unit-tested.
|
|
24
|
-
*
|
|
25
|
-
* Guards (2026-08-22 promotion policy):
|
|
26
|
-
* - audited candidate list + title coverage (pre-existing),
|
|
27
|
-
* - project-scoped content markers (absolute paths / session ids) — the
|
|
28
|
-
* global store is shared across projects and must stay portable,
|
|
29
|
-
* - thin content below the policy floor (framing outweighs the fact),
|
|
30
|
-
* - near-duplicate of an existing global entry by content overlap.
|
|
31
|
-
*/
|
|
32
|
-
export declare function filterPromotable(items: readonly WrapupItem[], globalState: HarnessState, candidates: readonly WrapupCandidate[], policy?: PromotionPolicy): PromotableSplit;
|
|
33
|
-
export interface ArchiveReviewSplit {
|
|
34
|
-
/** Archives that may proceed silently: topic already covered, no real
|
|
35
|
-
* distillation source, or the archive half of an already-approved split. */
|
|
36
|
-
silent: WrapupItem[];
|
|
37
|
-
/**
|
|
38
|
-
* Archives that would bury possibly-reusable content: not covered
|
|
39
|
-
* globally AND distilled from real user messages (sourceSeqs present).
|
|
40
|
-
* These MAY NOT archive silently — the command must get user
|
|
41
|
-
* confirmation first (the symmetric guard to filterPromotable: it stops
|
|
42
|
-
* over-archiving, not just over-writing).
|
|
43
|
-
*/
|
|
44
|
-
review: WrapupItem[];
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* The symmetric archive guard. `filterPromotable` is one-directional: it
|
|
48
|
-
* stops the model from WRITING duplicate global entries, but nothing stopped
|
|
49
|
-
* an unfounded ARCHIVE from hiding content that was actually only local.
|
|
50
|
-
* Guard criteria: an archive needs user confirmation when it is NOT covered
|
|
51
|
-
* globally AND the entry carries a real distillation source (sourceSeqs /
|
|
52
|
-
* sourceSession — i.e. it was distilled from actual user messages, so it
|
|
53
|
-
* may hold reusable value). Operational/empty entries archive silently as
|
|
54
|
-
* before. Split archives (archive + promote sub-object) skip this check:
|
|
55
|
-
* their promotion already crosses a human approval gate, so the archive is
|
|
56
|
-
* the completion of an approved action, not a silent burial.
|
|
57
|
-
*/
|
|
58
|
-
export declare function needsArchiveReview(item: WrapupItem, candidate: WrapupCandidate): boolean;
|
|
59
|
-
/** Partition archive items into silent vs review-required (see needsArchiveReview). */
|
|
60
|
-
export declare function splitArchiveGuards(items: readonly WrapupItem[], candidates: readonly WrapupCandidate[]): ArchiveReviewSplit;
|
|
61
|
-
/**
|
|
62
|
-
* Apply-time guard for a split promotion (archive + promote sub-object):
|
|
63
|
-
* the cleaned payload must pass the same promotion policy as a whole
|
|
64
|
-
* promote — no global coverage duplicate, no project-scoped content, not
|
|
65
|
-
* too thin, no near-duplicate global entry. A blocked split is dropped (the
|
|
66
|
-
* entry still archives plain) rather than half-promoting a redundancy.
|
|
67
|
-
*/
|
|
68
|
-
export declare function splitPromoteBlocked(item: WrapupItem, globalState: HarnessState, kind: RefinementKind, policy?: PromotionPolicy, candidateMetadata?: Record<string, unknown>): string | undefined;
|
|
69
|
-
/**
|
|
70
|
-
* Shared proposal builders for a WHOLE promotion — used by both the
|
|
71
|
-
* `/evolve wrapup` command and the gate's local-fate dimension so the two
|
|
72
|
-
* paths apply IDENTICAL edits (global create + local retirement stamp).
|
|
73
|
-
*
|
|
74
|
-
* The local stamp is a factory: the `promotedTo` id is only known after the
|
|
75
|
-
* global create lands (validation may slugify the id), so the caller applies
|
|
76
|
-
* the global proposal first and stamps the local copy with the created id.
|
|
77
|
-
*/
|
|
78
|
-
export declare function wholePromoteProposals(item: WrapupItem, candidate: WrapupCandidate, sessionId: string): {
|
|
79
|
-
global: RefinementProposal;
|
|
80
|
-
localStamp: (createdId: string) => RefinementProposal;
|
|
81
|
-
};
|
|
82
|
-
/**
|
|
83
|
-
* Shared proposal builders for a SPLIT promotion (A-form): archive a mixed
|
|
84
|
-
* local entry but promote ONLY the cleaned durable part the model extracted.
|
|
85
|
-
* Same usage contract as {@link wholePromoteProposals}: apply the global
|
|
86
|
-
* create, then stamp the original local entry with the created id.
|
|
87
|
-
*/
|
|
88
|
-
export declare function splitPromoteProposals(item: WrapupItem, candidate: WrapupCandidate, sessionId: string): {
|
|
89
|
-
global: RefinementProposal;
|
|
90
|
-
localStamp: (createdId: string) => RefinementProposal;
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* Valence stamp (P1 效价反馈): a keep-verdict item the assessor marked
|
|
94
|
-
* contradicted gets its negative-valence counter bumped — the entry stays
|
|
95
|
-
* live but sinks in injection ranking and the next assessment prefers
|
|
96
|
-
* archiving it. Promotes pass untouched (the human-approved global copy is
|
|
97
|
-
* the live truth); archives don't need the stamp (they already leave
|
|
98
|
-
* injection). Undefined when nothing applies.
|
|
99
|
-
*/
|
|
100
|
-
export declare function valenceStampProposal(item: WrapupItem, candidate: WrapupCandidate): RefinementProposal | undefined;
|
|
101
|
-
//# sourceMappingURL=wrapup-proposals.d.ts.map
|
package/lib/wrapup-proposals.js
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { ARCHIVED_AT_KEY, PROMOTED_AT_KEY, PROMOTED_TO_KEY, SOURCE_SEQS_KEY, SOURCE_SESSION_KEY, SOURCED_FROM_KEY, VALENCE_NEGATIVE_KEY } from "./types.js";
|
|
2
|
-
import { MEMORY_TYPE_KEY, isMemoryType } from "./types.js";
|
|
3
|
-
import { DEFAULT_PROMOTION_POLICY, mostSimilarGlobalEntry, projectScopedReason, secretLeakReason } from "./promotion.js";
|
|
4
|
-
import { globalCoverageDetected } from "./wrapup-candidates.js";
|
|
5
|
-
import { candidateKey } from "./wrapup-candidates.js";
|
|
6
|
-
/**
|
|
7
|
-
* Apply-time deterministic guard: re-check every promote verdict against the
|
|
8
|
-
* global store right before it lands. The LLM classification may be stale
|
|
9
|
-
* (a gate ran while assessing) or wrong; this ensures a promote never writes
|
|
10
|
-
* a duplicate, project-scoped, or too-thin global entry. Pure and unit-tested.
|
|
11
|
-
*
|
|
12
|
-
* Guards (2026-08-22 promotion policy):
|
|
13
|
-
* - audited candidate list + title coverage (pre-existing),
|
|
14
|
-
* - project-scoped content markers (absolute paths / session ids) — the
|
|
15
|
-
* global store is shared across projects and must stay portable,
|
|
16
|
-
* - thin content below the policy floor (framing outweighs the fact),
|
|
17
|
-
* - near-duplicate of an existing global entry by content overlap.
|
|
18
|
-
*/
|
|
19
|
-
export function filterPromotable(items, globalState, candidates, policy = DEFAULT_PROMOTION_POLICY) {
|
|
20
|
-
const byKey = new Map(candidates.map((candidate) => [candidateKey(candidate.kind, candidate.id), candidate]));
|
|
21
|
-
const promotable = [];
|
|
22
|
-
const skipped = [];
|
|
23
|
-
for (const item of items) {
|
|
24
|
-
if (item.verdict !== "promote")
|
|
25
|
-
continue;
|
|
26
|
-
const candidate = byKey.get(item.key);
|
|
27
|
-
if (!candidate) {
|
|
28
|
-
skipped.push({ key: item.key, reason: "not in the audited candidate list" });
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (candidate.coveredGlobally || globalCoverageDetected(globalState, candidate.kind, candidate)) {
|
|
32
|
-
skipped.push({ key: item.key, reason: "already covered globally" });
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (candidate.kind === "memory" && !isMemoryType(candidate.metadata[MEMORY_TYPE_KEY])) {
|
|
36
|
-
skipped.push({
|
|
37
|
-
key: item.key,
|
|
38
|
-
reason: `memory has no recall type (metadata.${MEMORY_TYPE_KEY} one of user|feedback|project|reference) — update the local entry to classify it first, then promote`,
|
|
39
|
-
});
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const scoped = projectScopedReason(`${candidate.title}\n${candidate.content}`, policy);
|
|
43
|
-
if (scoped) {
|
|
44
|
-
skipped.push({ key: item.key, reason: scoped });
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
// Metadata screens too: wholePromoteProposals copies candidate.metadata
|
|
48
|
-
// verbatim into the global entry, so a credential planted in local
|
|
49
|
-
// metadata must not ride the promotion path into the shared store
|
|
50
|
-
// (review audit 2026-08-28 B2).
|
|
51
|
-
const secret = secretLeakReason([candidate.title, candidate.content, JSON.stringify(candidate.metadata ?? {})].join("\n"));
|
|
52
|
-
if (secret) {
|
|
53
|
-
skipped.push({ key: item.key, reason: secret });
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (candidate.content.length < policy.minPromoteChars) {
|
|
57
|
-
skipped.push({
|
|
58
|
-
key: item.key,
|
|
59
|
-
reason: `too thin to promote (${candidate.content.length} < ${policy.minPromoteChars} chars) — keep local or merge`,
|
|
60
|
-
});
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const similar = mostSimilarGlobalEntry(globalState, candidate.kind, candidate.title, candidate.content, policy);
|
|
64
|
-
if (similar) {
|
|
65
|
-
skipped.push({
|
|
66
|
-
key: item.key,
|
|
67
|
-
reason: `near-duplicate of global ${candidate.kind}:${similar.id} "${similar.title}" (overlap ${similar.score.toFixed(2)}) — update that entry instead`,
|
|
68
|
-
});
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
promotable.push(item);
|
|
72
|
-
}
|
|
73
|
-
return { promotable, skipped };
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* The symmetric archive guard. `filterPromotable` is one-directional: it
|
|
77
|
-
* stops the model from WRITING duplicate global entries, but nothing stopped
|
|
78
|
-
* an unfounded ARCHIVE from hiding content that was actually only local.
|
|
79
|
-
* Guard criteria: an archive needs user confirmation when it is NOT covered
|
|
80
|
-
* globally AND the entry carries a real distillation source (sourceSeqs /
|
|
81
|
-
* sourceSession — i.e. it was distilled from actual user messages, so it
|
|
82
|
-
* may hold reusable value). Operational/empty entries archive silently as
|
|
83
|
-
* before. Split archives (archive + promote sub-object) skip this check:
|
|
84
|
-
* their promotion already crosses a human approval gate, so the archive is
|
|
85
|
-
* the completion of an approved action, not a silent burial.
|
|
86
|
-
*/
|
|
87
|
-
export function needsArchiveReview(item, candidate) {
|
|
88
|
-
if (item.verdict !== "archive")
|
|
89
|
-
return false;
|
|
90
|
-
if (item.promote)
|
|
91
|
-
return false;
|
|
92
|
-
if (candidate.coveredGlobally)
|
|
93
|
-
return false;
|
|
94
|
-
const seqs = candidate.metadata[SOURCE_SEQS_KEY];
|
|
95
|
-
const session = candidate.metadata[SOURCE_SESSION_KEY];
|
|
96
|
-
return (Array.isArray(seqs) && seqs.length > 0) || (typeof session === "string" && session.length > 0);
|
|
97
|
-
}
|
|
98
|
-
/** Partition archive items into silent vs review-required (see needsArchiveReview). */
|
|
99
|
-
export function splitArchiveGuards(items, candidates) {
|
|
100
|
-
const byKey = new Map(candidates.map((candidate) => [candidateKey(candidate.kind, candidate.id), candidate]));
|
|
101
|
-
const silent = [];
|
|
102
|
-
const review = [];
|
|
103
|
-
for (const item of items) {
|
|
104
|
-
if (item.verdict !== "archive")
|
|
105
|
-
continue;
|
|
106
|
-
const candidate = byKey.get(item.key);
|
|
107
|
-
if (candidate && needsArchiveReview(item, candidate)) {
|
|
108
|
-
review.push(item);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
silent.push(item);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return { silent, review };
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Apply-time guard for a split promotion (archive + promote sub-object):
|
|
118
|
-
* the cleaned payload must pass the same promotion policy as a whole
|
|
119
|
-
* promote — no global coverage duplicate, no project-scoped content, not
|
|
120
|
-
* too thin, no near-duplicate global entry. A blocked split is dropped (the
|
|
121
|
-
* entry still archives plain) rather than half-promoting a redundancy.
|
|
122
|
-
*/
|
|
123
|
-
export function splitPromoteBlocked(item, globalState, kind, policy = DEFAULT_PROMOTION_POLICY, candidateMetadata) {
|
|
124
|
-
if (!item.promote)
|
|
125
|
-
return "no split payload";
|
|
126
|
-
if (kind === "memory" && !isMemoryType(candidateMetadata?.[MEMORY_TYPE_KEY])) {
|
|
127
|
-
return `split promotion needs the source entry classified (metadata.${MEMORY_TYPE_KEY} one of user|feedback|project|reference) — update it first`;
|
|
128
|
-
}
|
|
129
|
-
if (globalCoverageDetected(globalState, kind, { id: "", title: item.promote.title })) {
|
|
130
|
-
return "split promotion duplicates a globally covered topic";
|
|
131
|
-
}
|
|
132
|
-
const scoped = projectScopedReason(`${item.promote.title}\n${item.promote.content}`, policy);
|
|
133
|
-
if (scoped)
|
|
134
|
-
return `split promotion is ${scoped}`;
|
|
135
|
-
const secret = secretLeakReason([item.promote.title, item.promote.content].join("\n"));
|
|
136
|
-
if (secret)
|
|
137
|
-
return `split promotion blocked: ${secret}`;
|
|
138
|
-
if (item.promote.content.length < policy.minPromoteChars) {
|
|
139
|
-
return `split promotion too thin (${item.promote.content.length} < ${policy.minPromoteChars} chars)`;
|
|
140
|
-
}
|
|
141
|
-
const similar = mostSimilarGlobalEntry(globalState, kind, item.promote.title, item.promote.content, policy);
|
|
142
|
-
if (similar) {
|
|
143
|
-
return `split promotion near-duplicates global ${kind}:${similar.id} "${similar.title}" (overlap ${similar.score.toFixed(2)})`;
|
|
144
|
-
}
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Shared proposal builders for a WHOLE promotion — used by both the
|
|
149
|
-
* `/evolve wrapup` command and the gate's local-fate dimension so the two
|
|
150
|
-
* paths apply IDENTICAL edits (global create + local retirement stamp).
|
|
151
|
-
*
|
|
152
|
-
* The local stamp is a factory: the `promotedTo` id is only known after the
|
|
153
|
-
* global create lands (validation may slugify the id), so the caller applies
|
|
154
|
-
* the global proposal first and stamps the local copy with the created id.
|
|
155
|
-
*/
|
|
156
|
-
export function wholePromoteProposals(item, candidate, sessionId) {
|
|
157
|
-
const now = new Date().toISOString();
|
|
158
|
-
return {
|
|
159
|
-
global: {
|
|
160
|
-
summary: `wrapup: promote local ${item.key} to the global store`,
|
|
161
|
-
rationale: item.reason,
|
|
162
|
-
expectedOutcome: `The entry is now visible to every session via the global store (sourcedFromLocal=${sessionId}:${candidate.id}).`,
|
|
163
|
-
edits: [
|
|
164
|
-
{
|
|
165
|
-
action: "create",
|
|
166
|
-
kind: candidate.kind,
|
|
167
|
-
id: candidate.id,
|
|
168
|
-
title: candidate.title,
|
|
169
|
-
content: candidate.content,
|
|
170
|
-
path: candidate.path,
|
|
171
|
-
metadata: {
|
|
172
|
-
...candidate.metadata,
|
|
173
|
-
[SOURCED_FROM_KEY]: `${sessionId}:${candidate.id}`,
|
|
174
|
-
[PROMOTED_AT_KEY]: now,
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
},
|
|
179
|
-
localStamp: (createdId) => ({
|
|
180
|
-
summary: `wrapup: stamp local ${item.key} as promoted to ${createdId} and retire it from injection`,
|
|
181
|
-
rationale: item.reason,
|
|
182
|
-
expectedOutcome: `The local copy keeps its data but stops being injected; the global copy is the live one.`,
|
|
183
|
-
edits: [
|
|
184
|
-
{
|
|
185
|
-
action: "update",
|
|
186
|
-
kind: candidate.kind,
|
|
187
|
-
id: candidate.id,
|
|
188
|
-
title: candidate.title,
|
|
189
|
-
content: candidate.content,
|
|
190
|
-
metadata: {
|
|
191
|
-
...candidate.metadata,
|
|
192
|
-
[PROMOTED_TO_KEY]: createdId,
|
|
193
|
-
[PROMOTED_AT_KEY]: now,
|
|
194
|
-
[ARCHIVED_AT_KEY]: now,
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
}),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Shared proposal builders for a SPLIT promotion (A-form): archive a mixed
|
|
203
|
-
* local entry but promote ONLY the cleaned durable part the model extracted.
|
|
204
|
-
* Same usage contract as {@link wholePromoteProposals}: apply the global
|
|
205
|
-
* create, then stamp the original local entry with the created id.
|
|
206
|
-
*/
|
|
207
|
-
export function splitPromoteProposals(item, candidate, sessionId) {
|
|
208
|
-
if (!item.promote)
|
|
209
|
-
throw new Error("split promote proposals require a promote payload");
|
|
210
|
-
const now = new Date().toISOString();
|
|
211
|
-
return {
|
|
212
|
-
global: {
|
|
213
|
-
summary: `wrapup: split — promote cleaned part of ${item.key} to the global store`,
|
|
214
|
-
rationale: item.reason,
|
|
215
|
-
expectedOutcome: `Only the durable part becomes visible globally; the snapshot half stays archived with the original.`,
|
|
216
|
-
edits: [
|
|
217
|
-
{
|
|
218
|
-
action: "create",
|
|
219
|
-
kind: candidate.kind,
|
|
220
|
-
id: candidate.id,
|
|
221
|
-
title: item.promote.title,
|
|
222
|
-
content: item.promote.content,
|
|
223
|
-
path: candidate.path,
|
|
224
|
-
metadata: {
|
|
225
|
-
...candidate.metadata,
|
|
226
|
-
[SOURCED_FROM_KEY]: `${sessionId}:${candidate.id}`,
|
|
227
|
-
[PROMOTED_AT_KEY]: now,
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
},
|
|
232
|
-
localStamp: (createdId) => ({
|
|
233
|
-
summary: `wrapup: split — archive original ${item.key}, stamped as promoted to ${createdId}`,
|
|
234
|
-
rationale: item.reason,
|
|
235
|
-
expectedOutcome: `The original leaves injection (data kept, restorable); the cleaned global copy is the live one.`,
|
|
236
|
-
edits: [
|
|
237
|
-
{
|
|
238
|
-
action: "update",
|
|
239
|
-
kind: candidate.kind,
|
|
240
|
-
id: candidate.id,
|
|
241
|
-
title: candidate.title,
|
|
242
|
-
content: candidate.content,
|
|
243
|
-
metadata: {
|
|
244
|
-
...candidate.metadata,
|
|
245
|
-
[PROMOTED_TO_KEY]: createdId,
|
|
246
|
-
[PROMOTED_AT_KEY]: now,
|
|
247
|
-
[ARCHIVED_AT_KEY]: now,
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
}),
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Valence stamp (P1 效价反馈): a keep-verdict item the assessor marked
|
|
256
|
-
* contradicted gets its negative-valence counter bumped — the entry stays
|
|
257
|
-
* live but sinks in injection ranking and the next assessment prefers
|
|
258
|
-
* archiving it. Promotes pass untouched (the human-approved global copy is
|
|
259
|
-
* the live truth); archives don't need the stamp (they already leave
|
|
260
|
-
* injection). Undefined when nothing applies.
|
|
261
|
-
*/
|
|
262
|
-
export function valenceStampProposal(item, candidate) {
|
|
263
|
-
if (item.verdict !== "keep" || !item.contradicted)
|
|
264
|
-
return undefined;
|
|
265
|
-
return {
|
|
266
|
-
summary: `wrapup: stamp contradicted valence on ${item.key} (${candidate.negativeCount + 1})`,
|
|
267
|
-
rationale: item.reason,
|
|
268
|
-
expectedOutcome: "The entry's negative-valence counter rises; injection downranks it and the next assessment prefers archiving it.",
|
|
269
|
-
edits: [
|
|
270
|
-
{
|
|
271
|
-
action: "update",
|
|
272
|
-
kind: candidate.kind,
|
|
273
|
-
id: candidate.id,
|
|
274
|
-
title: candidate.title,
|
|
275
|
-
content: candidate.content,
|
|
276
|
-
metadata: { ...candidate.metadata, [VALENCE_NEGATIVE_KEY]: candidate.negativeCount + 1 },
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
//# sourceMappingURL=wrapup-proposals.js.map
|