@jayyuen66/dsh-plugin-shared 0.1.9 → 0.2.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.
@@ -2,32 +2,32 @@ import type { OxlintConfig, OxlintOverride } from "oxlint";
2
2
  export type RuleTable = NonNullable<OxlintConfig["rules"]>;
3
3
  /**
4
4
  * 一条规则能被关掉的**全部**合法理由。写不进这六类的,就不该有关掉它的选项。
5
- * 每类的判据来自 oxlint/eslint 官方文档、上游 recommended 集合的归属,加上本仓的实测。
5
+ * 每类的判断依据来自 oxlint/eslint 官方文档、上游 recommended 集合的归属,加上本仓的实测。
6
6
  */
7
7
  export type OffReason =
8
- /** 规则的判据与官方契约方向相反,开了等于判官方写法违规。 */
9
- "契约冲突"
10
- /** 与本仓已启用的对侧规则互斥(开一侧就判另一侧死刑)。 */
11
- | "对侧互斥"
12
- /** 按它的意思改代码会把代码改坏,有实测的编译/运行后果。 */
13
- | "实测否证"
14
- /** 判据只在某种文体(用例文件 / 构建脚本)里成立,按文件种类收窄。 */
15
- | "文体作用域"
16
- /** 纯口味规则,上游不放进 recommended,开启只带来语义无关的重写。 */
8
+ /** 规则的判断标准与官方代码形态相反,开启等于判定官方写法违规。 */
9
+ "与官方形态冲突"
10
+ /** 与本仓已启用的另一条规则互相排除(开一侧就判另一侧全部违规)。 */
11
+ | "与已启用规则互斥"
12
+ /** 按它的意思改代码会把代码改坏,有实测的编译或运行后果。 */
13
+ | "实测有害"
14
+ /** 判断标准只在某类文件(用例文件、构建脚本)里成立,按文件种类收窄。 */
15
+ | "仅限特定文件"
16
+ /** 纯风格偏好类规则,上游不放进 recommended,开启只带来语义无关的重写。 */
17
17
  | "上游不推荐"
18
- /** 旧 `.oxlintrc.json` 继承下来、实测开了是 0 条 —— 没有存在的理由,直接作废。 */
19
- | "零代价作废";
18
+ /** 旧 `.oxlintrc.json` 继承下来、实测开启后零命中——没有存在的理由,直接作废。 */
19
+ | "零命中作废";
20
20
  export interface OffJustification {
21
21
  /** 关它的理由类别。 */
22
22
  kind: OffReason;
23
- /** 打开它在全仓产生的条数(口径见 OFF_JUSTIFICATIONS 的头注释)。 */
23
+ /** 打开它在全仓产生的条数(测量方法见 OFF_JUSTIFICATIONS 的头注释)。 */
24
24
  measured: number;
25
25
  /** 一句话依据;配置加载只要求这个字段非空。 */
26
26
  note: string;
27
27
  }
28
28
  /**
29
- * 实测口径:临时把基线里每条 `off`(rules 与 override 层都算)翻成 `error` 再跑 15 个成员、
30
- * 与基线取差数 `::error` 行,按路径分成 prod / test / scripts 三桶。`--deny=<rule>` 盖不住
29
+ * 实测方法:临时把基线里每条 `off`(rules 与 override 层都算)翻成 `error` 再跑 15 个成员、
30
+ * 与基线取差数 `::error` 行,按路径分成 prod / test / scripts 三类。`--deny=<rule>` 盖不住
31
31
  * `off`,最早一轮用它在 13 个包目录上量出的数因此全偏小(`vitest/no-hooks` 用 `-D` 量出来是
32
32
  * 0,实际是 181),整批作废重测。
33
33
  *
@@ -37,8 +37,8 @@ export declare const OFF_JUSTIFICATIONS: Record<string, OffJustification>;
37
37
  /**
38
38
  * 基线里**不设豁免**的规则清单:这些规则必须保持开启,谁要关掉就得先给出依据。
39
39
  *
40
- * 判据是「按文体分别核命中数」。`oxlint --deny=<rule>` 量的是整体,而 `--deny` 盖不住
41
- * override 层的 off(这条口径见本文件头),所以一条规则可以同时是「TS 侧零命中、保持开启」
40
+ * 方法是「按文件种类分别核对命中数」。`oxlint --deny=<rule>` 量的是整体,而 `--deny` 盖不住
41
+ * override 层的 off(这个测量方法见本文件头),所以一条规则可以同时是「TS 侧零命中、保持开启」
42
42
  * 与「`scripts/` 的无类型 .mjs 里有命中、按文体收窄」——`eslint/no-continue` 正是这一类,
43
43
  * 它的收窄在 SCRIPTS_OVERRIDES 里,不在这里。`eslint/no-await-in-loop` 则两侧都开着
44
44
  * (实测探针对 .mjs 照样报),两边都按代码写到位,没有收窄层。
@@ -49,20 +49,20 @@ export declare const READONLY_SYNC_ALLOWLIST: string[];
49
49
  * 组装 `jsPlugins`:本包解析到的 sonarjs 入口在前,调用方自带的在后并去重。
50
50
  *
51
51
  * 为什么要有这一层而不是直接 `[resolveSonarjsEntry(), ...(jsPlugins ?? [])]`:
52
- * 那写法无条件先求值,于是「调用方自带入口」这条官方逃生口在缺 sonarjs 时**用不了**——
52
+ * 那写法无条件先求值,于是「调用方自带入口」这条官方预留的退路在缺 sonarjs 时**用不了**——
53
53
  * 抛错发生在调用方的入口被用上之前,报错信息给的补救办法照做必然再报同一条。
54
- * 分支口径:
54
+ * 分支规则:
55
55
  * - 调用方带了**指向 sonarjs 的**入口 → 解析失败就跳过(它自备的就是 sonarjs),
56
56
  * 成功则去重后前置;
57
57
  * - 调用方只带了别家插件、或没带 → 解析失败照旧抛那条可操作的信息。SONARJS_RULES
58
- * 无条件引用 sonarjs,只带别家插件救不了它:那种失败会迟到到 oxlint 装载配置那一刻
59
- * (`Plugin 'sonarjs' not found`),可操作的成因必须在这里先抛出来,而不是被吞掉。
58
+ * 无条件引用 sonarjs,只带别家插件满足不了它:那种失败会推迟到 oxlint 装载配置那一刻
59
+ * (`Plugin 'sonarjs' not found`),可操作的成因必须在这里先抛出来,而不是留到后面被掩盖。
60
60
  * 「指向 sonarjs」按入口路径含 `sonarjs` 判定——调用方传的是自己解析到的插件路径,
61
- * 这是能落进字符串判据的全部信号。
61
+ * 这是能落进字符串判断的全部信号。
62
62
  *
63
63
  * 导出且把解析器做成入参,是为了「解析失败」那一支在**本仓**可测:本仓 devDependencies
64
- * 装着 sonarjs,真实的解析路径永远成功,那两个分支就只能靠注桩走到(与 `LauncherLocator`、
65
- * `ProjectKeyOptions.realpath` 同一套可注入面的取舍)。生产侧的 consumer 是
64
+ * 装着 sonarjs,真实的解析路径永远成功,那两个分支就只能靠注入 stub 走到(与 `LauncherLocator`、
65
+ * `ProjectKeyOptions.realpath` 同一套可注入选项的取舍)。生产侧的 consumer 是
66
66
  * `definePluginConfig`,故这不是「只被测试养着的导出」。
67
67
  *
68
68
  * @param callerPlugins - 调用方经 `jsPlugins` 传入的入口;undefined / 空数组均可
@@ -76,31 +76,31 @@ export declare function resolveJsPlugins(callerPlugins: NonNullable<OxlintConfig
76
76
  *
77
77
  * 这是一张**快照**,不是「上游新增规则自动落在生产文件之外」的机制:`categories` 是全局
78
78
  * error 开关,上游在任一 category 里新增 vitest 规则,它就会先作用到每一个 host.ts。
79
- * 拦这一步的是 test/config-oxlint.test.ts 的一致性用例——它拿 `oxlint --rules` 现量与
80
- * 本数组逐条对照,上游新增/删除规则即红,由人工同步名单(需要豁免的再补依据)。
79
+ * 拦这一步的是 test/config-oxlint.test.ts 的一致性用例——它拿 `oxlint --rules` 的实际输出与
80
+ * 本数组逐条对照,上游新增/删除规则即失败,由人工同步名单(需要豁免的再补依据)。
81
81
  */
82
82
  export declare const VITEST_ALL_RULES: string[];
83
83
  export declare const BASE_RULES: RuleTable;
84
84
  /**
85
- * 用例文件(含 `test/*.ts` 夹具与 helper)共享的豁免:规模类,以及「桩件要跨官方名义类型」
86
- * 的类型类判据。vitest 的用例结构类判据不在这里 —— 它们在基线整族 off、只在
87
- * TEST_FILE_OVERRIDES 里开回来,所以 helper 模块天然不在射程内,旧配置那份按目录豁免的
85
+ * 用例文件(含 `test/*.ts` 测试数据与 helper)共享的豁免:规模类,以及「stub 要跨官方名义类型」
86
+ * 的类型类判断。vitest 的用例结构类判断不在这里 —— 它们在基线整族 off、只在
87
+ * TEST_FILE_OVERRIDES 里开回来,所以 helper 模块天然不在范围内,旧配置那份按目录豁免的
88
88
  * TEST_HELPER_OVERRIDES 因此作废。
89
89
  */
90
90
  export declare const TEST_OVERRIDES: OxlintOverride;
91
91
  /**
92
- * vitest 的用例结构类判据**只**在这里生效。
92
+ * vitest 的用例结构类判断**只**在这里生效。
93
93
  * `expect-expect` 的 `assertFunctionNames` 取 vitest flavor 的官方默认
94
94
  * (`["expect", "expectTypeOf", "assert", "assertType"]`):本仓用例里有相当一部分用
95
95
  * `node:assert/strict` 断言,那不是「缺断言」,而是这条规则在 jest flavor 默认值下漏判
96
96
  * ——实测 17 条里 5 条属于这一族,补上默认值后就不报了。
97
97
  *
98
- * 剩下两族用规则自带的选项解决,不关规则、也不改判据方向:
98
+ * 剩下两族用规则自带的选项解决,不关规则、也不改判断方向:
99
99
  * - 断言写在包内 helper 里(`assertSameLedger()` 一类):`assertFunctionNames` 就是
100
100
  * "哪些函数算断言"的名单,走 {@link makeTestFileOverrides} 的 `assertionHelpers` 报名字。
101
- * 名单只收**裸标识符**:带 `*` / `.` 的通配会把"任何调用都算断言"写进配置,等于掏空规则,直接抛。
101
+ * 名单只收**裸标识符**:带 `*` / `.` 的通配会把"任何调用都算断言"写进配置,等于让规则失去作用,直接抛。
102
102
  * - 标题以大写术语开头(`PTC`、`W5`、`B1` 这类档位/模式名):`allowedPrefixes` 是
103
- * prefer-lowercase-title 给的同一条口径(官方例子就是 HTTP 方法前缀)。走 `titlePrefixes` 报名字。
103
+ * prefer-lowercase-title 给的同一个选项(官方例子就是 HTTP 方法前缀)。走 `titlePrefixes` 报名字。
104
104
  * 反过来的做法——把标题改成 `pTC` / `w5`——会把领域名字改成坏名字,本仓不采取。
105
105
  */
106
106
  export declare const VITEST_ASSERTION_DEFAULTS: string[];
@@ -116,7 +116,7 @@ export interface TestFileOverridesOptions {
116
116
  titlePrefixes?: string[];
117
117
  }
118
118
  /**
119
- * 生成用例文件的 override(vitest 结构类判据的开与关都在这张表里)。
119
+ * 生成用例文件的 override(vitest 结构类判断的开与关都在这张表里)。
120
120
  * @param opts - 本包需要用规则自带选项登记的名单(见文件头的两族说明)。
121
121
  * @returns 可直接放进 `overrides` 的配置项。
122
122
  */
@@ -133,8 +133,8 @@ export interface PluginLintOptions {
133
133
  offReasons?: Record<string, OffJustification>;
134
134
  /**
135
135
  * 本包"内部会断言"的用例 helper 名(如 `assertSameLedger`)。报进来的名字并入
136
- * `vitest/expect-expect` 的 `assertFunctionNames`,规则本身保持 error —— 这是规则给的口径,
137
- * 不是豁免。只收裸标识符,通配会掏空这条规则,直接抛。
136
+ * `vitest/expect-expect` 的 `assertFunctionNames`,规则本身保持 error —— 这是规则自带的机制,
137
+ * 不是豁免。只收裸标识符,通配会让这条规则失去作用,直接抛。
138
138
  */
139
139
  assertionHelpers?: string[];
140
140
  /** 允许开头的用例标题大写术语(`PTC`/`W5`/`B1` 这类档位或模式名),进 allowedPrefixes。 */
@@ -9,12 +9,12 @@ type PackageConfig = ViteUserConfig;
9
9
  type TestConfig = NonNullable<PackageConfig["test"]>;
10
10
  type CoverageConfig = NonNullable<TestConfig["coverage"]>;
11
11
  /**
12
- * 覆盖率覆盖项的形状。`include` 不在其中:统计面由 `coverageInclude` 单源供给,两处各写一份
12
+ * 覆盖率覆盖项的形状。`include` 不在其中:统计范围由 `coverageInclude` 单独供给,两处各写一份
13
13
  * 必然漂移。覆盖率对象整块替换会让四项 100% 阈值与排除表静默消失,所以覆盖项不走 `test`
14
14
  * 展开,而是逐键合并;`thresholds` 之内也逐键合并,单项可改、其余三项删不掉。
15
15
  */
16
16
  type CoverageOverride = Omit<Partial<CoverageConfig>, "include">;
17
- /** 覆盖率面与 test 块里真正因包而异的那一项。 */
17
+ /** 公共覆盖率配置与 test 块里真正因包而异的那一项。 */
18
18
  export interface PackageVitestOptions {
19
19
  /** `coverage.include` —— 本包该被统计的源文件(各包只有这里不同);空数组/空串在工厂即抛。 */
20
20
  coverageInclude: string[];
@@ -24,7 +24,7 @@ export interface PackageVitestOptions {
24
24
  test?: PackageConfig["test"];
25
25
  }
26
26
  /**
27
- * 公共面:各份配置里逐字相同的那部分,从今往后只有一处。
27
+ * 公共部分:各份配置里逐字相同的那部分,从今往后只有一处。
28
28
  * `testTimeout: 20_000` 是各包共同值(vitest 默认 5000);继承它只会放宽不会收紧,
29
29
  * 故不会把任何已过用例变成不稳定。
30
30
  */
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * 把捕获到的值渲染成一段人读文本。
3
3
  * `Error` 取 message(空串时回退 name)并接上 cause 链;`AggregateError` 附上成员;
4
- * 非 Error 但带可读 message 属性的取该属性,其余按 `String()` 兜底。
5
- * 全程不抛异常:敌意输入降级为占位文本。
4
+ * 非 Error 但带可读 message 属性的取该属性,其余按 `String()` 处理。
5
+ * 全程不抛异常:恶意输入改用占位文本。
6
6
  * @param error - 捕获到的值(catch 里的 `unknown`)。
7
7
  * @returns 可直接进界面通知或日志的文本。
8
8
  */
9
9
  export declare function errorText(error: unknown): string;
10
10
  /**
11
- * 是不是「设置冲突」那一类失败(宿主在两个界面同时改同一份设置时抛的形)。
11
+ * 是不是「设置冲突」那一类失败(宿主在两个界面同时改同一份设置时抛出的那种失败)。
12
12
  *
13
- * 认两枚痕迹而不是 `instanceof`:跨 realm 抛过来的错误类不是同一个构造函数,而
14
- * `code`/`name` 是宿主两边都写的那两个值。经 `fieldOf` 读——它的探测出口把敌意
15
- * getter / Proxy 陷阱的抛错塌成「取不到」,判定面因此天然只回答是/否,不外抛。
13
+ * 认两处痕迹而不是 `instanceof`:跨 realm 抛过来的错误类不是同一个构造函数,而
14
+ * `code`/`name` 是宿主两边都写的那两个值。经 `fieldOf` 读——它的带保护出口把恶意
15
+ * getter / Proxy trap 的抛错变成「取不到」,判定因此天然只回答是/否,不外抛。
16
16
  * @param error 捕获到的值
17
17
  * @returns 该失败是否应当按「设置已被别处改动」处理
18
18
  */
@@ -2,34 +2,34 @@ import type { ShellProcess } from "@deepseek-ai/dsh-shell";
2
2
  import type { JobOutcome } from "@deepseek-ai/dsh-jobs";
3
3
  /**
4
4
  * 官方结局类型在这里再出一个具名出口。`jobOutcomeOf` 的返回类型就是它,消费方把它写进自己的
5
- * 签名(生产者回调、测试断言)时若没有具名出口,只能把结构重述一遍——而重述正是本包要收敛掉的。
5
+ * 签名(生产者回调、测试断言)时若没有具名出口,只能把结构重述一遍——而重述正是本包要消除的。
6
6
  */
7
7
  export type { JobOutcome } from "@deepseek-ai/dsh-jobs";
8
- /** 映射只用得到的三个成员(不是整个 ShellProcess:生产者句柄的其余面与本函数无关)。 */
8
+ /** 映射只用得到的三个成员(不是整个 ShellProcess:生产者句柄的其余成员与本函数无关)。 */
9
9
  export type SettledProcess = Pick<ShellProcess, "status" | "exitCode" | "signal">;
10
10
  /**
11
- * 落定进程 → 作业结局。
12
- * @param proc 已落定的子进程句柄(`done` 已 resolve 之后再读,值才是终态)
11
+ * 已结束进程 → 作业结局。
12
+ * @param proc 已结束的子进程句柄(`done` 已 resolve 之后再读,值才是终态)
13
13
  * @returns 交给 {@link JobRegistry} 生产者的结局:killed / completed,理由在 detail
14
14
  */
15
15
  export declare function jobOutcomeOf(proc: SettledProcess): JobOutcome;
16
16
  /**
17
- * 只用得到的那一个成员。**不按 `ShellProcess` 整型收**:官方的 `kill` 返回 `Promise<boolean>`,
17
+ * 只用得到的那一个成员。**不按 `ShellProcess` 整个收**:官方的 `kill` 返回 `Promise<boolean>`,
18
18
  * 把那个返回类型写进签名等于要求每个调用方(含测试替身)都得交回一个 Promise,
19
- * 而这里真正需要的只是「有一个可以试着调的 kill」——返回值要么被丢弃,要么被收掉拒绝。
19
+ * 而这里真正需要的只是「有一个可以试着调的 kill」——返回值要么被丢弃,要么把拒绝接管下来。
20
20
  */
21
21
  export interface KillableProcess {
22
22
  kill: () => unknown;
23
23
  }
24
24
  /**
25
- * 收掉子进程,**忽略失败**:进程可能已自然退出或已被系统回收,此时 kill 失败不是本包的失败。
25
+ * 终止子进程,**忽略失败**:进程可能已自然退出或已被系统回收,此时 kill 失败不是本包的失败。
26
26
  *
27
- * 为什么不向调用方通报:调用点都在清理臂(卸载、被拒、超时)上,那里没有可行动的下一步;
28
- * 把异常抛出去只会让清理链半途而废,留下更难的现场。
27
+ * 为什么不向调用方通报:调用点都在清理分支(卸载、被拒、超时)上,那里没有可行动的下一步;
28
+ * 把异常抛出去只会让清理链半途而废,留下更难处理的现场。
29
29
  *
30
- * 同步抛错与**异步拒绝**都要收:官方句柄的 `kill` 返回 `Promise<boolean>`,同步 try/catch
31
- * 只拦得住前者,返回的 Promise 拒绝会一路逃到进程级 unhandledRejection——清理臂不该有能力
30
+ * 同步抛错与**异步拒绝**都要处理:官方句柄的 `kill` 返回 `Promise<boolean>`,同步 try/catch
31
+ * 只拦得住前者,返回的 Promise 拒绝最终会变成进程级 unhandledRejection——清理分支不该有能力
32
32
  * 把宿主进程的未处理拒绝计数器推高。
33
- * @param proc 待收的句柄
33
+ * @param proc 待清理的句柄
34
34
  */
35
35
  export declare function killProc(proc: KillableProcess): void;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * 反复保留后半行段直到 UTF-8 字节数 ≤ `maxBytes`;行数 ≤2 时停手(单条超限行原样保留,
3
- * 防死循环也防清空文件)。未超限直接返回入参**同一引用**,调用方可据此跳过写盘。
3
+ * 防死循环也防清空文件)。未超限直接返回入参**同一引用**,调用方可据此跳过磁盘写入。
4
4
  *
5
- * **配额必须是有限数**(这一档由调用方保证,本函数不代为归一):
5
+ * **配额必须是有限数**(这一点由调用方保证,本函数不代为归一):
6
6
  * - `0` 与负数**照样收缩**到 ≤2 行为止——「0 = 永不截断」是某消费方在**调用前**
7
7
  * `maxBytes > 0` 那道触发策略的承诺,不是本函数的语义(见它自己的 jsonl-fuse 头注)。
8
8
  * 把 0 当成「不裁」会把这个承诺写成一个假象。
@@ -11,14 +11,14 @@
11
11
  另一个在调用前就把非有限值与负数归一成 0。但那道归一在**消费方**,本包并不强制——
12
12
  新增消费方时没有任何东西会提醒它。
13
13
  *
14
- * 为什么不在这种预算上抛错拒收:抛错会被两个消费方各自的 catch 接走(一个上抛、一个内部吞),
14
+ * 为什么不在这种预算上抛错拒收:抛错会被两个消费方各自的 catch 接走(一个向外抛、一个内部捕获),
15
15
  * 等于把一条清晰的预算错误变成一次静默跳过,排查方向反而更差。这里"不设限"只是「这次不裁」,
16
- * 不像读请求体那样会同时放开内存上限,所以没有必须拒收的那条失败模型。形状不同的同一种错,
17
- * 各按各的失败模型处理。
16
+ * 不像读请求体那样会同时放开内存上限,所以没有必须拒收的那条失败方式。表现不同的同一种错,
17
+ * 各按各的失败方式处理。
18
18
  *
19
19
  * 两个消费方的具体调用点与配额归一位置记在 test/jsonl.test.ts 的同名用例里——这一段
20
20
  * 不出现兄弟包标识符:声明上的 JSDoc 会被 tsc 抄进 dist/types/*.d.ts 一起发出去,
21
- * 包名会让装了这个包的消费方以为存在一个并不存在的依赖(test/publish-manifest.test.ts 会红)。
21
+ * 包名会让装了这个包的消费方以为存在一个并不存在的依赖(test/publish-manifest.test.ts 会判定失败)。
22
22
  *
23
23
  * @param text 整份 JSONL 文本
24
24
  * @param maxBytes 字节上限;调用方须保证是有限数
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * 执行一次 lesson bus 调用,并把两类失败归到同一个出口。
3
3
  * @param call - 真正的调用(同步返回或返回 Promise 都接受)。
4
- * @param onFailure - 失败收口:同步抛错与异步拒绝都会到这里,且各自只被调一次。
5
- * 返回类型写 `unknown` 而不是 `void`:签名不能让"收口是 async 函数"变成类型错误,
4
+ * @param onFailure - 失败处理:同步抛错与异步拒绝都会到这里,且各自只被调一次。
5
+ * 返回类型写 `unknown` 而不是 `void`:签名不能让"失败处理是 async 函数"变成类型错误,
6
6
  * 而它返回的 Promise 由 report 接管(见该函数的注释),调用方不必也不该自己等它。
7
7
  */
8
8
  export declare function settleLessonCall(call: () => unknown, onFailure: (reason: unknown) => unknown): void;
@@ -8,10 +8,10 @@ export type Locale = BuiltInLocaleId;
8
8
  /** 缺省语言。设置项未设或值不合语法时落到这里。 */
9
9
  export declare const DEFAULT_LOCALE: Locale;
10
10
  /**
11
- * 官方 `@deepseek-ai/dsh-client-locale` 拥有的 settings 命名空间与字段名。那两枚常量官方
12
- * 也外销(同包的根入口),这里**不引它而是抄字面量**:引它就得把 client 包变成 host 半的运行期
11
+ * 官方 `@deepseek-ai/dsh-client-locale` 拥有的 settings 命名空间与字段名。那两个常量官方
12
+ * 也导出(同包的根入口),这里**不引它而是抄字面量**:引它就得把 client 包变成宿主侧的运行期
13
13
  * 依赖,而这两个值是纯字符串常量、不参与任何 client 行为。两侧必须逐字同名才读得到同一份偏好,
14
- * 所以这条耦合是本包 README 的登记项:官方那两枚常量一旦改名,本包必须同步改。
14
+ * 所以这条耦合是本包 README 的登记项:官方那两个常量一旦改名,本包必须同步改。
15
15
  */
16
16
  export declare const LOCALE_SETTINGS_NAMESPACE = "locale";
17
17
  export declare const LOCALE_PREFERENCE_FIELD = "preference";
@@ -19,7 +19,7 @@ export declare const LOCALE_PREFERENCE_FIELD = "preference";
19
19
  * 一个包的全部消息键值表(键名即语义,值是人读文案)。
20
20
  *
21
21
  * 键集绑 `Locale` 而不是手写 `{ zh; en }`:本文件的 `Locale` 已改成官方 `BuiltInLocaleId`
22
- * 的别名,宿主哪天加一门语言,这两处必须**一起**响——只改 `Locale` 而把键集钉死成
22
+ * 的别名,宿主哪天加一门语言,这两处必须**一起**响——只改 `Locale` 而把键集锁死成
23
23
  * `{ zh; en }`,新增那门语言会静默取不到文案(`messagesFor` 的 else 分支落回 zh)。
24
24
  * `Record` 与原 interface 在当前 `Locale`(恰为 `"zh"|"en"`)下结构等价,消费方字面量
25
25
  * 无需改动;差别只在宿主扩宽那一刻,从「静默回落」变成「编译报错」。
@@ -27,7 +27,7 @@ export declare const LOCALE_PREFERENCE_FIELD = "preference";
27
27
  * 值域收在 string 上,用**同构映射类型**而不是 `Record<string, string>`:实测(tsc)消费方
28
28
  * 全部用 `interface Messages { readonly xxx: string }` 声明,而 interface **拿不到**隐式索引
29
29
  * 签名——写成 `Messages extends Record<string, string>` 会让全部消费方 TS2322,那是外溢到
30
- * 9 个包的破坏性变更。映射类型对 interface 与 type 都成立(test/type-guards.test.ts 钉住)。
30
+ * 9 个包的破坏性变更。映射类型对 interface 与 type 都成立(test/type-guards.test.ts 固定)。
31
31
  * 收窄前实测到的缺口:`messagesFor({ zh: { g: 1 }, en: { g: "x" } }, "zh")` 零错误通过,即文件头
32
32
  * 「值不是字符串都在 tsc 阶段就红」原本只在消费方自己声明 `Messages` 时才成立。
33
33
  */
@@ -37,15 +37,15 @@ export type MessagesCatalog<Messages> = Record<Locale, {
37
37
  /**
38
38
  * 把设置里的原始值归一成受支持的 locale:只取**主语言子标签**判定(`en-US`、`zh-Hans-CN`
39
39
  * 都落位到 `en` / `zh`),没有「先按完整标签匹配」那一层——受支持的语言只有 `zh` 与 `en`
40
- * 两个主标签,完整标签匹配不会多认出任何一个,反而要多维护一张表。判不出来一律退
41
- * DEFAULT_LOCALE——文案语言不该成为报错面。
40
+ * 两个主标签,完整标签匹配不会多认出任何一个,反而要多维护一张表。判不出来一律回退到
41
+ * DEFAULT_LOCALE——文案语言不该成为报错来源。
42
42
  * @param raw - 设置里的原始值,类型不可信(来自用户文档)。
43
43
  * @returns 归一后的 locale。
44
44
  */
45
45
  export declare function resolveLocale(raw: unknown): Locale;
46
46
  /**
47
47
  * 从 `ctx.settings.describe(LOCALE_SETTINGS_NAMESPACE)` 的返回值里取偏好并归一。
48
- * describe 出来的形状由别的包拥有,所以入参按 unknown 处理:非对象、数组、缺字段
48
+ * describe 出来的结构由别的包拥有,所以入参按 unknown 处理:非对象、数组、缺字段
49
49
  * 都走默认语言,不抛。
50
50
  * @param described - describe 的原始返回值(可能 undefined)。
51
51
  * @returns 归一后的 locale。
@@ -57,7 +57,7 @@ export declare function resolveLocalePreference(described: unknown): Locale;
57
57
  * 按 `locale` 直接索引而不是 `locale === "en" ? … : …` 的二元分派:后者的 else 分支
58
58
  * 把「非 en 即 zh」这个假设写死在运行期,`Locale` 扩宽时它**照样编译通过**,新增那门
59
59
  * 语言静默拿不到文案。与 `MessagesCatalog` 的 `Record<Locale, …>` 合起来,
60
- * 扩宽会在 `catalog[locale]` 这一处落成 TS2322。
60
+ * 扩宽会在 `catalog[locale]` 这一处报出 TS2322。
61
61
  * @param catalog - 各语言齐全的消息表。
62
62
  * @param locale - 已归一的语言。
63
63
  * @returns 对应语言的消息表(引用返回,不做拷贝)。
@@ -1,14 +1,15 @@
1
1
  /**
2
- * 可注入面。`realpath` 是同步 fs 调用,`sep` 决定**整条**路径语义臂:分隔符字形与 `resolve`
3
- * 用的那套解析都由它选。两者注入是为了让两条臂在任何 runner 上都可判定——`path.sep` 在模块
4
- * 装载时就定死了,而只注入字形会让非 POSIX runner 上的 POSIX 用例拿到原生解析结果,键随之漂移。
2
+ * 可注入的选项。`realpath` 是同步 fs 调用,`sep` 决定**整条**路径语义分支:分隔符字形与
3
+ * `resolve` 用的那套解析都由它选。两者注入是为了让两条分支在任何 runner 上都可判定——
4
+ * `path.sep` 在模块装载时就定死了,而只注入字形会让非 POSIX runner 上的 POSIX 用例拿到
5
+ * 原生解析结果,键随之漂移。
5
6
  */
6
7
  export interface ProjectKeyOptions {
7
8
  realpath?: (target: string) => string;
8
9
  sep?: string;
9
10
  }
10
11
  /**
11
- * cwd → 项目桶键。非字符串 / trim 后为空 / 归一后落到根目录 → `"default"`
12
- * (与记忆网关的兜底桶名一致)。
12
+ * cwd → 项目键。非字符串 / trim 后为空 / 归一后落到根目录 → `"default"`
13
+ * (与记忆网关的回退键名一致)。
13
14
  */
14
15
  export declare function deriveProjectKey(cwd: unknown, opts?: ProjectKeyOptions): string;
@@ -1,41 +1,41 @@
1
1
  /** 对象类型守卫:非 `null` 的 `object` 且非数组(`Record` 索引签名投影的基础)。 */
2
2
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
3
  /**
4
- * 服务面校验:对象存在且**声明的每个方法**都可调用。
4
+ * 服务可用性校验:对象存在且**声明的每个方法**都可调用。
5
5
  *
6
- * 只做 `typeof value["shell"] === "object"` 的话,守卫声称的是「有这个服务」而实际证明的只是
7
- * 「有个对象」——运行到一半才炸。逐个点名方法,守卫才名副其实。
6
+ * 只做 `typeof value["shell"] === "object"` 的话,检查声称的是「有这个服务」而实际证明的只是
7
+ * 「有个对象」——运行到一半才报错。逐个写出方法名,检查才名副其实。
8
8
  *
9
9
  * `Object.prototype` 的默认成员不算服务实现:`hasMethods({}, ["toString"])` 若照单全收,
10
- * 一个普通对象就能冒充服务面。判据是**身份**——解析到的函数正是 `Object.prototype` 上
11
- * 那一枚才排除,所以类/中间原型上的真实实现(包括对原型成员的重写)照常可识别,
12
- * cordis 方法挂在原型上的承重面不受影响。
10
+ * 一个普通对象就能冒充服务。判断是**身份**——解析到的函数正是 `Object.prototype` 上
11
+ * 那一个才排除,所以类/中间原型上的真实实现(包括对原型成员的重写)照常可识别,
12
+ * cordis 方法挂在原型上的读取不受影响。
13
13
  * @param value 待验证的服务候选(来自 `ctx.get(...)` 的 unknown)
14
- * @param methods 该服务面必须可调用的方法名
15
- * @returns 是否为可用的服务面
14
+ * @param methods 该服务必须可调用的方法名
15
+ * @returns 是否为可用的服务
16
16
  */
17
17
  export declare function hasMethods(value: unknown, methods: readonly string[]): boolean;
18
18
  /**
19
19
  * 从 `unknown` 投影单个字段;非 `Record` → `undefined`。
20
20
  *
21
- * ⚠ **取值走原型链,这是承重的,不是疏忽**:`value[key]` 不做 `hasOwn` 判定,所以原型上的
22
- * 方法照样取得到。消费方正靠这一条做**能力探测**——cordis 服务对象的方法挂在原型上、不是
21
+ * ⚠ **取值走原型链,这是有意为之,不是疏忽**:`value[key]` 不做 `hasOwn` 判定,所以原型上的
22
+ * 方法照样取得到。消费方正靠这一条做**能力检查**——cordis 服务对象的方法挂在原型上、不是
23
23
  * 自有键,形如 `typeof fieldOf(settings, "describe") === "function"`、
24
24
  * `hasMethods(fieldOf(value, "timer"), ["timeout"])`、`typeof fieldOf(session,
25
25
  * "snapshotEvents") === "function"`(消费包的调用点记在 test/record.test.ts)。
26
26
  * 改成 `Object.hasOwn` 会让上面几处**一律判否**,那些包随即认不出合法的 cordis 服务——
27
- * 而这类误判的表现是「能力探测静默返回 false」,不是报错,极难往这里归因。
27
+ * 而这类误判的表现是「能力检查静默返回 false」,不是报错,极难往这里归因。
28
28
  *
29
29
  * 代价是明写在这里:键名取到的是原型成员时(`fieldOf({}, "__proto__")` 交回
30
30
  * `Object.prototype`、`fieldOf({}, "constructor")` 交回 `Object`)。这是本函数的取舍,
31
31
  * 不是让消费方猜的属性——现存调用点的键名全是字面量,没有一处来自数据。
32
- * test/record.test.ts 两侧都钉住了:原型方法取得到(承重面),自有键优先于原型键。
32
+ * test/record.test.ts 两侧都有用例固定:原型方法取得到(这是关键行为),自有键优先于原型键。
33
33
  *
34
- * ⚠ 本段刻意不点名兄弟包:**声明上的 JSDoc 会被 tsc 抄进 dist/types/record.d.ts 一起
34
+ * ⚠ 本段刻意不写出兄弟包的名字:**声明上的 JSDoc 会被 tsc 抄进 dist/types/record.d.ts 一起
35
35
  * 发出去**,包名会让装了这个包的消费方以为存在一个并不存在的依赖
36
- *(test/publish-manifest.test.ts 的产物面判据会红)。要写具体调用点就写在 test/ 里。
36
+ *(test/publish-manifest.test.ts 的产物检查会判定失败)。要写具体调用点就写在 test/ 里。
37
37
  *
38
- * 读取走的是探测式出口:被测对象带抛错 getter / Proxy 陷阱时按「取不到」交回
39
- * `undefined`,异常不冒泡(能力探测不该把流程炸掉)。
38
+ * 读取走的是带保护的出口:被测对象带抛错 getter / Proxy trap 时按「取不到」交回
39
+ * `undefined`,异常不冒泡(能力检查不该打断流程)。
40
40
  */
41
41
  export declare function fieldOf(value: unknown, key: string): unknown;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * 保留前 `maxChars` 个码元;切点切开代理对时丢掉尾部那个孤立高代理。
2
+ * 保留前 `maxChars` 个码元;截断位置切开代理对时丢掉尾部那个孤立高代理。
3
3
  * `maxChars <= 0` 与 NaN ⇒ 空串。
4
4
  */
5
5
  export declare function truncateEnd(text: string, maxChars: number): string;
6
6
  /**
7
- * 保留后 `minChars` 个码元;切点切开代理对时丢掉首部那个孤立低代理。
8
- * `minChars` 不足一枚码元(含 `<= 0`、NaN、`0 < minChars < 1`)与 `truncateEnd(text, 0)`
9
- * 同口径返回空串,不返回整串。
7
+ * 保留后 `minChars` 个码元;截断位置切开代理对时丢掉首部那个孤立低代理。
8
+ * `minChars` 不足一个码元(含 `<= 0`、NaN、`0 < minChars < 1`)与 `truncateEnd(text, 0)`
9
+ * 相同,返回空串,不返回整串。
10
10
  */
11
11
  export declare function truncateStart(text: string, minChars: number): string;
@@ -2,28 +2,28 @@ import type { SessionEvent } from "@deepseek-ai/dsh-session";
2
2
  export type { SessionEvent } from "@deepseek-ai/dsh-session";
3
3
  export type { PtcDispatchEventData, PtcDispatchStartEventData } from "@deepseek-ai/dsh-tools/types";
4
4
  /**
5
- * 一条 tool/call 的记账记录。
5
+ * 一条 tool/call 的记录。
6
6
  *
7
- * 字段一律 `readonly`:台账一旦入表就只作证据读,账目被就地改写会让证据链失去意义。
7
+ * 字段一律 `readonly`:记录一旦入表就只作证据读,记录被就地改写会让证据链失去意义。
8
8
  */
9
9
  export interface ToolCallRecord {
10
10
  /**
11
- * 调用名(edit/write/str_replace_editor/read/grep/…)——本模块的**分桶键**,必选。
11
+ * 调用名(edit/write/str_replace_editor/read/grep/…)——本模块的**分组键**,必选。
12
12
  *
13
13
  * 官方把它声明成必选 `string`(`tool/call` 见 packages/core/session/src/types.ts:361,
14
14
  * PtcDispatchStartEventData.name 见 packages/core/tools/src/types.ts:15),但运行期拦不住
15
- * (依据见 usableName)。所以可空性在**扫描层**就处理掉了,不带到类型面:callRecordOf 遇到
16
- * 缺可用 name 的调用类事件整条跳过,与 hasObjectData 同一口径。
15
+ * (依据见 usableName)。所以可空性在**扫描层**就处理掉了,不带到类型声明:callRecordOf 遇到
16
+ * 缺可用 name 的调用类事件整条跳过,与 hasObjectData 同一规则。
17
17
  *
18
18
  * 为什么不把过滤留给消费方:下游分类全建立在 name 上——editPathOf 的 str_replace_editor
19
- * 只读判定、门禁侧的 EDIT_TOOLS 与 refSearchTools 名单——而 editPathOf 的兜底分支会把任何
20
- * 非 str_replace_editor 的调用一律判成 write。无名调用一旦进了台账,只要它的 arguments
19
+ * 只读判定、检查侧的 EDIT_TOOLS 与 refSearchTools 名单——而 editPathOf 的回退分支会把任何
20
+ * 非 str_replace_editor 的调用一律判成 write。无名调用一旦进了记录,只要它的 arguments
21
21
  * 里有 file_path 就算成写操作。现在是靠三个消费方各自记得先判一次才没出事——那是同一条
22
22
  * 不变式的三份手抄,漏一份就出事。
23
23
  *
24
- * 与 callId 的差别是**为什么一个必选、一个可空**:callId 由 usableCallId 兜底,且它有
25
- * nc:<seq> 兜底席位(消费方的 noCallId 桶专门收那种行),缺了不影响归类;name 一旦缺了
26
- * 就没法分桶、没有兜底席位,只能在入口滤掉。
24
+ * 与 callId 的差别是**为什么一个必选、一个可空**:callId 由 usableCallId 回退,且它有
25
+ * `nc:<seq>` 回退值(消费方的 noCallId 组专门收那种行),缺了不影响归类;name 一旦缺了
26
+ * 就没法分组、没有回退值,只能在入口滤掉。
27
27
  */
28
28
  readonly name: string;
29
29
  /** callId:tool/result 串联键;缺失时由调用方按 seq 造 `nc:` 键。 */
@@ -32,8 +32,8 @@ export interface ToolCallRecord {
32
32
  readonly arguments: Record<string, unknown>;
33
33
  /**
34
34
  * arguments 原始值无法解析为对象(JSON 字符串非法、或非字符串非对象)。
35
- * 调用方据此保留各自语义——有的把坏参判成"这次编辑不记账",有的仍记账但路径留空。
36
- * 共享层只标记不决策:把两档策略统一掉,等于在其中一侧悄悄改掉门禁的松紧。
35
+ * 调用方据此保留各自语义——有的把无效参数判成"这次编辑不入记录",有的仍记录但路径留空。
36
+ * 共享层只标记不决策:把两档策略统一掉,等于在其中一侧悄悄改掉检查的宽严。
37
37
  */
38
38
  readonly badArguments: boolean;
39
39
  /**
@@ -48,24 +48,24 @@ export interface ToolResultRecord {
48
48
  readonly isError: boolean;
49
49
  readonly seq: number;
50
50
  }
51
- /** 一条事件产出的记账行(call 与 result 互斥;非记账事件两位都是 undefined)。 */
51
+ /** 一条事件产出的记录行(call 与 result 互斥;非记录事件两位都是 undefined)。 */
52
52
  export interface ToolEventRows {
53
53
  readonly call: ToolCallRecord | undefined;
54
54
  readonly result: ToolResultRecord | undefined;
55
55
  }
56
56
  /**
57
- * **单条事件**的记账行(`scanToolEvents` 的一步,也是它唯一的实现处)。
57
+ * **单条事件**的记录行(`scanToolEvents` 的一步,也是它唯一的实现处)。
58
58
  *
59
- * 之所以导出:有的消费方要把同一套台账折成投影单元(增量折叠),折叠与"注册表缺席时的全量
60
- * 扫描"必须逐字同源——两处各写一遍字段读取,就会在两行上给出不同的证据链(那是安全门禁的
59
+ * 之所以导出:有的消费方要把同一套记录折成投影单元(增量折叠),折叠与"注册表缺席时的全量
60
+ * 扫描"必须逐字同源——两处各写一遍字段读取,就会在两行上给出不同的证据链(那是安全检查的
61
61
  * fail-open)。折叠侧传 `event.seq`,扫描侧传数组下标,两者在官方 `seq = log.length` 的连号
62
- * 契约下是同一个值。
62
+ * 约定下是同一个值。
63
63
  */
64
64
  export declare function toolEventRowsOf(event: SessionEvent, index: number): ToolEventRows;
65
65
  /**
66
66
  * 扫描事件流 → tool/call 与 tool/result 两张表。
67
- * 只收集形状可用的事件,其余静默跳过——调用方(门禁/证据链)以空表安全降级,不因零星
68
- * 坏事件抛错。数组空洞(类型面不可表示、重放数据里却可能出现)同样跳过。
67
+ * 只收集形状可用的事件,其余静默跳过——调用方(检查/证据链)以空表安全回退,不因零星
68
+ * 坏事件抛错。数组空洞(类型声明不可表示、重放数据里却可能出现)同样跳过。
69
69
  * seq 取事件在入参数组里的下标(与折叠侧按 seq 归因的语义同一个值)。
70
70
  */
71
71
  export declare function scanToolEvents(events: readonly SessionEvent[]): {
@@ -76,7 +76,7 @@ export declare function scanToolEvents(events: readonly SessionEvent[]): {
76
76
  * {@link editPathOf} 的返回值。按 `kind` 判别联合。
77
77
  *
78
78
  * 只有两支:**`write` / `read-view`**。取不到路径时仍返回所属那一支、只把 `path` 置为
79
- * `undefined`——判据是工具名与 `command`,与路径取不取得到无关;再多一枚 `skip` 只会逼
79
+ * `undefined`——判断依据是工具名与 `command`,与路径取不取得到无关;再多一个 `skip` 只会逼
80
80
  * 调用方写出永不执行的分支。路径有没有由 `path === undefined` 表达。
81
81
  */
82
82
  export type EditTarget = {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@jayyuen66/dsh-plugin-shared",
3
- "version": "0.1.9",
4
- "description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + 卡片 apply/差异骨架 + 官方作业结局映射 + 项目键派生 + 代理对安全截断。作为库包被 import,不由用户启用",
3
+ "version": "0.2.0",
4
+ "description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + 卡片 apply/差异骨架 + 统一设置卡 UI 组件 + 官方作业结局映射 + 项目键派生 + 代理对安全截断。作为库包被 import,不由用户启用",
5
5
  "keywords": [
6
6
  "cordis",
7
7
  "dsh",
8
8
  "plugin",
9
+ "settings-card",
9
10
  "tool-events"
10
11
  ],
11
12
  "homepage": "https://github.com/JayYuen666/dsh-plugin-shared#readme",
@@ -45,6 +46,10 @@
45
46
  "types": "./dist/types/card-diff.d.ts",
46
47
  "default": "./dist/card-diff.js"
47
48
  },
49
+ "./lib/card-ui": {
50
+ "types": "./dist/types/card-ui.d.ts",
51
+ "default": "./dist/card-ui.js"
52
+ },
48
53
  "./lib/project-key": {
49
54
  "types": "./dist/types/project-key.d.ts",
50
55
  "default": "./dist/project-key.js"
@@ -125,11 +130,16 @@
125
130
  "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
126
131
  "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
127
132
  "@types/node": "26.6.5",
133
+ "@types/react": "19.3.0",
134
+ "@types/react-dom": "19.3.0",
128
135
  "@vitest/coverage-v8": "5.0.5",
129
136
  "eslint-plugin-sonarjs": "4.2.2",
130
- "oxfmt": "0.74.0",
131
- "oxlint": "1.89.0",
137
+ "happy-dom": "21.0.4",
138
+ "oxfmt": "0.75.0",
139
+ "oxlint": "1.90.0",
132
140
  "oxlint-tsgolint": "7.0.2005",
141
+ "react": "^19.3.0",
142
+ "react-dom": "^19.3.1",
133
143
  "rolldown": "1.2.15",
134
144
  "typescript": "7.0.2",
135
145
  "vite": "8.3.5",
@@ -143,7 +153,8 @@
143
153
  "@deepseek-ai/dsh-output-retention": "0.2.1-rc.3",
144
154
  "@deepseek-ai/dsh-session": "0.2.1-rc.3",
145
155
  "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
146
- "@deepseek-ai/dsh-tools": "0.2.1-rc.3"
156
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
157
+ "react": "^19.3.0"
147
158
  },
148
159
  "peerDependenciesMeta": {
149
160
  "@deepseek-ai/cordis": {
@@ -169,6 +180,9 @@
169
180
  },
170
181
  "@deepseek-ai/dsh-tools": {
171
182
  "optional": true
183
+ },
184
+ "react": {
185
+ "optional": true
172
186
  }
173
187
  },
174
188
  "icon": "icon.svg",