dsh-continual-evolve 0.15.6 → 0.15.8
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 +19 -8
- package/README.zh.md +19 -8
- package/client/client.js +6 -46
- package/lib/card-routes.js +3 -8
- package/lib/extraction-prompt.d.ts +17 -1
- package/lib/extraction-prompt.js +48 -0
- package/lib/extraction.js +37 -5
- package/lib/index.d.ts +34 -0
- package/lib/index.js +45 -0
- package/lib/memory-rules.d.ts +7 -0
- package/lib/memory-snapshot.d.ts +1 -3
- package/lib/memory-snapshot.js +1 -11
- package/lib/memory-tools.js +8 -1
- package/lib/review-prompt.d.ts +55 -0
- package/lib/review-prompt.js +105 -0
- package/lib/review.d.ts +99 -0
- package/lib/review.js +467 -0
- package/lib/skill-tools.js +3 -2
- package/lib/store-apply.d.ts +2 -0
- package/lib/store-apply.js +44 -8
- package/lib/store.d.ts +82 -22
- package/lib/store.js +188 -51
- package/lib/token-usage.d.ts +3 -2
- package/lib/token-usage.js +3 -2
- package/package.json +1 -1
package/lib/memory-rules.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export interface MemoryProposal {
|
|
|
38
38
|
body?: string | undefined;
|
|
39
39
|
/** Trajectory provenance for extraction writes ("820-831"). */
|
|
40
40
|
sourceSeqs?: string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Optional id of the same-workspace memory this proposal corrects or
|
|
43
|
+
* supersedes. The gate requires the target to exist and be active and
|
|
44
|
+
* forbids self-links; the link persists on the row for future
|
|
45
|
+
* correction-chasing. Append/replace only — no unlink path.
|
|
46
|
+
*/
|
|
47
|
+
corrects?: string | undefined;
|
|
41
48
|
}
|
|
42
49
|
/** The merged field values a create/update would write (delete checks none). */
|
|
43
50
|
export interface ProposedFields {
|
package/lib/memory-snapshot.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MEMORY_FILE_PREVIEW_LIMIT, type MemoryStore
|
|
1
|
+
import { MEMORY_FILE_PREVIEW_LIMIT, type MemoryStore } from "./store.js";
|
|
2
2
|
export { MEMORY_FILE_PREVIEW_LIMIT };
|
|
3
3
|
/** One memory row the card renders. */
|
|
4
4
|
export interface MemoryFileInfo {
|
|
@@ -32,8 +32,6 @@ export interface MemorySnapshot {
|
|
|
32
32
|
orphanFtsRows: number;
|
|
33
33
|
quarantined: number;
|
|
34
34
|
} | null;
|
|
35
|
-
/** Review-loop audit over the workspace's extraction ledger (zeros when none). */
|
|
36
|
-
review: ReviewSummary;
|
|
37
35
|
}
|
|
38
36
|
/**
|
|
39
37
|
* The read-only snapshot for one workspace partition. A workspace without
|
package/lib/memory-snapshot.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { basename } from "node:path";
|
|
11
|
-
import { MEMORY_FILE_PREVIEW_LIMIT
|
|
11
|
+
import { MEMORY_FILE_PREVIEW_LIMIT } from "./store.js";
|
|
12
12
|
import { skillUsagePartition } from "./skill-rules.js";
|
|
13
13
|
export { MEMORY_FILE_PREVIEW_LIMIT };
|
|
14
14
|
/**
|
|
@@ -27,7 +27,6 @@ export function memorySnapshot(store, root) {
|
|
|
27
27
|
files: [],
|
|
28
28
|
quarantined: [],
|
|
29
29
|
lastPatrol: null,
|
|
30
|
-
review: zeroReviewSummary(),
|
|
31
30
|
};
|
|
32
31
|
let all;
|
|
33
32
|
try {
|
|
@@ -66,17 +65,8 @@ export function memorySnapshot(store, root) {
|
|
|
66
65
|
.filter((record) => record.status === "quarantined")
|
|
67
66
|
.map((record) => ({ id: record.id, reason: quarantineReasonOf(store, record) })),
|
|
68
67
|
lastPatrol,
|
|
69
|
-
review: reviewOf(store, workspaceRoot),
|
|
70
68
|
};
|
|
71
69
|
}
|
|
72
|
-
function reviewOf(store, root) {
|
|
73
|
-
try {
|
|
74
|
-
return store.reviewSummary(root);
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
return zeroReviewSummary();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
70
|
function infoOf(record) {
|
|
81
71
|
return {
|
|
82
72
|
id: record.id,
|
package/lib/memory-tools.js
CHANGED
|
@@ -106,7 +106,14 @@ export function registerMemoryTools(tools, store) {
|
|
|
106
106
|
const limit = clampLimit(args.limit);
|
|
107
107
|
if (typeof args.id === "string" && args.id.length > 0) {
|
|
108
108
|
const record = store.get(cwd, args.id);
|
|
109
|
-
|
|
109
|
+
if (record === undefined) {
|
|
110
|
+
return `没有找到 id 为 "${args.id}" 的记忆。`;
|
|
111
|
+
}
|
|
112
|
+
// Presence heartbeat (mirrors skill_read): a body served
|
|
113
|
+
// by id means the model opened it for the task at hand.
|
|
114
|
+
// Search/list hits are noise and stay uncounted.
|
|
115
|
+
store.recordMemoryUse(cwd, record.id);
|
|
116
|
+
return formatRecord(record);
|
|
110
117
|
}
|
|
111
118
|
const query = typeof args.query === "string" ? args.query : "";
|
|
112
119
|
const records = query.length > 0 ? store.search(cwd, query, limit) : store.list(cwd).slice(0, limit);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content-quality review prompts (the model side of the autonomous
|
|
3
|
+
* evolution loop): a cheap gate that decides whether an item deserves a
|
|
4
|
+
* close look, and the evolve pass that judges content quality dimension by
|
|
5
|
+
* dimension and proposes the rewrite. Code owns every consequence — the
|
|
6
|
+
* model only returns verdict JSON, never touches the store.
|
|
7
|
+
*
|
|
8
|
+
* Memories are judged on five dimensions (accurate, usable, reusable,
|
|
9
|
+
* non-duplicate, fresh); skills on four (description trigger precision,
|
|
10
|
+
* body executability, reusability, non-duplicate). The prompts are
|
|
11
|
+
* deliberately behavior-blind: there are no task datasets here (unlike
|
|
12
|
+
* Hermes GEPA), only the item text plus its provenance.
|
|
13
|
+
*/
|
|
14
|
+
export declare const REVIEW_CHEAP_SYSTEM_PROMPT = "\u4F60\u662F\u8BB0\u5FC6\u5E93\u7684\u5185\u5BB9\u8BC4\u5BA1\u95E8\u3002\u5148\u8FC7\u5F52\u5C5E\u4E09\u6D4B\uFF1A\u2460\u4E3B\u8BED\u662F\u7528\u6237/\u73AF\u5883\u8FD8\u662F\u67D0\u4ED3\u5E93\u7684\u67D0\u7248\u673A\u5236\uFF08\u7248\u672C\u53F7\u3001\u9608\u503C\u3001\u5F00\u5173\u540D\u51FA\u73B0\u5373\u540E\u8005\uFF09\uFF1B\u2461\u62B9\u6389\u4ED3\u5E93\u540D\u7248\u672C\u53F7\u673A\u5236\u540D\u540E\u53E5\u5B50\u8FD8\u6210\u7ACB\u5417\uFF1B\u2462\u4ED3\u5E93\u4E8B\u5B9E\u4F4F ADR/docs\uFF0C\u8BB0\u5FC6\u53EA\u6536\u8DDF\u4EBA\u8D70\u7684\u3002\u5F52\u5C5E\u4E0D\u8FC7\u76F4\u63A5\u5224 false \u5E76\u6CE8\u660E\u653E\u9519\u5BB6\u3002\u518D\u5224\u503C\u4E0D\u503C\u5F97\u7EC6\u770B\uFF1A\u6709\u660E\u786E\u4E8B\u5B9E\u3001\u505A\u6CD5\u3001\u504F\u597D\u6216\u53EF\u590D\u7528\u89C4\u7A0B\u5C31\u6279\uFF1B\u7A7A\u8BDD\u3001\u7EAF\u8FC7\u7A0B\u8BB0\u5F55\u3001\u4E00\u6B21\u6027\u7684\u77AC\u65F6\u8F93\u51FA\u3001\u5F53\u8F6E\u5DF2\u53EF\u63A8\u5BFC\u7684\u4E00\u5F8B\u62D2\u3002\u53EA\u8F93\u51FA JSON\uFF1A{\"worthReview\": true|false, \"reason\": \"\u4E00\u53E5\u8BDD\"}\u3002";
|
|
15
|
+
/** Memory rubric: accurate, usable, reusable, non-duplicate, fresh, right-home. */
|
|
16
|
+
export declare const REVIEW_MEMORY_SYSTEM_PROMPT = "\u4F60\u662F\u8BB0\u5FC6\u5E93\u7684\u5185\u5BB9\u8BC4\u5BA1\u3002\u5148\u8FC7\u5F52\u5C5E\u4E09\u6D4B\uFF08\u4E3B\u8BED\u662F\u7528\u6237/\u73AF\u5883\u8FD8\u662F\u67D0\u4ED3\u5E93\u7684\u67D0\u7248\u673A\u5236\uFF1B\u62B9\u6389\u4ED3\u5E93\u540D\u7248\u672C\u53F7\u673A\u5236\u540D\u540E\u8FD8\u6210\u7ACB\u5417\uFF1B\u4ED3\u5E93\u4E8B\u5B9E\u4F4F ADR/docs\uFF09\uFF1A\u5F52\u5C5E\u4E0D\u8FC7\u76F4\u63A5 quarantine \u5E76\u6CE8\u660E\u653E\u9519\u5BB6\u3002\u518D\u6309\u4E94\u7EF4\u5224\uFF1A\u2460\u51C6\u786E\uFF08\u4E8B\u5B9E\u53EF\u4FE1\u3001\u65E0\u5E7B\u89C9\uFF09\uFF1B\u2461\u53EF\u7528\uFF08Why/How \u5177\u4F53\u3001\u53EF\u6267\u884C\uFF0C\u53CD\u9988\u7C7B\u5FC5\u987B\u5E26 Why \u4E0E How to apply\uFF09\uFF1B\u2462\u53EF\u590D\u7528\uFF08\u672A\u6765\u4F1A\u8BDD\u7528\u5F97\u4E0A\uFF09\uFF1B\u2463\u4E0D\u91CD\u590D\uFF08\u4E0E\u5019\u9009\u65E7\u6761\u6BD4\uFF0C\u6709\u91CD\u590D\u5C31\u6307\u51FA\u6765\uFF09\uFF1B\u2464\u4E0D\u8FC7\u65F6\uFF08\u672A\u88AB\u7EA0\u6B63\u94FE\u53D6\u4EE3\uFF09\u3002\u53EA\u8F93\u51FA JSON\uFF1A{\"verdict\": \"keep|rewrite|quarantine\", \"reason\": \"\u4E00\u53E5\u8BDD\", \"title\"?: \"\u6539\u5199\u540E\u6807\u9898\", \"description\"?: \"\u6539\u5199\u540E\u94A9\u5B50\", \"body\"?: \"\u6539\u5199\u540E\u6B63\u6587\u5168\u6587\", \"corrects\"?: \"\u88AB\u53D6\u4EE3\u7684\u540C\u533A\u6761\u76EEid\"}\u3002verdict \u8BF4\u660E\uFF1Akeep=\u4E0D\u52A8\uFF1Brewrite=\u7ED9\u51FA\u6539\u5199\u540E\u5168\u6587\u4E09\u4EF6\uFF1Bquarantine=\u6D89\u5BC6/\u6709\u5BB3/\u5B8C\u5168\u8FC7\u65F6/\u653E\u9519\u5BB6\u3002\u7981\u5220\u9664\uFF0C\u7981\u7F16\u9020\u5F15\u7528\u3002";
|
|
17
|
+
/** Skill rubric: trigger precision, executability, reusability, non-duplicate, right-home. */
|
|
18
|
+
export declare const REVIEW_SKILL_SYSTEM_PROMPT = "\u4F60\u662F\u6280\u80FD\u5E93\u7684\u5185\u5BB9\u8BC4\u5BA1\u3002\u5148\u8FC7\u5F52\u5C5E\u4E09\u6D4B\uFF08\u6280\u80FD\u662F\u5426\u7ED1\u5B9A\u5355\u4E00\u4ED3\u5E93\u7684\u8DEF\u5F84\u4E0E\u673A\u5236\uFF1B\u62B9\u6389\u4ED3\u5E93\u540D\u540E\u8FD8\u6210\u7ACB\u5417\uFF09\uFF1A\u653E\u9519\u5BB6\u76F4\u63A5 quarantine\u3002\u518D\u6309\u56DB\u7EF4\u5224\uFF1A\u2460\u89E6\u53D1\u7CBE\u5EA6\uFF08description \u662F\u5426\u8BA9\u4EBA/\u6A21\u578B\u5728\u8BE5\u7528\u65F6\u60F3\u8D77\u3001\u4E0D\u8BE5\u7528\u65F6\u4E0D\u6253\u6270\uFF09\uFF1B\u2461\u6B63\u6587\u53EF\u6267\u884C\u5B8C\u5907\uFF08\u6B65\u9AA4\u5B8C\u6574\u3001\u8F93\u5165\u8F93\u51FA\u660E\u786E\uFF09\uFF1B\u2462\u53EF\u590D\u7528\uFF08\u8DE8\u4EFB\u52A1\u53EF\u7528\uFF09\uFF1B\u2463\u4E0D\u91CD\u590D\uFF08\u4E0E\u5019\u9009\u65E2\u6709\u6280\u80FD\u6BD4\uFF09\u3002\u53EA\u8F93\u51FA JSON\uFF1A{\"verdict\": \"keep|rewrite|quarantine\", \"reason\": \"\u4E00\u53E5\u8BDD\", \"title\"?: \"\u6539\u5199\u540E\u6807\u9898\", \"description\"?: \"\u6539\u5199\u540E\u94A9\u5B50\", \"body\"?: \"\u6539\u5199\u540E\u6B63\u6587\u5168\u6587\"}\u3002verdict \u8BF4\u660E\uFF1Akeep=\u4E0D\u52A8\uFF1Brewrite=\u7ED9\u51FA\u6539\u5199\u540E\u5168\u6587\u4E09\u4EF6\uFF1Bquarantine=\u6D89\u5BC6/\u6709\u5BB3/\u5B8C\u5168\u8FC7\u65F6/\u653E\u9519\u5BB6\u3002\u7981\u5220\u9664\u3002";
|
|
19
|
+
export interface GateVerdict {
|
|
20
|
+
worthReview: boolean;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export interface EvolveVerdict {
|
|
24
|
+
verdict: "keep" | "rewrite" | "quarantine";
|
|
25
|
+
reason: string;
|
|
26
|
+
title?: string | undefined;
|
|
27
|
+
description?: string | undefined;
|
|
28
|
+
body?: string | undefined;
|
|
29
|
+
corrects?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** The cheap-gate prompt for one item (id + hook + body, truncated by caller). */
|
|
32
|
+
export declare function gatePrompt(entry: {
|
|
33
|
+
kind: string;
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
description: string;
|
|
37
|
+
body: string;
|
|
38
|
+
}): string;
|
|
39
|
+
/** The evolve prompt for one item plus its similar neighbors for dup-checking. */
|
|
40
|
+
export declare function evolvePrompt(entry: {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
description: string;
|
|
44
|
+
body: string;
|
|
45
|
+
extra?: string | undefined;
|
|
46
|
+
}, neighbors: readonly {
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
description: string;
|
|
50
|
+
}[]): string;
|
|
51
|
+
/** Parse the cheap-gate answer; `null` when the model did not return verdict JSON. */
|
|
52
|
+
export declare function parseGateAnswer(text: string): GateVerdict | null;
|
|
53
|
+
/** Parse the evolve answer; `null` when the model did not return verdict JSON. */
|
|
54
|
+
export declare function parseEvolveAnswer(text: string): EvolveVerdict | null;
|
|
55
|
+
//# sourceMappingURL=review-prompt.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content-quality review prompts (the model side of the autonomous
|
|
3
|
+
* evolution loop): a cheap gate that decides whether an item deserves a
|
|
4
|
+
* close look, and the evolve pass that judges content quality dimension by
|
|
5
|
+
* dimension and proposes the rewrite. Code owns every consequence — the
|
|
6
|
+
* model only returns verdict JSON, never touches the store.
|
|
7
|
+
*
|
|
8
|
+
* Memories are judged on five dimensions (accurate, usable, reusable,
|
|
9
|
+
* non-duplicate, fresh); skills on four (description trigger precision,
|
|
10
|
+
* body executability, reusability, non-duplicate). The prompts are
|
|
11
|
+
* deliberately behavior-blind: there are no task datasets here (unlike
|
|
12
|
+
* Hermes GEPA), only the item text plus its provenance.
|
|
13
|
+
*/
|
|
14
|
+
export const REVIEW_CHEAP_SYSTEM_PROMPT = "你是记忆库的内容评审门。先过归属三测:①主语是用户/环境还是某仓库的某版机制(版本号、阈值、开关名出现即后者);②抹掉仓库名版本号机制名后句子还成立吗;③仓库事实住 ADR/docs,记忆只收跟人走的。归属不过直接判 false 并注明放错家。再判值不值得细看:有明确事实、做法、偏好或可复用规程就批;空话、纯过程记录、一次性的瞬时输出、当轮已可推导的一律拒。只输出 JSON:{\"worthReview\": true|false, \"reason\": \"一句话\"}。";
|
|
15
|
+
/** Memory rubric: accurate, usable, reusable, non-duplicate, fresh, right-home. */
|
|
16
|
+
export const REVIEW_MEMORY_SYSTEM_PROMPT = "你是记忆库的内容评审。先过归属三测(主语是用户/环境还是某仓库的某版机制;抹掉仓库名版本号机制名后还成立吗;仓库事实住 ADR/docs):归属不过直接 quarantine 并注明放错家。再按五维判:①准确(事实可信、无幻觉);②可用(Why/How 具体、可执行,反馈类必须带 Why 与 How to apply);③可复用(未来会话用得上);④不重复(与候选旧条比,有重复就指出来);⑤不过时(未被纠正链取代)。只输出 JSON:{\"verdict\": \"keep|rewrite|quarantine\", \"reason\": \"一句话\", \"title\"?: \"改写后标题\", \"description\"?: \"改写后钩子\", \"body\"?: \"改写后正文全文\", \"corrects\"?: \"被取代的同区条目id\"}。verdict 说明:keep=不动;rewrite=给出改写后全文三件;quarantine=涉密/有害/完全过时/放错家。禁删除,禁编造引用。";
|
|
17
|
+
/** Skill rubric: trigger precision, executability, reusability, non-duplicate, right-home. */
|
|
18
|
+
export const REVIEW_SKILL_SYSTEM_PROMPT = "你是技能库的内容评审。先过归属三测(技能是否绑定单一仓库的路径与机制;抹掉仓库名后还成立吗):放错家直接 quarantine。再按四维判:①触发精度(description 是否让人/模型在该用时想起、不该用时不打扰);②正文可执行完备(步骤完整、输入输出明确);③可复用(跨任务可用);④不重复(与候选既有技能比)。只输出 JSON:{\"verdict\": \"keep|rewrite|quarantine\", \"reason\": \"一句话\", \"title\"?: \"改写后标题\", \"description\"?: \"改写后钩子\", \"body\"?: \"改写后正文全文\"}。verdict 说明:keep=不动;rewrite=给出改写后全文三件;quarantine=涉密/有害/完全过时/放错家。禁删除。";
|
|
19
|
+
/** The cheap-gate prompt for one item (id + hook + body, truncated by caller). */
|
|
20
|
+
export function gatePrompt(entry) {
|
|
21
|
+
return [
|
|
22
|
+
`kind=${entry.kind} id=${entry.id}`,
|
|
23
|
+
`title=${entry.title}`,
|
|
24
|
+
`钩子=${entry.description}`,
|
|
25
|
+
"全文:",
|
|
26
|
+
entry.body,
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
|
29
|
+
/** The evolve prompt for one item plus its similar neighbors for dup-checking. */
|
|
30
|
+
export function evolvePrompt(entry, neighbors) {
|
|
31
|
+
const lines = [
|
|
32
|
+
`id=${entry.id}`,
|
|
33
|
+
`title=${entry.title}`,
|
|
34
|
+
`钩子=${entry.description}`,
|
|
35
|
+
"全文:",
|
|
36
|
+
entry.body,
|
|
37
|
+
];
|
|
38
|
+
if (entry.extra !== undefined && entry.extra.length > 0) {
|
|
39
|
+
lines.push("", entry.extra);
|
|
40
|
+
}
|
|
41
|
+
if (neighbors.length > 0) {
|
|
42
|
+
lines.push("", "同类候选(判重复用):");
|
|
43
|
+
for (const neighbor of neighbors) {
|
|
44
|
+
lines.push(`- id=${neighbor.id} title=${neighbor.title} 钩子=${neighbor.description}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
/** Parse the cheap-gate answer; `null` when the model did not return verdict JSON. */
|
|
50
|
+
export function parseGateAnswer(text) {
|
|
51
|
+
const parsed = parseJsonObject(text);
|
|
52
|
+
if (parsed === null || typeof parsed.worthReview !== "boolean") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
worthReview: parsed.worthReview,
|
|
57
|
+
reason: typeof parsed.reason === "string" && parsed.reason.length > 0 ? parsed.reason.slice(0, 300) : "无理由",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Parse the evolve answer; `null` when the model did not return verdict JSON. */
|
|
61
|
+
export function parseEvolveAnswer(text) {
|
|
62
|
+
const parsed = parseJsonObject(text);
|
|
63
|
+
if (parsed === null || (parsed.verdict !== "keep" && parsed.verdict !== "rewrite" && parsed.verdict !== "quarantine")) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const verdict = {
|
|
67
|
+
verdict: parsed.verdict,
|
|
68
|
+
reason: typeof parsed.reason === "string" && parsed.reason.length > 0 ? parsed.reason.slice(0, 300) : "无理由",
|
|
69
|
+
};
|
|
70
|
+
if (typeof parsed.title === "string" && parsed.title.length > 0) {
|
|
71
|
+
verdict.title = parsed.title.slice(0, 500);
|
|
72
|
+
}
|
|
73
|
+
if (typeof parsed.description === "string" && parsed.description.length > 0) {
|
|
74
|
+
verdict.description = parsed.description.slice(0, 1000);
|
|
75
|
+
}
|
|
76
|
+
if (typeof parsed.body === "string" && parsed.body.length > 0) {
|
|
77
|
+
verdict.body = parsed.body.slice(0, 65536);
|
|
78
|
+
}
|
|
79
|
+
if (typeof parsed.corrects === "string" && parsed.corrects.length > 0) {
|
|
80
|
+
verdict.corrects = parsed.corrects.slice(0, 200);
|
|
81
|
+
}
|
|
82
|
+
return verdict;
|
|
83
|
+
}
|
|
84
|
+
/** Extract the first JSON object from model output (fenced or bare). */
|
|
85
|
+
function parseJsonObject(text) {
|
|
86
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
87
|
+
const candidate = (fenced?.[1] ?? text).trim();
|
|
88
|
+
const start = candidate.indexOf("{");
|
|
89
|
+
const end = candidate.lastIndexOf("}");
|
|
90
|
+
if (start === -1 || end <= start) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(candidate.slice(start, end + 1));
|
|
95
|
+
/* v8 ignore next 3 — the slice always starts with `{`, so a
|
|
96
|
+
successful parse is an object by construction; the guard stays. */
|
|
97
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
98
|
+
? parsed
|
|
99
|
+
: null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=review-prompt.js.map
|
package/lib/review.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { MemoryStore } from "./store.js";
|
|
2
|
+
/** The minimal LLM face review calls (same duck-typed host service as extraction). */
|
|
3
|
+
export interface ReviewLlm {
|
|
4
|
+
stream(options: Record<string, unknown>): AsyncIterable<unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface ReviewDeps {
|
|
7
|
+
store: MemoryStore;
|
|
8
|
+
llm: ReviewLlm | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface ReviewCandidate {
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
kind: "memory" | "skill";
|
|
13
|
+
id: string;
|
|
14
|
+
/** 0 = new, 1 = corrects-linked requeue, 2 = stale requeue. */
|
|
15
|
+
priority: number;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
body: string;
|
|
19
|
+
/** Memory type, present only for memory candidates. */
|
|
20
|
+
memoryType?: string | undefined;
|
|
21
|
+
/** Skill scope, present only for skill candidates. */
|
|
22
|
+
scope?: "project" | "global" | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface ReviewRunOptions {
|
|
25
|
+
/** UTC day `YYYY-MM-DD` the hard cap accounts against. */
|
|
26
|
+
day: string;
|
|
27
|
+
/** Hard cap for the day (100). Candidates past it count as dropped. */
|
|
28
|
+
budget: number;
|
|
29
|
+
/** Trial mode: verdicts ledgered, nothing applied or quarantined. */
|
|
30
|
+
dryRun: boolean;
|
|
31
|
+
/** Max items per run (batching keeps one prompt small). */
|
|
32
|
+
batchSize?: number | undefined;
|
|
33
|
+
/** Days of untouched age before an item requeues (default 30). */
|
|
34
|
+
staleDays?: number | undefined;
|
|
35
|
+
provider?: string | undefined;
|
|
36
|
+
model?: string | undefined;
|
|
37
|
+
sessionId?: string | undefined;
|
|
38
|
+
runId?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
export interface ReviewRunSummary {
|
|
41
|
+
reviewed: number;
|
|
42
|
+
approved: number;
|
|
43
|
+
applied: number;
|
|
44
|
+
dropped: number;
|
|
45
|
+
backlog: number;
|
|
46
|
+
cheapToken: number;
|
|
47
|
+
evolveToken: number;
|
|
48
|
+
}
|
|
49
|
+
/** A rewrite lands only when the body-length ratio stays within this bound. */
|
|
50
|
+
export declare const REWRITE_MAGNITUDE_CAP = 0.3;
|
|
51
|
+
/** Relative body change: |new-old| / max(old, 1). Above the cap → quarantine. */
|
|
52
|
+
export declare function rewriteMagnitude(oldBody: string, newBody: string): number;
|
|
53
|
+
/**
|
|
54
|
+
* Collect review candidates across every workspace carrying active items:
|
|
55
|
+
* new items first (newest first), then corrects-linked requeues, then stale
|
|
56
|
+
* requeues (30 days untouched). bounded by `limit`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function collectCandidates(store: MemoryStore, opts?: {
|
|
59
|
+
now?: number | undefined;
|
|
60
|
+
staleDays?: number | undefined;
|
|
61
|
+
limit?: number | undefined;
|
|
62
|
+
}): ReviewCandidate[];
|
|
63
|
+
/**
|
|
64
|
+
* Run one review batch: cheap gate per item, evolve pass on approval,
|
|
65
|
+
* safeguards before landing, one `item-review` row per terminal path, one
|
|
66
|
+
* daily merge at the end. Never throws: LLM and parse failures degrade to
|
|
67
|
+
* skip rows (the run keeps going), exactly like extraction's fail-ledgered
|
|
68
|
+
* discipline.
|
|
69
|
+
*/
|
|
70
|
+
export declare function runReviewBatch(deps: ReviewDeps, candidates: readonly ReviewCandidate[], opts: ReviewRunOptions): Promise<ReviewRunSummary>;
|
|
71
|
+
/** One-line error head for ledger rows (fail-loud shape, capped length). */
|
|
72
|
+
export declare function failureHead(error: unknown, max?: number): string;
|
|
73
|
+
/** Current UTC day `YYYY-MM-DD` for the hard-cap account. */
|
|
74
|
+
export declare function todayUtc(): string;
|
|
75
|
+
/** The event wiring surface (duck-typed cordis context, mirrors extraction). */
|
|
76
|
+
export interface ReviewSchedulerHost {
|
|
77
|
+
on(event: string, handler: (...args: never[]) => void): unknown;
|
|
78
|
+
logger(name: string): {
|
|
79
|
+
info(message: string): void;
|
|
80
|
+
warn(message: string): void;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export interface ReviewSchedulerConfig {
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
dryRun: boolean;
|
|
86
|
+
dailyBudget: number;
|
|
87
|
+
batchSize: number;
|
|
88
|
+
staleDays: number;
|
|
89
|
+
/** Minimum ms between two drains (default 5 minutes). */
|
|
90
|
+
minIntervalMs: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Wire the review triggers onto the host context. Global single-flight with
|
|
94
|
+
* a minimum interval: turn boundaries arm the pending workspace, compaction
|
|
95
|
+
* and session close drain immediately. Each drain reviews one batch bounded
|
|
96
|
+
* by the remaining daily budget — never more.
|
|
97
|
+
*/
|
|
98
|
+
export declare function createReviewScheduler(host: ReviewSchedulerHost, deps: ReviewDeps, config: ReviewSchedulerConfig): () => void;
|
|
99
|
+
//# sourceMappingURL=review.d.ts.map
|