oh-my-knowledge 0.37.0 → 0.38.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.
Files changed (92) hide show
  1. package/README.md +1 -1
  2. package/README.zh.md +1 -1
  3. package/dist/assets/agent-skills/omk/SKILL.md +1 -1
  4. package/dist/assets/agent-skills/omk/references/commands.md +21 -12
  5. package/dist/authoring/evolver.d.ts +4 -1
  6. package/dist/authoring/evolver.js +13 -8
  7. package/dist/cli/commands/doctor.d.ts +1 -0
  8. package/dist/cli/commands/doctor.js +26 -6
  9. package/dist/cli/commands/eval/gold/compare.js +7 -4
  10. package/dist/cli/commands/eval/index.d.ts +1 -0
  11. package/dist/cli/commands/eval/index.js +14 -3
  12. package/dist/cli/commands/evolve.d.ts +1 -0
  13. package/dist/cli/commands/evolve.js +45 -0
  14. package/dist/cli/commands/init.js +18 -6
  15. package/dist/cli/commands/list.d.ts +0 -2
  16. package/dist/cli/commands/list.js +2 -7
  17. package/dist/cli/commands/observe/inbox.d.ts +1 -0
  18. package/dist/cli/commands/observe/inbox.js +14 -4
  19. package/dist/cli/commands/observe/index.d.ts +11 -0
  20. package/dist/cli/commands/observe/index.js +35 -12
  21. package/dist/cli/commands/observe/ingest.d.ts +1 -0
  22. package/dist/cli/commands/observe/ingest.js +13 -4
  23. package/dist/cli/commands/observe/show.d.ts +1 -0
  24. package/dist/cli/commands/observe/show.js +12 -2
  25. package/dist/cli/commands/promote.js +1 -2
  26. package/dist/cli/commands/sample.js +15 -9
  27. package/dist/cli/commands/studio.d.ts +2 -0
  28. package/dist/cli/commands/studio.js +62 -12
  29. package/dist/cli/lib/cmd-flags.d.ts +8 -0
  30. package/dist/cli/lib/i18n-dict/common.js +1 -1
  31. package/dist/cli/lib/i18n-dict/evolve.d.ts +1 -1
  32. package/dist/cli/lib/i18n-dict/evolve.js +8 -0
  33. package/dist/cli/lib/i18n-dict/help.js +27 -45
  34. package/dist/cli/lib/i18n-dict/init.js +2 -2
  35. package/dist/cli/lib/parse-run-config.d.ts +0 -1
  36. package/dist/cli/lib/parse-run-config.js +6 -4
  37. package/dist/cli/lib/record-evolve-outcome.d.ts +31 -0
  38. package/dist/cli/lib/record-evolve-outcome.js +110 -0
  39. package/dist/cli/lib/resolve-skill-input.d.ts +6 -0
  40. package/dist/cli/lib/resolve-skill-input.js +4 -2
  41. package/dist/diagnosis/types.js +1 -1
  42. package/dist/doctor/index.js +4 -1
  43. package/dist/eval-core/artifact-index.d.ts +106 -0
  44. package/dist/eval-core/artifact-index.js +277 -0
  45. package/dist/eval-core/default-dirs.d.ts +34 -0
  46. package/dist/eval-core/default-dirs.js +36 -0
  47. package/dist/eval-core/evaluation-execution.js +3 -3
  48. package/dist/eval-core/evaluation-reporting.d.ts +7 -0
  49. package/dist/eval-core/evaluation-reporting.js +20 -5
  50. package/dist/eval-core/execution-strategy.js +3 -3
  51. package/dist/eval-core/measurement-dirs.d.ts +18 -0
  52. package/dist/eval-core/measurement-dirs.js +76 -0
  53. package/dist/eval-workflows/batch-evaluation-workflow.js +2 -1
  54. package/dist/eval-workflows/evaluation-pipeline/run-state.js +2 -1
  55. package/dist/eval-workflows/run-evaluation.js +5 -2
  56. package/dist/executors/codex-cli.js +2 -2
  57. package/dist/executors/script.js +43 -4
  58. package/dist/inputs/materialize-copy.d.ts +1 -1
  59. package/dist/inputs/materialize-copy.js +3 -3
  60. package/dist/inputs/skill-loader.d.ts +1 -1
  61. package/dist/inputs/skill-loader.js +1 -1
  62. package/dist/managed/index.d.ts +2 -0
  63. package/dist/managed/index.js +2 -0
  64. package/dist/managed/list-query.d.ts +7 -0
  65. package/dist/managed/list-query.js +11 -0
  66. package/dist/managed/source-probe.d.ts +15 -0
  67. package/dist/managed/source-probe.js +129 -0
  68. package/dist/managed/store.d.ts +10 -0
  69. package/dist/managed/store.js +25 -2
  70. package/dist/observability/inbox.js +6 -3
  71. package/dist/observability/skill-health-analyzer.d.ts +3 -0
  72. package/dist/observability/skill-health-analyzer.js +2 -0
  73. package/dist/renderer/html-renderer.js +4 -2
  74. package/dist/renderer/managed-history-renderer.d.ts +5 -0
  75. package/dist/renderer/managed-history-renderer.js +255 -0
  76. package/dist/renderer/observation-inbox-renderer.js +19 -19
  77. package/dist/renderer/skill-detail-renderer.js +17 -8
  78. package/dist/renderer/skill-list-renderer.js +1 -0
  79. package/dist/server/indexed-report-store.d.ts +8 -0
  80. package/dist/server/indexed-report-store.js +130 -0
  81. package/dist/server/job-store.d.ts +0 -1
  82. package/dist/server/job-store.js +0 -2
  83. package/dist/server/report-server.d.ts +17 -3
  84. package/dist/server/report-server.js +231 -85
  85. package/dist/server/report-store.d.ts +14 -8
  86. package/dist/server/report-store.js +46 -0
  87. package/dist/server/skill-index.d.ts +7 -1
  88. package/dist/server/skill-index.js +48 -12
  89. package/dist/types/executor.d.ts +4 -2
  90. package/dist/types/report.d.ts +15 -0
  91. package/dist/types/skill-index.d.ts +3 -0
  92. package/package.json +1 -1
package/README.md CHANGED
@@ -29,7 +29,7 @@ That's it — no editing required. `omk init` scaffolds two skill variants and t
29
29
 
30
30
  Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recommended for first-time users).
31
31
 
32
- Deeper: [CLI reference](docs/reference/cli.md) · [how it works](docs/explanation/architecture.md) · [eval sample format](docs/reference/eval-sample-format.md) · [executors](docs/reference/executors.md) · [artifact layout](docs/reference/artifact-layout.md)
32
+ Deeper: [who omk is for](docs/explanation/who-omk-is-for.md) · [CLI reference](docs/reference/cli.md) · [how it works](docs/explanation/architecture.md) · [eval sample format](docs/reference/eval-sample-format.md) · [executors](docs/reference/executors.md) · [artifact layout](docs/reference/artifact-layout.md)
33
33
 
34
34
  ## Use inside AI Coding Agents
35
35
 
package/README.zh.md CHANGED
@@ -29,7 +29,7 @@ omk eval --control code-review-v1 --treatment code-review-v2
29
29
 
30
30
  手把手教程:[5 分钟快速上手](docs/zh/quickstart-skill-eval.md)(推荐第一次跑评测的用户)。
31
31
 
32
- 深入:[CLI 参考](docs/zh/reference/cli.md) · [工作原理](docs/zh/explanation/architecture.md) · [评测用例格式](docs/zh/reference/eval-sample-format.md) · [执行器](docs/zh/reference/executors.md) · [artifact 布局](docs/zh/reference/artifact-layout.md)
32
+ 深入:[为谁、解决什么](docs/zh/explanation/who-omk-is-for.md) · [CLI 参考](docs/zh/reference/cli.md) · [工作原理](docs/zh/explanation/architecture.md) · [评测用例格式](docs/zh/reference/eval-sample-format.md) · [执行器](docs/zh/reference/executors.md) · [artifact 布局](docs/zh/reference/artifact-layout.md)
33
33
 
34
34
  ## 在 AI Coding Agent 中使用
35
35
 
@@ -142,7 +142,7 @@ omk studio --host 0.0.0.0 # 局域网访问(默认 127.0.0.1
142
142
  omk studio --no-open # 不自动开浏览器
143
143
  ```
144
144
 
145
- Studio 是 skill-centric:列表页(`/`)按 skill 卡片展示健康等级 / 0-100 参考分 / 待优化数 / 趋势;详情页(`/skills/<name>`)左栏列关键问题清单,右栏画健康趋势 + 三档阶段卡(doctor / eval / observe)。访问 `/observations/inbox` 查看 observe inbox 看板。
145
+ Studio 是 skill-centric:列表页(`/`)按 skill 卡片展示健康等级 / 0-100 参考分 / 待优化数 / 趋势;详情页(`/skills/<name>`)左栏列关键问题清单,右栏画健康趋势 + 三档阶段卡(doctor / eval / observe)。访问 `/observe-inbox` 查看 observe inbox 看板。
146
146
 
147
147
  ## 第五步:解读结果
148
148
 
@@ -25,10 +25,11 @@ omk doctor [target] [flags]
25
25
  - `--executor` `option`:执行器名,默认 claude。指定为测试 fixture 路径可在测试里跑(同 omk doctor)。
26
26
  - `--fix` `boolean`:交互式修复:根据 doctor 报告问题,用 LLM agent 修复 skill。
27
27
  - `--gate` `boolean`:静默模式,只在 fail 时输出 stderr 摘要,exit code 标识结果。
28
+ - `--global` `boolean`:写全局 ~/.oh-my-knowledge/doctors,而非项目 .omk/doctors
28
29
  - `--json` `boolean`:JSON 输出到 stdout,适合 CI / 外部脚本消费。
29
30
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
30
31
  - `--model` `option`:LLM model 名,默认 sonnet。
31
- - `--output-dir` `option`:报告输出目录,默认 ~/.oh-my-knowledge/doctors
32
+ - `--output-dir` `option`:报告输出目录,默认项目级 .omk/doctors(--global 写全局)。
32
33
  - `--samples` `option`:用例文件路径(.json/.yaml)。不传则按 target / cwd 顺序自动发现。
33
34
  - `--static-only` `boolean`:离线静态模式,只跑 4 条静态 rule(skill_readable / skill_metadata / dependencies_present / samples_contract_aligned),不调 LLM。
34
35
  - `--timeout` `option`:单次 LLM 会话超时秒数,默认 600(10 分钟)。
@@ -79,6 +80,7 @@ omk eval [flags]
79
80
  - `--dry-run` `boolean`:只 plan 不实跑
80
81
  - `--effort` `option`:被测 LLM 扩展思考预算 low/medium/high/xhigh/max(默认 low;跨 effort 报告不严格可比)。
81
82
  - `--executor` `option`:执行器:claude / claude-sdk / codex / codex-sdk / openai-api / gemini / 自定义命令(默认 claude)。
83
+ - `--global` `boolean`:报告写全局 ~/.oh-my-knowledge/reports,而非项目 .omk/
82
84
  - `--gold-dir` `option`:gold dataset 目录
83
85
  - `--judge-models` `option`:评委配置,格式 executor:model[,...],例 claude:haiku 或 claude:opus,openai:gpt-4o(≥ 2 个 = ensemble)。默认 <executor>:haiku。
84
86
  - `--judge-repeat` `option`:每个 dim 评 N 次
@@ -94,7 +96,7 @@ omk eval [flags]
94
96
  - `--no-judge` `boolean`:跳过 LLM judge
95
97
  - `--no-serve` `boolean`:不启 report server
96
98
  - `--no-strict-baseline` `boolean`:关闭 baseline 隔离
97
- - `--output-dir` `option`:报告输出目录
99
+ - `--output-dir` `option`:报告输出目录(默认项目级 .omk/reports)
98
100
  - `--repeat` `option`:每个 sample 重复跑 N 次
99
101
  - `--report-only` `boolean`:生成报告并打印 verdict,但始终 exit 0(不参与 CI gate)。
100
102
  - `--resume` `option`:从某次失败 run 续跑
@@ -234,6 +236,7 @@ omk evolve <skillPath> [flags]
234
236
  - `--significance-alpha` `option` (默认 `0.05`):显著性门的 diff CI 显著性水平(默认 0.05 = 95% CI)
235
237
  - `--skip-connectivity` `boolean`:跳过 LLM 连通性预检
236
238
  - `--skip-doctor` `boolean`:跳过 doctor 门禁(escape hatch,自负 garbage-in 风险)
239
+ - `--snapshot-only` `boolean`:只产候选、不写回 source:胜出版本留在 evolve/<skillName>.r{N}.md 供你挑选,再 omk promote 接受。受管 skill 默认会写回 source 并记证据(measurable)。
237
240
  - `--stop-on-assertions-pass` `boolean`:普通用例断言全过时提前停止
238
241
  - `--target` `option`:目标 composite 分数,达到即停。不传则跑满 rounds
239
242
  - `--test-ratio` `option` (默认 `0`):锁定 test 集比例(0..1,默认 0=关),需配 --holdout-ratio。全程不参与选择,收尾读一次给无偏泛化分
@@ -255,7 +258,7 @@ omk evolve skills/my-skill/SKILL.md --target 4.5 --model opus --improve-model op
255
258
 
256
259
  ## omk init
257
260
 
258
- 初始化 omk 项目脚手架(skills/ + eval-samples.json 模板)。
261
+ 初始化一个 omk 项目:在目标目录铺好待测知识载体(skills/)与评测用例(eval-samples.json),供 omk eval / doctor / evolve / observe / list 操作。默认是两版 code-review skill 的 A/B 起步模板。
259
262
 
260
263
  **用法:**
261
264
 
@@ -273,13 +276,13 @@ omk init [targetDir] [flags]
273
276
 
274
277
  **示例:**
275
278
 
276
- > 在当前目录初始化
279
+ > 在当前目录初始化一个 omk 项目
277
280
 
278
281
  ```bash
279
282
  omk init
280
283
  ```
281
284
 
282
- > 在指定目录初始化
285
+ > 在指定目录初始化一个 omk 项目
283
286
 
284
287
  ```bash
285
288
  omk init my-project
@@ -401,10 +404,11 @@ omk observe [sessionsDir] [flags]
401
404
  **Flags:**
402
405
 
403
406
  - `--from` `option`:起始时间 ISO,优先级高于 --last
407
+ - `--global` `boolean`:写全局 ~/.oh-my-knowledge/observe-health,而非项目 .omk/observe-health
404
408
  - `--kb` `option`:知识库 root,启用 KB-aware 分析
405
409
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
406
410
  - `--last` `option`:时间窗(7d / 24h / 30m)
407
- - `--output-dir` `option`:分析结果输出目录
411
+ - `--output-dir` `option`:健康报告输出目录,默认项目级 .omk/observe-health(--global 写全局)
408
412
  - `--skills` `option`:只看指定 skill,逗号分隔
409
413
  - `--to` `option`:结束时间 ISO
410
414
 
@@ -431,8 +435,9 @@ omk observe inbox [flags]
431
435
  - `--by-skill` `boolean`:按 skill 聚合输出
432
436
  - `--executor` `option`:LLM 增强复盘使用的执行器
433
437
  - `--explore` `option`:抽样 N 条 medium/low 长尾(replaces limit)
438
+ - `--global` `boolean`:直接读取全局 ~/.oh-my-knowledge/observe-inbox(跳过项目级与兜底)。
434
439
  - `--include-noise` `boolean`:explore 时也包含 noise 桶
435
- - `--input-dir` `option`:inbox 数据目录,默认 .omk/observations(项目级,相对于 cwd);目录不存在时兜底读 ~/.oh-my-knowledge/observations
440
+ - `--input-dir` `option`:inbox 数据目录,默认 .omk/observe-inbox(项目级,相对于 cwd);目录不存在时兜底读 ~/.oh-my-knowledge/observe-inbox
436
441
  - `--json` `boolean`:JSON 格式输出
437
442
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
438
443
  - `--limit` `option`:限制条数,默认 20
@@ -457,8 +462,9 @@ omk observe ingest <traceDir> [flags]
457
462
 
458
463
  **Flags:**
459
464
 
465
+ - `--global` `boolean`:写入全局 ~/.oh-my-knowledge/observe-inbox,而非项目 .omk/observe-inbox。
460
466
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
461
- - `--output-dir` `option`:输出目录,默认 .omk/observations(项目级,相对于 cwd)。
467
+ - `--output-dir` `option`:输出目录,默认 .omk/observe-inbox(项目级,相对于 cwd;--global 写全局)。
462
468
 
463
469
  ## omk observe show
464
470
 
@@ -476,6 +482,7 @@ omk observe show <inboxId> [flags]
476
482
 
477
483
  **Flags:**
478
484
 
485
+ - `--global` `boolean`:直接读取全局 ~/.oh-my-knowledge/observe-inbox(跳过项目级与兜底)。
479
486
  - `--input-dir` `option`:inbox 数据目录
480
487
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
481
488
 
@@ -586,7 +593,7 @@ omk sample [skillPath] [flags]
586
593
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
587
594
  - `--model` `option` (默认 `sonnet`):生成 LLM model 名,默认 sonnet。
588
595
  - `--no-mock` `boolean`:不生成 mocks,eval 时所有工具调用真实执行。
589
- - `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observations
596
+ - `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observe-inbox
590
597
  - `--reports-dir` `option`:报告目录(fix 模式用),默认 ~/.oh-my-knowledge/reports。
591
598
  - `--skill-dir` `option` (默认 `skills`):skill 根目录,默认 skills。batch 模式扫此目录。
592
599
  - `--treatment` `option`:指定 treatment 名(fix 模式用),默认推断自 skill 路径。
@@ -629,14 +636,16 @@ omk studio [flags]
629
636
 
630
637
  **Flags:**
631
638
 
632
- - `--analyses-dir` `option`:分析数据目录(可选)
639
+ - `--analyses-dir` `option`:观测健康报告目录(可选,默认项目级 .omk/observe-health,空则全局兜底)
633
640
  - `--dev` `boolean`:dev 模式:子进程启动 + 热更新
641
+ - `--doctors-dir` `option`:体检报告目录(可选,默认项目级 .omk/doctors,空则全局兜底)
642
+ - `--global` `boolean`:只看全局 reports / observe-health / doctors / observe-inbox 目录(~/.oh-my-knowledge/*),而非机器级聚合 / 项目优先;managed 不受影响
634
643
  - `--host` `option`:监听 host,默认 localhost。改为 0.0.0.0 暴露给局域网
635
644
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
636
645
  - `--no-open` `boolean`:不自动打开浏览器
637
- - `--observations-dir` `option`:观测数据目录(可选)
646
+ - `--observations-dir` `option`:观测收件箱数据目录(可选,默认 .omk/observe-inbox)
638
647
  - `--port` `option` (默认 `7799`):监听端口,默认 7799。传 0 让 OS 分配
639
- - `--reports-dir` `option`:报告目录,默认 ~/.oh-my-knowledge/reports
648
+ - `--reports-dir` `option`:只看指定报告目录(可选;默认机器级聚合:当前项目 + 全局 + 别项目索引)
640
649
 
641
650
  **示例:**
642
651
 
@@ -182,6 +182,9 @@ interface EvolveOptions {
182
182
  /** Feed rejected candidate edits back into the next round's improvement prompt
183
183
  * ("these were tried and did not help — don't repeat them"). Default true. */
184
184
  rejectMemory?: boolean;
185
+ /** 是否把胜出版本写回原 source 文件。默认 true(保「一键化」:evolve 跑完源即更新)。
186
+ * `--snapshot-only` 置 false → 不写 source,候选仍落在 `evolve/<skillName>.r{N}.md` 供人工挑选 / promote。 */
187
+ writeBackToSource?: boolean;
185
188
  onProgress?: ProgressCallback | null;
186
189
  onRoundProgress?: ((progress: EvolveRoundProgressInfo) => void) | null;
187
190
  }
@@ -263,5 +266,5 @@ export interface RoundReport {
263
266
  report: Report;
264
267
  }
265
268
  export declare function mergeEvolveReports(roundReports: RoundReport[], skillName: string, totalCostUSD: number, samples?: Sample[], skillPath?: string): Report;
266
- export declare function evolveSkill({ skillPath, samplesPath, rounds, target, stopOnAssertionsPass, autoFixSamples, sampleFixMaxAttempts, reuseLatestEval, model, judgeModels, improveModel, improveMode, executorName, concurrency, timeoutMs, skipConnectivity, effort, noDiagnostic, skipDoctor, holdoutRatio, significanceGate, significanceAlpha, testRatio, editBudget, rejectMemory, onProgress, onRoundProgress, }: EvolveOptions): Promise<EvolveResult>;
269
+ export declare function evolveSkill({ skillPath, samplesPath, rounds, target, stopOnAssertionsPass, autoFixSamples, sampleFixMaxAttempts, reuseLatestEval, model, judgeModels, improveModel, improveMode, executorName, concurrency, timeoutMs, skipConnectivity, effort, noDiagnostic, skipDoctor, holdoutRatio, significanceGate, significanceAlpha, testRatio, editBudget, rejectMemory, writeBackToSource, onProgress, onRoundProgress, }: EvolveOptions): Promise<EvolveResult>;
267
270
  export {};
@@ -2,8 +2,9 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
2
  import { resolve, join, dirname, basename } from 'node:path';
3
3
  import { runEvaluation } from '../eval-workflows/run-evaluation.js';
4
4
  import { createExecutor, DEFAULT_MODEL, JUDGE_MODEL } from '../executors/index.js';
5
- import { persistReport, DEFAULT_OUTPUT_DIR, generateRunId, hashString } from '../eval-core/evaluation-reporting.js';
6
- import { createFileStore } from '../server/report-store.js';
5
+ import { persistReport, DEFAULT_OUTPUT_DIR, runIdSuffix, hashString } from '../eval-core/evaluation-reporting.js';
6
+ import { createOverlayReportStore } from '../server/report-store.js';
7
+ import { projectReportsDir, globalReportsDir } from '../eval-core/measurement-dirs.js';
7
8
  import { analyzeResults } from '../analysis/report-diagnostics.js';
8
9
  import { loadSamples } from '../inputs/load-samples.js';
9
10
  import { hashArtifactSource } from '../inputs/content-hash.js';
@@ -87,7 +88,9 @@ function singleVariantReport(report, variantKey) {
87
88
  };
88
89
  }
89
90
  async function findReusableBaselineReport(opts) {
90
- const store = createFileStore(DEFAULT_OUTPUT_DIR);
91
+ // baseline 复用读 overlay(项目 .omk/reports ∪ 全局):eval 写默认翻项目后,复用既能命中 eval 新写的项目
92
+ // baseline,又继续覆盖全局(含 evolve 自身写到全局的合并报告),复用命中率与报告数字不降。
93
+ const store = createOverlayReportStore(projectReportsDir(), globalReportsDir());
91
94
  const { samples } = loadSamples(opts.samplesPath);
92
95
  const artifactHash = opts.artifactHash;
93
96
  const reports = await store.findByArtifactHash(artifactHash);
@@ -544,7 +547,7 @@ export function mergeEvolveReports(roundReports, skillName, totalCostUSD, sample
544
547
  if (originalKey)
545
548
  artifactHashes[variantLabels[i]] = hashes[originalKey];
546
549
  }
547
- const runId = `evolve-${skillName}-${generateRunId([skillName]).split('-').slice(-2).join('-')}`;
550
+ const runId = `evolve-${skillName}-${runIdSuffix()}`;
548
551
  const report = {
549
552
  kind: 'evaluation',
550
553
  id: runId,
@@ -570,7 +573,7 @@ export function mergeEvolveReports(roundReports, skillName, totalCostUSD, sample
570
573
  report.analysis = analyzeResults(report, { samples });
571
574
  return report;
572
575
  }
573
- export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target = null, stopOnAssertionsPass = false, autoFixSamples = false, sampleFixMaxAttempts = 2, reuseLatestEval = false, model = DEFAULT_MODEL, judgeModels, improveModel = DEFAULT_MODEL, improveMode = 'agent', executorName = 'claude', concurrency = 1, timeoutMs, skipConnectivity = false, effort, noDiagnostic, skipDoctor, holdoutRatio = 0, significanceGate = true, significanceAlpha = DEFAULT_BOOTSTRAP_ALPHA, testRatio = 0, editBudget = 0.2, rejectMemory = true, onProgress = null, onRoundProgress = null, }) {
576
+ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target = null, stopOnAssertionsPass = false, autoFixSamples = false, sampleFixMaxAttempts = 2, reuseLatestEval = false, model = DEFAULT_MODEL, judgeModels, improveModel = DEFAULT_MODEL, improveMode = 'agent', executorName = 'claude', concurrency = 1, timeoutMs, skipConnectivity = false, effort, noDiagnostic, skipDoctor, holdoutRatio = 0, significanceGate = true, significanceAlpha = DEFAULT_BOOTSTRAP_ALPHA, testRatio = 0, editBudget = 0.2, rejectMemory = true, writeBackToSource = true, onProgress = null, onRoundProgress = null, }) {
574
577
  if (judgeModels && judgeModels.length > 1) {
575
578
  throw new Error('evolveSkill does not support multi-judge ensemble (received '
576
579
  + `${judgeModels.length} judges). Pass a single-judge array, e.g. `
@@ -712,7 +715,8 @@ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target =
712
715
  onRoundProgress({ round: 0, totalRounds: rounds, phase: 'baseline', score: bestScore, costUSD: baselineCost, costReported: baselineCostReported, reused: baselineReused });
713
716
  if (stopOnAssertionsPass && allNonTripwireAssertionsPass(baselineReport, baselineVariantKey)) {
714
717
  stopReason = 'assertions-pass';
715
- writeFileSync(absSkillPath, currentBest);
718
+ if (writeBackToSource)
719
+ writeFileSync(absSkillPath, currentBest);
716
720
  const { samples } = loadSamples(absSamplesPath);
717
721
  const mergedReport = mergeEvolveReports(roundReports, skillName, totalCostUSD, samples, absSkillPath);
718
722
  persistReport(mergedReport, DEFAULT_OUTPUT_DIR);
@@ -908,8 +912,9 @@ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target =
908
912
  break;
909
913
  }
910
914
  }
911
- // Write best version back to original file only if an improvement was accepted
912
- if (bestRound > 0) {
915
+ // Write best version back to original file only if an improvement was accepted.
916
+ // `--snapshot-only`(writeBackToSource=false)跳过写回:候选仍在 evolve/<skillName>.r{N}.md,源不动。
917
+ if (bestRound > 0 && writeBackToSource) {
913
918
  writeFileSync(absSkillPath, currentBest);
914
919
  }
915
920
  // Merge all round reports into one and persist
@@ -17,6 +17,7 @@ export default class Doctor extends BaseCommand {
17
17
  samples: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  timeout: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
21
  dimensions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
22
  'static-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
23
  fix: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,4 +1,3 @@
1
- import { homedir } from 'node:os';
2
1
  import { existsSync, statSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
3
2
  import { dirname, join, resolve } from 'node:path';
4
3
  import { Args, Flags } from '@oclif/core';
@@ -8,6 +7,9 @@ import { numberStringParser } from '../oclif/parsers.js';
8
7
  import { CliExit } from '../lib/cli-exit.js';
9
8
  import { tCli } from '../lib/i18n.js';
10
9
  import { makeDoctorProgress } from '../lib/progress.js';
10
+ import { DEFAULT_DOCTORS_DIR } from '../../eval-core/default-dirs.js';
11
+ import { indexDoctorWrite, removeDoctorCard } from '../../eval-core/artifact-index.js';
12
+ import { projectDoctorsDir, globalDoctorsDir } from '../../eval-core/measurement-dirs.js';
11
13
  const DEFAULT_SAMPLE_FILENAMES = ['eval-samples.json', 'eval-samples.yaml', 'eval-samples.yml'];
12
14
  function findSamplesInDir(dir) {
13
15
  for (const name of DEFAULT_SAMPLE_FILENAMES) {
@@ -131,8 +133,14 @@ export default class Doctor extends BaseCommand {
131
133
  }),
132
134
  'output-dir': Flags.string({
133
135
  description: bilingual({
134
- zh: '报告输出目录,默认 ~/.oh-my-knowledge/doctors',
135
- en: 'Report output dir, default ~/.oh-my-knowledge/doctors.',
136
+ zh: '报告输出目录,默认项目级 .omk/doctors(--global 写全局)。',
137
+ en: 'Report output dir, default project-level .omk/doctors (--global for global).',
138
+ }),
139
+ }),
140
+ global: Flags.boolean({
141
+ description: bilingual({
142
+ zh: '写全局 ~/.oh-my-knowledge/doctors,而非项目 .omk/doctors',
143
+ en: 'Write to global ~/.oh-my-knowledge/doctors instead of project .omk/doctors',
136
144
  }),
137
145
  }),
138
146
  dimensions: Flags.string({
@@ -260,7 +268,9 @@ export default class Doctor extends BaseCommand {
260
268
  }
261
269
  renderDoctorReportText(report, lang);
262
270
  }
263
- persistDoctorReport(report, flags['output-dir'] ? resolve(flags['output-dir']) : undefined);
271
+ persistDoctorReport(report, flags['output-dir']
272
+ ? resolve(flags['output-dir'])
273
+ : (flags.global ? globalDoctorsDir() : projectDoctorsDir()));
264
274
  if (flags.fix) {
265
275
  const existing = report;
266
276
  if (existing.outcome !== 'failed') {
@@ -279,7 +289,7 @@ export default class Doctor extends BaseCommand {
279
289
  // scanDoctorReports 扫盘成本)。50 = ~每天 1 跑撑 1.5 个月 sparkline,够用。
280
290
  const DOCTOR_HISTORY_MAX_PER_SKILL = 50;
281
291
  function persistDoctorReport(report, outputDir) {
282
- const dir = outputDir ?? join(homedir(), '.oh-my-knowledge', 'doctors');
292
+ const dir = outputDir ?? DEFAULT_DOCTORS_DIR;
283
293
  mkdirSync(dir, { recursive: true });
284
294
  const safeId = report.id.replace(/[/\\:*?"<>|]/g, '_');
285
295
  for (const skill of report.skills) {
@@ -301,7 +311,14 @@ function persistDoctorReport(report, outputDir) {
301
311
  outcome: skill.status === 'fail' ? 'failed' : skill.status === 'warn' ? 'warnings_only' : 'passed',
302
312
  };
303
313
  const safeName = skill.skillName.replace(/[/\\:*?"<>|]/g, '_');
304
- writeFileSync(join(dir, `${safeName}-${safeId}.json`), JSON.stringify(perSkill, null, 2), 'utf8');
314
+ const cardId = `${safeName}-${safeId}`;
315
+ const filePath = join(dir, `${cardId}.json`);
316
+ writeFileSync(filePath, JSON.stringify(perSkill, null, 2), 'utf8');
317
+ // 产物发现索引:per-skill 报告落项目本地后,best-effort 追加全局轻卡片,让 studio 跨项目聚合。
318
+ indexDoctorWrite({
319
+ id: cardId, path: filePath, skillName: skill.skillName, reportId: report.id, timestamp: report.timestamp,
320
+ status: skill.status, passCount: counts.pass, warnCount: counts.warn, failCount: counts.fail,
321
+ }, dir);
305
322
  pruneDoctorHistory(dir, skill.skillName, DOCTOR_HISTORY_MAX_PER_SKILL);
306
323
  }
307
324
  }
@@ -332,5 +349,8 @@ export function pruneDoctorHistory(dir, skillName, maxKeep) {
332
349
  unlinkSync(join(dir, file));
333
350
  }
334
351
  catch { /* ignore */ }
352
+ // 连带删卡片:否则被 prune 掉的报告会经 listDoctorCards 合并在本项目 studio「复活」(正文已删、卡片还在)。
353
+ // 卡片 id = 文件 stem(`{name}-{id}`),与 indexDoctorWrite 写入口径一致。
354
+ removeDoctorCard(file.replace(/\.json$/, ''));
335
355
  }
336
356
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../../oclif/base-command.js';
4
4
  import { LANG_FLAG, bilingual } from '../../../oclif/i18n.js';
5
5
  import { integerStringParser } from '../../../oclif/parsers.js';
6
6
  import { CliExit } from '../../../lib/cli-exit.js';
7
- import { DEFAULT_REPORTS_DIR } from '../../../lib/parse-run-config.js';
7
+ import { projectReportsDir, globalReportsDir } from '../../../../eval-core/measurement-dirs.js';
8
8
  import { requireEvaluationReport } from '../../../lib/shared.js';
9
9
  export default class EvalGoldCompare extends BaseCommand {
10
10
  static description = bilingual({
@@ -62,7 +62,7 @@ export default class EvalGoldCompare extends BaseCommand {
62
62
  }
63
63
  const { loadGoldDataset } = await import('../../../../grading/gold-dataset.js');
64
64
  const { compareGoldToReport, formatGoldCompare } = await import('../../../../grading/gold-cli.js');
65
- const { createFileStore } = await import('../../../../server/report-store.js');
65
+ const { createFileStore, createOverlayReportStore } = await import('../../../../server/report-store.js');
66
66
  const { dataset, issues } = loadGoldDataset(goldDir);
67
67
  if (!dataset) {
68
68
  console.error('Cannot load gold dataset:');
@@ -72,8 +72,11 @@ export default class EvalGoldCompare extends BaseCommand {
72
72
  }
73
73
  for (const i of issues)
74
74
  console.error(`warn: ${i.message}`);
75
- const reportsDir = flags['reports-dir'] ?? DEFAULT_REPORTS_DIR;
76
- const store = createFileStore(resolve(reportsDir));
75
+ // 显式 --reports-dir 固定该目录;默认 overlay(项目 .omk/reports 盖全局),get(reportId) 项目→全局兜底,
76
+ // 不因 eval 写默认翻项目而对比落空。
77
+ const store = flags['reports-dir']
78
+ ? createFileStore(resolve(flags['reports-dir']))
79
+ : createOverlayReportStore(projectReportsDir(), globalReportsDir());
77
80
  const report = requireEvaluationReport(await store.get(reportId), reportId, lang);
78
81
  const samples = Math.max(100, Number(flags['bootstrap-samples'] ?? 1000) || 1000);
79
82
  const seedVal = flags.seed != null ? Number(flags.seed) : undefined;
@@ -18,6 +18,7 @@ export default class Eval extends BaseCommand {
18
18
  executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'judge-models': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
20
  'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
22
  'no-judge': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
23
  'no-cache': import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
24
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -84,8 +84,11 @@ function batchItemFallbackReport(batch, item) {
84
84
  };
85
85
  }
86
86
  async function loadBatchChildReports(batch, reportsDir, lang) {
87
- const { createFileStore } = await import('../../../server/report-store.js');
88
- const store = createFileStore(reportsDir);
87
+ // 读侧走 overlay(本次 reportsDir 优先 全局兜底):写默认翻项目后,batch.json 引用的存量 / 全局子报告
88
+ // 仍能 get 命中,不致降级到 batchItemFallbackReport(来自 batch metadata 的过时数据)
89
+ const { createOverlayReportStore } = await import('../../../server/report-store.js');
90
+ const { globalReportsDir } = await import('../../../eval-core/measurement-dirs.js');
91
+ const store = createOverlayReportStore(reportsDir, globalReportsDir());
89
92
  const reports = [];
90
93
  for (const item of batch.items) {
91
94
  const loaded = await store.get(item.reportId);
@@ -134,6 +137,8 @@ async function announceSavedReport({ report, filePath, reportsDir, values, lang,
134
137
  process.stderr.write(tCli('cli.run.report_saved', lang, { path: filePath }));
135
138
  if (!values['no-serve'] && process.stdout.isTTY) {
136
139
  const { createReportServer } = await import('../../../server/report-server.js');
140
+ // eval 结束的即时 serve 刻意钉本次 outputDir(--global 时即全局),不走 overlay:它的语义是「看我刚跑的这份」,
141
+ // 单目录保证刚写的报告(含 --global 写到全局的)一定可见;浏览全机器历史是 omk studio 的事(默认 overlay 项目优先)。
137
142
  const server = createReportServer({ reportsDir });
138
143
  const serverUrl = await server.start();
139
144
  const reportUrl = `${serverUrl}/reports/${report.id}`;
@@ -368,7 +373,13 @@ export default class Eval extends BaseCommand {
368
373
  }),
369
374
  }),
370
375
  'output-dir': Flags.string({
371
- description: bilingual({ zh: '报告输出目录', en: 'Report output dir' }),
376
+ description: bilingual({ zh: '报告输出目录(默认项目级 .omk/reports)', en: 'Report output dir (default project .omk/reports)' }),
377
+ }),
378
+ global: Flags.boolean({
379
+ description: bilingual({
380
+ zh: '报告写全局 ~/.oh-my-knowledge/reports,而非项目 .omk/',
381
+ en: 'Write report to global ~/.oh-my-knowledge/reports instead of project .omk/',
382
+ }),
372
383
  }),
373
384
  // ── 评测 toggle ──
374
385
  'no-judge': Flags.boolean({
@@ -41,6 +41,7 @@ export default class Evolve extends BaseCommand {
41
41
  'auto-fix-samples': import("@oclif/core/interfaces").BooleanFlag<boolean>;
42
42
  'sample-fix-max-attempts': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
43
43
  'reuse-latest-eval': import("@oclif/core/interfaces").BooleanFlag<boolean>;
44
+ 'snapshot-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
44
45
  'improve-mode': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
45
46
  'holdout-ratio': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
46
47
  'no-significance-gate': import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -7,6 +7,17 @@ import { enumStringParser, integerStringParser, numberStringParser } from '../oc
7
7
  import { CliExit } from '../lib/cli-exit.js';
8
8
  import { tCli } from '../lib/i18n.js';
9
9
  import { makeOnProgress } from '../lib/progress.js';
10
+ /** 受管联动旁路:evolve 写回 source 后记证据 + re-baseline。任何异常都不该让 evolve 失败,
11
+ * 故 try/catch 吞掉、返回 null(同 eval 的 recordEvidenceSafely 口径)。 */
12
+ async function recordEvolveOutcomeSafely(input) {
13
+ try {
14
+ const { recordEvolveOutcome } = await import('../lib/record-evolve-outcome.js');
15
+ return await recordEvolveOutcome(input);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
10
21
  const VALID_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
11
22
  function validateEvolveEffort(raw, lang) {
12
23
  if (!VALID_EFFORTS.has(raw)) {
@@ -77,6 +88,9 @@ export async function runEvolve(args, flags, lang) {
77
88
  throw new CliExit(1);
78
89
  }
79
90
  const skillPath = resolvedInput.skillPath;
91
+ // evolve 目标的形态(目录-skill / 文件-skill),供受管联动按形态精确匹配记录。**取解析后形态**而非「入参是不是
92
+ // 目录」:帮助文档鼓励传 `skills/foo/SKILL.md`,若按入参判会得 false、匹配不到 install 落的目录记录(漂移永不消)。
93
+ const skillIsDir = resolvedInput.isDirectorySkill;
80
94
  let samplesFile = flags.samples;
81
95
  if (samplesFile === 'eval-samples.json' && !existsSync(resolve(samplesFile))) {
82
96
  samplesFile = resolvedInput.samplesPath;
@@ -183,6 +197,8 @@ export async function runEvolve(args, flags, lang) {
183
197
  testRatio: Number(flags['test-ratio']),
184
198
  editBudget: flags['no-edit-budget'] ? 0 : Number(flags['edit-budget']),
185
199
  rejectMemory: !flags['no-reject-memory'],
200
+ // --snapshot-only:不写回 source,候选只留在 evolve/<skillName>.r{N}.md(供人工挑选 / promote)。
201
+ writeBackToSource: !flags['snapshot-only'],
186
202
  improveMode: flags['improve-mode'] === 'rewrite' ? 'rewrite' : 'agent',
187
203
  onProgress: makeOnProgress(lang),
188
204
  onRoundProgress({ round, totalRounds: _totalRounds, phase, score, delta, accepted, costUSD, costReported, error, significant }) {
@@ -246,6 +262,28 @@ export async function runEvolve(args, flags, lang) {
246
262
  if (result.reportId) {
247
263
  process.stderr.write(tCli('cli.evolve.report_link', lang, { id: result.reportId }));
248
264
  }
265
+ if (flags['snapshot-only']) {
266
+ // 不写回 source —— 候选留在 evolve/ 供人工挑选;受管记录不动。
267
+ process.stderr.write(tCli('cli.evolve.snapshot_only_hint', lang, {
268
+ dir: join(resolve(skillPath, '..'), 'evolve'),
269
+ }));
270
+ }
271
+ else {
272
+ // 受管 skill:把胜出版本记成带 verdict 的证据 + re-baseline → omk list 显 measurable。
273
+ // 升 promoted 仍由人 omk promote 决定(统计门 ≠ 人的接受)。未纳管 / 无改进 → 静默 no-op。
274
+ const recorded = await recordEvolveOutcomeSafely({
275
+ reportId: result.reportId,
276
+ bestRound: result.bestRound,
277
+ skillPath: resolvedInput.skillPath,
278
+ skillDir: resolvedInput.skillDir,
279
+ isDirectorySkill: skillIsDir,
280
+ });
281
+ if (recorded) {
282
+ process.stderr.write(tCli('cli.evolve.evidence_recorded_managed', lang, {
283
+ name: recorded.name, verdict: recorded.verdict,
284
+ }));
285
+ }
286
+ }
249
287
  console.log(JSON.stringify(result, null, 2));
250
288
  }
251
289
  catch (err) {
@@ -398,6 +436,13 @@ export default class Evolve extends BaseCommand {
398
436
  }),
399
437
  default: false,
400
438
  }),
439
+ 'snapshot-only': Flags.boolean({
440
+ description: bilingual({
441
+ zh: '只产候选、不写回 source:胜出版本留在 evolve/<skillName>.r{N}.md 供你挑选,再 omk promote 接受。受管 skill 默认会写回 source 并记证据(measurable)。',
442
+ en: 'Produce candidates only, do not write back to source: the winner stays in evolve/<skillName>.r{N}.md for you to pick and then omk promote. By default a managed skill is written back and evidence is recorded (measurable).',
443
+ }),
444
+ default: false,
445
+ }),
401
446
  'improve-mode': Flags.string({
402
447
  description: bilingual({
403
448
  zh: '改写策略(默认:agent)',
@@ -3,6 +3,15 @@ import { Args } from '@oclif/core';
3
3
  import { LANG_FLAG, bilingual, resolveLang } from '../oclif/i18n.js';
4
4
  import { BaseCommand } from '../oclif/base-command.js';
5
5
  import { tCli } from '../lib/i18n.js';
6
+ // 预置 .omk/.gitignore:测量 bulk + doctor --fix 备份(项目本地、不该入库)默认不入库;
7
+ // managed/ 治理档案 + 配置不在此列,默认 track。
8
+ const INIT_OMK_GITIGNORE = `# omk 测量 bulk + doctor --fix 备份(项目本地)——不入库;前导 / 锚定 .omk/ 顶层,不误伤嵌套同名目录。
9
+ /observe-health/
10
+ /doctors/
11
+ /observe-inbox/
12
+ /reports/
13
+ /backups/
14
+ `;
6
15
  const INIT_SAMPLES = `[
7
16
  {
8
17
  "sample_id": "s001",
@@ -83,21 +92,21 @@ description: 多维度代码审查,覆盖安全 / 健壮 / 可维护 / 性能,
83
92
  `;
84
93
  export default class Init extends BaseCommand {
85
94
  static description = bilingual({
86
- zh: '初始化 omk 项目脚手架(skills/ + eval-samples.json 模板)。',
87
- en: 'Scaffold an omk project (skills/ + eval-samples.json templates).',
95
+ zh: '初始化一个 omk 项目:在目标目录铺好待测知识载体(skills/)与评测用例(eval-samples.json),供 omk eval / doctor / evolve / observe / list 操作。默认是两版 code-review skill 的 A/B 起步模板。',
96
+ en: 'Initialize an omk project: scaffold knowledge artifacts (skills/) and eval samples (eval-samples.json) in the target dir for omk eval / doctor / evolve / observe / list to work on. Ships a two-variant code-review A/B starter template by default.',
88
97
  });
89
98
  static examples = [
90
99
  {
91
100
  description: bilingual({
92
- zh: '在当前目录初始化',
93
- en: 'Init in current directory',
101
+ zh: '在当前目录初始化一个 omk 项目',
102
+ en: 'Initialize an omk project in the current directory',
94
103
  }),
95
104
  command: '<%= config.bin %> init',
96
105
  },
97
106
  {
98
107
  description: bilingual({
99
- zh: '在指定目录初始化',
100
- en: 'Init in specified directory',
108
+ zh: '在指定目录初始化一个 omk 项目',
109
+ en: 'Initialize an omk project in a specified directory',
101
110
  }),
102
111
  command: '<%= config.bin %> init my-project',
103
112
  },
@@ -139,6 +148,9 @@ export default class Init extends BaseCommand {
139
148
  writeFileSync(join(targetDir, 'eval-samples.json'), INIT_SAMPLES);
140
149
  writeFileSync(join(targetDir, 'skills', 'code-review-v1', 'SKILL.md'), INIT_SKILL_V1);
141
150
  writeFileSync(join(targetDir, 'skills', 'code-review-v2', 'SKILL.md'), INIT_SKILL_V2);
151
+ // 像 dvc init 那样预置忽略规则,开发者不会误把测量 bulk 提交进库。
152
+ mkdirSync(join(targetDir, '.omk'), { recursive: true });
153
+ writeFileSync(join(targetDir, '.omk', '.gitignore'), INIT_OMK_GITIGNORE);
142
154
  console.log(tCli('cli.init.scaffolded', lang, { dir: targetDir }));
143
155
  console.log('');
144
156
  console.log(tCli('cli.init.next_steps_title', lang));
@@ -1,9 +1,7 @@
1
1
  import { BaseCommand } from '../oclif/base-command.js';
2
- import { probeSourceState } from '../lib/source-probe.js';
3
2
  import { sanitizeCell } from '../lib/cell-format.js';
4
3
  import { type ManagedListRow } from '../../managed/index.js';
5
4
  import type { CliLang } from '../lib/i18n.js';
6
- export { probeSourceState };
7
5
  /** CJK 全角字符按 2 列计宽,使含中文表头的列也能对齐。 */
8
6
  export declare function dispWidth(s: string): number;
9
7
  /** 按**显示宽度**截断(不是 code unit):逐码点累加 dispWidth,绝不切断 surrogate 对、CJK 也不溢出列。 */
@@ -2,12 +2,8 @@ import { Flags } from '@oclif/core';
2
2
  import { LANG_FLAG, bilingual } from '../oclif/i18n.js';
3
3
  import { BaseCommand } from '../oclif/base-command.js';
4
4
  import { tCli } from '../lib/i18n.js';
5
- import { probeSourceState } from '../lib/source-probe.js';
6
5
  import { sanitizeCell } from '../lib/cell-format.js';
7
- import { buildManagedListRows, globalManagedDir, loadAllManagedRecords, managedDir, resolveManagedDir, } from '../../managed/index.js';
8
- // 源探测(含 DoS / 软硬链 / 投毒守卫)已抽到 ../lib/source-probe.js,promote 复用同一套守卫与 drift 判定。
9
- // 此处 re-export 保持既有 import 入口不破(test/cli/list-probe.test.ts 仍从本文件取 probeSourceState)。
10
- export { probeSourceState };
6
+ import { globalManagedDir, listManagedRows, managedDir, resolveManagedDir, } from '../../managed/index.js';
11
7
  /** CJK 全角字符按 2 列计宽,使含中文表头的列也能对齐。 */
12
8
  export function dispWidth(s) {
13
9
  let w = 0;
@@ -82,8 +78,7 @@ export default class List extends BaseCommand {
82
78
  const lang = this.lang;
83
79
  await this.runWithCliExit(async () => {
84
80
  const dir = flags.global ? globalManagedDir() : resolveManagedDir(managedDir());
85
- const records = loadAllManagedRecords(dir);
86
- const rows = buildManagedListRows(records, probeSourceState);
81
+ const rows = listManagedRows(dir);
87
82
  if (flags.json) {
88
83
  // 版本化信封 —— 与 eval / doctor / diagnosis 等机读出口一致(都带 schemaVersion),让未来字段增删 /
89
84
  // 改名是可检测的版本 bump,而非脚本静默 break。rows 的逐行形态见 ManagedListRow。
@@ -7,6 +7,7 @@ export default class ObserveInbox extends BaseCommand {
7
7
  static flags: {
8
8
  lang: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
9
  'input-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  skill: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  explore: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;