@morehao/dsh-github-trending 0.8.23 → 0.9.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.
@@ -1052,8 +1052,9 @@ __export(ipc_exports, {
1052
1052
  parseSubscribeResult: () => parseSubscribeResult,
1053
1053
  parseVersionInfo: () => parseVersionInfo
1054
1054
  });
1055
+ import { createHash } from "crypto";
1055
1056
  import { validators } from "@morehao/trendradar-contract";
1056
- var IPC_SCHEMA_VERSION = 2;
1057
+ var IPC_SCHEMA_VERSION = 3;
1057
1058
  var SUPPORTED_DIGEST_SCHEMA = 2;
1058
1059
  var SUPPORTED_CONTRACT_MAJOR = 0;
1059
1060
  var SUBSCRIPTION_STATUSES = ["baseline", "ok", "no_change", "rate_limited", "failed"];
@@ -1112,20 +1113,50 @@ function normalizeError(value) {
1112
1113
  ...isRecord(value.details) ? { details: value.details } : {}
1113
1114
  };
1114
1115
  }
1116
+ function parseDigest(value, files) {
1117
+ if (value === void 0 || value === null) return void 0;
1118
+ if (!isRecord(value)) throw new IpcError("IPC_INVALID", "\u6838\u5FC3 run --json \u7684 digest \u4E0D\u662F\u5BF9\u8C61");
1119
+ const file = value.path;
1120
+ const bytes = value.bytes;
1121
+ const sha256 = value.sha256;
1122
+ const text = value.text;
1123
+ if (typeof file !== "string" || file === "" || typeof text !== "string" || typeof bytes !== "number" || typeof sha256 !== "string") {
1124
+ throw new IpcError("IPC_INVALID", "\u6838\u5FC3 run --json \u7684 digest \u7F3A path/bytes/sha256/text");
1125
+ }
1126
+ const buffer = Buffer.from(text, "utf8");
1127
+ if (buffer.length !== bytes) {
1128
+ throw new IpcError("IPC_INVALID", `digest.bytes=${bytes} \u4E0E text \u5B9E\u9645 ${buffer.length} \u5B57\u8282\u4E0D\u4E00\u81F4`);
1129
+ }
1130
+ const actual = `sha256:${createHash("sha256").update(buffer).digest("hex")}`;
1131
+ if (actual !== sha256) {
1132
+ throw new IpcError("IPC_INVALID", `digest.sha256 \u4E0E text \u4E0D\u4E00\u81F4\uFF1A\u58F0\u660E ${sha256}\uFF0C\u5B9E\u9645 ${actual}`);
1133
+ }
1134
+ const listed = files.find((item) => item.path === file);
1135
+ if (listed === void 0) {
1136
+ throw new IpcError("IPC_INVALID", `digest.path=${file} \u4E0D\u5728 files \u91CC`);
1137
+ }
1138
+ if (listed.sha256 !== sha256 || listed.bytes !== bytes) {
1139
+ throw new IpcError("IPC_INVALID", `digest \u4E0E files \u91CC ${file} \u7684 bytes/sha256 \u4E0D\u4E00\u81F4`);
1140
+ }
1141
+ return { path: file, bytes, sha256, text };
1142
+ }
1115
1143
  function parseRunResult(text, options) {
1116
1144
  const value = parseJsonObject(text);
1117
1145
  if (value.files === null || value.files === void 0) value.files = [];
1118
1146
  if (value.command !== "run" || value.ipcSchema !== IPC_SCHEMA_VERSION || !validators.runResult(value)) {
1119
- throw new IpcError("IPC_INVALID", "\u6838\u5FC3 run --json \u4E0D\u662F\u5951\u7EA6 RunResult\uFF08ipcSchema=2 / command=run\uFF09");
1147
+ throw new IpcError("IPC_INVALID", "\u6838\u5FC3 run --json \u4E0D\u662F\u5951\u7EA6 RunResult\uFF08ipcSchema=3 / command=run\uFF09");
1120
1148
  }
1121
1149
  const result = value;
1122
1150
  const error = normalizeError(result.error);
1123
1151
  const skipReason = value.skipReason;
1152
+ const files = Array.isArray(result.files) ? result.files : [];
1153
+ const digest = parseDigest(value.digest, files);
1124
1154
  return {
1125
1155
  ok: result.ok,
1126
1156
  skipped: result.skipped === true,
1127
1157
  dryRun: options.dryRun,
1128
- files: Array.isArray(result.files) ? result.files : [],
1158
+ files,
1159
+ ...digest === void 0 ? {} : { digest },
1129
1160
  contentHash: typeof result.contentHash === "string" ? result.contentHash : void 0,
1130
1161
  branch: typeof result.branch === "string" ? result.branch : void 0,
1131
1162
  commit: typeof result.commit === "string" ? result.commit : void 0,
@@ -1246,7 +1277,7 @@ __export(release_exports, {
1246
1277
  readCoreRelease: () => readCoreRelease,
1247
1278
  verifyCached: () => verifyCached
1248
1279
  });
1249
- import { createHash } from "crypto";
1280
+ import { createHash as createHash2 } from "crypto";
1250
1281
  import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
1251
1282
  import { createRequire } from "module";
1252
1283
  import { pipeline } from "stream/promises";
@@ -1306,7 +1337,7 @@ function describeRelease(release, options = {}) {
1306
1337
  };
1307
1338
  }
1308
1339
  function sha256File(file) {
1309
- return createHash("sha256").update(readFileSync2(file)).digest("hex");
1340
+ return createHash2("sha256").update(readFileSync2(file)).digest("hex");
1310
1341
  }
1311
1342
  function markerPath(binaryPath) {
1312
1343
  return `${binaryPath}.sha256`;
@@ -1378,7 +1409,7 @@ async function download(descriptor, options) {
1378
1409
  }
1379
1410
  mkdirSync2(descriptor.cacheDir, { recursive: true, mode: 448 });
1380
1411
  const temp = `${descriptor.binaryPath}.${process.pid}.${Date.now()}.part`;
1381
- const hash = createHash("sha256");
1412
+ const hash = createHash2("sha256");
1382
1413
  const total = Number(response.headers.get("content-length") ?? "0") || void 0;
1383
1414
  let bytes = 0;
1384
1415
  try {
@@ -2816,44 +2847,15 @@ async function runSubscribe(input, deps) {
2816
2847
  }
2817
2848
  }
2818
2849
 
2819
- // src/host/version.ts
2820
- var version_exports = {};
2821
- __export(version_exports, {
2822
- versionAtLeast: () => versionAtLeast
2823
- });
2824
- function parseTriplet(value) {
2825
- const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(value.trim());
2826
- if (match === null) return void 0;
2827
- return [Number(match[1]), Number(match[2]), Number(match[3])];
2828
- }
2829
- function versionAtLeast(a, b) {
2830
- const left = parseTriplet(a);
2831
- const right = parseTriplet(b);
2832
- if (left === void 0 || right === void 0) return false;
2833
- for (let i = 0; i < 3; i += 1) {
2834
- const l = left[i];
2835
- const r = right[i];
2836
- if (l > r) return true;
2837
- if (l < r) return false;
2838
- }
2839
- return true;
2840
- }
2841
-
2842
2850
  // src/host/runner.ts
2843
2851
  var runner_exports = {};
2844
2852
  __export(runner_exports, {
2845
- FORCE_MIN_CORE_VERSION: () => FORCE_MIN_CORE_VERSION,
2846
- REPO_MIN_CORE_VERSION: () => REPO_MIN_CORE_VERSION,
2847
2853
  Runner: () => Runner,
2848
- isoWeekName: () => isoWeekName,
2849
- periodFileBase: () => periodFileBase,
2850
2854
  projectUsage: () => projectUsage
2851
2855
  });
2852
- import { createHash as createHash2 } from "crypto";
2853
- import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync, statSync as statSync5 } from "fs";
2856
+ import { createHash as createHash3 } from "crypto";
2857
+ import { existsSync as existsSync4, statSync as statSync5 } from "fs";
2854
2858
  import path9 from "path";
2855
- var FORCE_MIN_CORE_VERSION = "0.7.1";
2856
- var REPO_MIN_CORE_VERSION = "0.8.0";
2857
2859
  function repoProblem(repoPath) {
2858
2860
  if (repoPath.trim() === "") return { code: "CONFIG_INVALID", message: "\u8FD8\u6CA1\u6709\u9009\u62E9\u5F52\u6863\u4ED3\u5E93" };
2859
2861
  if (!existsSync4(repoPath)) return { code: "REPO_NOT_GIT", message: `\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${repoPath}` };
@@ -2874,30 +2876,9 @@ function runDate(settings, now = /* @__PURE__ */ new Date()) {
2874
2876
  return now.toISOString().slice(0, 10);
2875
2877
  }
2876
2878
  }
2877
- function isInside(root, target) {
2878
- const rel = path9.relative(root, target);
2879
- return rel !== "" && !rel.startsWith("..") && !path9.isAbsolute(rel);
2880
- }
2881
- function normalizeSha256(value) {
2882
- if (typeof value !== "string") return void 0;
2883
- const hex = value.trim().replace(/^sha256:/i, "").toLowerCase();
2884
- return /^[0-9a-f]{64}$/.test(hex) ? hex : void 0;
2885
- }
2886
2879
  function counterOf(value) {
2887
2880
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2888
2881
  }
2889
- function isoWeekName(date) {
2890
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
2891
- if (match === null) return date;
2892
- const d = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
2893
- if (Number.isNaN(d.getTime())) return date;
2894
- return isoWeekKey(d, "UTC");
2895
- }
2896
- function periodFileBase(period, date) {
2897
- if (period === "monthly") return date.slice(0, 7);
2898
- if (period === "weekly") return isoWeekName(date);
2899
- return date;
2900
- }
2901
2882
  function projectUsage(outcome) {
2902
2883
  const usageTokens = outcome.usage?.totalTokens;
2903
2884
  const releasesTokens = outcome.releases?.usage?.totalTokens;
@@ -2972,15 +2953,7 @@ var Runner = class {
2972
2953
  `\u6838\u5FC3\u4E0D\u652F\u6301 provider\u300C${settings.model.provider}\u300D\uFF1B\u53EF\u7528\uFF1A${handshakeResult.supportedProviders.join(" / ")}`
2973
2954
  );
2974
2955
  }
2975
- const forceRequested = options.force === true;
2976
- const forceSupported = versionAtLeast(handshakeResult.version.version, FORCE_MIN_CORE_VERSION);
2977
- const effectiveForce = forceRequested && forceSupported;
2978
- if (forceRequested && !forceSupported) {
2979
- store.appendLog(
2980
- logFile,
2981
- `[force] \u6838\u5FC3 ${handshakeResult.version.version} \u4F4E\u4E8E ${FORCE_MIN_CORE_VERSION}\uFF0C\u4E0D\u652F\u6301 --force\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u666E\u901A\u91CD\u8DD1`
2982
- );
2983
- }
2956
+ const effectiveForce = options.force === true;
2984
2957
  const model = await this.openModelChannel({ settings, credentials, env, logFile });
2985
2958
  const apiKey = model.apiKey;
2986
2959
  proxy = model.proxy;
@@ -3018,15 +2991,7 @@ var Runner = class {
3018
2991
  ...core.tag === void 0 ? {} : { coreTag: core.tag },
3019
2992
  ...effectiveForce ? { forced: true } : {}
3020
2993
  });
3021
- const preview = this.capturePreview({
3022
- id,
3023
- date,
3024
- period,
3025
- settings,
3026
- outcome,
3027
- secrets: [apiKey, github.token],
3028
- ...effectiveForce ? { force: true } : {}
3029
- });
2994
+ const preview = this.capturePreview({ id, outcome, secrets: [apiKey, github.token] });
3030
2995
  if (preview !== void 0) summary.preview = preview;
3031
2996
  this.record(summary, settings);
3032
2997
  return summary;
@@ -3117,14 +3082,7 @@ var Runner = class {
3117
3082
  `\u6838\u5FC3\u4E0D\u652F\u6301 provider\u300C${settings.model.provider}\u300D\uFF1B\u53EF\u7528\uFF1A${handshakeResult.supportedProviders.join(" / ")}`
3118
3083
  );
3119
3084
  }
3120
- let effectiveRepo = requestedRepo;
3121
- if (requestedRepo !== void 0 && !versionAtLeast(handshakeResult.version.version, REPO_MIN_CORE_VERSION)) {
3122
- store.appendLog(
3123
- logFile,
3124
- `[release] \u6838\u5FC3 ${handshakeResult.version.version} \u4F4E\u4E8E ${REPO_MIN_CORE_VERSION}\uFF0C\u5355\u4ED3\u5206\u6790\u964D\u7EA7\u4E3A\u5168\u91CF`
3125
- );
3126
- effectiveRepo = void 0;
3127
- }
3085
+ const effectiveRepo = requestedRepo;
3128
3086
  const model = await this.openModelChannel({ settings, credentials, env, logFile });
3129
3087
  proxy = model.proxy;
3130
3088
  const github = await resolveGithubToken(credentials, settings.github.authMode, settings.github.tokenRef, { env });
@@ -3365,70 +3323,38 @@ var Runner = class {
3365
3323
  };
3366
3324
  }
3367
3325
  /**
3368
- * 从本次核心返回的 artifact 清单里选当天的 `.md`,只读复制到 previews/。
3326
+ * 把本次运行渲染的 digest 原文写进 previews/(ipc v3 起由核心随 RunResult 回传)。
3369
3327
  *
3370
- * 这是插件**唯一**读取用户仓库的场景,而且只读核心自己刚写出的那一份:
3371
- * - 路径必须在 `repo.path` 内(词法 + realpath 各判一次,挡 `..`/绝对路径/符号链接);
3372
- * - 上限 512 KB、拒绝 NUL、sha256 必须与契约声明一致;
3328
+ * 这里**不再读用户仓库**:分支可能在运行结束前就被合并、切回 base 并删除,
3329
+ * 任何「事后去工作树里找文件」的做法都会读到旧版或空。文本、字节数与 sha256
3330
+ * 的跨字段一致性已在 `parseRunResult` 里校验过;这里只做展示侧的防御:
3331
+ * - 上限 512 KB(与预览读取上限一致)、拒绝 NUL;
3332
+ * - 落盘前统一 `redact()`,避免把密钥写进预览文件;
3373
3333
  * - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
3374
3334
  */
3375
3335
  capturePreview(input) {
3376
- const { outcome, settings, date, period, id } = input;
3377
- if (!outcome.ok || outcome.dryRun || date === void 0) return void 0;
3378
- if (outcome.skipped && input.force !== true) return void 0;
3336
+ const { outcome, id } = input;
3337
+ if (!outcome.ok || outcome.dryRun) return void 0;
3379
3338
  const skip = (reason, extra = {}) => {
3380
3339
  this.deps.logger?.("preview.skip", { id, reason, ...extra });
3381
3340
  return void 0;
3382
3341
  };
3383
- const markdown = outcome.files.filter((file) => {
3384
- const ext = path9.extname(file.path).toLowerCase();
3385
- const base = path9.basename(file.path).toLowerCase();
3386
- const normalized = file.path.replace(/\\/g, "/");
3387
- const inReleases = normalized === "releases" || normalized.startsWith("releases/");
3388
- return ext === ".md" && base !== "readme.md" && base !== "index.md" && !inReleases;
3389
- });
3390
- if (markdown.length === 0) return skip("no-markdown");
3391
- const expected = period === void 0 ? `${date}.md` : `${periodFileBase(period, date)}.md`;
3392
- const named = markdown.filter((file) => path9.basename(file.path) === expected);
3393
- const chosen = named.length > 0 ? named[0] : markdown.length === 1 ? markdown[0] : void 0;
3394
- if (chosen === void 0) return skip("ambiguous", { candidates: markdown.length, expected, period: period ?? null });
3395
- const repoPath = settings.repo.path;
3396
- const resolved = path9.resolve(repoPath, chosen.path);
3397
- if (!isInside(repoPath, resolved)) return skip("outside-repo", { path: chosen.path });
3398
- let fileReal;
3399
- try {
3400
- const repoReal = realpathSync(repoPath);
3401
- fileReal = realpathSync(resolved);
3402
- if (!isInside(repoReal, fileReal)) return skip("symlink-outside-repo", { path: chosen.path });
3403
- } catch {
3404
- return skip("missing-file", { path: chosen.path });
3405
- }
3406
- let buffer;
3407
- try {
3408
- const stat2 = statSync5(fileReal);
3409
- if (!stat2.isFile()) return skip("not-a-file", { path: chosen.path });
3410
- if (stat2.size > PREVIEW_MAX_BYTES) return skip("too-large", { bytes: stat2.size });
3411
- buffer = readFileSync3(fileReal);
3412
- } catch {
3413
- return skip("read-failed", { path: chosen.path });
3414
- }
3415
- if (buffer.includes(0)) return skip("contains-nul", { path: chosen.path });
3416
- const declared = normalizeSha256(chosen.sha256);
3417
- const actual = createHash2("sha256").update(buffer).digest("hex");
3418
- if (declared === void 0 || declared !== actual) {
3419
- return skip("sha-mismatch", { path: chosen.path, declared: declared ?? null, actual });
3420
- }
3421
- const text = redact(buffer.toString("utf8"), input.secrets);
3342
+ const digest = outcome.digest;
3343
+ if (digest === void 0) return skip("no-digest");
3344
+ const buffer = Buffer.from(digest.text, "utf8");
3345
+ if (buffer.length > PREVIEW_MAX_BYTES) return skip("too-large", { path: digest.path, bytes: buffer.length });
3346
+ if (buffer.includes(0)) return skip("contains-nul", { path: digest.path });
3347
+ const text = redact(digest.text, input.secrets);
3422
3348
  const stored = Buffer.from(text, "utf8");
3423
3349
  try {
3424
3350
  this.deps.store.writePreview(id, text);
3425
3351
  } catch {
3426
- return skip("write-failed", { path: chosen.path });
3352
+ return skip("write-failed", { path: digest.path });
3427
3353
  }
3428
3354
  return {
3429
- file: path9.relative(repoPath, resolved).split(path9.sep).join("/"),
3355
+ file: digest.path,
3430
3356
  bytes: stored.length,
3431
- sha256: createHash2("sha256").update(stored).digest("hex")
3357
+ sha256: createHash3("sha256").update(stored).digest("hex")
3432
3358
  };
3433
3359
  }
3434
3360
  /** 写历史与状态;同一天的 skipped 运行不覆盖 lastDigest。 */
@@ -3839,7 +3765,7 @@ __export(import_cli_exports, {
3839
3765
  importFromCli: () => importFromCli,
3840
3766
  parseSimpleYaml: () => parseSimpleYaml
3841
3767
  });
3842
- import { readFileSync as readFileSync4 } from "fs";
3768
+ import { readFileSync as readFileSync3 } from "fs";
3843
3769
  import os4 from "os";
3844
3770
  import path10 from "path";
3845
3771
  function cliConfigCandidates(env = process.env, home = os4.homedir()) {
@@ -3931,7 +3857,7 @@ function isSecretKey(key) {
3931
3857
  function importFromCli(filePath, base = DEFAULT_SETTINGS) {
3932
3858
  let text;
3933
3859
  try {
3934
- text = readFileSync4(filePath, "utf8");
3860
+ text = readFileSync3(filePath, "utf8");
3935
3861
  } catch (error) {
3936
3862
  return {
3937
3863
  ok: false,
@@ -4950,7 +4876,6 @@ export {
4950
4876
  Scheduler,
4951
4877
  schedule_exports,
4952
4878
  subscribe_exports,
4953
- version_exports,
4954
4879
  Runner,
4955
4880
  runner_exports,
4956
4881
  readPluginInfo,
package/lib/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  createUpdateService,
10
10
  ensureGithubFlow,
11
11
  readPluginInfo
12
- } from "./chunk-DGEO5BHK.js";
12
+ } from "./chunk-JMIWABMY.js";
13
13
 
14
14
  // src/index.ts
15
15
  var name = "dsh-github-trending";
package/lib/internal.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ErrorCode, FileArtifact, Usage, ReleasesReport, SubscriptionItem, SubscriptionDoc, SubscriptionStatus, ErrorInfo, RunRequest, RunResult, State, StateRun, SubscriptionList, SubscriptionVolume } from '@morehao/trendradar-contract';
1
+ import { ErrorCode, FileArtifact, DigestArtifact, Usage, ReleasesReport, SubscriptionItem, SubscriptionDoc, SubscriptionStatus, ErrorInfo, RunRequest, RunResult, State, StateRun, SubscriptionList, SubscriptionVolume } from '@morehao/trendradar-contract';
2
2
  import { ExecOptions, ExecResult } from '@morehao/dsh-plugin-kit/exec';
3
3
  import * as exec from '@morehao/dsh-plugin-kit/exec';
4
4
  export { exec };
@@ -71,8 +71,8 @@ declare namespace errors {
71
71
  export { type errors_AnyCode as AnyCode, type errors_ClassifiedError as ClassifiedError, type errors_ErrorArea as ErrorArea, type errors_GitReason as GitReason, errors_PLUGIN_CODES as PLUGIN_CODES, type errors_PluginCode as PluginCode, errors_areaOf as areaOf, errors_classifyStderr as classifyStderr, errors_hintOf as hintOf, errors_isRetriable as isRetriable, errors_reasonOf as reasonOf, errors_redact as redact };
72
72
  }
73
73
 
74
- /** 插件实现所对应的 IPC 契约版本(契约包 `ipc.v2.json` 的 wire 整数)。 */
75
- declare const IPC_SCHEMA_VERSION: 2;
74
+ /** 插件实现所对应的 IPC 契约版本(契约包 `ipc.v3.json` 的 wire 整数)。 */
75
+ declare const IPC_SCHEMA_VERSION: 3;
76
76
  /** 插件能接受的 digest 契约版本(与核心 `version --json` 的 digestSchema 比对)。 */
77
77
  declare const SUPPORTED_DIGEST_SCHEMA: 2;
78
78
  /** 契约包 `contractVersion` 的主版本;0.x 期间同主版本即视为同一契约线。 */
@@ -99,6 +99,8 @@ interface RunOutcome {
99
99
  skipped: boolean;
100
100
  dryRun: boolean;
101
101
  files: FileArtifact[];
102
+ /** 本次渲染的 digest 原文(ipc v3);没有渲染(dry-run / 纯发版 / 渲染前失败)时缺省。 */
103
+ digest?: DigestArtifact;
102
104
  contentHash?: string;
103
105
  branch?: string;
104
106
  commit?: string;
@@ -173,6 +175,7 @@ type SubscribeAction = 'list' | 'add' | 'remove' | 'releases';
173
175
  */
174
176
  declare function parseSubscribeResult(text: string): SubscribeOutcome;
175
177
 
178
+ declare const ipc_DigestArtifact: typeof DigestArtifact;
176
179
  declare const ipc_ErrorCode: typeof ErrorCode;
177
180
  declare const ipc_ErrorInfo: typeof ErrorInfo;
178
181
  declare const ipc_FileArtifact: typeof FileArtifact;
@@ -206,7 +209,7 @@ declare const ipc_parseRunResult: typeof parseRunResult;
206
209
  declare const ipc_parseSubscribeResult: typeof parseSubscribeResult;
207
210
  declare const ipc_parseVersionInfo: typeof parseVersionInfo;
208
211
  declare namespace ipc {
209
- export { ipc_ErrorCode as ErrorCode, ipc_ErrorInfo as ErrorInfo, ipc_FileArtifact as FileArtifact, ipc_IPC_SCHEMA_VERSION as IPC_SCHEMA_VERSION, ipc_IpcError as IpcError, type ipc_ParseRunOptions as ParseRunOptions, ipc_ReleasesReport as ReleasesReport, type ipc_RunError as RunError, type ipc_RunOutcome as RunOutcome, ipc_RunRequest as RunRequest, ipc_RunResult as RunResult, ipc_SUPPORTED_CONTRACT_MAJOR as SUPPORTED_CONTRACT_MAJOR, ipc_SUPPORTED_DIGEST_SCHEMA as SUPPORTED_DIGEST_SCHEMA, ipc_State as State, ipc_StateRun as StateRun, type ipc_SubscribeAction as SubscribeAction, type ipc_SubscribeOutcome as SubscribeOutcome, ipc_SubscriptionDoc as SubscriptionDoc, ipc_SubscriptionItem as SubscriptionItem, ipc_SubscriptionList as SubscriptionList, ipc_SubscriptionStatus as SubscriptionStatus, ipc_SubscriptionVolume as SubscriptionVolume, ipc_Usage as Usage, type ipc_VersionInfo as VersionInfo, ipc_isSubscriptionDoc as isSubscriptionDoc, ipc_isSubscriptionStatus as isSubscriptionStatus, ipc_isVersionInfo as isVersionInfo, ipc_parseJsonObject as parseJsonObject, ipc_parseRunResult as parseRunResult, ipc_parseSubscribeResult as parseSubscribeResult, ipc_parseVersionInfo as parseVersionInfo };
212
+ export { ipc_DigestArtifact as DigestArtifact, ipc_ErrorCode as ErrorCode, ipc_ErrorInfo as ErrorInfo, ipc_FileArtifact as FileArtifact, ipc_IPC_SCHEMA_VERSION as IPC_SCHEMA_VERSION, ipc_IpcError as IpcError, type ipc_ParseRunOptions as ParseRunOptions, ipc_ReleasesReport as ReleasesReport, type ipc_RunError as RunError, type ipc_RunOutcome as RunOutcome, ipc_RunRequest as RunRequest, ipc_RunResult as RunResult, ipc_SUPPORTED_CONTRACT_MAJOR as SUPPORTED_CONTRACT_MAJOR, ipc_SUPPORTED_DIGEST_SCHEMA as SUPPORTED_DIGEST_SCHEMA, ipc_State as State, ipc_StateRun as StateRun, type ipc_SubscribeAction as SubscribeAction, type ipc_SubscribeOutcome as SubscribeOutcome, ipc_SubscriptionDoc as SubscriptionDoc, ipc_SubscriptionItem as SubscriptionItem, ipc_SubscriptionList as SubscriptionList, ipc_SubscriptionStatus as SubscriptionStatus, ipc_SubscriptionVolume as SubscriptionVolume, ipc_Usage as Usage, type ipc_VersionInfo as VersionInfo, ipc_isSubscriptionDoc as isSubscriptionDoc, ipc_isSubscriptionStatus as isSubscriptionStatus, ipc_isVersionInfo as isVersionInfo, ipc_parseJsonObject as parseJsonObject, ipc_parseRunResult as parseRunResult, ipc_parseSubscribeResult as parseSubscribeResult, ipc_parseVersionInfo as parseVersionInfo };
210
213
  }
211
214
 
212
215
  type Provider = string;
@@ -1617,16 +1620,6 @@ declare namespace pendingPr {
1617
1620
  export { type pendingPr_CheckPendingPrOptions as CheckPendingPrOptions, type pendingPr_ParsedPullUrl as ParsedPullUrl, type pendingPr_PendingPrStatus as PendingPrStatus, type pendingPr_PullState as PullState, pendingPr_checkPendingPr as checkPendingPr, pendingPr_clearPendingPrCache as clearPendingPrCache, pendingPr_parsePullUrl as parsePullUrl };
1618
1621
  }
1619
1622
 
1620
- /**
1621
- * 支持 `run --force`(忽略摘要缓存重新生成)的最低核心版本。
1622
- * 与 core patch 发版 `0.7.1` 对齐;`dsh.core.version` 应 >= 它,dev override 指向更旧核心时靠闸门降级。
1623
- */
1624
- declare const FORCE_MIN_CORE_VERSION = "0.7.1";
1625
- /**
1626
- * 支持 `run --stages releases --repo`(单仓发版分析)的最低核心版本。
1627
- * 与 core v0.8.0 对齐;核心更旧时单仓触发降级为全量(不传 --repo)。
1628
- */
1629
- declare const REPO_MIN_CORE_VERSION = "0.8.0";
1630
1623
  interface RunSummary {
1631
1624
  id: string;
1632
1625
  ok: boolean;
@@ -1687,13 +1680,6 @@ interface RunnerDeps {
1687
1680
  /** 预览副本这类「可失败但不阻断运行」的事件走这里,不写进 warnings。 */
1688
1681
  logger?: (event: string, data?: Record<string, unknown>) => void;
1689
1682
  }
1690
- /**
1691
- * 与 core `render.NameOf` 对齐的产物基名(不含扩展名):
1692
- * daily → YYYY-MM-DD;weekly → YYYY-Wnn(ISO 周年/周);monthly → YYYY-MM。
1693
- * 用于在 outcome.files 里按周期精确挑出本期 digest 的 `.md`(而不是猜)。
1694
- */
1695
- declare function isoWeekName(date: string): string;
1696
- declare function periodFileBase(period: Period, date: string): string;
1697
1683
  /**
1698
1684
  * 从契约 `RunOutcome` 投影出「token 总量」与「发版摘要」:
1699
1685
  * - `tokens` = usage.totalTokens + releases.usage.totalTokens(含订阅阶段);
@@ -1761,11 +1747,13 @@ declare class Runner {
1761
1747
  private summarize;
1762
1748
  private failureSummary;
1763
1749
  /**
1764
- * 从本次核心返回的 artifact 清单里选当天的 `.md`,只读复制到 previews/。
1750
+ * 把本次运行渲染的 digest 原文写进 previews/(ipc v3 起由核心随 RunResult 回传)。
1765
1751
  *
1766
- * 这是插件**唯一**读取用户仓库的场景,而且只读核心自己刚写出的那一份:
1767
- * - 路径必须在 `repo.path` 内(词法 + realpath 各判一次,挡 `..`/绝对路径/符号链接);
1768
- * - 上限 512 KB、拒绝 NUL、sha256 必须与契约声明一致;
1752
+ * 这里**不再读用户仓库**:分支可能在运行结束前就被合并、切回 base 并删除,
1753
+ * 任何「事后去工作树里找文件」的做法都会读到旧版或空。文本、字节数与 sha256
1754
+ * 的跨字段一致性已在 `parseRunResult` 里校验过;这里只做展示侧的防御:
1755
+ * - 上限 512 KB(与预览读取上限一致)、拒绝 NUL;
1756
+ * - 落盘前统一 `redact()`,避免把密钥写进预览文件;
1769
1757
  * - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
1770
1758
  */
1771
1759
  private capturePreview;
@@ -1775,18 +1763,14 @@ declare class Runner {
1775
1763
  coreStatus(fresh?: boolean): Promise<CoreStatus>;
1776
1764
  }
1777
1765
 
1778
- declare const runner_FORCE_MIN_CORE_VERSION: typeof FORCE_MIN_CORE_VERSION;
1779
- declare const runner_REPO_MIN_CORE_VERSION: typeof REPO_MIN_CORE_VERSION;
1780
1766
  type runner_RunRequestOptions = RunRequestOptions;
1781
1767
  type runner_RunSummary = RunSummary;
1782
1768
  type runner_Runner = Runner;
1783
1769
  declare const runner_Runner: typeof Runner;
1784
1770
  type runner_RunnerDeps = RunnerDeps;
1785
- declare const runner_isoWeekName: typeof isoWeekName;
1786
- declare const runner_periodFileBase: typeof periodFileBase;
1787
1771
  declare const runner_projectUsage: typeof projectUsage;
1788
1772
  declare namespace runner {
1789
- export { runner_FORCE_MIN_CORE_VERSION as FORCE_MIN_CORE_VERSION, runner_REPO_MIN_CORE_VERSION as REPO_MIN_CORE_VERSION, type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps, runner_isoWeekName as isoWeekName, runner_periodFileBase as periodFileBase, runner_projectUsage as projectUsage };
1773
+ export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps, runner_projectUsage as projectUsage };
1790
1774
  }
1791
1775
 
1792
1776
  /** 订阅命令超时:核心只做一次 GitHub 请求/本地文件读写,8 s 足够。 */
@@ -1980,23 +1964,6 @@ declare namespace schedule {
1980
1964
  export { schedule_RELEASE_CATCH_UP_MINUTES as RELEASE_CATCH_UP_MINUTES, type schedule_ReleaseScheduleConfig as ReleaseScheduleConfig, type schedule_ScheduleConfig as ScheduleConfig, schedule_Scheduler as Scheduler, type schedule_SchedulerDeps as SchedulerDeps, schedule_daysInMonth as daysInMonth, schedule_isDue as isDue, schedule_isPeriodTriggerDay as isPeriodTriggerDay, schedule_isReleaseDue as isReleaseDue, schedule_isoWeekKey as isoWeekKey, schedule_nextOccurrence as nextOccurrence, schedule_nextOccurrenceFor as nextOccurrenceFor, schedule_nextReleaseOccurrence as nextReleaseOccurrence, schedule_parseClock as parseClock, schedule_zonedDateString as zonedDateString, schedule_zonedTimeToUtc as zonedTimeToUtc };
1981
1965
  }
1982
1966
 
1983
- /**
1984
- * 版本比较:只用于「核心是否足够新到支持某个 CLI flag」这类能力闸门。
1985
- *
1986
- * 核心的 `version --json` 只给 `x.y.z`(偶带预发布后缀),这里按三段数字比较,
1987
- * 后缀忽略。不做完整 semver 区间——项目只需要「>= 阈值」这一种判断,够用且不易错。
1988
- */
1989
- /**
1990
- * `a >= b` 时返回 true。任一方不可解析时返回 false(保守:当作「不满足」,
1991
- * 让调用方降级为不带新 flag 的行为,避免向旧核心传未知参数)。
1992
- */
1993
- declare function versionAtLeast(a: string, b: string): boolean;
1994
-
1995
- declare const version_versionAtLeast: typeof versionAtLeast;
1996
- declare namespace version {
1997
- export { version_versionAtLeast as versionAtLeast };
1998
- }
1999
-
2000
1967
  /**
2001
1968
  * Host HTTP 面:`/api/morehao/github-trending/*`。
2002
1969
  *
@@ -2346,4 +2313,4 @@ declare namespace tools {
2346
2313
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
2347
2314
  }
2348
2315
 
2349
- export { channel, config, core, credentials, errors, github, githubAuth, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, subscribe, tools, update as updateService, version };
2316
+ export { channel, config, core, credentials, errors, github, githubAuth, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, subscribe, tools, update as updateService };
package/lib/internal.js CHANGED
@@ -25,9 +25,8 @@ import {
25
25
  state_exports,
26
26
  subscribe_exports,
27
27
  tools_exports,
28
- update_exports,
29
- version_exports
30
- } from "./chunk-DGEO5BHK.js";
28
+ update_exports
29
+ } from "./chunk-JMIWABMY.js";
31
30
 
32
31
  // src/internal.ts
33
32
  import * as exec from "@morehao/dsh-plugin-kit/exec";
@@ -59,6 +58,5 @@ export {
59
58
  state_exports as state,
60
59
  subscribe_exports as subscribe,
61
60
  tools_exports as tools,
62
- update_exports as updateService,
63
- version_exports as version
61
+ update_exports as updateService
64
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.8.23",
3
+ "version": "0.9.0",
4
4
  "description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@morehao/dsh-plugin-kit": "^0.4.1",
55
- "@morehao/trendradar-contract": "0.5.0"
55
+ "@morehao/trendradar-contract": "0.6.0"
56
56
  },
57
57
  "dsh": {
58
58
  "bundle": {
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "core": {
62
62
  "repo": "morehao/trendradar",
63
- "version": "0.8.8"
63
+ "version": "0.9.0"
64
64
  },
65
65
  "client": {
66
66
  "platform": "web",