@aiwayds/dsh-topics-memory 0.21.0 → 0.23.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 +5 -0
- package/README.zh.md +5 -0
- package/lib/commands.js +27 -11
- package/lib/config.d.ts +54 -1
- package/lib/config.js +38 -1
- package/lib/digest.d.ts +73 -0
- package/lib/digest.js +384 -0
- package/lib/distill.d.ts +39 -0
- package/lib/distill.js +250 -15
- package/lib/embedding.d.ts +113 -0
- package/lib/embedding.js +208 -0
- package/lib/ilog.d.ts +145 -0
- package/lib/ilog.js +236 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +23 -6
- package/lib/pipeline-events.d.ts +36 -0
- package/lib/pipeline-events.js +121 -0
- package/lib/quality.d.ts +47 -7
- package/lib/quality.js +124 -20
- package/lib/retrieval.d.ts +119 -0
- package/lib/retrieval.js +290 -0
- package/lib/service.d.ts +60 -3
- package/lib/service.js +320 -44
- package/lib/store.d.ts +15 -12
- package/lib/store.js +43 -3
- package/lib/tools.js +6 -2
- package/package.json +2 -2
- package/src/commands.ts +35 -16
- package/src/config.ts +47 -1
- package/src/digest.ts +416 -0
- package/src/distill.ts +275 -14
- package/src/embedding.ts +294 -0
- package/src/ilog.ts +306 -0
- package/src/index.ts +23 -5
- package/src/pipeline-events.ts +132 -0
- package/src/quality.ts +145 -21
- package/src/retrieval.ts +357 -0
- package/src/service.ts +326 -52
- package/src/store.ts +46 -10
- package/src/tools.ts +6 -2
package/README.md
CHANGED
|
@@ -201,6 +201,7 @@ First-time setup belongs to `/topics onboard`; day-to-day tuning is `/topics set
|
|
|
201
201
|
| `consolidateCadence` | `daily` | Consolidation-lane cadence: `daily`/`3d`/`7d`/`off`. At session start the plugin checks the last consolidation time (`meta/consolidate-state.json`) and, when due, runs the LLM gardener in the background (reusing the distill model route): local lexical clustering only feeds near-look-alike candidate clusters; four op kinds merge/promote/deprecate/refresh, out-of-scope ops are dropped; a failed call never advances the stamp, so the next start retries |
|
|
202
202
|
| `deprecatedTtlDays` | `15` | Deprecated topics older than N days are dropped at session start (local rule, no model; each drop is its own git commit — history stays recoverable); `0` disables the sweep |
|
|
203
203
|
| `usageBoost` | `0.15` | Usage boost (ADR 0015): topics injected/opened in the last 30 days score higher at retrieval (gate-scoped, capped at 0.2, never granted to zero-lexical candidates, structural gate not waived); `0` disables |
|
|
204
|
+
| `zeroOpenDecay` | `false` | Zero-open decay: halve the retrieval score of zero-open high-frequency slugs before the threshold cut (see the section below) |
|
|
204
205
|
| `pushDebounceSeconds` | `45` | GitHub-mode debounced push interval |
|
|
205
206
|
| `jevEnabled` | `false` | System One decision layer master switch (experimental): `false` = zero behavior change — see the Jev decision layer section above |
|
|
206
207
|
| `jevBackend` | `zen` | Decision endpoint: `zen` (free) / `native` / `openrouter` |
|
|
@@ -210,6 +211,10 @@ First-time setup belongs to `/topics onboard`; day-to-day tuning is `/topics set
|
|
|
210
211
|
| `jevLayaFallback` | `false` | Local laya pace-maker: fire a parallel laya call on every jev request; its answer takes over (degraded, relative-ranking only) when the primary fails |
|
|
211
212
|
| `jevLayaUrl` | `http://127.0.0.1:8000/v1/systemone` | laya-serve endpoint for the pace-maker |
|
|
212
213
|
|
|
214
|
+
### Zero-open decay (default off)
|
|
215
|
+
|
|
216
|
+
`zeroOpenDecay: true` halves the hit score of slugs the stats panel lists as 零打开高频 — truly rendered ≥5 injections in the rolling 30 days with zero `topic_open` events ever. The decay applies at score finalization, before the match threshold, so a decayed candidate that drops below it lands in nearMisses carrying the `zero-open-decay` reason — the injection log shows why it stopped ranking. Motivation: 24 high-frequency topics held the injection leaderboard with zero opens while score and opens proved only weakly correlated (the 0.3 threshold stays untouched). Default `false` means zero behavior change; unreadable injection/open logs skip the decay (fail-open). Toggle live with `/topics set zero-open-decay on|off`.
|
|
217
|
+
|
|
213
218
|
## Acknowledgements
|
|
214
219
|
|
|
215
220
|
This project's shape is directly inspired and supported by:
|
package/README.zh.md
CHANGED
|
@@ -187,6 +187,7 @@ dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory
|
|
|
187
187
|
| `consolidateCadence` | `daily` | 整理 lane 节拍:`daily`/`3d`/`7d`/`off`。会话启动时检查上次整理时间(`meta/consolidate-state.json`),到期即在后台自动跑 LLM 园丁(复用蒸馏模型路由):本地词面聚类只送「长得像」的候选簇,四类动作 merge/promote/deprecate/refresh,越权 op 一律丢弃;模型调用失败不推进节拍,下次启动重试 |
|
|
188
188
|
| `deprecatedTtlDays` | `15` | deprecated 条目超过 N 天在会话启动时自动删除(本地规则不依赖模型,逐条 git commit 可回溯找回);`0` 关闭清扫 |
|
|
189
189
|
| `usageBoost` | `0.15` | 使用加成(ADR 0015):近 30 天被注入命中/点开过的 Topic 检索加分(计入门槛分、帽 0.2、零词面相关不加、结构门不豁免);`0` 关闭 |
|
|
190
|
+
| `zeroOpenDecay` | `false` | 零打开衰减:零打开高频 slug 的命中分在阈值裁剪前减半(见下方小节) |
|
|
190
191
|
| `pushDebounceSeconds` | `45` | GitHub 模式去抖推送间隔 |
|
|
191
192
|
| `jevEnabled` | `false` | System One 决策层总开关(实验):`false` = 零行为变化——见上方「Jev 决策层」一节 |
|
|
192
193
|
| `jevBackend` | `zen` | 决策端点:`zen`(免费)/ `native` / `openrouter` |
|
|
@@ -197,6 +198,10 @@ dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory
|
|
|
197
198
|
| `jevLayaUrl` | `http://127.0.0.1:8000/v1/systemone` | 陪跑用的 laya-serve 端点 |
|
|
198
199
|
| `jevDebug` | 关 | 诊断日志开关(走宿主 logger,默认静默) |
|
|
199
200
|
|
|
201
|
+
### 零打开衰减(默认关)
|
|
202
|
+
|
|
203
|
+
`zeroOpenDecay: true` 时,命中分在 matchThreshold 裁剪前乘 0.5:滚动 30 天内被真正注入 ≥5 次、且 opens.jsonl 全期 0 次打开的 slug(即 `/topics stats` 的「零打开高频」清单——stats 面板与打分衰减共用同一条共享规则)不再霸榜;跌破阈值的候选自然落入 nearMisses 并带 `zero-open-decay` 标签,注入日志可见原因。动机:24 个「注入 ≥5 次、0 打开」的高频话题长期霸榜,而 score 与打开仅弱相关(阈值 0.3 不动)。默认 `false` 零行为变化;injections/opens 读取失败跳过衰减(fail-open);`/topics set zero-open-decay on|off` 运行时切换。
|
|
204
|
+
|
|
200
205
|
### laya 本地陪跑(实验,默认关)
|
|
201
206
|
|
|
202
207
|
`jevLayaFallback: true` 且本地跑着 [laya](https://github.com/NandhaKishorM/laya) serve 时,每次 jev 调用并发一路本地 laya 请求:主路答案驱动决策,laya 答案并排落账——一组永久运行的 laya vs jev 同题对照。laya 是**纯遥测**(真实负载实测批内排序与主路一致率 0/14、绝对分不可用),主路失败照旧回退旧路径;laya 没起 = 毫秒级连接拒绝。
|
package/lib/commands.js
CHANGED
|
@@ -12,7 +12,7 @@ import { serializeTopicDoc, slugify, firstParagraph } from "./okf.js";
|
|
|
12
12
|
import { buildGraph, renderGraphHtml } from "./viz.js";
|
|
13
13
|
import { CONFIG_KEYS, displayKey, parseConfigValue } from "./config.js";
|
|
14
14
|
import { reloadSecrets } from "./jev/gate.js";
|
|
15
|
-
import { aggregateStats } from "./ilog.js";
|
|
15
|
+
import { aggregateStats, aggregateOpenPanel, ZERO_OPEN_MIN_INJECTIONS } from "./ilog.js";
|
|
16
16
|
import { askDistillSingle, createOnboardHandler, llmDirectoryNotice, makePanel, parseDistillInput, pickLlmDirectory, } from "./onboard.js";
|
|
17
17
|
export const HELP = [
|
|
18
18
|
'dsh-topics-memory — OKF topic 记忆(本地 bundle,git 可追溯,可选 GitHub 同步)',
|
|
@@ -20,7 +20,7 @@ export const HELP = [
|
|
|
20
20
|
' /topics status bundle 健康:topic 数、观察积压、冲突、同步状态',
|
|
21
21
|
' /topics distill 手动触发一次蒸馏(观察池 → Topic,输出 marked/created/updated/gc 摘要)',
|
|
22
22
|
' /topics consolidate 手动触发一次整理(合并重复/晋升 stable/废弃过时/刷新元数据,无视 cadence 立即跑)',
|
|
23
|
-
' /topics stats 注入统计:hit rate、
|
|
23
|
+
' /topics stats 注入统计:hit rate、open rate、注入 vs 打开、near-miss 分布与调参建议',
|
|
24
24
|
' /topics list 列出全部 Topic',
|
|
25
25
|
' /topics show <slug> 查看一个 Topic 全文(含反向引用)',
|
|
26
26
|
' /topics history <slug> 一个 Topic 的结论变更史(git log)',
|
|
@@ -277,16 +277,14 @@ async function renderStats(service) {
|
|
|
277
277
|
}
|
|
278
278
|
lines.push(`| 慢道参与轮 | ${slowRounds}(快 ${records.length - slowRounds} / 慢或混合 ${slowRounds}${medianLag}) |`);
|
|
279
279
|
}
|
|
280
|
-
//
|
|
281
|
-
//
|
|
280
|
+
// Injection-vs-open metrics (v5): the denominator counts only pointers
|
|
281
|
+
// that truly rendered (deduped/echoed/dropped all excluded, slow pointers
|
|
282
|
+
// included) and the numerator only counts DISTINCT slugs from that
|
|
283
|
+
// rendered set opened inside the injection window — search opens and
|
|
284
|
+
// never-rendered slugs cannot inflate the rate.
|
|
282
285
|
const opens = await service.store.readOpenRecords();
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
r.hits.filter((h) => !(r.deduped ?? []).includes(h.slug) && !(r.echoed ?? []).includes(h.slug)).length +
|
|
286
|
-
(r.slow?.length ?? 0), 0);
|
|
287
|
-
const rate = entries === 0 ? 0 : Math.min(1, opens.length / entries);
|
|
288
|
-
lines.push(`| 指针打开率 | ${(rate * 100).toFixed(1)}%(${opens.length} 次 topic_open / ${entries} 条注入指针) |`);
|
|
289
|
-
}
|
|
286
|
+
const panel = aggregateOpenPanel(records, opens);
|
|
287
|
+
lines.push(`| open rate | ${(panel.openRate * 100).toFixed(1)}%(${panel.opensInWindow} 个被打开指针 / ${panel.pointerEntries} 条注入指针) |`);
|
|
290
288
|
const echoedCount = records.reduce((acc, r) => acc + (r.echoed?.length ?? 0), 0);
|
|
291
289
|
if (echoedCount > 0) {
|
|
292
290
|
lines.push(`| 回声抑制 | ${echoedCount} 次(本会话蒸馏出的 topic 不回注) |`);
|
|
@@ -296,6 +294,24 @@ async function renderStats(service) {
|
|
|
296
294
|
for (const t of stats.topTopics.slice(0, 5))
|
|
297
295
|
lines.push(`| \`${cell(t.slug)}\` | ${t.count} |`);
|
|
298
296
|
}
|
|
297
|
+
// ---- 注入 vs 打开 panel ----
|
|
298
|
+
// Per-topic cut: what the header's windowed rate cannot show — which
|
|
299
|
+
// topics the model actually bothers to open (wide-scope slug attribution).
|
|
300
|
+
if (panel.topics.length > 0) {
|
|
301
|
+
lines.push('', '注入 vs 打开(Top-10,注入=真正渲染的指针;打开按 slug 归因,全窗口):', '', '| Topic | 注入 | 打开 | 打开率 |', '| --- | --- | --- | --- |', ...panel.topics.map((t) => `| \`${cell(t.slug)}\` | ${t.injections} | ${t.opens} | ${(t.openRate * 100).toFixed(1)}% |`));
|
|
302
|
+
}
|
|
303
|
+
if (panel.zeroOpenTotal > 0) {
|
|
304
|
+
const shown = panel.zeroOpen.map((z) => `\`${cell(z.slug)}\``).join('、');
|
|
305
|
+
const more = panel.zeroOpenTotal > panel.zeroOpen.length ? `(共 ${panel.zeroOpenTotal} 个)` : '';
|
|
306
|
+
lines.push('', `零打开高频(注入 ≥${ZERO_OPEN_MIN_INJECTIONS}、打开 0):${shown}${more}`);
|
|
307
|
+
}
|
|
308
|
+
lines.push('', panel.hasSourceData ? `打开来源:pointer ${panel.pointerOpens} / search ${panel.searchOpens}` : '打开来源:暂无来源数据(需新版本 open 记录)');
|
|
309
|
+
if (panel.pointerEntries > 0) {
|
|
310
|
+
lines.push('', '| 分桶 | 指针 | 打开率 |', '| --- | --- | --- |');
|
|
311
|
+
for (const b of panel.bands) {
|
|
312
|
+
lines.push(`| ${b.label} | ${b.pointers} | ${b.pointers === 0 ? '—' : `${(b.openRate * 100).toFixed(1)}%`} |`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
299
315
|
if (stats.nearMissHistogram.length > 0) {
|
|
300
316
|
lines.push('', `Near-miss 分布(低于阈值 ${service.cfg.matchThreshold.toFixed(2)} 或被结构门挡下,对数刻度):`, '', '```', ...renderNearMissSparkline(stats.nearMissHistogram), '```');
|
|
301
317
|
const hint = tuningHint(stats, service.cfg.matchThreshold);
|
package/lib/config.d.ts
CHANGED
|
@@ -70,6 +70,12 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
70
70
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
71
71
|
* 0 = off. */
|
|
72
72
|
usageBoost: z<number, number, "volatile-defined">;
|
|
73
|
+
/** Zero-open decay (v5, default off): halve the retrieval score of slugs
|
|
74
|
+
* injected ≥5 times in the rolling 30 days with zero opens ever — the
|
|
75
|
+
* 零打开高频 leaderboard stops out-ranking topics the model actually
|
|
76
|
+
* reads. false = zero behavior change; the reason tag `zero-open-decay`
|
|
77
|
+
* rides the hit/near-miss reasons when on. */
|
|
78
|
+
zeroOpenDecay: z<boolean, boolean, "volatile-defined">;
|
|
73
79
|
/** Debounced push delay in GitHub mode. */
|
|
74
80
|
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
75
81
|
/** System One decision-model integration (design 2026-09-25), default-off
|
|
@@ -97,6 +103,21 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
97
103
|
/** laya-serve systemone endpoint for the pace-maker (default: the local
|
|
98
104
|
* laya-serve bind from the laya repo's serve mode). */
|
|
99
105
|
jevLayaUrl: z<string, string, "volatile-defined">;
|
|
106
|
+
/** Local embedding provider (A4 semantic reranker, spec §3.1): ''
|
|
107
|
+
* sentinel = the whole semantic layer is OFF (pure lexical); 'ollama' =
|
|
108
|
+
* the local ollama embed API (localhost is the ADR 0017 local exception).
|
|
109
|
+
* v1 ships no remote provider — a remote embedding may only ever enter a
|
|
110
|
+
* slow-lane/offline path, never the fast lane (red line ①). */
|
|
111
|
+
embeddingProvider: z<string, string, "volatile-defined">;
|
|
112
|
+
/** Embedding model; '' = provider default (ollama → nomic-embed-text),
|
|
113
|
+
* same sentinel pattern as distillModel. */
|
|
114
|
+
embeddingModel: z<string, string, "volatile-defined">;
|
|
115
|
+
/** Injection-package master gate (A-line, spec §2/§3): off | shadow | on.
|
|
116
|
+
* Carries the WHOLE new-behavior surface — query sanitization, the
|
|
117
|
+
* three-layer package, nearFloor semantic rerank, degradation. off = zero
|
|
118
|
+
* behavior change anywhere (byte-for-byte legacy); shadow = compute +
|
|
119
|
+
* bypass-ledger only, nothing lands in context; on = engage. */
|
|
120
|
+
packageMode: z<string, string, "volatile-defined">;
|
|
100
121
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
101
122
|
/** GitHub repo `owner/name`; empty = local-only mode (ADR 0008). */
|
|
102
123
|
repo: z<string, string, "volatile-defined">;
|
|
@@ -162,6 +183,12 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
162
183
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
163
184
|
* 0 = off. */
|
|
164
185
|
usageBoost: z<number, number, "volatile-defined">;
|
|
186
|
+
/** Zero-open decay (v5, default off): halve the retrieval score of slugs
|
|
187
|
+
* injected ≥5 times in the rolling 30 days with zero opens ever — the
|
|
188
|
+
* 零打开高频 leaderboard stops out-ranking topics the model actually
|
|
189
|
+
* reads. false = zero behavior change; the reason tag `zero-open-decay`
|
|
190
|
+
* rides the hit/near-miss reasons when on. */
|
|
191
|
+
zeroOpenDecay: z<boolean, boolean, "volatile-defined">;
|
|
165
192
|
/** Debounced push delay in GitHub mode. */
|
|
166
193
|
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
167
194
|
/** System One decision-model integration (design 2026-09-25), default-off
|
|
@@ -189,6 +216,21 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
189
216
|
/** laya-serve systemone endpoint for the pace-maker (default: the local
|
|
190
217
|
* laya-serve bind from the laya repo's serve mode). */
|
|
191
218
|
jevLayaUrl: z<string, string, "volatile-defined">;
|
|
219
|
+
/** Local embedding provider (A4 semantic reranker, spec §3.1): ''
|
|
220
|
+
* sentinel = the whole semantic layer is OFF (pure lexical); 'ollama' =
|
|
221
|
+
* the local ollama embed API (localhost is the ADR 0017 local exception).
|
|
222
|
+
* v1 ships no remote provider — a remote embedding may only ever enter a
|
|
223
|
+
* slow-lane/offline path, never the fast lane (red line ①). */
|
|
224
|
+
embeddingProvider: z<string, string, "volatile-defined">;
|
|
225
|
+
/** Embedding model; '' = provider default (ollama → nomic-embed-text),
|
|
226
|
+
* same sentinel pattern as distillModel. */
|
|
227
|
+
embeddingModel: z<string, string, "volatile-defined">;
|
|
228
|
+
/** Injection-package master gate (A-line, spec §2/§3): off | shadow | on.
|
|
229
|
+
* Carries the WHOLE new-behavior surface — query sanitization, the
|
|
230
|
+
* three-layer package, nearFloor semantic rerank, degradation. off = zero
|
|
231
|
+
* behavior change anywhere (byte-for-byte legacy); shadow = compute +
|
|
232
|
+
* bypass-ledger only, nothing lands in context; on = engage. */
|
|
233
|
+
packageMode: z<string, string, "volatile-defined">;
|
|
192
234
|
}>>, "plain">;
|
|
193
235
|
export type TopicsConfigValue = {
|
|
194
236
|
repo: string;
|
|
@@ -216,6 +258,10 @@ export type TopicsConfigValue = {
|
|
|
216
258
|
consolidateCadence: string;
|
|
217
259
|
deprecatedTtlDays: number;
|
|
218
260
|
usageBoost: number;
|
|
261
|
+
/** Zero-open decay (v5). Optional in this TS view only (same sentinel
|
|
262
|
+
* pattern as the jev keys): bare-harness DEFAULTS predates it —
|
|
263
|
+
* consumers must treat undefined as the documented default (false). */
|
|
264
|
+
zeroOpenDecay?: boolean;
|
|
219
265
|
pushDebounceSeconds: number;
|
|
220
266
|
/** System One integration keys (design 2026-09-25 §4). Optional in this TS
|
|
221
267
|
* view only: index.ts's bare-harness DEFAULTS literal predates them and
|
|
@@ -228,8 +274,15 @@ export type TopicsConfigValue = {
|
|
|
228
274
|
jevSecretFile?: string;
|
|
229
275
|
jevLayaFallback?: boolean;
|
|
230
276
|
jevLayaUrl?: string;
|
|
277
|
+
/** Injection-quality line keys (spec §7). Optional in this TS view only
|
|
278
|
+
* (same sentinel pattern as the jev keys): index.ts's bare-harness
|
|
279
|
+
* DEFAULTS literal predates them — consumers must treat undefined as the
|
|
280
|
+
* documented default ('' / '' / 'off' = every new behavior zeroed). */
|
|
281
|
+
embeddingProvider?: string;
|
|
282
|
+
embeddingModel?: string;
|
|
283
|
+
packageMode?: 'off' | 'shadow' | 'on';
|
|
231
284
|
};
|
|
232
|
-
export declare const CONFIG_KEYS: readonly ["repo", "autoInject", "injectDedup", "suppressEcho", "topK", "perTopicBudget", "totalBudget", "matchThreshold", "tagBoost", "injectMode", "qualityLane", "graphDepth", "recencyWindowDays", "autoObserve", "includeSubagents", "observationMaxChars", "distillEveryTurns", "distillOnSessionEnd", "distillProvider", "distillModel", "distillBatchSize", "distillMaxModelCalls", "consolidateCadence", "deprecatedTtlDays", "usageBoost", "pushDebounceSeconds", "jevEnabled", "jevBackend", "jevModel", "jevTimeoutMs", "jevSecretFile", "jevLayaFallback", "jevLayaUrl"];
|
|
285
|
+
export declare const CONFIG_KEYS: readonly ["repo", "autoInject", "injectDedup", "suppressEcho", "topK", "perTopicBudget", "totalBudget", "matchThreshold", "tagBoost", "injectMode", "qualityLane", "graphDepth", "recencyWindowDays", "autoObserve", "includeSubagents", "observationMaxChars", "distillEveryTurns", "distillOnSessionEnd", "distillProvider", "distillModel", "distillBatchSize", "distillMaxModelCalls", "consolidateCadence", "deprecatedTtlDays", "usageBoost", "zeroOpenDecay", "pushDebounceSeconds", "jevEnabled", "jevBackend", "jevModel", "jevTimeoutMs", "jevSecretFile", "jevLayaFallback", "jevLayaUrl", "embeddingProvider", "embeddingModel", "packageMode"];
|
|
233
286
|
export type ConfigKey = (typeof CONFIG_KEYS)[number];
|
|
234
287
|
/** CamelCase → dash-display (topK → top-k) for command output. */
|
|
235
288
|
export declare function displayKey(key: string): string;
|
package/lib/config.js
CHANGED
|
@@ -70,6 +70,12 @@ export const TopicsConfig = z.object({
|
|
|
70
70
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
71
71
|
* 0 = off. */
|
|
72
72
|
usageBoost: z.number().default(0.15).volatile(),
|
|
73
|
+
/** Zero-open decay (v5, default off): halve the retrieval score of slugs
|
|
74
|
+
* injected ≥5 times in the rolling 30 days with zero opens ever — the
|
|
75
|
+
* 零打开高频 leaderboard stops out-ranking topics the model actually
|
|
76
|
+
* reads. false = zero behavior change; the reason tag `zero-open-decay`
|
|
77
|
+
* rides the hit/near-miss reasons when on. */
|
|
78
|
+
zeroOpenDecay: z.boolean().default(false).volatile(),
|
|
73
79
|
/** Debounced push delay in GitHub mode. */
|
|
74
80
|
pushDebounceSeconds: z.number().default(45).volatile(),
|
|
75
81
|
/** System One decision-model integration (design 2026-09-25), default-off
|
|
@@ -97,6 +103,21 @@ export const TopicsConfig = z.object({
|
|
|
97
103
|
/** laya-serve systemone endpoint for the pace-maker (default: the local
|
|
98
104
|
* laya-serve bind from the laya repo's serve mode). */
|
|
99
105
|
jevLayaUrl: z.string().default('http://127.0.0.1:8000/v1/systemone').volatile(),
|
|
106
|
+
/** Local embedding provider (A4 semantic reranker, spec §3.1): ''
|
|
107
|
+
* sentinel = the whole semantic layer is OFF (pure lexical); 'ollama' =
|
|
108
|
+
* the local ollama embed API (localhost is the ADR 0017 local exception).
|
|
109
|
+
* v1 ships no remote provider — a remote embedding may only ever enter a
|
|
110
|
+
* slow-lane/offline path, never the fast lane (red line ①). */
|
|
111
|
+
embeddingProvider: z.string().default('').volatile(),
|
|
112
|
+
/** Embedding model; '' = provider default (ollama → nomic-embed-text),
|
|
113
|
+
* same sentinel pattern as distillModel. */
|
|
114
|
+
embeddingModel: z.string().default('').volatile(),
|
|
115
|
+
/** Injection-package master gate (A-line, spec §2/§3): off | shadow | on.
|
|
116
|
+
* Carries the WHOLE new-behavior surface — query sanitization, the
|
|
117
|
+
* three-layer package, nearFloor semantic rerank, degradation. off = zero
|
|
118
|
+
* behavior change anywhere (byte-for-byte legacy); shadow = compute +
|
|
119
|
+
* bypass-ledger only, nothing lands in context; on = engage. */
|
|
120
|
+
packageMode: z.string().default('off').volatile(),
|
|
100
121
|
});
|
|
101
122
|
export const CONFIG_KEYS = [
|
|
102
123
|
'repo',
|
|
@@ -124,6 +145,7 @@ export const CONFIG_KEYS = [
|
|
|
124
145
|
'consolidateCadence',
|
|
125
146
|
'deprecatedTtlDays',
|
|
126
147
|
'usageBoost',
|
|
148
|
+
'zeroOpenDecay',
|
|
127
149
|
'pushDebounceSeconds',
|
|
128
150
|
'jevEnabled',
|
|
129
151
|
'jevBackend',
|
|
@@ -132,6 +154,9 @@ export const CONFIG_KEYS = [
|
|
|
132
154
|
'jevSecretFile',
|
|
133
155
|
'jevLayaFallback',
|
|
134
156
|
'jevLayaUrl',
|
|
157
|
+
'embeddingProvider',
|
|
158
|
+
'embeddingModel',
|
|
159
|
+
'packageMode',
|
|
135
160
|
];
|
|
136
161
|
/** CamelCase → dash-display (topK → top-k) for command output. */
|
|
137
162
|
export function displayKey(key) {
|
|
@@ -152,7 +177,8 @@ export function parseConfigValue(key, raw) {
|
|
|
152
177
|
case 'autoObserve':
|
|
153
178
|
case 'distillOnSessionEnd':
|
|
154
179
|
case 'includeSubagents':
|
|
155
|
-
case 'jevEnabled':
|
|
180
|
+
case 'jevEnabled':
|
|
181
|
+
case 'zeroOpenDecay': {
|
|
156
182
|
if (raw === 'on' || raw === 'true')
|
|
157
183
|
return true;
|
|
158
184
|
if (raw === 'off' || raw === 'false')
|
|
@@ -206,10 +232,21 @@ export function parseConfigValue(key, raw) {
|
|
|
206
232
|
}
|
|
207
233
|
case 'distillProvider':
|
|
208
234
|
case 'distillModel':
|
|
235
|
+
case 'embeddingModel':
|
|
209
236
|
case 'jevModel':
|
|
210
237
|
case 'jevSecretFile':
|
|
211
238
|
case 'jevLayaUrl':
|
|
212
239
|
return raw;
|
|
240
|
+
case 'embeddingProvider': {
|
|
241
|
+
if (raw === '' || raw === 'ollama')
|
|
242
|
+
return raw;
|
|
243
|
+
return { error: 'embedding-provider 仅支持 ollama(留空=语义层关;远程 provider 不进快道)' };
|
|
244
|
+
}
|
|
245
|
+
case 'packageMode': {
|
|
246
|
+
if (raw === 'off' || raw === 'shadow' || raw === 'on')
|
|
247
|
+
return raw;
|
|
248
|
+
return { error: 'package-mode 取值 off|shadow|on' };
|
|
249
|
+
}
|
|
213
250
|
case 'jevLayaFallback': {
|
|
214
251
|
if (raw === 'on' || raw === 'true')
|
|
215
252
|
return true;
|
package/lib/digest.d.ts
CHANGED
|
@@ -77,3 +77,76 @@ export declare function assemblePointer(entries: readonly DigestInput[], cfg: As
|
|
|
77
77
|
* total budget. Zero hits → empty text (ADR 0006: 零命中零注入).
|
|
78
78
|
*/
|
|
79
79
|
export declare function assembleInjection(entries: readonly DigestInput[], cfg: AssembleConfig, slow?: readonly SlowPointerInput[]): AssembleResult;
|
|
80
|
+
/** One retrieval-side candidate, roster metadata pre-extracted by the caller. */
|
|
81
|
+
export interface PackageCandidate {
|
|
82
|
+
slug: string;
|
|
83
|
+
title: string;
|
|
84
|
+
status: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
/** Conclusion section first line (fallback for a missing description). */
|
|
87
|
+
conclusion?: string;
|
|
88
|
+
score: number;
|
|
89
|
+
/** Retrieval reason tags (`triggers:0.83` / `title:0.50` / `tags:…` / …). */
|
|
90
|
+
reasons: string[];
|
|
91
|
+
/** Edge/lane provenance: `depends:dN` | `slow-pick` | `semantic…` | raw. */
|
|
92
|
+
via?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface PackageInput {
|
|
95
|
+
query: string;
|
|
96
|
+
/** Query lexemes the caller retrieved with (tokenize + §3.3 cleaning). */
|
|
97
|
+
queryTokens: string[];
|
|
98
|
+
/** Lexical strong hits (gate-passed); lands in the direct layer. */
|
|
99
|
+
direct: PackageCandidate[];
|
|
100
|
+
/** Graph expansion + slow-lane picks; lands in the neighborhood layer. */
|
|
101
|
+
neighborhood: PackageCandidate[];
|
|
102
|
+
/** nearFloor band (0.20–0.30) head; lands in the merged weak-signal line. */
|
|
103
|
+
weak: PackageCandidate[];
|
|
104
|
+
/** ADR 0015 usage signal — within-layer ordering tiebreak only (v1). */
|
|
105
|
+
usageBySlug?: Map<string, number>;
|
|
106
|
+
}
|
|
107
|
+
export interface PackageOpts {
|
|
108
|
+
/**
|
|
109
|
+
* `'on'` renders the live package; `'shadow'` means the caller double-runs
|
|
110
|
+
* (old assemblePointer + this) for the bypass ledger (spec §8.3). The pure
|
|
111
|
+
* output is identical in both modes — the mode is call-site bookkeeping.
|
|
112
|
+
*/
|
|
113
|
+
mode?: 'on' | 'shadow';
|
|
114
|
+
}
|
|
115
|
+
export interface PackageResult {
|
|
116
|
+
text: string;
|
|
117
|
+
usedTokens: number;
|
|
118
|
+
included: string[];
|
|
119
|
+
dropped: {
|
|
120
|
+
slug: string;
|
|
121
|
+
reason: string;
|
|
122
|
+
}[];
|
|
123
|
+
/** Final per-layer rosters (what the 包头 counts report). */
|
|
124
|
+
layers: {
|
|
125
|
+
direct: string[];
|
|
126
|
+
neighborhood: string[];
|
|
127
|
+
weak: string[];
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Total package cap — pinned, does not float with hit strength (A3 决议 3). */
|
|
131
|
+
export declare const PACKAGE_TOTAL = 600;
|
|
132
|
+
export declare const PACKAGE_DIRECT_MAX = 2;
|
|
133
|
+
export declare const PACKAGE_DIRECT_BUDGET = 120;
|
|
134
|
+
export declare const PACKAGE_NEIGHBOR_MAX = 3;
|
|
135
|
+
export declare const PACKAGE_NEIGHBOR_BUDGET = 40;
|
|
136
|
+
export declare const PACKAGE_WEAK_MAX = 2;
|
|
137
|
+
/**
|
|
138
|
+
* Padded-trivial arm of the downgrade guard (spec §2.3 名词实体口径, an
|
|
139
|
+
* implementation-time item per §11): after stripping whitespace, a query this
|
|
140
|
+
* short counts as 琐碎 even when the tokenizer expands CJK bigrams past 3
|
|
141
|
+
* tokens. Calibrated on the two named scenarios: 场景① ack query strips to
|
|
142
|
+
* 15 chars (downgrade), a real review query strips to 18 (kept).
|
|
143
|
+
*/
|
|
144
|
+
export declare const PACKAGE_TRIVIAL_QUERY_CHARS = 16;
|
|
145
|
+
/**
|
|
146
|
+
* Assemble the three-layer package. Returns `null` — the whole-package skip
|
|
147
|
+
* signal — when the roster is empty (ADR 0006 零命中零注入) or when the
|
|
148
|
+
* downgrade guard fires (spec §2.3: 琐碎 query 且与全部候选 slug/title 零
|
|
149
|
+
* 词面实体交集). Packing is layered greedy: direct first, then neighborhood,
|
|
150
|
+
* then the weak line — over budget, lower layers are cut first.
|
|
151
|
+
*/
|
|
152
|
+
export declare function assemblePackage(input: PackageInput, opts?: PackageOpts): PackageResult | null;
|