Repository navigation
Releases: HorusJiang/dsh-jev-tools
Releases · HorusJiang/dsh-jev-tools
Release list
v0.1.13
Added
- 插件图标:清单判定 → 决策路径。
package.json增加icon: "icon.svg"——一行红叉、一行绿勾,
绿勾之后直接延伸成青→紫的箭头:被否掉的停在原地,通过的那条成为路径。DSH 插件页的包卡片、
详情页与插件行会显示它:Host 的readPluginMeta()(@deepseek-ai/dsh-app-boot)读
package.json.icon,按扩展名映射 MIME 后编码成image/svg+xml;base64的 data URL 交给
<img src>,只有未声明或解码失败时才回落到默认风车插画。files里同时加上icon.svg:
files是 allowlist,不带上就是"字段在、文件不在",装了插件照样没有图标。颜色是固定值,
因为<img>里拿不到currentColor,一份文件同时服务浅色与深色两套主题。
English
Added
- The plugin icon: a checklist that becomes a decision path.
package.jsongains
icon: "icon.svg"— one crossed-out row, one checked row, and the checked row running on as a
cyan→violet arrow: the rejected one stops where it is, the accepted one becomes the path. The
Plugins page shows it on package cards, detail pages and plugin rows: the Host's
readPluginMeta()(@deepseek-ai/dsh-app-boot) readspackage.json.icon, maps the extension to
a MIME type and hands the page animage/svg+xml;base64data URL as<img src>, falling back to
the default pinwheel only when the field is absent or the image will not decode.icon.svgis
listed infilesas well:filesis an allowlist, so omitting it ships the field without the
file and the installed plugin shows no icon. The colours are fixed values, because an<img>
gets nocurrentColor, so one file serves both the light and the dark theme.
npm i dsh-jev-tools@0.1.13 · CHANGELOG.md · CHANGELOG.en.md
v0.1.12
Fixed
- 设置卡片读不到自己的设置,三个开关全是禁用且空白、两个端点框只读。 两个独立原因叠在一起:
(1) DSH 按挂载行的 id 发布一个 bundle 的设置命名空间(SettingsForms.describe()公布的是
entry.options.id,写入也按它解析),而卡片(占plugins.bundle.config座位)与插件页的配置座位都按
包名索引;(2) 设置页只发布标了.volatile()的字段——volatileForm()对一个 volatile 字段都没有的
schema 返回空,于是连命名空间都不会出现在describe()里。行 id 叫jev-tools、schema 又一处
.volatile()都没有时,卡片两边都取不到东西,而且任何地方都不报错——这就是 0.1.6→0.1.11
「设置页改不了」的全部原因(issue #3)。现在挂载行 id 就是包名,卡片要读要写的六个字段都标了
.volatile(),并由test/bundle-contract.test.ts把包名、行 id、卡片请求的命名空间,以及这六个字段的
可编辑性钉在一起。 - 删除了
settings.installSection调用。 DSH 0.2.0-rc.2 的settings服务没有这个方法,每次挂载都会抛
TypeError再被自己的 catch 吞成一条警告——一条把「注册失败」说成「暂时不可用」的假象。设置页本来就由条目
导出的 Config schema 自动生成,写入会让该条目的 fiber 重启、apply重新解析配置,所以「改完立即生效」
不依赖任何注册回调。 - 命名空间对不上时,卡片现在会说出来。 过去它画一个看起来像「什么都没配置」的空表单;现在它直接点名
找不到的命名空间,以及决定它的那份文件。
Changed
- 配置解析改走 standard-schema 路径并解包 volatile 标记。
.volatile()会把被标记字段的解析结果包成一个
标记对象(写回时用它判断哪些键要落盘),所以配置经Config['~standard'].validate(value)(Loader 自己走
的就是这条)解析后再递归解包,model、baseUrl这类字段拿到的仍是字符串而不是对象。
@deepseek-ai/schemastery升到^3.18.4(.volatile()自该版本起提供,也正是宿主自带的版本),
并为isVolatile新增依赖@deepseek-ai/cosmokit。 - 卡片改走浏览器唯一的设置通道。 现在读写都经
ctx.configForms.get(命名空间)(插件页上四个官方设置页
用的就是这一条):读取取共享设置镜像的快照,写入按{ op, path, value }操作列表带 revision 提交。此前
直接调remote.settings,而浏览器里settings.describe()只有一个读取方——设置 UI 自己;第三方卡片拿
不到它,于是宿主已经把命名空间发布出来,卡片仍然读不到。读不到时的提示现在会连宿主实际提供的命名
空间一起列出。 - 解析必须幂等。 宿主交给
apply的是它已经解析过的配置(.volatile()的标记对象都在里面),
所以要先解包输入再校验。少了这一步,enabled会被当成对象校验失败并抛错,而apply里任何抛错都会让
整条 fiber 挂掉:插件在界面上显示「异常」、设置页一个命名空间都不提供,看起来又像"卡片读不到自己的
设置"。test/config.test.ts现在钉住这一条(Loader 形状的输入必须与原始输入解析出同一份值)。
English
Fixed
- The configuration card could not read its own settings, so all three toggles were disabled and
blank and both endpoint fields were read-only. Two causes stacked: (1) DSH publishes a bundle's
settings namespace under its mount row id (SettingsForms.describe()reports
entry.options.id, and a write resolves the same way), while the card (the occupant of
plugins.bundle.config) and the plugin page's config seat are keyed by the package name; and
(2) the page publishes only fields marked.volatile()—volatileForm()returns nothing for a
schema with no volatile field, so the namespace never appears indescribe()at all. With the row
idjev-toolsand not one.volatile()in the schema, the card could read nothing from either
side, with no error printed anywhere. That is the whole of "the settings page cannot change
anything" from 0.1.6 to 0.1.11 (issue #3). The mount row id is now the package name and the six
fields the card reads and writes are marked.volatile();test/bundle-contract.test.tspins the
package name, the row id, the namespace the card asks for, and the editability of those six fields
together. - The
settings.installSectioncall is gone. DSH 0.2.0-rc.2'ssettingsservice has no such
method, so every mount threw aTypeErrorthat the plugin's own catch turned into a warning — a
warning that reported a failed registration as merely unavailable. The page is generated from the
entry's exported Config schema anyway, and a write restarts that entry's fiber soapply
re-resolves the config: "a toggle takes effect immediately" never depended on a registration
callback. - A namespace that is not published is now named. The card used to draw an empty form that looked
exactly like "nothing is configured"; it now names the namespace it was looking for, and the file
that decides it.
Changed
- Configuration is resolved through the standard-schema path, and volatile markers are unwrapped.
.volatile()wraps a marked field's resolved value in a marker object (the writer uses it to know
which keys to persist), so the config is resolved throughConfig['~standard'].validate(value)—
the very path the Loader takes — and then unwrapped recursively, leavingmodelandbaseUrlas
the strings they denote.@deepseek-ai/schemasterymoves to^3.18.4(the version that provides
.volatile(), and the one the host itself ships), and@deepseek-ai/cosmokitis added for
isVolatile. - The card now reads and writes through the browser's one settings seam. Both directions go
throughctx.configForms.get(namespace)— the same seam the four official settings pages use:
reads take the shared mirror's snapshot, writes submit an operation list ({ op, path, value })
with the revision read before editing. It used to callremote.settingsdirectly, and the
browser's singlesettings.describe()reader is the settings UI itself; a third-party card never
gets it — so the host could publish the namespace and the card would still read nothing. The
diagnostic now lists the namespaces the host actually serves. - Resolution has to be idempotent. The Loader hands
applythe config it already resolved —
volatile markers included — so the input is unwrapped before it is validated. Without that,
enabledwas validated as an object and threw, and any throw insideapplyfails the whole
fiber: the plugin shows as failed and serves no settings namespace at all, which looks exactly
like "the card cannot read its own settings".test/config.test.tsnow pins it: the Loader-shaped
input must resolve to the same values as the raw one.
npm i dsh-jev-tools@0.1.12 · CHANGELOG.md · CHANGELOG.en.md
v0.1.11
Fixed
- 判定端点终于能在界面上改。
baseUrl与model自 0.1.6 起就是宿主侧设置,后端每次判定都读它们的
实时值,但配置卡片只画了 key 输入框和三个开关——而卡片占用的plugins.bundle.config座位是按包名
单占用的,卡片没画的字段,界面上就够不到。于是这两个设置虽然声明了、接线了、README 也写着「设置页
可改」,实际只有手工编辑组合配置行才能改,还要重启宿主。依据:issue #3 的报告人描述的正是这件事——
想接 OpenRouter 的 Jev,却发现只能在启动时设置。现在卡片里有「判定端点」区:服务地址与模型,暂存后
显式保存;地址必须以http(s)://开头且非空,否则拦住保存并说明原因;只写真正变化的字段,避免
「打开看一眼」变成一次覆写。 - 两处会撒谎的文案跟着端点走。 隐私说明原本硬写
api.typesafe.ai,端点在 0.1.6 可配置之后这句话
就是错的;现在印的是实际生效的主机。申请 key 的链接同理:端点指向 OpenRouter 时给
openrouter.ai/settings/keys,其余情况仍去 TypeSafe 控制台。
English
Fixed
- The judgment endpoint is finally editable in the UI.
baseUrlandmodelhave been host-side
settings since 0.1.6, and the backend reads their live values on every judgment, but the
configuration card drew only the key input and three toggles — and theplugins.bundle.configseat
that card occupies is single-occupant per package name, so a field the card does not draw is a
field the page cannot reach. Both settings were therefore declared, wired and documented as
"editable on the settings page" while in fact being editable only by hand-editing the composition
row and restarting the host. Evidence: the reporter of issue #3 described exactly that — wanting to
point Jev at OpenRouter and finding it could only be set at startup. The card now carries a
"Judgment endpoint" section with the service address and the model, staged and written on an
explicit save; the address must start withhttp(s)://and be non-empty or the save is blocked
with a reason; and only genuinely changed fields are written, so looking at the card is not an
override. - Two lines of copy stopped lying about the destination. The privacy note hardcoded
api.typesafe.ai, which became wrong the moment the endpoint was made configurable; it now names
the host actually in force. The key link follows the same value: an OpenRouter endpoint gets
openrouter.ai/settings/keys, everything else keeps the TypeSafe console.
npm i dsh-jev-tools@0.1.11 · CHANGELOG.md · CHANGELOG.en.md
v0.1.10
Added
- 基线覆盖率进台账。 每次 prune 判定现在记录确定性基线是否真的测得:返回内容记为
baselineKeptTokens;返回null(内容在 DSH 自身预算内)按原始 token 数记录——这是一次
实测"基线本就不削",而不是缺数据;服务缺席或抛错记为baselineUnavailable: no-service | error。
累计值随之新增baselineMeasured与baselineUnmeasured。依据:2026-09-25 的两次 prune 判定在
旧逻辑下写法完全相同——headless 组合实测到baselineKeptTokens: 4441.5,web 组合记的是
no-service(Web 面把确定性剪枝器挂在 agent preset 之后),当时台账看不出这两者的区别。
Changed
- 没有基线就不再报净增量。
/jev-status把覆盖率与净增量一起印出;baselineMeasured为 0 时
只报削減量,并明说净增量目前无数据。旧逻辑用baselineKept = entry.baselineKeptTokens ?? original
把"从未测到"读成"基线不削",于是净增量等于本插件的全部削减——那是假设,不是结果。 - README 两处措辞改准:剪枝保留的是散点的相关片段,不是"从中间砍掉一段";净增量则连同它的
覆盖前提一起表述。
English
Added
- Baseline coverage in the ledger. Every prune judgment now records whether the deterministic
baseline was really measured: content answers are stored asbaselineKeptTokens; anullanswer
(the payload is inside DSH's own budget) is stored as the full original token count — a
measurement that the baseline removes nothing, not missing data; an absent service or a thrown
call is stored asbaselineUnavailable: no-service | error. The totals gainbaselineMeasured
andbaselineUnmeasured. Evidence: on 2026-09-25 two prune judgments were indistinguishable in
the old ledger — a headless composition measuredbaselineKeptTokens: 4441.5, while a web
composition recordedno-service(the Web surface mounts the deterministic pruner behind an
agent preset).
Changed
- A net gain is no longer reported without a baseline.
/jev-statusprints the coverage next
to the increment, and withbaselineMeasuredat 0 it reports only the removal and says the net
gain has no data. The old totals computedbaselineKept = entry.baselineKeptTokens ?? original,
which read "never measured" as "the baseline removes nothing" — making the net gain equal to
everything the plugin removed: an assumption, not a result. - Two README statements are now accurate: pruning keeps scattered relevant segments rather than
cutting one contiguous middle block, and the net increment is stated with its coverage premise.
npm i dsh-jev-tools@0.1.10 · CHANGELOG.md · CHANGELOG.en.md
v0.1.9
Fixed
- 注入的 notice 让整轮对话失败。 Harness 自 0.1.7 起把会话写成格式 v4,而 v4 没有共享的
plugin来源 kind:每条消息要说明自己是谁产出的,旧的{ kind: 'plugin', plugin: … }
包装被写入器直接拒绝——它不会丢掉那一条消息,而是让整轮以
format v4 message requires a producer-owned source kind结束(界面上就是「本轮运行失败」)。
四条能力线的 notice 全部命中:prune、screen、suggest,以及降级提示。依据:2026-09-24
两个会话都在 notice 注入的那一刻断在 step 中间——f87f8e58的 prune 判定
(23:55:53,5376 → 3187 tokens)与5f45b2e2的 screen 判定(23:56:51)——日志停在
tool/call,没有turn/end;再用 Harness 0.1.7-rc.1 的assertV4MessageSources复现:
kind: 'plugin'抛错,kind: 'plugin:dsh-jev-tools'通过。
Changed
- notice 的来源改为单一模块产出。 现在只有
src/source.ts构造它;写入的 kind
plugin:dsh-jev-tools正是 Harness 自己的 v3→v4 迁移为历史 notice 记录的身份,因此升级前
写入的 notice 与今天写入的归到同一个 kind,而不是分裂成两个。
English
Fixed
- An injected notice failed the whole turn. Since 0.1.7 the harness writes sessions in format
v4, which has no sharedpluginsource kind: each message states who produced it, and the
retired{ kind: 'plugin', plugin: … }wrapper is refused by the writer — it does not drop that
one message, it ends the turn withformat v4 message requires a producer-owned source kind
("this turn failed" in the UI). All four notice lines hit it:prune,screen,suggest, and
the degrade notice. Evidence: on 2026-09-24 two sessions stopped mid-step at the exact moment a
notice was injected —f87f8e58at its prune judgment (23:55:53, 5376 → 3187 tokens) and
5f45b2e2at its screen judgment (23:56:51) — the log ends ontool/callwith noturn/end;
reproducing it with the harness 0.1.7-rc.1assertV4MessageSources:kind: 'plugin'throws,
kind: 'plugin:dsh-jev-tools'passes.
Changed
- One module owns the notice source.
src/source.tsbuilds it for every capability, and the
kind it writes —plugin:dsh-jev-tools— is the identity the harness's own v3→v4 migration
records for this plugin's historical notices, so notices written before the upgrade and notices
written today stay under one kind instead of splitting in two.
npm i dsh-jev-tools@0.1.9 · CHANGELOG.md · CHANGELOG.en.md
v0.1.8
Fixed
- 技能推荐从未触发过。 这是本次唯一一个「整个能力没在工作、而外部完全看不出来」的缺陷。
skills.list()没有传scope,而注册表在省略scope时只读 global 层——agent preset 挂载的
插件注册在 preset 层,于是一个会话真实持有的二十多个技能,插件一个都看不见,目录恒低于 15 的
门槛,每一次都静默返回。依据:修复前 6 个会话、227 条台账记录里suggest一条都没有,
同期prune218 条、screen9 条;修复后首次出现judged,并向会话注入了建议。 jev_ask/jev_gate每会话只能调用 3 次。 两者都调budget.tryConsume(agentId, -1):-1是一个
永不前进的伪 turn,而传入的是prune.perTurnLimit(一个 per-turn 值),于是turns.get(-1)
在整个会话里只增不减,第 4 次起永久被拒。实测:连续 3 次成功、随后 3 次全部skip: "budget-turn"。
现在显式调用走单独的tryConsumeSession(),只受sessionCallLimit约束——per-turn 上限的理由是
限制自动能力的串行延迟,对一次主动索取答案的调用并不成立。- 拒绝在会话里只剩一个占位符。 渲染器只读
report,而declined()返回
{ problem, message, detail }——没有report。于是没配 key、请求非法、额度耗尽、网络失败
四种完全不同的失败在会话里长得一模一样:(no judgment)(闸门为(no gate verdict))。
这违背了 README 的约定——fail-open 的代价是这些失败只能在会话里被看见。 - 拒绝记录丢失会话标识。 失败路径硬编码
agentId: '',成功路径写真实 agentId,
同一个工具一半的台账因此无法归因。 - 拒绝文案张冠李戴。 提示语声称「本会话至多 200 次」,实际执行的却是 per-turn 检查。
现在文案只描述真正触发它的那个上限。 - 弃权后同一 turn 内重复判定。 turn 只在建议真正产出后才上锁,于是弃权——含糊 turn 的常态
——什么都不锁,后续每个 step 都重发一次判定。实测 3 个用户 turn 产生 4 条判定记录,
且每次都在消耗prune共享的每 turn 额度。现在请求发出前就锁,且锁本身不写记录:
一个 turn 一条suggest记录成为不变量。
Added
- 技能推荐的每一次放弃都进台账。 新增
no-skills、catalog-unavailable、catalog-too-small
三个跳过原因。在此之前它的 8 条提前返回全部静默,于是「从未运行」与「运行了但每次放弃」
在外部无法区分——而台账存在的唯一理由就是回答这个问题。记录按 (turn, 原因) 去重:
agent/pre-step每个 step 都触发,不去重会拿同一条记录冲掉 1000 条的台账窗口。 - 技能推荐改为按最新一条用户消息判定(过短时回退到最近三条)。这是语义选择而非准确性改进:
建议回答「此刻问什么」,三消息窗口回答「这段会话在做什么」——后者是剪枝的问题。受控复现
(同 criteria、只换任务文本)显示窗口只稀释分布:冠军 0.510 → 0.470,top2 边际 0.24 → 0.16,
不改变冠军。
Changed
- README 的额度说明改准:
sessionCallLimit明确含两个工具,prune.perTurnLimit明确是
prune / screen / suggest 三者共享。数据边界表补上jev_ask与jev_gate——两个工具都会把
调用者给的正文送出本机,此前那一节漏了它们。 - 海报与渲染脚本移出
docs/(改到仓库根的poster/):files白名单包含整个docs/,
3 MB 的营销素材没有理由进入每个用户的npm i。
English
Fixed
- Skill suggestion had never fired. The only defect here where a whole capability did nothing and
nothing outside could tell.skills.list()passed noscope, and the registry reads the global
layer alone whenscopeis omitted — while a plugin mounted by an agent preset registers into that
preset's layer. A session holding twenty-odd skills was therefore invisible to the plugin, the
catalog stayed under the 15 floor, and every turn returned silently. Evidence: before the fix,
227 ledger rows across 6 sessions contained not onesuggestrow, against 218 forpruneand 9
forscreen; after it, the firstjudgedrow appeared and a suggestion reached the session. jev_ask/jev_gatecould be called three times per session. Both called
budget.tryConsume(agentId, -1):-1is a pseudo-turn that never advances, and the ceiling passed
in isprune.perTurnLimit— a per-turn value — soturns.get(-1)only ever grew. The fourth call
onwards was refused permanently. Measured: three successes, then threeskip: "budget-turn"rows.
Explicit calls now take a separatetryConsumeSession()bounded only bysessionCallLimit: the
per-turn ceiling exists to bound the latency an automatic capability adds, which has no rationale
for a call that is asking for one answer.- A refusal reached the session as a placeholder. The renderer reads only
report, anddeclined()
returned{ problem, message, detail }without it — so a missing key, an invalid request, an exhausted
quota and a network failure were indistinguishable in the session:(no judgment)(and
(no gate verdict)for the gate). That contradicts what the README promises — the price of fail-open
is that these failures are visible only in the session. - Refusal rows lost the session id. The failure path hardcoded
agentId: ''while the success path
recorded the real one, so half of a tool's ledger could not be attributed. - The refusal text named the wrong ceiling. It claimed "this session allows at most 200", while the
check actually performed was the per-turn one. The text now describes the ceiling that stopped it. - An abstention re-judged on every later step of the same turn. The turn was latched only once a
notice was actually produced, so an abstention — the normal outcome on a vague turn — latched nothing
and every subsequent step re-sent the judgment. Measured: three user turns produced four judgment
rows, each spending the per-turn allowancepruneshares. The latch is now set before the request
goes out, and the latch hit writes no row: onesuggestrow per turn is the invariant.
Added
- Every skill-suggestion decline is now recorded. Three skip reasons are added:
no-skills,
catalog-unavailable,catalog-too-small. Until now all eight early returns were silent, so "never
ran" and "ran and declined every time" were indistinguishable from outside — the one question the
ledger exists to answer. Rows are deduplicated by (turn, reason):agent/pre-stepfires on every step,
and without that the same line would flush the 1000-row ledger window. - Skill suggestion now judges the latest user message (falling back to the last three when it is too
short). That is a semantic choice, not an accuracy gain: a suggestion answers "what is being asked
now", while a three-message window answers "what is this session doing" — pruning's question. A
controlled rerun (same criteria, only the task text changed) showed the window merely diluting the
distribution: winner 0.510 → 0.470, top-two margin 0.24 → 0.16, winner unchanged.
Changed
- The README's quota wording is now accurate:
sessionCallLimitnames both tools, and
prune.perTurnLimitsays it is shared by prune, screen and suggest. The data-boundary table gains
jev_askandjev_gate— both send caller-supplied text off the machine, and the section had omitted
them. - The poster and its render script moved out of
docs/(toposter/at the repository root): the
filesallowlist covers all ofdocs/, and 3 MB of marketing artwork has no reason to reach every
npm i.
npm i dsh-jev-tools@0.1.8 · CHANGELOG.md · CHANGELOG.en.md
v0.1.7
Added
- 结构性失败不再静默。没配 key、或端点/密钥不匹配导致 401 时,插件会在会话里说一次(每个会话、
每种原因各一次,靠会话历史去重)。fail-open 的意义是判定失败不影响任务,代价是这两类失败在别处
都不报错——一个填错的baseUrl或一把属于别的主机的 key,表现就是"插件什么都没做"。提示同时
到达用户与模型,后者因此不会继续相信一个已经瞎了的剪枝层。 - 成本可见。台账新增累计 input token 计数器(输入计费、输出免费),
/jev-status与
npm run measure -- --ledger都按$0.042/百万折成美元显示。此前estimateCostUsd()除了单元
测试没有任何调用者,而度量脚本自己另写了一份价格常量。 - 标定报告增加 AUC 与阈值扫掠。
npm run measure -- samples.jsonl现在回答两个此前没回答的问题:
ROC AUC(模型到底能不能区分两类,与"标定得好不好"是两回事),以及 0.05–0.95 的阈值表
(精确率/召回/F1)与最佳操作点——这是闸门类能力需要的数字,与已有的 ECE/Brier 互补。 - CI 与发布自动化。
.github/workflows/ci.yml(ubuntu + windows × node 24:npm ci→
npm test→ tarball 检查),.github/workflows/release.yml(只有 tag 能发布:先校验 tag 与
package.json版本一致,再跑同一套门禁,最后经 npm trusted publishing 发布,无长期 token)。
配套scripts/check-tarball.mjs:断言发布包里既没有本机状态(.npm-cache/、trigger-rate.json
等),也不缺用户需要的文件——files白名单只有在没人放宽它时才是一道控制。
Changed
- 判定价格只声明一处(
src/request.ts的USD_PER_MTOK),度量脚本不再自带副本。
English
Added
- Structural failures are no longer silent. With no key configured, or a key the endpoint refuses
(401), the plugin says so once in the session — once per session, per reason, de-duplicated
through the session history. Fail-open is the right rule (a failed judgment must not fail the task),
and its price is that neither failure is reported anywhere else: a mistypedbaseUrlor a key issued
for a different host looks exactly like a plugin doing nothing. The notice reaches the model as well
as the reader, so a model stops trusting a pruning layer that has gone blind. - Spend is visible. The ledger gains a cumulative input-token counter (input is billed, output is
free), and both/jev-statusandnpm run measure -- --ledgershow it in dollars at$0.042per
million. Until nowestimateCostUsd()had no caller outside its unit test, and the measurement script
carried its own second copy of the price. - The calibration report gains AUC and a threshold sweep.
npm run measure -- samples.jsonlnow
answers two questions it did not before: ROC AUC (can the model separate the classes at all — a
different question from whether it is calibrated) and a 0.05–0.95 threshold table with
precision/recall/F1 and the best operating point. That is the number a gate needs, and it complements
the existing ECE/Brier. - CI and release automation.
.github/workflows/ci.yml(ubuntu + windows × node 24:npm ci→
npm test→ tarball check) and.github/workflows/release.yml(a tag is the only thing that
publishes: the tag is checked against the version inpackage.json, the same gates run, and the
publish goes through npm trusted publishing with no long-lived token). Together with
scripts/check-tarball.mjs, which asserts the published tarball carries no local state
(.npm-cache/,trigger-rate.json, …) and is not missing anything a consumer needs — an allowlist is
a control only for as long as nobody widens it.
Changed
- The judgment price is declared once (
USD_PER_MTOKinsrc/request.ts); the measurement script no
longer keeps its own copy.
npm i dsh-jev-tools@0.1.7 · CHANGELOG.md · CHANGELOG.en.md