@morehao/dsh-github-trending 0.6.1 → 0.7.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/README.md CHANGED
@@ -31,6 +31,8 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
31
31
  2. **填模型**:点「沿用 DSH 配置」从宿主 provider 目录里挑 provider 与模型;可导出密钥的直接传给核心,OAuth 类凭据走本机回环代理。
32
32
  3. **试跑一次**:「试跑(不提交)」只抓取与渲染,不写文件、不提交。
33
33
 
34
+ > **重新生成(忽略缓存)**:手动「重新生成」会忽略核心持久化的摘要缓存,强制重新抓取与总结,并让榜单页以本次结果为准。若重新生成后内容与分支上已有产物逐字节相同,核心仍按 append-only 语义**不新增提交**(面板提示「内容无变化,未新增提交」);内容有变化才追加一个提交。定时运行永不强制重跑。
35
+
34
36
  > 设置页分「内容来源 / 模型 / 仓库 / 认证 / 榜单 / 调度」六个 tab;**除「内容来源」(只有状态与核心操作,没有可保存字段)外,每个 tab 独立保存与校验**;某个 tab 有未保存改动时,左侧对应 tab 会显示黄点。
35
37
 
36
38
  ## 榜单预览与按周期回看
@@ -133,7 +135,7 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
133
135
 
134
136
  | 工具 | 参数 | 返回 |
135
137
  |---|---|---|
136
- | `trending_run` | `{ dryRun?: boolean, date?: string, period?: 'daily'\|'weekly'\|'monthly' }` | 结构化摘要:文件数、commit/分支、PR、内容哈希、错误码与下一步 |
138
+ | `trending_run` | `{ dryRun?: boolean, date?: string, period?: 'daily'\|'weekly'\|'monthly', force?: boolean }` | 结构化摘要:文件数、commit/分支、PR、内容哈希、错误码与下一步 |
137
139
  | `trending_history` | `{ limit?: number }` | 上次/下次运行、最近 N 条记录、核心版本、模型、未合并 PR |
138
140
 
139
141
  工具名是稳定对外接口:**只新增不改名**。
package/client.js CHANGED
@@ -294,6 +294,10 @@ var DICT = {
294
294
  "overview.live.note": "核心一次完成抓取、总结、渲染与提交,结束前没有分段进度;这里只显示已用时间和粗阶段。",
295
295
  "overview.live.started": "开始于",
296
296
  "run.dryRunShort": "试跑",
297
+ "run.force": "重新生成",
298
+ "run.force.hint": "忽略摘要缓存,强制重新抓取与总结",
299
+ "run.force.noChange": "内容无变化,未新增提交",
300
+ "run.forced.tag": "强制重跑",
297
301
  "run.usage.commit": "提交",
298
302
  "runs.count": "最近 {count} 次,超出即裁剪",
299
303
  "runs.files": "{count} 个文件",
@@ -352,6 +356,7 @@ var DICT = {
352
356
  "digest.select": "选择日报日期",
353
357
  "digest.render": "渲染",
354
358
  "digest.raw": "原文",
359
+ "digest.regenerate": "重新生成这一期",
355
360
  "digest.mode": "预览方式",
356
361
  "digest.copyPath": "复制路径",
357
362
  "digest.copied": "已复制",
@@ -801,6 +806,10 @@ var DICT = {
801
806
  "overview.live.note": "The core fetches, summarizes, renders and commits in one run; there is no per-step progress before it finishes. Only elapsed time and a coarse phase are shown here.",
802
807
  "overview.live.started": "Started",
803
808
  "run.dryRunShort": "Dry run",
809
+ "run.force": "Regenerate",
810
+ "run.force.hint": "Ignore the summary cache and re-fetch and re-summarize",
811
+ "run.force.noChange": "No content change; no new commit",
812
+ "run.forced.tag": "Forced",
804
813
  "run.usage.commit": "Commit",
805
814
  "runs.count": "Last {count} runs; older ones are trimmed",
806
815
  "runs.files": "{count} files",
@@ -859,6 +868,7 @@ var DICT = {
859
868
  "digest.select": "Choose a digest date",
860
869
  "digest.render": "Rendered",
861
870
  "digest.raw": "Raw",
871
+ "digest.regenerate": "Regenerate this period",
862
872
  "digest.mode": "Preview mode",
863
873
  "digest.copyPath": "Copy path",
864
874
  "digest.copied": "Copied",
@@ -2740,14 +2750,18 @@ function useRunController(store) {
2740
2750
  const [dryRun, setDryRun] = import_react.default.useState(false);
2741
2751
  const [localStartedAt, setLocalStartedAt] = import_react.default.useState(null);
2742
2752
  const run = import_react.default.useCallback(
2743
- async (overrideDry) => {
2753
+ async (overrideDry, options) => {
2744
2754
  const dry = overrideDry ?? dryRun;
2745
2755
  setBusy(true);
2746
2756
  setError(null);
2747
2757
  setSummary(null);
2748
2758
  setLocalStartedAt((/* @__PURE__ */ new Date()).toISOString());
2749
2759
  try {
2750
- const result = await apiCall("POST", "/run", { dryRun: dry });
2760
+ const body = { dryRun: dry };
2761
+ if (options?.force === true) body.force = true;
2762
+ if (options?.date !== void 0 && options.date !== "") body.date = options.date;
2763
+ if (options?.period !== void 0) body.period = options.period;
2764
+ const result = await apiCall("POST", "/run", body);
2751
2765
  setSummary(result.summary);
2752
2766
  await refresh({ quiet: true });
2753
2767
  } catch (failure) {
@@ -2936,6 +2950,9 @@ function digestTarget(record, timeZone) {
2936
2950
  const period = recordPeriod(record);
2937
2951
  return { period, bucket: periodBucket(period, info.date) };
2938
2952
  }
2953
+ function regenTarget(day) {
2954
+ return { period: day.period, date: day.date };
2955
+ }
2939
2956
  function buildPeriodGroups(records, options) {
2940
2957
  const now = options.now ?? /* @__PURE__ */ new Date();
2941
2958
  const today = zonedDateString(now, options.timeZone);
@@ -3325,7 +3342,10 @@ function recordSummary(t, record) {
3325
3342
  if (!record.ok) {
3326
3343
  return [record.errorCode, record.errorMessage].filter((item) => item !== void 0 && item !== "").join(" · ") || t("state.failed");
3327
3344
  }
3328
- if (record.skipped) return skipHint(t, record.skipReason, t("runs.noChange"));
3345
+ if (record.skipped) {
3346
+ if (record.forced === true && record.skipReason === "unchanged") return t("run.force.noChange");
3347
+ return skipHint(t, record.skipReason, t("runs.noChange"));
3348
+ }
3329
3349
  if (record.kind === "dry-run") return t("runs.dryHint");
3330
3350
  if (record.branch !== void 0) return record.branch;
3331
3351
  if (record.commit !== void 0) return record.commit;
@@ -3459,7 +3479,8 @@ function RunsView(props) {
3459
3479
  { className: "dshgt-run-what" },
3460
3480
  record.kind === "dry-run" ? t("state.dry") : record.skipped ? t("state.skipped") : record.ok ? t("state.ok") : t("state.failed"),
3461
3481
  h("span", { className: "dshgt-run-muted" }, recordSummary(t, record)),
3462
- record.legacy === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("runs.legacy")) : null
3482
+ record.legacy === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("runs.legacy")) : null,
3483
+ record.forced === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("run.forced.tag")) : null
3463
3484
  ),
3464
3485
  h("span", { className: "dshgt-run-files" }, t("runs.files", { count: record.files })),
3465
3486
  record.tokens === void 0 ? null : h("span", { className: "dshgt-run-tokens dshgt-mono", title: t("run.usage.tokens") }, `${formatTokens(record.tokens)} tok`),
@@ -4074,6 +4095,7 @@ function EmptyDigest(props) {
4074
4095
  "div",
4075
4096
  { className: "dshgt-actions" },
4076
4097
  h(Btn, { small: true, onClick: props.onOpenRuns }, t("digest.empty.viewRuns")),
4098
+ props.onRegenerate === void 0 ? null : h(Btn, { small: true, onClick: props.onRegenerate, disabled: props.regenDisabled === true }, t("digest.regenerate")),
4077
4099
  nearest === void 0 ? null : h(
4078
4100
  Btn,
4079
4101
  { small: true, onClick: () => props.onSelect({ period: nearest.period, bucket: nearest.bucket }) },
@@ -4083,7 +4105,7 @@ function EmptyDigest(props) {
4083
4105
  );
4084
4106
  }
4085
4107
  function DigestView(props) {
4086
- const { t, lang, store, selected } = props;
4108
+ const { t, lang, store, selected, controller } = props;
4087
4109
  const [mode, setMode] = import_react.default.useState("render");
4088
4110
  const [payload, setPayload] = import_react.default.useState(null);
4089
4111
  const [loading, setLoading] = import_react.default.useState(false);
@@ -4119,20 +4141,20 @@ function DigestView(props) {
4119
4141
  setLoading(false);
4120
4142
  return () => void 0;
4121
4143
  }
4122
- const controller = new AbortController();
4144
+ const controller2 = new AbortController();
4123
4145
  setLoading(true);
4124
4146
  setError(null);
4125
4147
  setPayload(null);
4126
- getPreview(previewRunId, controller.signal).then((result) => {
4127
- if (controller.signal.aborted) return;
4148
+ getPreview(previewRunId, controller2.signal).then((result) => {
4149
+ if (controller2.signal.aborted) return;
4128
4150
  setPayload(result);
4129
4151
  setLoading(false);
4130
4152
  }).catch((failure) => {
4131
- if (controller.signal.aborted) return;
4153
+ if (controller2.signal.aborted) return;
4132
4154
  setError(failure instanceof Error ? failure.message : String(failure));
4133
4155
  setLoading(false);
4134
4156
  });
4135
- return () => controller.abort();
4157
+ return () => controller2.abort();
4136
4158
  }, [previewRunId]);
4137
4159
  if (state === null) {
4138
4160
  return h("div", { className: "dshgt-empty" }, store.error ?? t("quickfill.loading"));
@@ -4253,6 +4275,12 @@ function DigestView(props) {
4253
4275
  }
4254
4276
  const showEmpty = day.status !== "preview" || error !== null;
4255
4277
  const record = day.record;
4278
+ const regenerate = () => {
4279
+ const target = regenTarget(day);
4280
+ props.onSelect({ period: target.period, bucket: day.bucket });
4281
+ void controller.run(false, { force: true, date: target.date, period: target.period });
4282
+ };
4283
+ const regenerateDisabled = controller.running || controller.busy;
4256
4284
  return h(
4257
4285
  "div",
4258
4286
  { className: "dshgt-view dshgt-digest" },
@@ -4268,7 +4296,9 @@ function DigestView(props) {
4268
4296
  ...nearest === void 0 ? {} : { nearest },
4269
4297
  ...error === null ? {} : { error },
4270
4298
  onOpenRuns: props.onOpenRuns,
4271
- onSelect: props.onSelect
4299
+ onSelect: props.onSelect,
4300
+ onRegenerate: regenerate,
4301
+ regenDisabled: regenerateDisabled
4272
4302
  }) : h(
4273
4303
  "div",
4274
4304
  { className: "dshgt-digest-body" },
@@ -4284,6 +4314,7 @@ function DigestView(props) {
4284
4314
  h("span", { className: "dshgt-dim" }, record.preview === void 0 ? "—" : formatBytes(record.preview.bytes)),
4285
4315
  payload?.preview.truncated === true ? h("span", { className: "dshgt-tag" }, t("digest.truncated")) : null,
4286
4316
  h("span", { className: "dshgt-grow" }),
4317
+ h(Btn, { small: true, onClick: regenerate, disabled: regenerateDisabled }, t("digest.regenerate")),
4287
4318
  h(
4288
4319
  "div",
4289
4320
  { className: "dshgt-seg", role: "group", "aria-label": t("digest.mode") },
@@ -5550,7 +5581,7 @@ function apply(ctx) {
5550
5581
  state.github.configured
5551
5582
  ].filter(Boolean).length;
5552
5583
  const digestTail = state?.status.lastResult?.date ?? state?.status.history[0]?.date;
5553
- const showActions = view === "overview" || view === "runs";
5584
+ const showActions = view === "overview" || view === "runs" || view === "digest";
5554
5585
  const nextRun = schedule === void 0 || !schedule.enabled ? t("shell.scheduleOff") : schedule.nextRunAt === void 0 ? `${schedule.time} · ${schedule.timezone}` : nextRunText(schedule.nextRunAt);
5555
5586
  const relNext = schedule !== void 0 && schedule.enabled && schedule.nextRunAt !== void 0 ? relativeFuture(schedule.nextRunAt, t) : void 0;
5556
5587
  const viewNode = view === "overview" ? h(OverviewView, {
@@ -5565,6 +5596,7 @@ function apply(ctx) {
5565
5596
  t,
5566
5597
  lang,
5567
5598
  store: value,
5599
+ controller,
5568
5600
  ...digestTarget2 === void 0 ? {} : { selected: digestTarget2 },
5569
5601
  onSelect: setDigestTarget,
5570
5602
  onOpenRuns: openRuns
@@ -5641,6 +5673,15 @@ function apply(ctx) {
5641
5673
  },
5642
5674
  t("run.dryRunShort")
5643
5675
  ),
5676
+ h(
5677
+ Btn,
5678
+ {
5679
+ key: "force",
5680
+ onClick: () => void controller.run(false, { force: true }),
5681
+ disabled: controller.running || controller.busy
5682
+ },
5683
+ t("run.force")
5684
+ ),
5644
5685
  h(
5645
5686
  Btn,
5646
5687
  {
@@ -1611,6 +1611,7 @@ function buildCoreArgs(channelPath, options) {
1611
1611
  if (options.date !== void 0 && options.date !== "") args.push("--date", options.date);
1612
1612
  if (options.period !== void 0) args.push("--period", options.period);
1613
1613
  if (options.stages !== void 0 && options.stages.length > 0) args.push("--stages", options.stages.join(","));
1614
+ if (options.force === true) args.push("--force");
1614
1615
  return args;
1615
1616
  }
1616
1617
  async function runCore(options) {
@@ -1625,7 +1626,8 @@ async function runCore(options) {
1625
1626
  dryRun: options.dryRun,
1626
1627
  date: options.date,
1627
1628
  period: options.period,
1628
- stages: options.stages
1629
+ stages: options.stages,
1630
+ force: options.force
1629
1631
  });
1630
1632
  const child = spawn2(options.core.path, args, {
1631
1633
  env: sanitizedEnv(process.env, {
@@ -2722,9 +2724,33 @@ async function runSubscribe(input, deps) {
2722
2724
  }
2723
2725
  }
2724
2726
 
2727
+ // src/host/version.ts
2728
+ var version_exports = {};
2729
+ __export(version_exports, {
2730
+ versionAtLeast: () => versionAtLeast
2731
+ });
2732
+ function parseTriplet(value) {
2733
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(value.trim());
2734
+ if (match === null) return void 0;
2735
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
2736
+ }
2737
+ function versionAtLeast(a, b) {
2738
+ const left = parseTriplet(a);
2739
+ const right = parseTriplet(b);
2740
+ if (left === void 0 || right === void 0) return false;
2741
+ for (let i = 0; i < 3; i += 1) {
2742
+ const l = left[i];
2743
+ const r = right[i];
2744
+ if (l > r) return true;
2745
+ if (l < r) return false;
2746
+ }
2747
+ return true;
2748
+ }
2749
+
2725
2750
  // src/host/runner.ts
2726
2751
  var runner_exports = {};
2727
2752
  __export(runner_exports, {
2753
+ FORCE_MIN_CORE_VERSION: () => FORCE_MIN_CORE_VERSION,
2728
2754
  Runner: () => Runner,
2729
2755
  isoWeekName: () => isoWeekName,
2730
2756
  periodFileBase: () => periodFileBase,
@@ -2733,6 +2759,7 @@ __export(runner_exports, {
2733
2759
  import { createHash as createHash2 } from "crypto";
2734
2760
  import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync, statSync as statSync5 } from "fs";
2735
2761
  import path9 from "path";
2762
+ var FORCE_MIN_CORE_VERSION = "0.7.1";
2736
2763
  function repoProblem(repoPath) {
2737
2764
  if (repoPath.trim() === "") return { code: "CONFIG_INVALID", message: "\u8FD8\u6CA1\u6709\u9009\u62E9\u5F52\u6863\u4ED3\u5E93" };
2738
2765
  if (!existsSync4(repoPath)) return { code: "REPO_NOT_GIT", message: `\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${repoPath}` };
@@ -2851,6 +2878,15 @@ var Runner = class {
2851
2878
  `\u6838\u5FC3\u4E0D\u652F\u6301 provider\u300C${settings.model.provider}\u300D\uFF1B\u53EF\u7528\uFF1A${handshakeResult.supportedProviders.join(" / ")}`
2852
2879
  );
2853
2880
  }
2881
+ const forceRequested = options.force === true;
2882
+ const forceSupported = versionAtLeast(handshakeResult.version.version, FORCE_MIN_CORE_VERSION);
2883
+ const effectiveForce = forceRequested && forceSupported;
2884
+ if (forceRequested && !forceSupported) {
2885
+ store.appendLog(
2886
+ logFile,
2887
+ `[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`
2888
+ );
2889
+ }
2854
2890
  const model = await this.openModelChannel({ settings, credentials, env, logFile });
2855
2891
  const apiKey = model.apiKey;
2856
2892
  proxy = model.proxy;
@@ -2868,6 +2904,7 @@ var Runner = class {
2868
2904
  period,
2869
2905
  dryRun,
2870
2906
  trigger: options.trigger === "schedule" ? "schedule" : "manual",
2907
+ ...effectiveForce ? { force: true } : {},
2871
2908
  ...model.baseURLOverride === void 0 ? {} : { baseURLOverride: model.baseURLOverride },
2872
2909
  signal: controller.signal,
2873
2910
  secrets: [apiKey, github.token],
@@ -2884,7 +2921,8 @@ var Runner = class {
2884
2921
  channel: result.channelKind,
2885
2922
  proxy: proxy !== void 0,
2886
2923
  coreSource: core.source,
2887
- ...core.tag === void 0 ? {} : { coreTag: core.tag }
2924
+ ...core.tag === void 0 ? {} : { coreTag: core.tag },
2925
+ ...effectiveForce ? { forced: true } : {}
2888
2926
  });
2889
2927
  const preview = this.capturePreview({
2890
2928
  id,
@@ -2892,7 +2930,8 @@ var Runner = class {
2892
2930
  period,
2893
2931
  settings,
2894
2932
  outcome,
2895
- secrets: [apiKey, github.token]
2933
+ secrets: [apiKey, github.token],
2934
+ ...effectiveForce ? { force: true } : {}
2896
2935
  });
2897
2936
  if (preview !== void 0) summary.preview = preview;
2898
2937
  this.record(summary, settings);
@@ -3192,7 +3231,8 @@ var Runner = class {
3192
3231
  ...input.channel === void 0 ? {} : { channel: input.channel },
3193
3232
  proxy: input.proxy,
3194
3233
  ...input.coreSource === void 0 ? {} : { coreSource: input.coreSource },
3195
- ...input.coreTag === void 0 ? {} : { coreTag: input.coreTag }
3234
+ ...input.coreTag === void 0 ? {} : { coreTag: input.coreTag },
3235
+ ...input.forced === true ? { forced: true } : {}
3196
3236
  };
3197
3237
  }
3198
3238
  failureSummary(input) {
@@ -3223,7 +3263,8 @@ var Runner = class {
3223
3263
  */
3224
3264
  capturePreview(input) {
3225
3265
  const { outcome, settings, date, period, id } = input;
3226
- if (!outcome.ok || outcome.dryRun || outcome.skipped || date === void 0) return void 0;
3266
+ if (!outcome.ok || outcome.dryRun || date === void 0) return void 0;
3267
+ if (outcome.skipped && input.force !== true) return void 0;
3227
3268
  const skip = (reason, extra = {}) => {
3228
3269
  this.deps.logger?.("preview.skip", { id, reason, ...extra });
3229
3270
  return void 0;
@@ -3294,6 +3335,7 @@ var Runner = class {
3294
3335
  ...summary.error.reason === void 0 ? {} : { errorReason: summary.error.reason }
3295
3336
  },
3296
3337
  ...summary.skipReason === void 0 ? {} : { skipReason: summary.skipReason },
3338
+ ...summary.forced === true ? { forced: true } : {},
3297
3339
  durationMs: summary.durationMs,
3298
3340
  files: summary.files,
3299
3341
  ...summary.date === void 0 ? {} : { date: summary.date },
@@ -3319,7 +3361,9 @@ var Runner = class {
3319
3361
  ...summary.ok ? { lastSuccessAt: record.at } : {},
3320
3362
  ...summary.ok && summary.period !== void 0 ? { periodSuccess } : {},
3321
3363
  ...summary.ok && summary.prUrl !== void 0 ? { pendingPr: { url: summary.prUrl, createdAt: record.at, ...summary.date === void 0 ? {} : { date: summary.date } } } : {},
3322
- ...summary.ok && !summary.skipped && summary.contentHash !== void 0 ? {
3364
+ // 幂等:内容未变(skipped)通常不刷新 lastDigest;但显式 force 时即便 core 判定
3365
+ // unchanged,也要把本次作为最新展示(contentHash 仍是真实 hash,不伪造)。
3366
+ ...summary.ok && summary.contentHash !== void 0 && (!summary.skipped || summary.forced === true) ? {
3323
3367
  lastDigest: {
3324
3368
  date: summary.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3325
3369
  contentHash: summary.contentHash,
@@ -4060,6 +4104,7 @@ async function dispatch(method, pathname, url, request, deps) {
4060
4104
  const base = {
4061
4105
  dryRun: body.dryRun === true,
4062
4106
  ...typeof body.date === "string" && body.date !== "" ? { date: body.date } : {},
4107
+ ...body.force === true ? { force: true } : {},
4063
4108
  trigger: "manual"
4064
4109
  };
4065
4110
  if (isPeriod(body.period)) {
@@ -4333,6 +4378,7 @@ var RUN_OUTPUT_SCHEMA = {
4333
4378
  ok: { type: "boolean" },
4334
4379
  skipped: { type: "boolean" },
4335
4380
  dryRun: { type: "boolean" },
4381
+ forced: { type: "boolean" },
4336
4382
  durationMs: { type: "number" },
4337
4383
  coreVersion: { type: "string" },
4338
4384
  files: { type: "number" },
@@ -4393,11 +4439,12 @@ function describeRecord(record) {
4393
4439
  function createTrendingRunTool(runner) {
4394
4440
  return {
4395
4441
  name: "trending_run",
4396
- description: "\u6267\u884C GitHub \u8D8B\u52BF\u699C\u5355\uFF1A\u5B9A\u4F4D\u5E76\u63E1\u624B\u672C\u5730\u6838\u5FC3\uFF0Cspawn \u6838\u5FC3 run\uFF0C\u8FD4\u56DE\u7ED3\u6784\u5316\u6458\u8981\uFF08\u6587\u4EF6\u6570\u3001commit\u3001PR\u3001\u9519\u8BEF\u7801\uFF09\u3002\u7701\u7565 period \u65F6\u9ED8\u8BA4\u4EA7\u51FA\u65E5/\u5468/\u6708\u4E09\u699C\uFF08\u4F9D\u6B21\u8FD0\u884C\u4E09\u6B21\uFF0C\u8FD4\u56DE summaries\uFF09\u3002dryRun=true \u53EA\u6293\u53D6\u4E0E\u6E32\u67D3\u3001\u4E0D\u5199\u6587\u4EF6\u4E5F\u4E0D\u63D0\u4EA4\u3002\u540C\u65E5\u540C\u5185\u5BB9\u91CD\u8DD1\u4F1A\u8FD4\u56DE skipped=true\uFF08\u96F6\u65B0\u589E commit\uFF09\u3002",
4442
+ description: "\u6267\u884C GitHub \u8D8B\u52BF\u699C\u5355\uFF1A\u5B9A\u4F4D\u5E76\u63E1\u624B\u672C\u5730\u6838\u5FC3\uFF0Cspawn \u6838\u5FC3 run\uFF0C\u8FD4\u56DE\u7ED3\u6784\u5316\u6458\u8981\uFF08\u6587\u4EF6\u6570\u3001commit\u3001PR\u3001\u9519\u8BEF\u7801\uFF09\u3002\u7701\u7565 period \u65F6\u9ED8\u8BA4\u4EA7\u51FA\u65E5/\u5468/\u6708\u4E09\u699C\uFF08\u4F9D\u6B21\u8FD0\u884C\u4E09\u6B21\uFF0C\u8FD4\u56DE summaries\uFF09\u3002dryRun=true \u53EA\u6293\u53D6\u4E0E\u6E32\u67D3\u3001\u4E0D\u5199\u6587\u4EF6\u4E5F\u4E0D\u63D0\u4EA4\u3002force=true \u5FFD\u7565\u6458\u8981\u7F13\u5B58\u3001\u5F3A\u5236\u91CD\u65B0\u751F\u6210\uFF08\u5185\u5BB9\u65E0\u53D8\u5316\u65F6\u4E0D\u65B0\u589E\u63D0\u4EA4\uFF0C\u4F46\u4F1A\u5237\u65B0\u9884\u89C8\uFF09\u3002\u540C\u65E5\u540C\u5185\u5BB9\u91CD\u8DD1\u4F1A\u8FD4\u56DE skipped=true\uFF08\u96F6\u65B0\u589E commit\uFF09\u3002",
4397
4443
  parameters: {
4398
4444
  dryRun: { type: "boolean", description: "\u53EA\u8BD5\u8DD1\uFF1A\u4E0D\u5199\u6587\u4EF6\u3001\u4E0D\u63D0\u4EA4\u3001\u4E0D\u5F00 PR" },
4399
4445
  date: { type: "string", description: "\u6307\u5B9A\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u7701\u7565\u5219\u7528\u8C03\u5EA6\u65F6\u533A\u7684\u4ECA\u5929" },
4400
- period: { type: "string", enum: ["daily", "weekly", "monthly"], description: "\u53EA\u8DD1\u6307\u5B9A\u5468\u671F\uFF1B\u7701\u7565\u5219\u9ED8\u8BA4\u4EA7\u51FA\u65E5/\u5468/\u6708\u4E09\u699C" }
4446
+ period: { type: "string", enum: ["daily", "weekly", "monthly"], description: "\u53EA\u8DD1\u6307\u5B9A\u5468\u671F\uFF1B\u7701\u7565\u5219\u9ED8\u8BA4\u4EA7\u51FA\u65E5/\u5468/\u6708\u4E09\u699C" },
4447
+ force: { type: "boolean", description: "\u5FFD\u7565\u6458\u8981\u7F13\u5B58\u3001\u5F3A\u5236\u91CD\u65B0\u751F\u6210\uFF08\u5185\u5BB9\u65E0\u53D8\u5316\u65F6\u4E0D\u65B0\u589E\u63D0\u4EA4\uFF0C\u4F46\u4F1A\u5237\u65B0\u9884\u89C8\uFF09" }
4401
4448
  },
4402
4449
  output: {
4403
4450
  schema: RUN_OUTPUT_SCHEMA,
@@ -4408,13 +4455,14 @@ function createTrendingRunTool(runner) {
4408
4455
  const base = {
4409
4456
  dryRun: args.dryRun === true,
4410
4457
  ...typeof args.date === "string" && args.date !== "" ? { date: args.date } : {},
4458
+ ...args.force === true ? { force: true } : {},
4411
4459
  trigger: "tool"
4412
4460
  };
4413
4461
  const summaries = isPeriod(args.period) ? [await runner.run({ ...base, period: args.period })] : await runner.runMany(base);
4414
4462
  const lines = [];
4415
4463
  for (const summary of summaries) {
4416
4464
  const periodLabel = summary.period ?? "\u2014";
4417
- const head = summary.ok ? summary.skipped ? "\u65E0\u53D8\u66F4\uFF08skipped\uFF09" : summary.dryRun ? "\u8BD5\u8DD1\u5B8C\u6210" : "\u8FD0\u884C\u6210\u529F" : `\u8FD0\u884C\u5931\u8D25\uFF1A${summary.error?.code ?? "INTERNAL"}`;
4465
+ const head = summary.ok ? summary.skipped ? summary.forced ? "\u65E0\u53D8\u5316\uFF08\u5F3A\u5236\u91CD\u8DD1\uFF0C\u672A\u65B0\u589E\u63D0\u4EA4\uFF09" : "\u65E0\u53D8\u66F4\uFF08skipped\uFF09" : summary.dryRun ? "\u8BD5\u8DD1\u5B8C\u6210" : "\u8FD0\u884C\u6210\u529F" : `\u8FD0\u884C\u5931\u8D25\uFF1A${summary.error?.code ?? "INTERNAL"}`;
4418
4466
  lines.push(`${summary.ok ? "\u2713" : "\u2717"} [${periodLabel}] ${head}`);
4419
4467
  lines.push(`\xB7 \u6838\u5FC3 ${summary.coreVersion || "(\u672A\u77E5)"} \xB7 ${formatDuration(summary.durationMs)} \xB7 ${summary.files} \u4E2A\u6587\u4EF6`);
4420
4468
  if (summary.tokens !== void 0) {
@@ -4444,6 +4492,7 @@ function createTrendingRunTool(runner) {
4444
4492
  ok: summary.ok,
4445
4493
  skipped: summary.skipped,
4446
4494
  dryRun: summary.dryRun,
4495
+ ...summary.forced === true ? { forced: true } : {},
4447
4496
  durationMs: summary.durationMs,
4448
4497
  files: summary.files,
4449
4498
  ...summary.tokens === void 0 ? {} : { tokens: summary.tokens },
@@ -4455,6 +4504,7 @@ function createTrendingRunTool(runner) {
4455
4504
  id: primary?.id,
4456
4505
  skipped: summaries.every((summary) => summary.skipped),
4457
4506
  dryRun: base.dryRun,
4507
+ ...base.force === true ? { forced: true } : {},
4458
4508
  durationMs: summaries.reduce((sum, summary) => sum + summary.durationMs, 0),
4459
4509
  files: summaries.reduce((sum, summary) => sum + summary.files, 0),
4460
4510
  ...(() => {
@@ -4530,6 +4580,7 @@ export {
4530
4580
  Scheduler,
4531
4581
  schedule_exports,
4532
4582
  subscribe_exports,
4583
+ version_exports,
4533
4584
  Runner,
4534
4585
  runner_exports,
4535
4586
  readPluginInfo,
package/lib/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  createTrendingRunTool,
9
9
  createUpdateService,
10
10
  readPluginInfo
11
- } from "./chunk-WDBZAMWN.js";
11
+ } from "./chunk-VF6DOIQQ.js";
12
12
  import "./chunk-R7BK7SJK.js";
13
13
 
14
14
  // src/index.ts
package/lib/internal.d.ts CHANGED
@@ -555,6 +555,8 @@ interface RunRecord {
555
555
  errorReason?: string;
556
556
  /** 跳过原因:merged / pr-closed / unchanged。 */
557
557
  skipReason?: string;
558
+ /** 本次是显式「强制重跑(忽略缓存)」;调度永不置位。仅用于展示与区分幂等。 */
559
+ forced?: boolean;
558
560
  durationMs: number;
559
561
  files: number;
560
562
  /** 运行所属的调度时区日期(YYYY-MM-DD);旧记录可能没有。 */
@@ -973,6 +975,8 @@ interface RunCoreOptions {
973
975
  dryRun: boolean;
974
976
  /** 谁触发的运行:核心用它在定时运行时尊重「人工关闭的 PR」。 */
975
977
  trigger?: 'manual' | 'schedule';
978
+ /** 忽略摘要缓存、强制重新生成(核心 `run --force`);不影响 append-only 提交判断。 */
979
+ force?: boolean;
976
980
  /** 回环代理地址:覆盖配置里的 model.baseURL(代理模式专用)。 */
977
981
  baseURLOverride?: string;
978
982
  /**
@@ -999,6 +1003,7 @@ declare function buildCoreArgs(channelPath: string, options: {
999
1003
  date?: string;
1000
1004
  period?: Period;
1001
1005
  stages?: string[];
1006
+ force?: boolean;
1002
1007
  }): string[];
1003
1008
  declare function runCore(options: RunCoreOptions): Promise<RunCoreResult>;
1004
1009
 
@@ -1436,6 +1441,11 @@ declare namespace pendingPr {
1436
1441
  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 };
1437
1442
  }
1438
1443
 
1444
+ /**
1445
+ * 支持 `run --force`(忽略摘要缓存重新生成)的最低核心版本。
1446
+ * 与 core patch 发版 `0.7.1` 对齐;`dsh.core.version` 应 >= 它,dev override 指向更旧核心时靠闸门降级。
1447
+ */
1448
+ declare const FORCE_MIN_CORE_VERSION = "0.7.1";
1439
1449
  interface RunSummary {
1440
1450
  id: string;
1441
1451
  ok: boolean;
@@ -1454,6 +1464,8 @@ interface RunSummary {
1454
1464
  preview?: PreviewMeta;
1455
1465
  /** 核心给出的跳过原因(merged / pr-closed / unchanged)。 */
1456
1466
  skipReason?: string;
1467
+ /** 本次是显式「强制重跑(忽略缓存)」;调度永不置位。 */
1468
+ forced?: boolean;
1457
1469
  error?: {
1458
1470
  code: string;
1459
1471
  message: string;
@@ -1481,6 +1493,8 @@ interface RunRequestOptions {
1481
1493
  date?: string;
1482
1494
  /** 标的周期;缺省取 settings.trending.since(手动/工具运行)。 */
1483
1495
  period?: Period;
1496
+ /** 强制重跑:忽略摘要缓存、重新生成(核心 `run --force`);仅手动/工具触发,调度永不设置。 */
1497
+ force?: boolean;
1484
1498
  trigger: 'manual' | 'schedule' | 'tool';
1485
1499
  }
1486
1500
  interface RunnerDeps {
@@ -1577,6 +1591,7 @@ declare class Runner {
1577
1591
  coreStatus(fresh?: boolean): Promise<CoreStatus>;
1578
1592
  }
1579
1593
 
1594
+ declare const runner_FORCE_MIN_CORE_VERSION: typeof FORCE_MIN_CORE_VERSION;
1580
1595
  type runner_RunRequestOptions = RunRequestOptions;
1581
1596
  type runner_RunSummary = RunSummary;
1582
1597
  type runner_Runner = Runner;
@@ -1586,7 +1601,7 @@ declare const runner_isoWeekName: typeof isoWeekName;
1586
1601
  declare const runner_periodFileBase: typeof periodFileBase;
1587
1602
  declare const runner_projectUsage: typeof projectUsage;
1588
1603
  declare namespace runner {
1589
- export { 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 };
1604
+ export { runner_FORCE_MIN_CORE_VERSION as FORCE_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 };
1590
1605
  }
1591
1606
 
1592
1607
  /** 订阅命令超时:核心只做一次 GitHub 请求/本地文件读写,8 s 足够。 */
@@ -1769,6 +1784,23 @@ declare namespace schedule {
1769
1784
  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 };
1770
1785
  }
1771
1786
 
1787
+ /**
1788
+ * 版本比较:只用于「核心是否足够新到支持某个 CLI flag」这类能力闸门。
1789
+ *
1790
+ * 核心的 `version --json` 只给 `x.y.z`(偶带预发布后缀),这里按三段数字比较,
1791
+ * 后缀忽略。不做完整 semver 区间——项目只需要「>= 阈值」这一种判断,够用且不易错。
1792
+ */
1793
+ /**
1794
+ * `a >= b` 时返回 true。任一方不可解析时返回 false(保守:当作「不满足」,
1795
+ * 让调用方降级为不带新 flag 的行为,避免向旧核心传未知参数)。
1796
+ */
1797
+ declare function versionAtLeast(a: string, b: string): boolean;
1798
+
1799
+ declare const version_versionAtLeast: typeof versionAtLeast;
1800
+ declare namespace version {
1801
+ export { version_versionAtLeast as versionAtLeast };
1802
+ }
1803
+
1772
1804
  /**
1773
1805
  * Host HTTP 面:`/api/morehao/github-trending/*`。
1774
1806
  *
@@ -1875,6 +1907,10 @@ declare function createTrendingRunTool(runner: Runner): {
1875
1907
  enum: string[];
1876
1908
  description: string;
1877
1909
  };
1910
+ force: {
1911
+ type: string;
1912
+ description: string;
1913
+ };
1878
1914
  };
1879
1915
  output: {
1880
1916
  schema: {
@@ -1893,6 +1929,9 @@ declare function createTrendingRunTool(runner: Runner): {
1893
1929
  readonly dryRun: {
1894
1930
  readonly type: "boolean";
1895
1931
  };
1932
+ readonly forced: {
1933
+ readonly type: "boolean";
1934
+ };
1896
1935
  readonly durationMs: {
1897
1936
  readonly type: "number";
1898
1937
  };
@@ -1943,6 +1982,7 @@ declare function createTrendingRunTool(runner: Runner): {
1943
1982
  dryRun?: boolean;
1944
1983
  date?: string;
1945
1984
  period?: string;
1985
+ force?: boolean;
1946
1986
  }, exec?: {
1947
1987
  signal?: AbortSignal;
1948
1988
  }): Promise<{
@@ -1952,6 +1992,9 @@ declare function createTrendingRunTool(runner: Runner): {
1952
1992
  errorHint?: string | undefined;
1953
1993
  warnings: string[];
1954
1994
  tokens: number;
1995
+ durationMs: number;
1996
+ files: number;
1997
+ forced?: boolean | undefined;
1955
1998
  ok: boolean;
1956
1999
  summaries: {
1957
2000
  errorCode?: string | undefined;
@@ -1960,19 +2003,18 @@ declare function createTrendingRunTool(runner: Runner): {
1960
2003
  branch?: string | undefined;
1961
2004
  releases?: ReleasesSummary | undefined;
1962
2005
  tokens?: number | undefined;
2006
+ durationMs: number;
2007
+ files: number;
2008
+ forced?: boolean | undefined;
1963
2009
  id: string;
1964
2010
  period: Period | undefined;
1965
2011
  ok: boolean;
1966
2012
  skipped: boolean;
1967
2013
  dryRun: boolean;
1968
- durationMs: number;
1969
- files: number;
1970
2014
  }[];
1971
2015
  id: string | undefined;
1972
2016
  skipped: boolean;
1973
2017
  dryRun: boolean;
1974
- durationMs: number;
1975
- files: number;
1976
2018
  } | {
1977
2019
  markdown: string;
1978
2020
  errorCode?: string | undefined;
@@ -1980,6 +2022,9 @@ declare function createTrendingRunTool(runner: Runner): {
1980
2022
  errorHint?: string | undefined;
1981
2023
  warnings: string[];
1982
2024
  tokens?: undefined;
2025
+ durationMs: number;
2026
+ files: number;
2027
+ forced?: boolean | undefined;
1983
2028
  ok: boolean;
1984
2029
  summaries: {
1985
2030
  errorCode?: string | undefined;
@@ -1988,19 +2033,18 @@ declare function createTrendingRunTool(runner: Runner): {
1988
2033
  branch?: string | undefined;
1989
2034
  releases?: ReleasesSummary | undefined;
1990
2035
  tokens?: number | undefined;
2036
+ durationMs: number;
2037
+ files: number;
2038
+ forced?: boolean | undefined;
1991
2039
  id: string;
1992
2040
  period: Period | undefined;
1993
2041
  ok: boolean;
1994
2042
  skipped: boolean;
1995
2043
  dryRun: boolean;
1996
- durationMs: number;
1997
- files: number;
1998
2044
  }[];
1999
2045
  id: string | undefined;
2000
2046
  skipped: boolean;
2001
2047
  dryRun: boolean;
2002
- durationMs: number;
2003
- files: number;
2004
2048
  }>;
2005
2049
  };
2006
2050
  declare function createTrendingHistoryTool(store: Store, scheduler: Scheduler): {
@@ -2104,4 +2148,4 @@ declare namespace tools {
2104
2148
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
2105
2149
  }
2106
2150
 
2107
- export { channel, config, core, credentials, errors, exec, github, 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 };
2151
+ export { channel, config, core, credentials, errors, exec, github, 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 };
package/lib/internal.js CHANGED
@@ -24,8 +24,9 @@ import {
24
24
  state_exports,
25
25
  subscribe_exports,
26
26
  tools_exports,
27
- update_exports
28
- } from "./chunk-WDBZAMWN.js";
27
+ update_exports,
28
+ version_exports
29
+ } from "./chunk-VF6DOIQQ.js";
29
30
  import {
30
31
  exec_exports
31
32
  } from "./chunk-R7BK7SJK.js";
@@ -56,5 +57,6 @@ export {
56
57
  state_exports as state,
57
58
  subscribe_exports as subscribe,
58
59
  tools_exports as tools,
59
- update_exports as updateService
60
+ update_exports as updateService,
61
+ version_exports as version
60
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "core": {
61
61
  "repo": "morehao/trendradar",
62
- "version": "0.7.0"
62
+ "version": "0.7.1"
63
63
  },
64
64
  "client": {
65
65
  "platform": "web",