dsh-continual-evolve 0.15.13 → 0.15.14
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/review.js +87 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ English | [中文](README.zh.md)
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
package/lib/review.js
CHANGED
|
@@ -21,7 +21,7 @@ import { runChain } from "./chain.js";
|
|
|
21
21
|
import { resolveCallRoute } from "./llm-call.js";
|
|
22
22
|
import { cwdOf } from "./memory-section.js";
|
|
23
23
|
import { collectCandidates } from "./review-candidates.js";
|
|
24
|
-
import { reviewChain } from "./review-ring.js";
|
|
24
|
+
import { failureHead, reviewChain } from "./review-ring.js";
|
|
25
25
|
import { subagentLoaderFor } from "./subagent.js";
|
|
26
26
|
import { zeroUsage } from "./token-usage.js";
|
|
27
27
|
/** Defaults for the review settings the host may override — and edit live. */
|
|
@@ -51,51 +51,81 @@ export async function runReviewBatch(deps, candidates, opts) {
|
|
|
51
51
|
// order the ring declares. Without a route there is no ring to walk.
|
|
52
52
|
const ring = route === undefined || llm === undefined ? undefined : assembleRing({ llm, route, sessionId: opts.sessionId });
|
|
53
53
|
let position = 0;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
// The run's account is written whatever happens to the loop: a fault that
|
|
55
|
+
// escapes one item (or the loop itself) is rethrown *after* the rollup is
|
|
56
|
+
// merged, so a dead run can never leave `reviewed` incremented with every
|
|
57
|
+
// other counter at zero and nothing saying why (2026-10-14 incident).
|
|
58
|
+
let escaped;
|
|
59
|
+
let failed = false;
|
|
60
|
+
try {
|
|
61
|
+
for (const candidate of batch) {
|
|
62
|
+
try {
|
|
63
|
+
const remaining = deps.store.consumeReviewBudget(opts.day, opts.budget);
|
|
64
|
+
if (remaining < 0) {
|
|
65
|
+
// Hard cap hit: this item and everything after it drops (low
|
|
66
|
+
// priority never rolls over — it simply waits for a future run).
|
|
67
|
+
summary.dropped += batch.length - position;
|
|
68
|
+
for (const item of batch.slice(position)) {
|
|
69
|
+
deps.store.logReview({
|
|
70
|
+
runId, workspaceId: item.workspaceId, itemKind: item.kind, itemId: item.id,
|
|
71
|
+
verdict: "skip", reason: "budget-exhausted", applied: 0,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
skipReasons.push("budget-exhausted");
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
summary.reviewed += 1;
|
|
78
|
+
if (candidate.priority === 0) {
|
|
79
|
+
newItems += 1;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
requeued += 1;
|
|
83
|
+
}
|
|
84
|
+
if (route === undefined || ring === undefined) {
|
|
85
|
+
deps.store.logReview({
|
|
86
|
+
runId, workspaceId: candidate.workspaceId, itemKind: candidate.kind, itemId: candidate.id,
|
|
87
|
+
verdict: "skip", reason: "no-route", applied: 0,
|
|
88
|
+
});
|
|
89
|
+
skipReasons.push("no-route");
|
|
90
|
+
position += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const item = {
|
|
94
|
+
deps, loader: ring.loader, route, runId, candidate, opts, summary, skipReasons,
|
|
95
|
+
gate: { usage: zeroUsage(), raw: undefined },
|
|
96
|
+
};
|
|
97
|
+
await runChain(ring.links, item);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// A fault escaping the chain (a store write, a wiring bug) gets its
|
|
101
|
+
// own row carrying the error text and costs the batch only this
|
|
102
|
+
// item — the model-facing failures already degrade inside the ring.
|
|
61
103
|
deps.store.logReview({
|
|
62
|
-
runId, workspaceId:
|
|
63
|
-
verdict: "skip", reason:
|
|
104
|
+
runId, workspaceId: candidate.workspaceId, itemKind: candidate.kind, itemId: candidate.id,
|
|
105
|
+
verdict: "skip", reason: `review-failed: ${failureHead(error)}`,
|
|
106
|
+
model: route?.model, applied: 0,
|
|
64
107
|
});
|
|
108
|
+
skipReasons.push("review-failed");
|
|
65
109
|
}
|
|
66
|
-
skipReasons.push("budget-exhausted");
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
summary.reviewed += 1;
|
|
70
|
-
if (candidate.priority === 0) {
|
|
71
|
-
newItems += 1;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
requeued += 1;
|
|
75
|
-
}
|
|
76
|
-
if (route === undefined || ring === undefined) {
|
|
77
|
-
deps.store.logReview({
|
|
78
|
-
runId, workspaceId: candidate.workspaceId, itemKind: candidate.kind, itemId: candidate.id,
|
|
79
|
-
verdict: "skip", reason: "no-route", applied: 0,
|
|
80
|
-
});
|
|
81
|
-
skipReasons.push("no-route");
|
|
82
110
|
position += 1;
|
|
83
|
-
continue;
|
|
84
111
|
}
|
|
85
|
-
const item = {
|
|
86
|
-
deps, loader: ring.loader, route, runId, candidate, opts, summary, skipReasons,
|
|
87
|
-
gate: { usage: zeroUsage(), raw: undefined },
|
|
88
|
-
};
|
|
89
|
-
await runChain(ring.links, item);
|
|
90
|
-
position += 1;
|
|
91
112
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
failed = true;
|
|
115
|
+
escaped = error;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
summary.backlog = Math.max(0, candidates.length - summary.reviewed - summary.dropped);
|
|
119
|
+
deps.store.mergeDaily(opts.day, opts.budget, {
|
|
120
|
+
newItems, requeued, approved: summary.approved, applied: summary.applied,
|
|
121
|
+
cheapToken: summary.cheapToken, evolveToken: summary.evolveToken,
|
|
122
|
+
backlog: summary.backlog, dropped: summary.dropped,
|
|
123
|
+
skipReasons: skipReasons.length > 0 ? skipReasons : undefined,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (failed) {
|
|
127
|
+
throw escaped;
|
|
128
|
+
}
|
|
99
129
|
return summary;
|
|
100
130
|
}
|
|
101
131
|
/** Current UTC day `YYYY-MM-DD` for the hard-cap account. */
|
|
@@ -140,26 +170,39 @@ export function createReviewScheduler(host, deps, config) {
|
|
|
140
170
|
const workspace = triggerWorkspace ?? pendingWorkspace;
|
|
141
171
|
pendingWorkspace = undefined;
|
|
142
172
|
void (async () => {
|
|
173
|
+
const settings = config.read();
|
|
174
|
+
const day = todayUtc();
|
|
143
175
|
try {
|
|
144
|
-
const settings = config.read();
|
|
145
|
-
const day = todayUtc();
|
|
146
176
|
const daily = deps.store.getDaily(day);
|
|
147
177
|
const remaining = settings.dailyBudget - (daily?.reviewed ?? 0);
|
|
148
178
|
if (remaining <= 0) {
|
|
179
|
+
host.logger("continual-evolve").info(`review drain skipped: daily budget spent (${daily?.reviewed ?? 0}/${settings.dailyBudget})`);
|
|
149
180
|
return;
|
|
150
181
|
}
|
|
151
182
|
const candidates = collectCandidates(deps.store, { staleDays: settings.staleDays, limit: settings.batchSize * 2 });
|
|
152
183
|
const ordered = workspace === undefined
|
|
153
184
|
? candidates
|
|
154
185
|
: [...candidates.filter((c) => c.workspaceId === workspace), ...candidates.filter((c) => c.workspaceId !== workspace)];
|
|
155
|
-
await runReviewBatch(deps, ordered.slice(0, settings.batchSize), {
|
|
186
|
+
const summary = await runReviewBatch(deps, ordered.slice(0, settings.batchSize), {
|
|
156
187
|
day, budget: settings.dailyBudget, dryRun: settings.dryRun,
|
|
157
188
|
batchSize: settings.batchSize, staleDays: settings.staleDays,
|
|
158
189
|
provider: lastRoute?.provider, model: lastRoute?.model, sessionId: lastRoute?.sessionId,
|
|
159
190
|
});
|
|
191
|
+
host.logger("continual-evolve").info(`review drain: reviewed ${summary.reviewed}, approved ${summary.approved}, ` +
|
|
192
|
+
`applied ${summary.applied}, dropped ${summary.dropped}, backlog ${summary.backlog}`);
|
|
160
193
|
}
|
|
161
194
|
catch (error) {
|
|
162
|
-
|
|
195
|
+
const head = failureHead(error);
|
|
196
|
+
host.logger("continual-evolve").warn(`review drain failed: ${head}`);
|
|
197
|
+
// Two records on purpose: the logger is the live channel, the day's
|
|
198
|
+
// rollup is the durable one every later reading sees. A drain that
|
|
199
|
+
// died must leave a trace in the ledger, not only in a log file.
|
|
200
|
+
try {
|
|
201
|
+
deps.store.mergeDaily(day, settings.dailyBudget, { skipReasons: [`drain-failed: ${head}`] });
|
|
202
|
+
}
|
|
203
|
+
catch (recordError) {
|
|
204
|
+
host.logger("continual-evolve").warn(`review drain failure not ledgered: ${failureHead(recordError)}`);
|
|
205
|
+
}
|
|
163
206
|
}
|
|
164
207
|
finally {
|
|
165
208
|
running = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.14",
|
|
4
4
|
"description": "ZCode-style workspace memory for DeepSeek Harness: one central SQLite store partitioned by workspace, index injection, and sole-writer gated writes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|