@morehao/dsh-github-trending 0.1.4 → 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/internal.d.ts CHANGED
@@ -1,60 +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@d3bf720a869732e7a2ee04fa39d32dd50868fd61
6
- * aggregate:eb7f1ac9e117a709d15d87de89cf30e309c479e62dfa3d320cb8f154d3f1dc1e
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
- runAt: string;
47
- [key: string]: unknown;
48
- }
49
- interface State {
50
- ipcSchema: 1;
51
- lastRun?: StateRun;
52
- [key: string]: unknown;
53
- }
54
- declare const IPC_SCHEMA_VERSION: 1;
55
-
56
- /**
57
- * 错误码:核心码(`ipc.v1` 的 17 个)+ 插件自己的码。
5
+ * 错误码:核心码(契约包 `ipc.v2` / `errors.v2` 的 17 个)+ 插件自己的码。
58
6
  *
59
7
  * 核心当前只在 stderr 打 `error: <CODE>: <message>`,模型/抓取的失败甚至没有码,
60
8
  * 所以这里承担两件事:
@@ -78,13 +26,22 @@ declare function isRetriable(code: string): boolean;
78
26
  /** 错误码 → 界面落点。UI 用这个决定红字出现在哪个区块。 */
79
27
  type ErrorArea = 'core' | 'model' | 'repo' | 'auth' | 'run';
80
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;
81
36
  /** 每种码一句「下一步怎么做」,避免界面只给码不给路。 */
82
- declare function hintOf(code: string): string;
37
+ declare function hintOf(code: string, message?: string, reason?: string): string;
83
38
  /**
84
39
  * 从核心的 stderr 里解出一个结构化错误。
85
40
  *
86
41
  * 优先级:`error: <CODE>: ...` 的显式码 > 文本归类 > INTERNAL。
87
- * message 只取最后一条非空行(核心把根因打在最外层,前面的行多是上下文)。
42
+ * 显式码命中的那一行往往只是首行(如 `error: GIT_OPERATION_FAILED: To github.com:...`),
43
+ * 真正的根因在其后的多行里(`! [rejected] ... (non-fast-forward)`、`hint: ...`),
44
+ * 因此把这些诊断行一并保留进 message,避免界面只看到半句。
88
45
  */
89
46
  declare function classifyStderr(stderr: string): ClassifiedError;
90
47
  /**
@@ -96,38 +53,32 @@ declare function redact(input: string, secrets?: Array<string | undefined>): str
96
53
  type errors_AnyCode = AnyCode;
97
54
  type errors_ClassifiedError = ClassifiedError;
98
55
  type errors_ErrorArea = ErrorArea;
56
+ type errors_GitReason = GitReason;
99
57
  declare const errors_PLUGIN_CODES: typeof PLUGIN_CODES;
100
58
  type errors_PluginCode = PluginCode;
101
59
  declare const errors_areaOf: typeof areaOf;
102
60
  declare const errors_classifyStderr: typeof classifyStderr;
103
61
  declare const errors_hintOf: typeof hintOf;
104
62
  declare const errors_isRetriable: typeof isRetriable;
63
+ declare const errors_reasonOf: typeof reasonOf;
105
64
  declare const errors_redact: typeof redact;
106
65
  declare namespace errors {
107
- 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 };
108
67
  }
109
68
 
110
- /**
111
- * 契约类型与运行时守卫。
112
- *
113
- * 类型来自 `contract/schema/ipc.v1.json` 的生成物(`ipc.gen.ts`);本文件只放
114
- * 契约没有、但插件必须理解的东西:核心 `version --json` 的握手形状、核心 `run --json`
115
- * 的输出形状,以及把两种结果形状归一化成 `RunOutcome` 的解析器。
116
- *
117
- * 为什么同时接受两种形状:`contract/schema/ipc.v1.json` 定义的是 `RunResult`
118
- * (`ok`/`startedAt`/`error.code`),而当前核心的 `run --json` 输出的是一份更小的
119
- * `runOutput`(`files`/`skipped`/`branch`/`prUrl`/`digest.contentHash`),错误只走
120
- * stderr 文本。核心补上信封后,`parseRunResult` 无需改动即可接住。
121
- */
122
-
69
+ /** 插件实现所对应的 IPC 契约版本(契约包 `ipc.v2.json` 的 wire 整数)。 */
70
+ declare const IPC_SCHEMA_VERSION: 2;
123
71
  /** 插件能接受的 digest 契约版本(与核心 `version --json` 的 digestSchema 比对)。 */
124
- declare const SUPPORTED_DIGEST_SCHEMA = 1;
72
+ declare const SUPPORTED_DIGEST_SCHEMA: 2;
73
+ /** 契约包 `contractVersion` 的主版本;0.x 期间同主版本即视为同一契约线。 */
74
+ declare const SUPPORTED_CONTRACT_MAJOR = 0;
125
75
  /** 核心 `version --json` 的输出。 */
126
76
  interface VersionInfo {
127
77
  version: string;
128
78
  commit?: string;
129
79
  ipcSchema: number;
130
80
  digestSchema: number;
81
+ contractVersion?: string;
131
82
  supportedProviders?: string[];
132
83
  }
133
84
  /** 插件侧的错误信息:`code` 可能是核心码,也可能是插件码(`CORE_*` 等)。 */
@@ -147,6 +98,8 @@ interface RunOutcome {
147
98
  branch?: string;
148
99
  commit?: string;
149
100
  prUrl?: string;
101
+ /** 核心给出的跳过原因:merged | pr-closed | unchanged(可选,旧核心没有)。 */
102
+ skipReason?: string;
150
103
  error?: RunError;
151
104
  warnings: string[];
152
105
  startedAt: string;
@@ -165,6 +118,7 @@ declare class IpcError extends Error {
165
118
  declare function parseJsonObject(text: string): Record<string, unknown>;
166
119
  declare function isVersionInfo(value: unknown): value is VersionInfo;
167
120
  declare function parseVersionInfo(text: string): VersionInfo;
121
+ /** 契约包 `RunResult` → 插件内部 `RunOutcome` 的唯一映射。 */
168
122
  interface ParseRunOptions {
169
123
  coreVersion: string;
170
124
  startedAt: string;
@@ -175,38 +129,40 @@ interface ParseRunOptions {
175
129
  /**
176
130
  * 把核心 `run --json` 的 stdout 归一化成 `RunOutcome`。
177
131
  *
178
- * 两种形状的判别:有 `ipcSchema` 且 `command === 'run'` 就是契约 `RunResult`;
179
- * 否则按当前核心的 `runOutput` 处理。两者都不像就直接 `IPC_INVALID`,
180
- * 绝不「猜字段」——结果信封读错会把失败当成功写进状态。
132
+ * 唯一接受的形状是契约 `RunResult`(`ipcSchema=2` / `command=run`),并用契约包
133
+ * 自带的 ajv 校验器校验。任何其它形状直接 `IPC_INVALID`——结果信封读错会把失败
134
+ * 当成功写进状态。
181
135
  */
182
136
  declare function parseRunResult(text: string, options: ParseRunOptions): RunOutcome;
183
137
 
184
- type ipc_ErrorCode = ErrorCode;
185
- type ipc_ErrorInfo = ErrorInfo;
186
- 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;
187
141
  declare const ipc_IPC_SCHEMA_VERSION: typeof IPC_SCHEMA_VERSION;
188
142
  type ipc_IpcError = IpcError;
189
143
  declare const ipc_IpcError: typeof IpcError;
190
144
  type ipc_ParseRunOptions = ParseRunOptions;
191
145
  type ipc_RunError = RunError;
192
146
  type ipc_RunOutcome = RunOutcome;
193
- type ipc_RunRequest = RunRequest;
194
- 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;
195
150
  declare const ipc_SUPPORTED_DIGEST_SCHEMA: typeof SUPPORTED_DIGEST_SCHEMA;
196
- type ipc_State = State;
197
- type ipc_StateRun = StateRun;
151
+ declare const ipc_State: typeof State;
152
+ declare const ipc_StateRun: typeof StateRun;
198
153
  type ipc_VersionInfo = VersionInfo;
199
154
  declare const ipc_isVersionInfo: typeof isVersionInfo;
200
155
  declare const ipc_parseJsonObject: typeof parseJsonObject;
201
156
  declare const ipc_parseRunResult: typeof parseRunResult;
202
157
  declare const ipc_parseVersionInfo: typeof parseVersionInfo;
203
158
  declare namespace ipc {
204
- 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 };
205
160
  }
206
161
 
207
162
  type Provider = string;
208
163
  type CommitMode = 'branch' | 'direct';
209
- type AuthMode = 'device' | 'pat' | 'gh';
164
+ /** 只保留两种真正可用的方式:本机 `gh` 登录态、手动 PAT。历史的 `device` 在归一化时迁移为 `gh`。 */
165
+ type AuthMode = 'pat' | 'gh';
210
166
  type Since = 'daily' | 'weekly';
211
167
  interface ModelSettings {
212
168
  provider: Provider;
@@ -271,6 +227,16 @@ declare const DEFAULT_SETTINGS: Settings;
271
227
  /** 核心支持的 provider 默认端点(空字符串 = 必须由用户填)。 */
272
228
  declare const PROVIDER_BASE_URLS: Record<string, string>;
273
229
  declare const CORE_PROVIDERS: readonly ["deepseek", "openai-compatible"];
230
+ /** 设置页分区;分段保存时只提交并校验对应分区,互不牵连。 */
231
+ declare const SETTINGS_SECTIONS: readonly ["source", "model", "repo", "auth", "scope", "schedule"];
232
+ type SettingsSection = (typeof SETTINGS_SECTIONS)[number];
233
+ /** 校验问题字段 → 所属分区(用于分段保存时只把本分区的问题回给界面)。 */
234
+ declare function sectionOfField(field: string): SettingsSection | undefined;
235
+ /**
236
+ * 分段保存:只把 `incoming` 里 `section` 的字段合并进 `stored`,其余分区保持已保存值。
237
+ * 这样一个 tab 的保存不会顺带提交或覆盖其它 tab 里尚未保存的改动。
238
+ */
239
+ declare function mergeSettingsSection(stored: Settings, incoming: Settings, section: SettingsSection): Settings;
274
240
  /**
275
241
  * 把任意来源的对象(磁盘上的旧版本、HTTP 请求体、CLI 导入)收敛成完整设置。
276
242
  * 未知字段一律丢弃:settings.json 只有以下形状,避免旧版本字段悄悄生效。
@@ -349,18 +315,22 @@ type config_PRSettings = PRSettings;
349
315
  type config_Problem = Problem;
350
316
  type config_Provider = Provider;
351
317
  type config_RepoSettings = RepoSettings;
318
+ declare const config_SETTINGS_SECTIONS: typeof SETTINGS_SECTIONS;
352
319
  type config_ScheduleSettings = ScheduleSettings;
353
320
  type config_Settings = Settings;
321
+ type config_SettingsSection = SettingsSection;
354
322
  type config_Since = Since;
355
323
  type config_TrendingSettings = TrendingSettings;
356
324
  declare const config_effectiveBaseURL: typeof effectiveBaseURL;
357
325
  declare const config_isValidTimezone: typeof isValidTimezone;
326
+ declare const config_mergeSettingsSection: typeof mergeSettingsSection;
358
327
  declare const config_normalizeSettings: typeof normalizeSettings;
328
+ declare const config_sectionOfField: typeof sectionOfField;
359
329
  declare const config_toCoreConfig: typeof toCoreConfig;
360
330
  declare const config_toYaml: typeof toYaml;
361
331
  declare const config_validateSettings: typeof validateSettings;
362
332
  declare namespace config {
363
- export { type config_AuthMode as AuthMode, config_CORE_PROVIDERS as CORE_PROVIDERS, type config_CommitMode as CommitMode, type config_CoreConfig as CoreConfig, config_DEFAULT_SETTINGS as DEFAULT_SETTINGS, type config_GithubSettings as GithubSettings, type config_ModelSettings as ModelSettings, type config_OutputSettings as OutputSettings, config_PROVIDER_BASE_URLS as PROVIDER_BASE_URLS, type config_PRSettings as PRSettings, type config_Problem as Problem, type config_Provider as Provider, type config_RepoSettings as RepoSettings, type config_ScheduleSettings as ScheduleSettings, type config_Settings as Settings, type config_Since as Since, type config_TrendingSettings as TrendingSettings, config_effectiveBaseURL as effectiveBaseURL, config_isValidTimezone as isValidTimezone, config_normalizeSettings as normalizeSettings, config_toCoreConfig as toCoreConfig, config_toYaml as toYaml, config_validateSettings as validateSettings };
333
+ export { type config_AuthMode as AuthMode, config_CORE_PROVIDERS as CORE_PROVIDERS, type config_CommitMode as CommitMode, type config_CoreConfig as CoreConfig, config_DEFAULT_SETTINGS as DEFAULT_SETTINGS, type config_GithubSettings as GithubSettings, type config_ModelSettings as ModelSettings, type config_OutputSettings as OutputSettings, config_PROVIDER_BASE_URLS as PROVIDER_BASE_URLS, type config_PRSettings as PRSettings, type config_Problem as Problem, type config_Provider as Provider, type config_RepoSettings as RepoSettings, config_SETTINGS_SECTIONS as SETTINGS_SECTIONS, type config_ScheduleSettings as ScheduleSettings, type config_Settings as Settings, type config_SettingsSection as SettingsSection, type config_Since as Since, type config_TrendingSettings as TrendingSettings, config_effectiveBaseURL as effectiveBaseURL, config_isValidTimezone as isValidTimezone, config_mergeSettingsSection as mergeSettingsSection, config_normalizeSettings as normalizeSettings, config_sectionOfField as sectionOfField, config_toCoreConfig as toCoreConfig, config_toYaml as toYaml, config_validateSettings as validateSettings };
364
334
  }
365
335
 
366
336
  declare const PLUGIN_DIR_NAME = "github-trending";
@@ -385,11 +355,30 @@ declare namespace paths {
385
355
  }
386
356
 
387
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";
388
364
  interface DigestIdentity {
389
365
  date: string;
390
366
  contentHash: string;
367
+ /** 产出该 hash 的算法;v2 新写的一律是 jcs-sha256-v1。 */
368
+ contentHashAlgo: string;
391
369
  coreVersion: string;
392
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
+ }
393
382
  interface RunRecord {
394
383
  id: string;
395
384
  at: string;
@@ -399,12 +388,19 @@ interface RunRecord {
399
388
  /** `null` 表示没有错误。 */
400
389
  errorCode?: string;
401
390
  errorMessage?: string;
391
+ /** push 等失败的机器可判定原因(stale-lease / non-fast-forward / auth ...)。 */
392
+ errorReason?: string;
393
+ /** 跳过原因:merged / pr-closed / unchanged。 */
394
+ skipReason?: string;
402
395
  durationMs: number;
403
396
  files: number;
397
+ /** 运行所属的调度时区日期(YYYY-MM-DD);旧记录可能没有。 */
398
+ date?: string;
404
399
  contentHash?: string;
405
400
  branch?: string;
406
401
  commit?: string;
407
402
  prUrl?: string;
403
+ preview?: PreviewMeta;
408
404
  coreVersion: string;
409
405
  model: {
410
406
  provider: string;
@@ -413,13 +409,19 @@ interface RunRecord {
413
409
  languages: string[];
414
410
  warnings: string[];
415
411
  logFile?: string;
412
+ /** true = 契约 v1 时代留下的旧记录(contentHash 算法不同,不可与 v2 比较)。 */
413
+ legacy?: boolean;
416
414
  }
417
415
  interface PendingPr {
418
416
  url: string;
419
417
  createdAt: string;
418
+ /** 该 PR 对应的 digest 日期(YYYY-MM-DD);用于判断「同日重跑」是否真的会撞分支。 */
419
+ date?: string;
420
420
  }
421
421
  interface PluginState {
422
422
  lastRunAt?: string;
423
+ /** 最近一次成功(ok=true,含 skipped)的运行时间;调度用它锁当天。 */
424
+ lastSuccessAt?: string;
423
425
  lastDigest?: DigestIdentity;
424
426
  lastResult?: RunRecord;
425
427
  pendingPr?: PendingPr;
@@ -427,6 +429,12 @@ interface PluginState {
427
429
  interface HistoryFile {
428
430
  records: RunRecord[];
429
431
  }
432
+ interface LegacyMigrationResult {
433
+ stateChanged: boolean;
434
+ clearedDigest: boolean;
435
+ clearedPendingPr: boolean;
436
+ historyMarked: number;
437
+ }
430
438
  /** 原子写:同目录临时文件写完再 rename,权限 0600。 */
431
439
  declare function writeJsonAtomic(file: string, value: unknown): void;
432
440
  interface StoreOptions {
@@ -442,26 +450,50 @@ declare class Store {
442
450
  get statePath(): string;
443
451
  get historyPath(): string;
444
452
  get logsPath(): string;
453
+ get previewsPath(): string;
445
454
  readSettings(): Settings;
446
455
  writeSettings(settings: Settings): void;
447
456
  readState(): PluginState;
448
457
  writeState(state: PluginState): void;
458
+ /**
459
+ * 把 v1 时代遗留的 state / history 一次性清洗落盘:清掉不可信的 lastDigest 与
460
+ * 与之绑定的 pendingPr,并给旧历史记录打 legacy 标。插件启动时调用一次。
461
+ */
462
+ migrateLegacyState(): LegacyMigrationResult;
449
463
  readHistory(): RunRecord[];
464
+ /** 原始历史(不做 legacy 标注):写入路径用它,避免把「读时标注」持久化。 */
465
+ private readHistoryRaw;
450
466
  /** 记录一次运行:历史裁剪到 30 条,state 同步更新。 */
451
467
  recordRun(record: RunRecord, next?: Partial<PluginState>): void;
452
468
  newLogPath(id: string): string;
453
469
  appendLog(file: string, line: string): void;
454
470
  hasLog(file: string): boolean;
455
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;
456
483
  }
457
484
  /** 运行 id:时间前缀便于按时间排序,随机后缀避免同毫秒撞车。 */
458
485
  declare function newRunId(now?: Date): string;
459
486
 
487
+ declare const state_CONTENT_HASH_ALGO_V2: typeof CONTENT_HASH_ALGO_V2;
460
488
  type state_DigestIdentity = DigestIdentity;
461
489
  declare const state_HISTORY_LIMIT: typeof HISTORY_LIMIT;
462
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;
463
494
  type state_PendingPr = PendingPr;
464
495
  type state_PluginState = PluginState;
496
+ type state_PreviewMeta = PreviewMeta;
465
497
  type state_RunRecord = RunRecord;
466
498
  type state_Store = Store;
467
499
  declare const state_Store: typeof Store;
@@ -469,7 +501,7 @@ type state_StoreOptions = StoreOptions;
469
501
  declare const state_newRunId: typeof newRunId;
470
502
  declare const state_writeJsonAtomic: typeof writeJsonAtomic;
471
503
  declare namespace state {
472
- 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 };
473
505
  }
474
506
 
475
507
  interface CoreRelease {
@@ -489,7 +521,7 @@ interface ReleaseDescriptor extends CoreRelease {
489
521
  binaryPath: string;
490
522
  cached: boolean;
491
523
  }
492
- /** 从本包的 package.json 读核心版本(`sync-contract --ref vX` 会同时更新它)。 */
524
+ /** 从本包的 package.json 读核心版本(`scripts/set-core-version.mjs` 会更新它)。 */
493
525
  declare function readCoreRelease(pkg?: {
494
526
  dsh?: {
495
527
  core?: {
@@ -697,6 +729,8 @@ interface RunCoreOptions {
697
729
  token?: string;
698
730
  date?: string;
699
731
  dryRun: boolean;
732
+ /** 谁触发的运行:核心用它在定时运行时尊重「人工关闭的 PR」。 */
733
+ trigger?: 'manual' | 'schedule';
700
734
  /** 回环代理地址:覆盖配置里的 model.baseURL(代理模式专用)。 */
701
735
  baseURLOverride?: string;
702
736
  signal?: AbortSignal;
@@ -820,10 +854,11 @@ interface CredentialServices {
820
854
  declare function credentialServices(get: (name: string) => unknown): CredentialServices | undefined;
821
855
  declare function describeRef(services: CredentialServices | undefined, ref: string, env?: NodeJS.ProcessEnv): Promise<CredentialDescription>;
822
856
  declare function resolveRef(services: CredentialServices | undefined, ref: string, env?: NodeJS.ProcessEnv): Promise<ResolvedSecret>;
823
- /** GitHub token:按 authMode 取,`gh` 模式读本机 gh 的登录态。 */
824
- declare function resolveGithubToken(services: CredentialServices | undefined, authMode: 'device' | 'pat' | 'gh', tokenRef: string, deps?: {
857
+ /** GitHub token:按 authMode 取,`gh` 模式读本机 gh 的登录态,`pat` 读凭据/环境变量。 */
858
+ declare function resolveGithubToken(services: CredentialServices | undefined, authMode: 'pat' | 'gh', tokenRef: string, deps?: {
825
859
  env?: NodeJS.ProcessEnv;
826
860
  execGh?: () => Promise<string | undefined>;
861
+ freshGh?: boolean;
827
862
  }): Promise<{
828
863
  token?: string;
829
864
  source: string;
@@ -840,6 +875,57 @@ declare namespace credentials {
840
875
  export { type credentials_CredentialDescription as CredentialDescription, type credentials_CredentialServices as CredentialServices, type credentials_ResolvedSecret as ResolvedSecret, credentials_credentialServices as credentialServices, credentials_describeRef as describeRef, credentials_resolveGithubToken as resolveGithubToken, credentials_resolveRef as resolveRef };
841
876
  }
842
877
 
878
+ /**
879
+ * GitHub 认证探测:供设置页「测试认证」按钮调用。
880
+ *
881
+ * 只做一次 `GET https://api.github.com/user`,把「有没有 token、token 是否有效」
882
+ * 一次性验证清楚并回显登录账号;token 不进日志、不进响应明文。
883
+ */
884
+
885
+ interface GithubProbeResult {
886
+ ok: boolean;
887
+ login?: string;
888
+ /** token 来源(gh / env / credentials)。 */
889
+ source?: string;
890
+ code?: string;
891
+ message?: string;
892
+ }
893
+ declare function probeGithubAuth(options: {
894
+ credentials: CredentialServices | undefined;
895
+ authMode: 'pat' | 'gh';
896
+ tokenRef: string;
897
+ env?: NodeJS.ProcessEnv;
898
+ fetchImpl?: typeof fetch;
899
+ }): Promise<GithubProbeResult>;
900
+
901
+ type github_GithubProbeResult = GithubProbeResult;
902
+ declare const github_probeGithubAuth: typeof probeGithubAuth;
903
+ declare namespace github {
904
+ export { type github_GithubProbeResult as GithubProbeResult, github_probeGithubAuth as probeGithubAuth };
905
+ }
906
+
907
+ interface DefaultBranchResult {
908
+ branch?: string;
909
+ /** `origin-head` = 远端默认分支;`head` = 本地当前分支兜底;`none` = 读不到。 */
910
+ source: 'origin-head' | 'head' | 'none';
911
+ message?: string;
912
+ }
913
+ /**
914
+ * 读取仓库默认分支:
915
+ * 1. `refs/remotes/origin/HEAD` → 远端默认分支(最准确);
916
+ * 2. 回退到本地当前分支 `HEAD`(空仓库/未 fetch 时仍能给个值);
917
+ * 3. 都失败返回 `none`,由界面提示。
918
+ */
919
+ declare function readDefaultBranch(repoPath: string, options?: {
920
+ env?: NodeJS.ProcessEnv;
921
+ }): Promise<DefaultBranchResult>;
922
+
923
+ type repo_DefaultBranchResult = DefaultBranchResult;
924
+ declare const repo_readDefaultBranch: typeof readDefaultBranch;
925
+ declare namespace repo {
926
+ export { type repo_DefaultBranchResult as DefaultBranchResult, repo_readDefaultBranch as readDefaultBranch };
927
+ }
928
+
843
929
  /**
844
930
  * 从宿主的 provider 目录快速填充模型配置(FR-P-03 / AC-P-03)。
845
931
  *
@@ -910,6 +996,18 @@ interface CandidateOptions {
910
996
  env?: NodeJS.ProcessEnv;
911
997
  }
912
998
  declare function listProviderCandidates(options: CandidateOptions): Promise<LlmProviderCandidate[]>;
999
+ /** 宿主当前的默认模型选择(`ctx.agentDefaultModel`);没有该服务或读取失败时返回 undefined。 */
1000
+ interface DefaultSelection {
1001
+ provider: string;
1002
+ model: string;
1003
+ reasoningEffort?: string;
1004
+ }
1005
+ declare function readDefaultSelection(get: (name: string) => unknown): DefaultSelection | undefined;
1006
+ /**
1007
+ * 把「DSH 默认模型」落成模型设置:能匹配到已生效路由就复用其认证形态,
1008
+ * 否则按代理模式(hostProvider = 该路由 id)交给宿主转发。
1009
+ */
1010
+ declare function applyDefaultSelection(settings: Settings, selection: DefaultSelection, candidate?: LlmProviderCandidate): Settings;
913
1011
  /**
914
1012
  * 把候选落成模型设置。`mode: 'proxy'` 时清空 baseURL 与 apiKeyRef——
915
1013
  * 端点由插件在运行时注入回环代理地址,留存旧值只会误导。
@@ -923,17 +1021,20 @@ declare function resolveModelKey(settings: Settings, credentials: CredentialServ
923
1021
  }>;
924
1022
 
925
1023
  type quickfill_CandidateOptions = CandidateOptions;
1024
+ type quickfill_DefaultSelection = DefaultSelection;
926
1025
  type quickfill_LlmModelCandidate = LlmModelCandidate;
927
1026
  type quickfill_LlmProviderCandidate = LlmProviderCandidate;
928
1027
  type quickfill_LlmServices = LlmServices;
929
1028
  declare const quickfill_applyCandidate: typeof applyCandidate;
1029
+ declare const quickfill_applyDefaultSelection: typeof applyDefaultSelection;
930
1030
  declare const quickfill_coreProviderFor: typeof coreProviderFor;
931
1031
  declare const quickfill_credentialServices: typeof credentialServices;
932
1032
  declare const quickfill_listProviderCandidates: typeof listProviderCandidates;
933
1033
  declare const quickfill_llmServices: typeof llmServices;
1034
+ declare const quickfill_readDefaultSelection: typeof readDefaultSelection;
934
1035
  declare const quickfill_resolveModelKey: typeof resolveModelKey;
935
1036
  declare namespace quickfill {
936
- export { type quickfill_CandidateOptions as CandidateOptions, type quickfill_LlmModelCandidate as LlmModelCandidate, type quickfill_LlmProviderCandidate as LlmProviderCandidate, type quickfill_LlmServices as LlmServices, quickfill_applyCandidate as applyCandidate, quickfill_coreProviderFor as coreProviderFor, quickfill_credentialServices as credentialServices, quickfill_listProviderCandidates as listProviderCandidates, quickfill_llmServices as llmServices, quickfill_resolveModelKey as resolveModelKey };
1037
+ export { type quickfill_CandidateOptions as CandidateOptions, type quickfill_DefaultSelection as DefaultSelection, type quickfill_LlmModelCandidate as LlmModelCandidate, type quickfill_LlmProviderCandidate as LlmProviderCandidate, type quickfill_LlmServices as LlmServices, quickfill_applyCandidate as applyCandidate, quickfill_applyDefaultSelection as applyDefaultSelection, quickfill_coreProviderFor as coreProviderFor, quickfill_credentialServices as credentialServices, quickfill_listProviderCandidates as listProviderCandidates, quickfill_llmServices as llmServices, quickfill_readDefaultSelection as readDefaultSelection, quickfill_resolveModelKey as resolveModelKey };
937
1038
  }
938
1039
 
939
1040
  /**
@@ -1026,6 +1127,59 @@ declare namespace proxy {
1026
1127
  export { type proxy_LlmLike as LlmLike, type proxy_ProxyHandle as ProxyHandle, type proxy_ProxyOptions as ProxyOptions, proxy_foldMessages as foldMessages, proxy_startProxy as startProxy };
1027
1128
  }
1028
1129
 
1130
+ /**
1131
+ * 未合并 PR 的「对账」:把 `state.pendingPr` 和 GitHub 上的真实状态对齐。
1132
+ *
1133
+ * 背景:插件把「上次成功创建的 PR」记在 `pendingPr`,但它不会感知这个 PR 之后
1134
+ * 在 GitHub 上被**合并或关闭**——于是界面/工具会一直显示「未合并」,而且在同日
1135
+ * 重跑时给出误导性的判断。这里做一次只读核对(GET /pulls/{n}):
1136
+ *
1137
+ * - open → 仍是未合并 PR,保留;
1138
+ * - merged → 已完成,清掉陈旧记录;
1139
+ * - closed → 已放弃,清掉陈旧记录;
1140
+ * - unknown → 无 token / 解析失败 / 网络异常,先不动(下次再试)。
1141
+ *
1142
+ * 只读、可注入 `fetchImpl`,并对同一 URL 做 60 s 缓存,避免 `/state` 5 s 轮询
1143
+ * 每次都打 GitHub。任何失败都降级为 `unknown`,绝不让对账影响主流程。
1144
+ */
1145
+ type PullState = 'open' | 'closed' | 'merged' | 'unknown';
1146
+ interface PendingPrStatus {
1147
+ state: PullState;
1148
+ url: string;
1149
+ number?: number;
1150
+ title?: string;
1151
+ reason?: string;
1152
+ }
1153
+ interface ParsedPullUrl {
1154
+ owner: string;
1155
+ repo: string;
1156
+ number: number;
1157
+ }
1158
+ /** 只认标准的 `https://github.com/<owner>/<repo>/pull/<n>` 形态。 */
1159
+ declare function parsePullUrl(url: string): ParsedPullUrl | undefined;
1160
+ /** 清空缓存(测试与「强制刷新」用)。 */
1161
+ declare function clearPendingPrCache(): void;
1162
+ interface CheckPendingPrOptions {
1163
+ url: string;
1164
+ token?: string;
1165
+ fetchImpl?: typeof fetch;
1166
+ now?: number;
1167
+ /** false = 跳过缓存,强制请求。 */
1168
+ cache?: boolean;
1169
+ }
1170
+ declare function checkPendingPr(options: CheckPendingPrOptions): Promise<PendingPrStatus>;
1171
+
1172
+ type pendingPr_CheckPendingPrOptions = CheckPendingPrOptions;
1173
+ type pendingPr_ParsedPullUrl = ParsedPullUrl;
1174
+ type pendingPr_PendingPrStatus = PendingPrStatus;
1175
+ type pendingPr_PullState = PullState;
1176
+ declare const pendingPr_checkPendingPr: typeof checkPendingPr;
1177
+ declare const pendingPr_clearPendingPrCache: typeof clearPendingPrCache;
1178
+ declare const pendingPr_parsePullUrl: typeof parsePullUrl;
1179
+ declare namespace pendingPr {
1180
+ 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 };
1181
+ }
1182
+
1029
1183
  interface RunSummary {
1030
1184
  id: string;
1031
1185
  ok: boolean;
@@ -1037,7 +1191,11 @@ interface RunSummary {
1037
1191
  files: number;
1038
1192
  contentHash?: string;
1039
1193
  branch?: string;
1194
+ commit?: string;
1040
1195
  prUrl?: string;
1196
+ preview?: PreviewMeta;
1197
+ /** 核心给出的跳过原因(merged / pr-closed / unchanged)。 */
1198
+ skipReason?: string;
1041
1199
  error?: {
1042
1200
  code: string;
1043
1201
  message: string;
@@ -1045,6 +1203,8 @@ interface RunSummary {
1045
1203
  area: string;
1046
1204
  retriable: boolean;
1047
1205
  inferred?: boolean;
1206
+ /** 机器可判定的细分原因(push 失败时由核心给出)。 */
1207
+ reason?: string;
1048
1208
  };
1049
1209
  warnings: string[];
1050
1210
  logFile?: string;
@@ -1065,6 +1225,8 @@ interface RunnerDeps {
1065
1225
  env?: NodeJS.ProcessEnv;
1066
1226
  /** 运行结束(成功或失败)后回调,用于刷新调度/界面缓存。 */
1067
1227
  onFinished?: (summary: RunSummary) => void;
1228
+ /** 预览副本这类「可失败但不阻断运行」的事件走这里,不写进 warnings。 */
1229
+ logger?: (event: string, data?: Record<string, unknown>) => void;
1068
1230
  }
1069
1231
  declare class Runner {
1070
1232
  private active;
@@ -1078,6 +1240,15 @@ declare class Runner {
1078
1240
  run(options: RunRequestOptions): Promise<RunSummary>;
1079
1241
  private summarize;
1080
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;
1081
1252
  /** 写历史与状态;同一天的 skipped 运行不覆盖 lastDigest。 */
1082
1253
  private record;
1083
1254
  /** 给设置页/工具用的「核心现在什么状态」。 */
@@ -1093,6 +1264,22 @@ declare namespace runner {
1093
1264
  export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps };
1094
1265
  }
1095
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
+
1096
1283
  /**
1097
1284
  * 调度(FR-P-08 / 规格 §9)。
1098
1285
  *
@@ -1136,7 +1323,8 @@ declare class Scheduler {
1136
1323
  private readonly deps;
1137
1324
  private timerHandle;
1138
1325
  private stopped;
1139
- private lastFiredFor;
1326
+ /** 当天已尝试次数与时间;只在内存里,进程重启后重新计数。 */
1327
+ private readonly attempts;
1140
1328
  constructor(deps: SchedulerDeps);
1141
1329
  start(): void;
1142
1330
  stop(): void;
@@ -1186,7 +1374,12 @@ interface RouteDeps {
1186
1374
  scheduler: Scheduler;
1187
1375
  get: (name: string) => unknown;
1188
1376
  env?: NodeJS.ProcessEnv;
1377
+ /** 注入用:pendingPr 对账的 fetch 实现(测试不联网)。 */
1378
+ fetchImpl?: typeof fetch;
1189
1379
  version: string;
1380
+ /** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
1381
+ pluginName?: string;
1382
+ repository?: string;
1190
1383
  logger?: (event: string, data?: Record<string, unknown>) => void;
1191
1384
  }
1192
1385
  declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
@@ -1440,4 +1633,4 @@ declare namespace tools {
1440
1633
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
1441
1634
  }
1442
1635
 
1443
- export { channel, config, core, credentials, errors, exec, handshake$1 as handshake, importCli, ipc, locate, paths, preflight, proxy, quickfill, release, 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 };