@morehao/dsh-github-trending 0.2.0 → 0.3.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/lib/index.js CHANGED
@@ -5,22 +5,16 @@ import {
5
5
  Store,
6
6
  createRoutes,
7
7
  createTrendingHistoryTool,
8
- createTrendingRunTool
9
- } from "./chunk-HIJIQJCZ.js";
8
+ createTrendingRunTool,
9
+ readPluginInfo
10
+ } from "./chunk-X4PBXIZE.js";
10
11
  import "./chunk-XLIFTAVL.js";
11
12
 
12
13
  // src/index.ts
13
- import { createRequire } from "module";
14
14
  var name = "dsh-github-trending";
15
15
  var inject = ["webServer"];
16
16
  function readVersion() {
17
- try {
18
- const require2 = createRequire(import.meta.url);
19
- const pkg = require2("../package.json");
20
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
21
- } catch {
22
- return "0.0.0";
23
- }
17
+ return readPluginInfo().version;
24
18
  }
25
19
  var VERSION = readVersion();
26
20
  function apply(ctx, config = {}) {
@@ -31,6 +25,18 @@ function apply(ctx, config = {}) {
31
25
  ctx.logger?.info?.(`[dsh-github-trending] ${event}${suffix}`);
32
26
  };
33
27
  const store = new Store({ env, ...config.stateDir === void 0 ? {} : { dir: config.stateDir } });
28
+ try {
29
+ const migration = store.migrateLegacyState();
30
+ if (migration.stateChanged || migration.historyMarked > 0) {
31
+ log("state.migrated", {
32
+ clearedDigest: migration.clearedDigest,
33
+ clearedPendingPr: migration.clearedPendingPr,
34
+ historyMarked: migration.historyMarked
35
+ });
36
+ }
37
+ } catch (error) {
38
+ ctx.logger?.warn?.(`[dsh-github-trending] \u65E7\u72B6\u6001\u8FC1\u79FB\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
39
+ }
34
40
  if (config.corePath !== void 0 && config.corePath !== "") {
35
41
  const settings = store.readSettings();
36
42
  if (settings.corePath === "") store.writeSettings({ ...settings, corePath: config.corePath });
@@ -39,6 +45,7 @@ function apply(ctx, config = {}) {
39
45
  store,
40
46
  get,
41
47
  env,
48
+ logger: log,
42
49
  onFinished: (summary) => log("run.finished", { id: summary.id, ok: summary.ok, skipped: summary.skipped, files: summary.files })
43
50
  });
44
51
  const scheduler = new Scheduler({
@@ -51,7 +58,18 @@ function apply(ctx, config = {}) {
51
58
  },
52
59
  onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
53
60
  });
54
- const handler = createRoutes({ store, runner, scheduler, get, env, version: VERSION, logger: log });
61
+ const pluginInfo = readPluginInfo();
62
+ const handler = createRoutes({
63
+ store,
64
+ runner,
65
+ scheduler,
66
+ get,
67
+ env,
68
+ version: VERSION,
69
+ pluginName: pluginInfo.name,
70
+ ...pluginInfo.repository === void 0 ? {} : { repository: pluginInfo.repository },
71
+ logger: log
72
+ });
55
73
  const register = () => {
56
74
  const webServer = ctx.webServer ?? get("webServer");
57
75
  if (webServer === void 0 || typeof webServer.register !== "function") {
package/lib/internal.d.ts CHANGED
@@ -1,61 +1,8 @@
1
+ import { ErrorCode, FileArtifact, ErrorInfo, RunRequest, RunResult, State, StateRun } from '@morehao/trendradar-contract';
1
2
  import http from 'node:http';
2
3
 
3
4
  /**
4
- * 由 scripts/gen-ipc-types.mjs 从 contract/schema/ipc.v1.json 生成,**不要手改**。
5
- * 上游:https://github.com/morehao/trendradar.git@5cf0ad26c5748cf2bc9a1cc4ce66a09411d590f7
6
- * aggregate:4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde
7
- * 重新生成:npm run gen:ipc
8
- */
9
- type ErrorCode = "INTERNAL" | "CONFIG_INVALID" | "PAYLOAD_TOO_LARGE" | "FETCH_FAILED" | "FETCH_RATE_LIMITED" | "PARSE_FAILED" | "MODEL_UNAUTHORIZED" | "MODEL_RATE_LIMITED" | "MODEL_TIMEOUT" | "MODEL_FAILED" | "MODEL_BAD_OUTPUT" | "GIT_NOT_INSTALLED" | "REPO_NOT_GIT" | "REPO_DIRTY" | "GIT_OPERATION_FAILED" | "PR_FAILED" | "NOT_MERGEABLE";
10
- interface ErrorInfo {
11
- code: ErrorCode;
12
- message: string;
13
- retriable: boolean;
14
- details?: Record<string, unknown>;
15
- [key: string]: unknown;
16
- }
17
- interface FileArtifact {
18
- path: string;
19
- bytes: number;
20
- sha256: string;
21
- [key: string]: unknown;
22
- }
23
- interface RunRequest {
24
- ipcSchema: 1;
25
- command: "run" | "serve" | "config" | "version";
26
- configPath?: string;
27
- date?: string;
28
- dryRun?: boolean;
29
- outputDir?: string;
30
- }
31
- interface RunResult {
32
- ipcSchema: 1;
33
- ok: boolean;
34
- command: "run" | "serve" | "config" | "version";
35
- startedAt: string;
36
- finishedAt: string;
37
- skipped: boolean;
38
- files: FileArtifact[];
39
- error?: ErrorInfo;
40
- warnings?: string[];
41
- [key: string]: unknown;
42
- }
43
- interface StateRun {
44
- date: string;
45
- contentHash: string;
46
- outputDir?: string;
47
- runAt: string;
48
- [key: string]: unknown;
49
- }
50
- interface State {
51
- ipcSchema: 1;
52
- lastRun?: StateRun;
53
- [key: string]: unknown;
54
- }
55
- declare const IPC_SCHEMA_VERSION: 1;
56
-
57
- /**
58
- * 错误码:核心码(`ipc.v1` 的 17 个)+ 插件自己的码。
5
+ * 错误码:核心码(契约包 `ipc.v2` / `errors.v2` 的 17 个)+ 插件自己的码。
59
6
  *
60
7
  * 核心当前只在 stderr 打 `error: <CODE>: <message>`,模型/抓取的失败甚至没有码,
61
8
  * 所以这里承担两件事:
@@ -79,8 +26,15 @@ declare function isRetriable(code: string): boolean;
79
26
  /** 错误码 → 界面落点。UI 用这个决定红字出现在哪个区块。 */
80
27
  type ErrorArea = 'core' | 'model' | 'repo' | 'auth' | 'run';
81
28
  declare function areaOf(code: string): ErrorArea;
29
+ /**
30
+ * push 失败的细分原因。核心新版本会在 `details.reason` 里给出;旧核心只有
31
+ * stderr 时按文本兜底。`stale-lease` 必须排在 `non-fast-forward` 之前:
32
+ * 前者是 lease 基线过期,后者是历史分叉,下一步完全不同。
33
+ */
34
+ type GitReason = 'stale-lease' | 'non-fast-forward' | 'auth' | 'protected' | 'hook' | 'pathspec' | 'unknown';
35
+ declare function reasonOf(code: string, message?: string, details?: Record<string, unknown>): string | undefined;
82
36
  /** 每种码一句「下一步怎么做」,避免界面只给码不给路。 */
83
- declare function hintOf(code: string, message?: string): string;
37
+ declare function hintOf(code: string, message?: string, reason?: string): string;
84
38
  /**
85
39
  * 从核心的 stderr 里解出一个结构化错误。
86
40
  *
@@ -99,38 +53,32 @@ declare function redact(input: string, secrets?: Array<string | undefined>): str
99
53
  type errors_AnyCode = AnyCode;
100
54
  type errors_ClassifiedError = ClassifiedError;
101
55
  type errors_ErrorArea = ErrorArea;
56
+ type errors_GitReason = GitReason;
102
57
  declare const errors_PLUGIN_CODES: typeof PLUGIN_CODES;
103
58
  type errors_PluginCode = PluginCode;
104
59
  declare const errors_areaOf: typeof areaOf;
105
60
  declare const errors_classifyStderr: typeof classifyStderr;
106
61
  declare const errors_hintOf: typeof hintOf;
107
62
  declare const errors_isRetriable: typeof isRetriable;
63
+ declare const errors_reasonOf: typeof reasonOf;
108
64
  declare const errors_redact: typeof redact;
109
65
  declare namespace errors {
110
- export { type errors_AnyCode as AnyCode, type errors_ClassifiedError as ClassifiedError, type errors_ErrorArea as ErrorArea, 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_redact as redact };
66
+ 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 };
111
67
  }
112
68
 
113
- /**
114
- * 契约类型与运行时守卫。
115
- *
116
- * 类型来自 `contract/schema/ipc.v1.json` 的生成物(`ipc.gen.ts`);本文件只放
117
- * 契约没有、但插件必须理解的东西:核心 `version --json` 的握手形状、核心 `run --json`
118
- * 的输出形状,以及把两种结果形状归一化成 `RunOutcome` 的解析器。
119
- *
120
- * 为什么同时接受两种形状:`contract/schema/ipc.v1.json` 定义的是 `RunResult`
121
- * (`ok`/`startedAt`/`error.code`),而当前核心的 `run --json` 输出的是一份更小的
122
- * `runOutput`(`files`/`skipped`/`branch`/`prUrl`/`digest.contentHash`),错误只走
123
- * stderr 文本。核心补上信封后,`parseRunResult` 无需改动即可接住。
124
- */
125
-
69
+ /** 插件实现所对应的 IPC 契约版本(契约包 `ipc.v2.json` 的 wire 整数)。 */
70
+ declare const IPC_SCHEMA_VERSION: 2;
126
71
  /** 插件能接受的 digest 契约版本(与核心 `version --json` 的 digestSchema 比对)。 */
127
- declare const SUPPORTED_DIGEST_SCHEMA = 1;
72
+ declare const SUPPORTED_DIGEST_SCHEMA: 2;
73
+ /** 契约包 `contractVersion` 的主版本;0.x 期间同主版本即视为同一契约线。 */
74
+ declare const SUPPORTED_CONTRACT_MAJOR = 0;
128
75
  /** 核心 `version --json` 的输出。 */
129
76
  interface VersionInfo {
130
77
  version: string;
131
78
  commit?: string;
132
79
  ipcSchema: number;
133
80
  digestSchema: number;
81
+ contractVersion?: string;
134
82
  supportedProviders?: string[];
135
83
  }
136
84
  /** 插件侧的错误信息:`code` 可能是核心码,也可能是插件码(`CORE_*` 等)。 */
@@ -150,6 +98,8 @@ interface RunOutcome {
150
98
  branch?: string;
151
99
  commit?: string;
152
100
  prUrl?: string;
101
+ /** 核心给出的跳过原因:merged | pr-closed | unchanged(可选,旧核心没有)。 */
102
+ skipReason?: string;
153
103
  error?: RunError;
154
104
  warnings: string[];
155
105
  startedAt: string;
@@ -168,6 +118,7 @@ declare class IpcError extends Error {
168
118
  declare function parseJsonObject(text: string): Record<string, unknown>;
169
119
  declare function isVersionInfo(value: unknown): value is VersionInfo;
170
120
  declare function parseVersionInfo(text: string): VersionInfo;
121
+ /** 契约包 `RunResult` → 插件内部 `RunOutcome` 的唯一映射。 */
171
122
  interface ParseRunOptions {
172
123
  coreVersion: string;
173
124
  startedAt: string;
@@ -178,33 +129,34 @@ interface ParseRunOptions {
178
129
  /**
179
130
  * 把核心 `run --json` 的 stdout 归一化成 `RunOutcome`。
180
131
  *
181
- * 两种形状的判别:有 `ipcSchema` 且 `command === 'run'` 就是契约 `RunResult`;
182
- * 否则按当前核心的 `runOutput` 处理。两者都不像就直接 `IPC_INVALID`,
183
- * 绝不「猜字段」——结果信封读错会把失败当成功写进状态。
132
+ * 唯一接受的形状是契约 `RunResult`(`ipcSchema=2` / `command=run`),并用契约包
133
+ * 自带的 ajv 校验器校验。任何其它形状直接 `IPC_INVALID`——结果信封读错会把失败
134
+ * 当成功写进状态。
184
135
  */
185
136
  declare function parseRunResult(text: string, options: ParseRunOptions): RunOutcome;
186
137
 
187
- type ipc_ErrorCode = ErrorCode;
188
- type ipc_ErrorInfo = ErrorInfo;
189
- type ipc_FileArtifact = FileArtifact;
138
+ declare const ipc_ErrorCode: typeof ErrorCode;
139
+ declare const ipc_ErrorInfo: typeof ErrorInfo;
140
+ declare const ipc_FileArtifact: typeof FileArtifact;
190
141
  declare const ipc_IPC_SCHEMA_VERSION: typeof IPC_SCHEMA_VERSION;
191
142
  type ipc_IpcError = IpcError;
192
143
  declare const ipc_IpcError: typeof IpcError;
193
144
  type ipc_ParseRunOptions = ParseRunOptions;
194
145
  type ipc_RunError = RunError;
195
146
  type ipc_RunOutcome = RunOutcome;
196
- type ipc_RunRequest = RunRequest;
197
- type ipc_RunResult = RunResult;
147
+ declare const ipc_RunRequest: typeof RunRequest;
148
+ declare const ipc_RunResult: typeof RunResult;
149
+ declare const ipc_SUPPORTED_CONTRACT_MAJOR: typeof SUPPORTED_CONTRACT_MAJOR;
198
150
  declare const ipc_SUPPORTED_DIGEST_SCHEMA: typeof SUPPORTED_DIGEST_SCHEMA;
199
- type ipc_State = State;
200
- type ipc_StateRun = StateRun;
151
+ declare const ipc_State: typeof State;
152
+ declare const ipc_StateRun: typeof StateRun;
201
153
  type ipc_VersionInfo = VersionInfo;
202
154
  declare const ipc_isVersionInfo: typeof isVersionInfo;
203
155
  declare const ipc_parseJsonObject: typeof parseJsonObject;
204
156
  declare const ipc_parseRunResult: typeof parseRunResult;
205
157
  declare const ipc_parseVersionInfo: typeof parseVersionInfo;
206
158
  declare namespace ipc {
207
- export { type ipc_ErrorCode as ErrorCode, type ipc_ErrorInfo as ErrorInfo, type ipc_FileArtifact as FileArtifact, ipc_IPC_SCHEMA_VERSION as IPC_SCHEMA_VERSION, ipc_IpcError as IpcError, type ipc_ParseRunOptions as ParseRunOptions, type ipc_RunError as RunError, type ipc_RunOutcome as RunOutcome, type ipc_RunRequest as RunRequest, type ipc_RunResult as RunResult, ipc_SUPPORTED_DIGEST_SCHEMA as SUPPORTED_DIGEST_SCHEMA, type ipc_State as State, type ipc_StateRun as StateRun, type ipc_VersionInfo as VersionInfo, ipc_isVersionInfo as isVersionInfo, ipc_parseJsonObject as parseJsonObject, ipc_parseRunResult as parseRunResult, ipc_parseVersionInfo as parseVersionInfo };
159
+ 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, 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_VersionInfo as VersionInfo, ipc_isVersionInfo as isVersionInfo, ipc_parseJsonObject as parseJsonObject, ipc_parseRunResult as parseRunResult, ipc_parseVersionInfo as parseVersionInfo };
208
160
  }
209
161
 
210
162
  type Provider = string;
@@ -403,11 +355,30 @@ declare namespace paths {
403
355
  }
404
356
 
405
357
  declare const HISTORY_LIMIT = 30;
358
+ /** 预览副本上限:最多保留 30 份,和 history 的 30 天窗口对齐。 */
359
+ declare const PREVIEW_LIMIT = 30;
360
+ /** 一次预览只读这么多字节;超过的不复制,避免把大文件塞进状态目录。 */
361
+ declare const PREVIEW_MAX_BYTES: number;
362
+ /** 契约 v2 的固定内容哈希算法;旧版状态没有这个字段,据此识别 legacy。 */
363
+ declare const CONTENT_HASH_ALGO_V2 = "jcs-sha256-v1";
406
364
  interface DigestIdentity {
407
365
  date: string;
408
366
  contentHash: string;
367
+ /** 产出该 hash 的算法;v2 新写的一律是 jcs-sha256-v1。 */
368
+ contentHashAlgo: string;
409
369
  coreVersion: string;
410
370
  }
371
+ /**
372
+ * 预览副本的元数据:只记「哪份文件、多大、内容 sha256」,正文进 previews/。
373
+ * `file` 是仓库内相对路径,不落绝对路径(避免把用户目录写进状态)。
374
+ */
375
+ interface PreviewMeta {
376
+ file: string;
377
+ bytes: number;
378
+ sha256: string;
379
+ /** 只读接口截断时为 true;当前 512 KB 上限下正常不会出现。 */
380
+ truncated?: boolean;
381
+ }
411
382
  interface RunRecord {
412
383
  id: string;
413
384
  at: string;
@@ -417,12 +388,19 @@ interface RunRecord {
417
388
  /** `null` 表示没有错误。 */
418
389
  errorCode?: string;
419
390
  errorMessage?: string;
391
+ /** push 等失败的机器可判定原因(stale-lease / non-fast-forward / auth ...)。 */
392
+ errorReason?: string;
393
+ /** 跳过原因:merged / pr-closed / unchanged。 */
394
+ skipReason?: string;
420
395
  durationMs: number;
421
396
  files: number;
397
+ /** 运行所属的调度时区日期(YYYY-MM-DD);旧记录可能没有。 */
398
+ date?: string;
422
399
  contentHash?: string;
423
400
  branch?: string;
424
401
  commit?: string;
425
402
  prUrl?: string;
403
+ preview?: PreviewMeta;
426
404
  coreVersion: string;
427
405
  model: {
428
406
  provider: string;
@@ -431,6 +409,8 @@ interface RunRecord {
431
409
  languages: string[];
432
410
  warnings: string[];
433
411
  logFile?: string;
412
+ /** true = 契约 v1 时代留下的旧记录(contentHash 算法不同,不可与 v2 比较)。 */
413
+ legacy?: boolean;
434
414
  }
435
415
  interface PendingPr {
436
416
  url: string;
@@ -440,6 +420,8 @@ interface PendingPr {
440
420
  }
441
421
  interface PluginState {
442
422
  lastRunAt?: string;
423
+ /** 最近一次成功(ok=true,含 skipped)的运行时间;调度用它锁当天。 */
424
+ lastSuccessAt?: string;
443
425
  lastDigest?: DigestIdentity;
444
426
  lastResult?: RunRecord;
445
427
  pendingPr?: PendingPr;
@@ -447,6 +429,12 @@ interface PluginState {
447
429
  interface HistoryFile {
448
430
  records: RunRecord[];
449
431
  }
432
+ interface LegacyMigrationResult {
433
+ stateChanged: boolean;
434
+ clearedDigest: boolean;
435
+ clearedPendingPr: boolean;
436
+ historyMarked: number;
437
+ }
450
438
  /** 原子写:同目录临时文件写完再 rename,权限 0600。 */
451
439
  declare function writeJsonAtomic(file: string, value: unknown): void;
452
440
  interface StoreOptions {
@@ -462,26 +450,50 @@ declare class Store {
462
450
  get statePath(): string;
463
451
  get historyPath(): string;
464
452
  get logsPath(): string;
453
+ get previewsPath(): string;
465
454
  readSettings(): Settings;
466
455
  writeSettings(settings: Settings): void;
467
456
  readState(): PluginState;
468
457
  writeState(state: PluginState): void;
458
+ /**
459
+ * 把 v1 时代遗留的 state / history 一次性清洗落盘:清掉不可信的 lastDigest 与
460
+ * 与之绑定的 pendingPr,并给旧历史记录打 legacy 标。插件启动时调用一次。
461
+ */
462
+ migrateLegacyState(): LegacyMigrationResult;
469
463
  readHistory(): RunRecord[];
464
+ /** 原始历史(不做 legacy 标注):写入路径用它,避免把「读时标注」持久化。 */
465
+ private readHistoryRaw;
470
466
  /** 记录一次运行:历史裁剪到 30 条,state 同步更新。 */
471
467
  recordRun(record: RunRecord, next?: Partial<PluginState>): void;
472
468
  newLogPath(id: string): string;
473
469
  appendLog(file: string, line: string): void;
474
470
  hasLog(file: string): boolean;
475
471
  readLog(file: string, maxBytes?: number): string;
472
+ /** 预览副本路径;写入前才建目录,读接口不会凭空创建目录。 */
473
+ previewPath(id: string): string;
474
+ /** 写预览副本:同目录临时文件 + rename、0600;写完裁到 30 份。 */
475
+ writePreview(id: string, text: string): void;
476
+ /** 读预览副本;不存在/不可读返回 undefined,超过上限时截断。 */
477
+ readPreview(id: string, maxBytes?: number): {
478
+ text: string;
479
+ truncated: boolean;
480
+ } | undefined;
481
+ /** 只保留最近 `keep` 份预览副本;按 mtime 倒序,任何错误都忽略。 */
482
+ prunePreviews(keep?: number): void;
476
483
  }
477
484
  /** 运行 id:时间前缀便于按时间排序,随机后缀避免同毫秒撞车。 */
478
485
  declare function newRunId(now?: Date): string;
479
486
 
487
+ declare const state_CONTENT_HASH_ALGO_V2: typeof CONTENT_HASH_ALGO_V2;
480
488
  type state_DigestIdentity = DigestIdentity;
481
489
  declare const state_HISTORY_LIMIT: typeof HISTORY_LIMIT;
482
490
  type state_HistoryFile = HistoryFile;
491
+ type state_LegacyMigrationResult = LegacyMigrationResult;
492
+ declare const state_PREVIEW_LIMIT: typeof PREVIEW_LIMIT;
493
+ declare const state_PREVIEW_MAX_BYTES: typeof PREVIEW_MAX_BYTES;
483
494
  type state_PendingPr = PendingPr;
484
495
  type state_PluginState = PluginState;
496
+ type state_PreviewMeta = PreviewMeta;
485
497
  type state_RunRecord = RunRecord;
486
498
  type state_Store = Store;
487
499
  declare const state_Store: typeof Store;
@@ -489,7 +501,7 @@ type state_StoreOptions = StoreOptions;
489
501
  declare const state_newRunId: typeof newRunId;
490
502
  declare const state_writeJsonAtomic: typeof writeJsonAtomic;
491
503
  declare namespace state {
492
- export { type state_DigestIdentity as DigestIdentity, state_HISTORY_LIMIT as HISTORY_LIMIT, type state_HistoryFile as HistoryFile, type state_PendingPr as PendingPr, type state_PluginState as PluginState, type state_RunRecord as RunRecord, state_Store as Store, type state_StoreOptions as StoreOptions, state_newRunId as newRunId, state_writeJsonAtomic as writeJsonAtomic };
504
+ export { state_CONTENT_HASH_ALGO_V2 as CONTENT_HASH_ALGO_V2, type state_DigestIdentity as DigestIdentity, state_HISTORY_LIMIT as HISTORY_LIMIT, type state_HistoryFile as HistoryFile, type state_LegacyMigrationResult as LegacyMigrationResult, state_PREVIEW_LIMIT as PREVIEW_LIMIT, state_PREVIEW_MAX_BYTES as PREVIEW_MAX_BYTES, type state_PendingPr as PendingPr, type state_PluginState as PluginState, type state_PreviewMeta as PreviewMeta, type state_RunRecord as RunRecord, state_Store as Store, type state_StoreOptions as StoreOptions, state_newRunId as newRunId, state_writeJsonAtomic as writeJsonAtomic };
493
505
  }
494
506
 
495
507
  interface CoreRelease {
@@ -509,7 +521,7 @@ interface ReleaseDescriptor extends CoreRelease {
509
521
  binaryPath: string;
510
522
  cached: boolean;
511
523
  }
512
- /** 从本包的 package.json 读核心版本(`sync-contract --ref vX` 会同时更新它)。 */
524
+ /** 从本包的 package.json 读核心版本(`scripts/set-core-version.mjs` 会更新它)。 */
513
525
  declare function readCoreRelease(pkg?: {
514
526
  dsh?: {
515
527
  core?: {
@@ -717,6 +729,8 @@ interface RunCoreOptions {
717
729
  token?: string;
718
730
  date?: string;
719
731
  dryRun: boolean;
732
+ /** 谁触发的运行:核心用它在定时运行时尊重「人工关闭的 PR」。 */
733
+ trigger?: 'manual' | 'schedule';
720
734
  /** 回环代理地址:覆盖配置里的 model.baseURL(代理模式专用)。 */
721
735
  baseURLOverride?: string;
722
736
  signal?: AbortSignal;
@@ -1177,7 +1191,11 @@ interface RunSummary {
1177
1191
  files: number;
1178
1192
  contentHash?: string;
1179
1193
  branch?: string;
1194
+ commit?: string;
1180
1195
  prUrl?: string;
1196
+ preview?: PreviewMeta;
1197
+ /** 核心给出的跳过原因(merged / pr-closed / unchanged)。 */
1198
+ skipReason?: string;
1181
1199
  error?: {
1182
1200
  code: string;
1183
1201
  message: string;
@@ -1185,6 +1203,8 @@ interface RunSummary {
1185
1203
  area: string;
1186
1204
  retriable: boolean;
1187
1205
  inferred?: boolean;
1206
+ /** 机器可判定的细分原因(push 失败时由核心给出)。 */
1207
+ reason?: string;
1188
1208
  };
1189
1209
  warnings: string[];
1190
1210
  logFile?: string;
@@ -1205,6 +1225,8 @@ interface RunnerDeps {
1205
1225
  env?: NodeJS.ProcessEnv;
1206
1226
  /** 运行结束(成功或失败)后回调,用于刷新调度/界面缓存。 */
1207
1227
  onFinished?: (summary: RunSummary) => void;
1228
+ /** 预览副本这类「可失败但不阻断运行」的事件走这里,不写进 warnings。 */
1229
+ logger?: (event: string, data?: Record<string, unknown>) => void;
1208
1230
  }
1209
1231
  declare class Runner {
1210
1232
  private active;
@@ -1218,6 +1240,15 @@ declare class Runner {
1218
1240
  run(options: RunRequestOptions): Promise<RunSummary>;
1219
1241
  private summarize;
1220
1242
  private failureSummary;
1243
+ /**
1244
+ * 从本次核心返回的 artifact 清单里选当天的 `.md`,只读复制到 previews/。
1245
+ *
1246
+ * 这是插件**唯一**读取用户仓库的场景,而且只读核心自己刚写出的那一份:
1247
+ * - 路径必须在 `repo.path` 内(词法 + realpath 各判一次,挡 `..`/绝对路径/符号链接);
1248
+ * - 上限 512 KB、拒绝 NUL、sha256 必须与契约声明一致;
1249
+ * - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
1250
+ */
1251
+ private capturePreview;
1221
1252
  /** 写历史与状态;同一天的 skipped 运行不覆盖 lastDigest。 */
1222
1253
  private record;
1223
1254
  /** 给设置页/工具用的「核心现在什么状态」。 */
@@ -1233,6 +1264,22 @@ declare namespace runner {
1233
1264
  export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps };
1234
1265
  }
1235
1266
 
1267
+ interface PluginInfo {
1268
+ name: string;
1269
+ version: string;
1270
+ repository?: string;
1271
+ }
1272
+ /** 只放行 GitHub 的 http(s) 地址;ssh / git+ 前缀先归一化。 */
1273
+ declare function normalizeRepositoryUrl(value: unknown): string | undefined;
1274
+ declare function readPluginInfo(): PluginInfo;
1275
+
1276
+ type pluginInfo_PluginInfo = PluginInfo;
1277
+ declare const pluginInfo_normalizeRepositoryUrl: typeof normalizeRepositoryUrl;
1278
+ declare const pluginInfo_readPluginInfo: typeof readPluginInfo;
1279
+ declare namespace pluginInfo {
1280
+ export { type pluginInfo_PluginInfo as PluginInfo, pluginInfo_normalizeRepositoryUrl as normalizeRepositoryUrl, pluginInfo_readPluginInfo as readPluginInfo };
1281
+ }
1282
+
1236
1283
  /**
1237
1284
  * 调度(FR-P-08 / 规格 §9)。
1238
1285
  *
@@ -1276,7 +1323,8 @@ declare class Scheduler {
1276
1323
  private readonly deps;
1277
1324
  private timerHandle;
1278
1325
  private stopped;
1279
- private lastFiredFor;
1326
+ /** 当天已尝试次数与时间;只在内存里,进程重启后重新计数。 */
1327
+ private readonly attempts;
1280
1328
  constructor(deps: SchedulerDeps);
1281
1329
  start(): void;
1282
1330
  stop(): void;
@@ -1329,6 +1377,9 @@ interface RouteDeps {
1329
1377
  /** 注入用:pendingPr 对账的 fetch 实现(测试不联网)。 */
1330
1378
  fetchImpl?: typeof fetch;
1331
1379
  version: string;
1380
+ /** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
1381
+ pluginName?: string;
1382
+ repository?: string;
1332
1383
  logger?: (event: string, data?: Record<string, unknown>) => void;
1333
1384
  }
1334
1385
  declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
@@ -1582,4 +1633,4 @@ declare namespace tools {
1582
1633
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
1583
1634
  }
1584
1635
 
1585
- export { channel, config, core, credentials, errors, exec, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, tools };
1636
+ 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, tools };
package/lib/internal.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  locate_exports,
12
12
  paths_exports,
13
13
  pending_pr_exports,
14
+ plugin_info_exports,
14
15
  preflight_exports,
15
16
  proxy_exports,
16
17
  quickfill_exports,
@@ -22,7 +23,7 @@ import {
22
23
  schedule_exports,
23
24
  state_exports,
24
25
  tools_exports
25
- } from "./chunk-HIJIQJCZ.js";
26
+ } from "./chunk-X4PBXIZE.js";
26
27
  import {
27
28
  exec_exports
28
29
  } from "./chunk-XLIFTAVL.js";
@@ -40,6 +41,7 @@ export {
40
41
  locate_exports as locate,
41
42
  paths_exports as paths,
42
43
  pending_pr_exports as pendingPr,
44
+ plugin_info_exports as pluginInfo,
43
45
  preflight_exports as preflight,
44
46
  proxy_exports as proxy,
45
47
  quickfill_exports as quickfill,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,13 +14,11 @@
14
14
  "./cordis.patch.yml": "./cordis.patch.yml",
15
15
  "./locale/*.json": "./locale/*.json",
16
16
  "./icon": "./icon.svg",
17
- "./contract/*": "./contract/*",
18
17
  "./package.json": "./package.json"
19
18
  },
20
19
  "files": [
21
20
  "lib",
22
21
  "client.js",
23
- "contract",
24
22
  "locale",
25
23
  "cordis.patch.yml",
26
24
  "icon.svg",
@@ -51,13 +49,16 @@
51
49
  "optional": true
52
50
  }
53
51
  },
52
+ "dependencies": {
53
+ "@morehao/trendradar-contract": "0.2.0"
54
+ },
54
55
  "dsh": {
55
56
  "bundle": {
56
57
  "patch": "./cordis.patch.yml"
57
58
  },
58
59
  "core": {
59
60
  "repo": "morehao/trendradar",
60
- "version": "0.2.0"
61
+ "version": "0.3.1"
61
62
  },
62
63
  "client": {
63
64
  "platform": "web",
@@ -75,8 +76,8 @@
75
76
  "dev:client": "node scripts/build-client.mjs --watch",
76
77
  "typecheck": "tsc --noEmit",
77
78
  "check:types": "tsc -p tsconfig.client.json",
78
- "gen:ipc": "node scripts/gen-ipc-types.mjs",
79
- "check:contract": "node scripts/sync-contract.mjs --check && node scripts/gen-ipc-types.mjs --check",
79
+ "set-core-version": "node scripts/set-core-version.mjs",
80
+ "check:contract": "node --test test/contract.test.mjs",
80
81
  "check:lockfile": "node scripts/check-lockfile.mjs",
81
82
  "check:syntax": "node scripts/check-syntax.mjs",
82
83
  "check:naming": "node scripts/check-naming.mjs",
@@ -1,10 +0,0 @@
1
- # trendradar contract hash (v1)
2
- # algorithm: sha256 per file (sorted), then sha256 of the concatenated lines
3
- f34aa3642938bad32df912768d543925f91858da66a8e726e4e4bf88a07d086c fixtures/digest-degraded.json
4
- 7bc67dcdc36c7b81df1915c4b50025cef6f0304ef2feb24f393d17771df252c9 fixtures/digest-empty-lang.json
5
- 4fe9d91ce3f0ddcc17982e4f0acac950ebc6ec7f63427fe962ea581f3114c0c4 fixtures/digest-normal.json
6
- d52adbbb473d7ee2d856398a5651f96ba2019ee53ba6e14c27ef2d792b5db723 schema/api.v1.yaml
7
- ca9b640f374cded12a09ea00f0686eac14eee59eef94c27a61016175bc0d4ebb schema/digest.v1.json
8
- cf8eb4f069e71c8738ccd5c4ffbfb266124ee86161fe8ad0677c9782d3ef8552 schema/ipc.v1.json
9
- 2140c9d538bc935c092a83fb6377e3878b2c060992bf458f2437898f4649d41d schema/model.v1.json
10
- aggregate: 4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde
@@ -1,37 +0,0 @@
1
- {
2
- "repo": "https://github.com/morehao/trendradar.git",
3
- "ref": "v0.2.0",
4
- "commit": "5cf0ad26c5748cf2bc9a1cc4ce66a09411d590f7",
5
- "aggregate": "4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde",
6
- "syncedAt": "2026-10-30T18:42:04.265Z",
7
- "files": [
8
- {
9
- "path": "schema/api.v1.yaml",
10
- "sha256": "d52adbbb473d7ee2d856398a5651f96ba2019ee53ba6e14c27ef2d792b5db723"
11
- },
12
- {
13
- "path": "schema/digest.v1.json",
14
- "sha256": "ca9b640f374cded12a09ea00f0686eac14eee59eef94c27a61016175bc0d4ebb"
15
- },
16
- {
17
- "path": "schema/ipc.v1.json",
18
- "sha256": "cf8eb4f069e71c8738ccd5c4ffbfb266124ee86161fe8ad0677c9782d3ef8552"
19
- },
20
- {
21
- "path": "schema/model.v1.json",
22
- "sha256": "2140c9d538bc935c092a83fb6377e3878b2c060992bf458f2437898f4649d41d"
23
- },
24
- {
25
- "path": "fixtures/digest-degraded.json",
26
- "sha256": "f34aa3642938bad32df912768d543925f91858da66a8e726e4e4bf88a07d086c"
27
- },
28
- {
29
- "path": "fixtures/digest-empty-lang.json",
30
- "sha256": "7bc67dcdc36c7b81df1915c4b50025cef6f0304ef2feb24f393d17771df252c9"
31
- },
32
- {
33
- "path": "fixtures/digest-normal.json",
34
- "sha256": "4fe9d91ce3f0ddcc17982e4f0acac950ebc6ec7f63427fe962ea581f3114c0c4"
35
- }
36
- ]
37
- }