@moonquake2004/dsh-doctor 0.7.2 → 0.8.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/CHANGELOG.md +39 -0
- package/dsh-doctor.mjs +104 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,45 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [0.8.0] — 2026-09-16
|
|
20
|
+
|
|
21
|
+
### 结构性修复:假绿灯的**类**被消灭,而不是又一个实例
|
|
22
|
+
|
|
23
|
+
用户指出:**刚复查过又犯错,说明规则本身有缺陷**。复查确认了缺陷的性质——**R3 的强制点选错了**:
|
|
24
|
+
它被写在 `report()` 里,而判定并不都在那里产生。机械枚举(而非凭记忆)发现 **21 处代码位置手写 ✓/⊖/✗**,
|
|
25
|
+
其中我只加固过 3 处;另外两处从未查过:
|
|
26
|
+
|
|
27
|
+
| 位置 | 修前行为 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `--boot-check` | 零对象仍打印"✓ 所有可探测 entry 均可导入" |
|
|
30
|
+
| `--safe-add` | 零对象 `ok:true, stage:'verified'` **并写"已知良好"快照**(把"未验证"固化成"已验证") |
|
|
31
|
+
| `--post-upgrade` | 同样打印"✓ 装载模拟通过"并写快照 |
|
|
32
|
+
| **顶层聚合** | 极简 PATH 下 18 skip / 2 pass,仍打印 **"✓ 全部通过"**(从未查过) |
|
|
33
|
+
| **JSON 形态** | `ok: true` 只要求"无失败","全部跳过"也算通过 |
|
|
34
|
+
|
|
35
|
+
**改为收口点结构**(R10 边界律 + R11 闭集律):
|
|
36
|
+
|
|
37
|
+
- **`makeVerdict(state, opts)`** —— 唯一能构造 `pass` 的地方;`checked === 0` 时**构造期即抛错**(不是渲染期补救);
|
|
38
|
+
- **`printVerdict()` / `printItem()` / `VERDICT_MARKS`** —— **唯一**打印 `✓/⊖/✗` 的三个位置,
|
|
39
|
+
调用点传**状态名**而不是符号(于是符号泄漏在结构上不可能);
|
|
40
|
+
- **`aggregateVerdict()`** —— 聚合判定如实给出 `verified / skipped / failed`:
|
|
41
|
+
全部跳过时输出 `⊖ 没有任何检查实际验证过(N 项全部跳过)——这不等于通过`;
|
|
42
|
+
有跳过时输出 `✓ N 项通过、M 项未检查(skip)——未检查的部分不代表通过`;
|
|
43
|
+
- **JSON 契约收紧**:`ok` 只有在**确实验证过(verified > 0)且无失败**时才为 true,并新增
|
|
44
|
+
`verified / skipped / failed` 三个字段。
|
|
45
|
+
|
|
46
|
+
### Added — 审计与规则的机制化
|
|
47
|
+
|
|
48
|
+
- **`scripts/audit.sh`**(R12 可重审律 / R13 环境泛化律):一条命令、五个环境、结果可比——
|
|
49
|
+
空环境 / 极简 PATH(无 dsh、pnpm、zstd)/ 畸形输入(BOM、截断 JSON)/ 清单一致性 / 闭集与全套测试。
|
|
50
|
+
当前 **7/7 通过**。
|
|
51
|
+
- **闭集测试**(R11):断言源码中任何 `✓/⊖/✗` 都必须落在三个收口点内——**今后新加一处手写绿灯会直接红**。
|
|
52
|
+
- **规则补 4 条**(`docs/check-authoring-rules.md`):**R10 边界律**(每条规则必须指明强制点,且是所有同类输出的必经之路)、
|
|
53
|
+
**R11 闭集律**、**R12 可重审律**、**R13 环境泛化律**;§4 记录本次复查,包括**我在这轮审计里又犯的一次 R8**
|
|
54
|
+
(审计脚本第一版断言"空环境 verified 必须为 0",而空 profile 里 P15 确实验证了 3 个文件——断言强度超过了证据)。
|
|
55
|
+
|
|
56
|
+
测试 124 → 127。
|
|
57
|
+
|
|
19
58
|
## [0.7.2] — 2026-09-16
|
|
20
59
|
|
|
21
60
|
### 用新规则复查旧错误 —— **确认重复犯了同一个错**
|
package/dsh-doctor.mjs
CHANGED
|
@@ -270,6 +270,71 @@ function reportSkip(section, id, detail, src) {
|
|
|
270
270
|
results.push({ section, id, ok: true, skip: true, detail, src: src ?? 'builtin' });
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
|
|
274
|
+
/* ================= 判定收口点(R10 边界律 + R11 闭集律) =================
|
|
275
|
+
* 2026-09 复查的结论:**"绿"此前可以在任意位置被手写出来**,所以 R3 的不变量只要放在
|
|
276
|
+
* report() 或某个输出路径里,别的路径就能绕过——修了三处、漏了顶层聚合与更新提示。
|
|
277
|
+
*
|
|
278
|
+
* 现在:**所有 ✓ / ⊖ / ✗ 只能由 printVerdict() 打印**,而"通过"这个状态只能由 makeVerdict() 构造,
|
|
279
|
+
* 且 `pass` 在 `checked === 0` 时**直接抛错**(构造期就挡住,而不是渲染期才补救)。
|
|
280
|
+
* 测试里有一条闭集断言:源码中任何出现 ✓/⊖/✗ 的代码行,都必须落在 printVerdict 的函数体内。
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 构造判定。**唯一能产生 'pass' 的地方**。
|
|
285
|
+
* @param {'pass'|'fail'|'skip'|'action'} state
|
|
286
|
+
* @param {{checked?: number, detail?: string, reason?: string, totals?: {verified:number, skipped:number, failed:number}}} opts
|
|
287
|
+
*/
|
|
288
|
+
function makeVerdict(state, opts = {}) {
|
|
289
|
+
if (state === 'pass') {
|
|
290
|
+
const n = opts.checked;
|
|
291
|
+
if (typeof n !== 'number' || n <= 0) {
|
|
292
|
+
throw new Error(`makeVerdict('pass') 需要 checked > 0:零检查对象不得构造为"通过"(R3 覆盖律;configured=${JSON.stringify(opts.checked)})`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { state, ...opts };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 条目行打印器:**判定之下的明细**(某条 entry 失败、某个会话损坏……)。
|
|
300
|
+
* 与 printVerdict 的分工:判定回答"整件事成不成立",条目回答"具体哪几条"。
|
|
301
|
+
* 两者构成本工具的**闭集**——闭集测试断言:源码里任何 ✓/⊖/✗ 都必须落在这两个函数体内。
|
|
302
|
+
*/
|
|
303
|
+
const VERDICT_MARKS = { pass: '✓', fail: '✗', skip: '⊖', warn: '⚠', action: '✓' };
|
|
304
|
+
|
|
305
|
+
function printItem(state, text, indent = 2) {
|
|
306
|
+
console.log(`${' '.repeat(indent)}${VERDICT_MARKS[state] ?? '?'} ${text}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** **唯一**打印 ✓ / ⊖ / ✗ 的**判定**的地方。任何新输出路径都必须走这里,否则闭集测试会红。 */
|
|
310
|
+
function printVerdict(v, opts = {}) {
|
|
311
|
+
const out = opts.stream === 'stderr' ? console.error : console.log;
|
|
312
|
+
const line = (state, text) => out(opts.indent ? ` ${VERDICT_MARKS[state]} ${text}` : `${VERDICT_MARKS[state]} ${text}`);
|
|
313
|
+
if (v.state === 'pass') return line('pass', v.detail ?? `通过(检查 ${v.checked} 个对象)`);
|
|
314
|
+
if (v.state === 'fail') return line('fail', v.detail ?? '失败');
|
|
315
|
+
if (v.state === 'skip') return line('skip', `${v.reason ?? '未检查'}——**这不等于通过**`);
|
|
316
|
+
return line('action', v.detail ?? ''); // action:动作确认(不是验证断言)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 报告级聚合判定:把"验证过多少 / 跳过多少"如实呈现。
|
|
321
|
+
* 修前它对"18 项 skip + 2 项 pass"照样打印 "✓ 全部通过"——断言的强度超过了证据(R8)。
|
|
322
|
+
*/
|
|
323
|
+
function aggregateVerdict(records) {
|
|
324
|
+
const failed = records.filter((r) => !r.skip && !r.ok);
|
|
325
|
+
const verified = records.filter((r) => !r.skip && r.ok && (r.examined ?? 0) > 0).length;
|
|
326
|
+
const skipped = records.filter((r) => r.skip).length;
|
|
327
|
+
const totals = { verified, skipped, failed: failed.length };
|
|
328
|
+
if (failed.length) return makeVerdict('fail', { totals, detail: `${failed.length} 个问题` });
|
|
329
|
+
if (verified === 0) {
|
|
330
|
+
return makeVerdict('skip', { totals, reason: `没有任何检查实际验证过(${skipped} 项全部跳过)` });
|
|
331
|
+
}
|
|
332
|
+
if (skipped > 0) {
|
|
333
|
+
return makeVerdict('pass', { checked: verified, totals, detail: `${verified} 项通过、${skipped} 项未检查(skip)——**未检查的部分不代表通过**` });
|
|
334
|
+
}
|
|
335
|
+
return makeVerdict('pass', { checked: verified, totals, detail: `全部通过(${verified} 项,均已验证)` });
|
|
336
|
+
}
|
|
337
|
+
|
|
273
338
|
/** 解析 --profile 参数:名字(如 web)→ $DSH_HOME/profiles/<name>;含路径分隔符/~/开头 → 直接当 profile 目录(契约 harness 传绝对路径)。 */
|
|
274
339
|
function resolveProfile(name) {
|
|
275
340
|
if (!name) throw new Error('无效 profile 名');
|
|
@@ -2887,7 +2952,7 @@ async function run() {
|
|
|
2887
2952
|
const base = writePreUpgrade(profDir);
|
|
2888
2953
|
if (jsonOut) console.log(JSON.stringify({ ok: true, ...base }, null, 2));
|
|
2889
2954
|
else {
|
|
2890
|
-
|
|
2955
|
+
printVerdict(makeVerdict('action', { detail: `已记录升级前基线(${profDir})` }));
|
|
2891
2956
|
console.log(` 核心版本: ${base.coreVersion ?? '(未能确定)'} | bundle ${base.bundleCount} 个 | entry ${Object.keys(base.entries).length} 条`);
|
|
2892
2957
|
console.log(' 下一步:升级 dsh,然后运行');
|
|
2893
2958
|
console.log(` npx @moonquake2004/dsh-doctor --post-upgrade --profile ${profileArg || 'web'}`);
|
|
@@ -2897,7 +2962,7 @@ async function run() {
|
|
|
2897
2962
|
}
|
|
2898
2963
|
const base = readPreUpgrade(profDir);
|
|
2899
2964
|
if (!base) {
|
|
2900
|
-
|
|
2965
|
+
printVerdict(makeVerdict('fail', { detail: `没找到升级前基线(${join(profDir, PRE_UPGRADE_FILE)})——请先在升级前运行 --pre-upgrade` }), { stream: 'stderr' }); // 错误路径:非判定
|
|
2901
2966
|
process.exit(1);
|
|
2902
2967
|
}
|
|
2903
2968
|
const nowCore = coreVersion();
|
|
@@ -2920,12 +2985,12 @@ async function run() {
|
|
|
2920
2985
|
for (const l of lines) console.log(` · ${l}`);
|
|
2921
2986
|
const verdictPost = bootVerdict(results);
|
|
2922
2987
|
if (verdictPost.state === 'skip') {
|
|
2923
|
-
|
|
2988
|
+
printVerdict(makeVerdict('skip', { reason: `${verdictPost.reason}——不改写已知良好基线` }), { indent: true });
|
|
2924
2989
|
} else if (verdictPost.state === 'pass') {
|
|
2925
|
-
|
|
2990
|
+
printVerdict(makeVerdict('pass', { checked: verdictPost.checked, detail: '装载模拟通过——升级未破坏任何可探测 entry' }), { indent: true });
|
|
2926
2991
|
writeSnapshot(profDir, nowSnap);
|
|
2927
2992
|
} else {
|
|
2928
|
-
|
|
2993
|
+
printVerdict(makeVerdict('fail', { detail: `装载模拟失败 ${failed.length} 条(这就是"升级后起不来"的直接原因):` }), { indent: true });
|
|
2929
2994
|
for (const f of failed) {
|
|
2930
2995
|
console.log(` [${f.bundle}] ${f.id} → ${f.spec}`);
|
|
2931
2996
|
console.log(` ${f.kind}: ${f.error}`);
|
|
@@ -2948,15 +3013,15 @@ async function run() {
|
|
|
2948
3013
|
const profDir = resolveProfile(profileArg || 'web');
|
|
2949
3014
|
const r = safeAdd(profileArg || 'web', safeAddArg);
|
|
2950
3015
|
if (jsonOut) console.log(JSON.stringify(r, null, 2));
|
|
2951
|
-
else if (r.ok && r.stage === 'verified')
|
|
3016
|
+
else if (r.ok && r.stage === 'verified') printVerdict(makeVerdict('pass', { checked: r.checked, detail: `已安装并预检通过(${r.checked} 条 entry 均可导入)——重启 dsh 即可` }));
|
|
2952
3017
|
else if (r.ok && r.stage === 'quarantined') {
|
|
2953
3018
|
console.log(`⚠ 已安装,但该插件的 entry 导入失败,已自动隔离以避免 dsh 起不来:`);
|
|
2954
3019
|
for (const f of r.failures || []) console.log(` ${f}`);
|
|
2955
3020
|
console.log(` 隔离项:${r.quarantined.join(', ')}(用 --unquarantine <包名> 放回,修好版本后再重试)`);
|
|
2956
3021
|
console.log(` dsh 现在可以正常启动。`);
|
|
2957
|
-
} else if (r.stage === 'install')
|
|
3022
|
+
} else if (r.stage === 'install') printVerdict(makeVerdict('fail', { detail: `安装命令本身失败(exit ${r.exit}),已还原 manifest` }));
|
|
2958
3023
|
else {
|
|
2959
|
-
|
|
3024
|
+
printVerdict(makeVerdict('fail', { detail: '安装后无法启动,且隔离也救不回来 → 已整体回滚到安装前状态' }));
|
|
2960
3025
|
for (const f of r.failures || []) console.log(` ${f}`);
|
|
2961
3026
|
}
|
|
2962
3027
|
process.exit(r.ok ? 0 : 2);
|
|
@@ -2966,13 +3031,13 @@ async function run() {
|
|
|
2966
3031
|
try {
|
|
2967
3032
|
if (quarantineArg || unquarantineArg) {
|
|
2968
3033
|
const r = quarantineBundle(profDir, quarantineArg || unquarantineArg, !!unquarantineArg);
|
|
2969
|
-
if (!r.ok) {
|
|
3034
|
+
if (!r.ok) { printVerdict(makeVerdict('fail', { detail: r.error })); process.exit(1); }
|
|
2970
3035
|
const payload = { ok: true, action: unquarantineArg ? 'unquarantine' : 'quarantine', ...r, next: '重启 dsh;随后用 --boot-check 复查,或用 --unquarantine 撤销' };
|
|
2971
3036
|
if (jsonOut) console.log(JSON.stringify(payload, null, 2));
|
|
2972
3037
|
else if (unquarantineArg) {
|
|
2973
|
-
|
|
3038
|
+
printVerdict(makeVerdict('action', { detail: `已放回 ${unquarantineArg}(当前启动列表 ${r.bundles} 项)——重启 dsh 生效` }));
|
|
2974
3039
|
} else {
|
|
2975
|
-
|
|
3040
|
+
printVerdict(makeVerdict('action', { detail: `已隔离 ${quarantineArg}(启动列表现为 ${r.bundles} 项;原始 package.json 已备份)` }));
|
|
2976
3041
|
console.log(` 重启 dsh,然后用 --boot-check 复查;要放回:--unquarantine ${quarantineArg}`);
|
|
2977
3042
|
}
|
|
2978
3043
|
process.exit(0);
|
|
@@ -3005,7 +3070,7 @@ async function run() {
|
|
|
3005
3070
|
if (jsonOut) {
|
|
3006
3071
|
console.log(JSON.stringify({ ok: true, skip: true, reason: '启动列表为空:没有任何 entry 可探测,未做装载模拟', profile: profDir, checked: 0, failed: 0, drift, results }, null, 2));
|
|
3007
3072
|
} else {
|
|
3008
|
-
console.log(`装载模拟(${profDir}
|
|
3073
|
+
console.log(`装载模拟(${profDir}):`); printVerdict(makeVerdict('skip', { reason: '无可检查对象——启动列表为空,未做任何装载模拟' }));
|
|
3009
3074
|
console.log(' 若你预期这里有插件:检查 profile 的 dsh.profile.bundles 是否为空,或插件是否装到了别的 profile。');
|
|
3010
3075
|
}
|
|
3011
3076
|
process.exit(0);
|
|
@@ -3016,10 +3081,10 @@ async function run() {
|
|
|
3016
3081
|
} else {
|
|
3017
3082
|
console.log(`装载模拟(${profDir}):检查 ${results.length} 条 entry,失败 ${failed.length} 条`);
|
|
3018
3083
|
for (const r of results) {
|
|
3019
|
-
if (r.status === 'failed')
|
|
3020
|
-
else if (r.status === 'skipped')
|
|
3084
|
+
if (r.status === 'failed') printItem('fail', `[${r.bundle}] ${r.id} → ${r.spec}\n ${r.kind}: ${r.error}\n 修复方向:${r.hint}\n 先起来:npx @moonquake2004/dsh-doctor --quarantine ${r.bundle}`);
|
|
3085
|
+
else if (r.status === 'skipped') printItem('skip', `${r.id}(${r.reason})`);
|
|
3021
3086
|
}
|
|
3022
|
-
if (!failed.length)
|
|
3087
|
+
if (!failed.length) printVerdict(makeVerdict('pass', { checked: results.length, detail: '所有可探测 entry 均可导入——启动失败若仍发生,问题多在配置合并或原生环境,请贴 --json 输出' }), { indent: true });
|
|
3023
3088
|
}
|
|
3024
3089
|
process.exit(failed.length ? 2 : 0);
|
|
3025
3090
|
} catch (e) {
|
|
@@ -3228,7 +3293,15 @@ async function run() {
|
|
|
3228
3293
|
// 于是把 warn 当成误报。状态本就不该由消费者自行推导。
|
|
3229
3294
|
const statusOf = (r) => (r.skip ? 'skip' : (!r.ok ? (((r.section === 'security') ? r.severity !== 'critical' : catalogSeverity.get(r.id) === 'warn') ? 'warn' : 'fail') : 'pass'));
|
|
3230
3295
|
const checksWithStatus = results.map((r) => ({ ...r, status: statusOf(r) }));
|
|
3231
|
-
|
|
3296
|
+
const aggJson = aggregateVerdict(results);
|
|
3297
|
+
console.log(JSON.stringify({
|
|
3298
|
+
// ok 只有在"确实验证过且无失败"时才为 true —— 修前它只看 bad.length,于是"全部跳过"也会 ok:true
|
|
3299
|
+
ok: aggJson.state === 'pass',
|
|
3300
|
+
verified: aggJson.totals.verified,
|
|
3301
|
+
skipped: aggJson.totals.skipped,
|
|
3302
|
+
failed: aggJson.totals.failed,
|
|
3303
|
+
checks: checksWithStatus, catalog: catalogMeta, update: updateInfo, ...(securityMeta.enabled ? { security: securityMeta } : {}),
|
|
3304
|
+
}, null, 2));
|
|
3232
3305
|
} else {
|
|
3233
3306
|
const sectionOrder = { env: 0, profile: 1, session: 2, catalog: 3 };
|
|
3234
3307
|
const ordered = [...results].sort((a, b) => (sectionOrder[a.section] ?? 9) - (sectionOrder[b.section] ?? 9));
|
|
@@ -3237,18 +3310,27 @@ async function run() {
|
|
|
3237
3310
|
if (r.section !== lastSection) { console.log(`\n== ${r.section === 'security' ? '🔒 安全' : r.section.toUpperCase()} ==`); lastSection = r.section; }
|
|
3238
3311
|
const sev = catalogSeverity.get(r.id);
|
|
3239
3312
|
// 安全检查:skip 显示 ⊖;critical/high 失败 ✗;medium 及以下失败 ⚠(不影响退出码)
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
: '
|
|
3243
|
-
|
|
3313
|
+
// 状态名(不是符号):符号只由 printItem/printVerdict 决定,闭集测试据此断言
|
|
3314
|
+
const mark = r.skip ? 'skip'
|
|
3315
|
+
: (!r.ok ? (((r.section === 'security' && r.severity !== 'critical' && r.severity !== 'high') || sev === 'warn') ? 'warn' : 'fail')
|
|
3316
|
+
: 'pass');
|
|
3317
|
+
printItem(mark, `[${r.id}]${r.severity ? `(${r.severity}${r.skip ? '/skip' : ''})` : ''} ${r.detail}${r.src === 'catalog' ? ' [目录]' : ''}`, 1);
|
|
3244
3318
|
if (!r.ok && r.fix) console.log(` ↳ 修复: ${r.fix}`);
|
|
3245
3319
|
}
|
|
3246
3320
|
if (updateInfo.available && !updateInfo.applied) {
|
|
3247
3321
|
console.log(`\n⚠ 新版本 ${updateInfo.latest} 可用(当前 ${updateInfo.current})→ 运行 \`dsh-doctor --update\` 或 \`dsh plugin update\``);
|
|
3248
3322
|
} else if (updateInfo.applied) {
|
|
3249
|
-
console.log(
|
|
3323
|
+
console.log(''); printVerdict(makeVerdict('action', { detail: updateInfo.applied }));
|
|
3324
|
+
}
|
|
3325
|
+
const agg = aggregateVerdict(results.map((r) => ({ ...r, examined: r.examined })));
|
|
3326
|
+
if (agg.state === 'fail') {
|
|
3327
|
+
console.log('');
|
|
3328
|
+
printVerdict(makeVerdict('fail', { detail: `${bad.length} 个内置问题${secExit > 0 ? ` + 安全 ${secExit === 2 ? 'CRITICAL' : 'HIGH'} 级失败` : ''}(profile=${profileArg},目录=${catalogMeta.source},${catalogMeta.checks} 条)` }));
|
|
3329
|
+
} else {
|
|
3330
|
+
console.log('');
|
|
3331
|
+
printVerdict(agg);
|
|
3332
|
+
console.log(`(profile=${profileArg},目录=${catalogMeta.source},${catalogMeta.checks} 条)`);
|
|
3250
3333
|
}
|
|
3251
|
-
console.log(`\n${(bad.length === 0 && secExit === 0) ? '✓ 全部通过' : `✗ ${bad.length} 个内置问题${secExit > 0 ? ` + 安全 ${secExit === 2 ? 'CRITICAL' : 'HIGH'} 级失败` : ''}`}(profile=${profileArg},目录=${catalogMeta.source},${catalogMeta.checks} 条)`);
|
|
3252
3334
|
}
|
|
3253
3335
|
// 最终退出码:内置失败 → 1;安全 HIGH → 1、CRITICAL → 2(取 max)
|
|
3254
3336
|
process.exit(Math.max(bad.length > 0 ? 1 : 0, secExit));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Offline diagnostic for DeepSeek Harness — 28 built-in + 5 catalog checks across env/profile/session (Layer A checks-as-data), self-update (Layer B), and a semi-automatic LLM observer (Layer C, --observe); Doctor panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|