pi-patch-keeper 1.1.0 → 1.1.1
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 +1 -1
- package/index.ts +17 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ npm 发布版:`pi install npm:pi-patch-keeper`
|
|
|
8
8
|
|
|
9
9
|
安装后 `/reload` 或重启会话生效(扩展源码改动需重启)。
|
|
10
10
|
|
|
11
|
-
**是什么**:把「对别人的东西」的改动(**12 个面**:PiDeck builtin ×5 双份 · npm 包 ×4 · app.asar ×3)从「升级后手工重跑」变成**启动自愈 +
|
|
11
|
+
**是什么**:把「对别人的东西」的改动(**12 个面**:PiDeck builtin ×5 双份 · npm 包 ×4 · app.asar ×3)从「升级后手工重跑」变成**启动自愈 + 明确报告**——外置、模块化、抗上游升级。全量清单由本机补丁登记表维护(PATCH-REGISTRY,随快照包分发)。
|
|
12
12
|
|
|
13
13
|
## 来源与借鉴
|
|
14
14
|
|
package/index.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - 任何失败都只 notify,不抛错、不影响会话(纪律:不合法就跳过 + 记日志,绝不崩溃)。
|
|
21
21
|
*/
|
|
22
22
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
23
|
-
import { existsSync, readFileSync, writeFileSync, copyFileSync, readdirSync } from "node:fs";
|
|
23
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, readdirSync, renameSync } from "node:fs";
|
|
24
24
|
import { createHash } from "node:crypto";
|
|
25
25
|
import * as os from "node:os";
|
|
26
26
|
import * as path from "node:path";
|
|
@@ -140,7 +140,7 @@ function healPiMemory(): Result {
|
|
|
140
140
|
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
141
141
|
const bak = `${PI_MEMORY}.bak-exitsummary-${stamp}`;
|
|
142
142
|
if (!existsSync(bak)) copyFileSync(PI_MEMORY, bak);
|
|
143
|
-
|
|
143
|
+
writeAtomic(PI_MEMORY, next);
|
|
144
144
|
} catch (e) { return { ok: false, detail: `写入失败:${(e as Error).message}` }; }
|
|
145
145
|
return { ok: true, detail: "已自动修补(exit summary 空行规范化)—— 本轮即生效" };
|
|
146
146
|
}
|
|
@@ -157,7 +157,7 @@ function healMemorySection(): Result {
|
|
|
157
157
|
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
158
158
|
const bak = `${PI_MEMORY}.bak-memsec-${stamp}`;
|
|
159
159
|
if (!existsSync(bak)) copyFileSync(PI_MEMORY, bak);
|
|
160
|
-
|
|
160
|
+
writeAtomic(PI_MEMORY, next);
|
|
161
161
|
} catch (e) { return { ok: false, detail: `写入失败:${(e as Error).message}` }; }
|
|
162
162
|
return { ok: true, detail: "已自动修补(memory 段改 section)—— 重启后生效" };
|
|
163
163
|
}
|
|
@@ -174,7 +174,7 @@ function healTactician(): Result {
|
|
|
174
174
|
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
175
175
|
const bak = `${TACT}.bak-tactician-${stamp}`;
|
|
176
176
|
if (!existsSync(bak)) copyFileSync(TACT, bak);
|
|
177
|
-
|
|
177
|
+
writeAtomic(TACT, next);
|
|
178
178
|
} catch (e) { return { ok: false, detail: `写入失败:${(e as Error).message}` }; }
|
|
179
179
|
return { ok: true, detail: "已自动修补(tactician 改 section)—— 重启后生效" };
|
|
180
180
|
}
|
|
@@ -192,6 +192,13 @@ function safeRead(p: string): string | null {
|
|
|
192
192
|
try { return readFileSync(p, "utf8"); } catch { return null; }
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/** 原子写:临时文件 + rename(崩溃/被杀不留半写文件)。 */
|
|
196
|
+
function writeAtomic(file: string, data: string | Buffer): void {
|
|
197
|
+
const tmp = file + ".tmp-" + process.pid;
|
|
198
|
+
writeFileSync(tmp, data);
|
|
199
|
+
renameSync(tmp, file);
|
|
200
|
+
}
|
|
201
|
+
|
|
195
202
|
/** 统计 needle 在原串里的出现次数。 */
|
|
196
203
|
function count(hay: string, needle: string): number {
|
|
197
204
|
let n = 0, i = 0;
|
|
@@ -230,7 +237,7 @@ function healPiLens(): Result {
|
|
|
230
237
|
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
231
238
|
const bak = `${PI_LENS}.bak-wordindex-${stamp}`;
|
|
232
239
|
if (!existsSync(bak)) copyFileSync(PI_LENS, bak);
|
|
233
|
-
|
|
240
|
+
writeAtomic(PI_LENS, next);
|
|
234
241
|
} catch (e) { return { ok: false, detail: `写入失败:${(e as Error).message}` }; }
|
|
235
242
|
return { ok: true, detail: state === "upstream" ? `上游已修复 #4583 —— 跳过 A,仅补 B${a1 ? "(A 残留,建议 npm 重装清除)" : ""}` : `已自动修补(原状态 ${state})—— 本轮即生效` };
|
|
236
243
|
}
|
|
@@ -265,7 +272,7 @@ function healMemoryFormat(): Result {
|
|
|
265
272
|
const txt = safeRead(p);
|
|
266
273
|
if (txt === null) continue;
|
|
267
274
|
const norm = normMd(txt);
|
|
268
|
-
if (norm !== txt) { try {
|
|
275
|
+
if (norm !== txt) { try { writeAtomic(p, norm); fixed += 1; } catch { /* 静默 */ } }
|
|
269
276
|
}
|
|
270
277
|
};
|
|
271
278
|
walk(dir);
|
|
@@ -320,7 +327,7 @@ function fingerprintDrift(): string[] {
|
|
|
320
327
|
let prev: Record<string, string[]> | null = null;
|
|
321
328
|
const prevRaw = safeRead(FINGERPRINT);
|
|
322
329
|
if (prevRaw) { try { prev = JSON.parse(prevRaw) as Record<string, string[]>; } catch { prev = null; } }
|
|
323
|
-
try {
|
|
330
|
+
try { writeAtomic(FINGERPRINT, JSON.stringify(cur, null, 1)); } catch { /* 静默 */ }
|
|
324
331
|
if (prev === null) return [];
|
|
325
332
|
const out: string[] = [];
|
|
326
333
|
for (const k of ["packages", "defaultTools", "enabledModels", "extensions", "codemode"] as const) {
|
|
@@ -371,7 +378,7 @@ function manualDrift(): string | null {
|
|
|
371
378
|
let prev: { ver?: string; sig?: string } | null = null;
|
|
372
379
|
const raw = safeRead(MANUAL_STATE);
|
|
373
380
|
if (raw) { try { prev = JSON.parse(raw); } catch { prev = null; } }
|
|
374
|
-
try {
|
|
381
|
+
try { writeAtomic(MANUAL_STATE, JSON.stringify({ ver, sig })); } catch { /* ignore */ }
|
|
375
382
|
if (prev && prev.ver === ver && prev.sig !== sig) return `手册内容已变但版本号未升(${ver})—— bump-manual 或撤销未登记改动`;
|
|
376
383
|
return null;
|
|
377
384
|
}
|
|
@@ -411,7 +418,7 @@ function fixForceOrder(): string | null {
|
|
|
411
418
|
try {
|
|
412
419
|
copyFileSync(SETTINGS, `${SETTINGS}.bak-force-order-${Date.now()}`);
|
|
413
420
|
s.packages = next;
|
|
414
|
-
|
|
421
|
+
writeAtomic(SETTINGS, JSON.stringify(s, null, 2));
|
|
415
422
|
} catch (e) { return `force 顺序自动修复失败:${(e as Error).message}`; }
|
|
416
423
|
return `force 顺序已自动修复:${FORCE_TAIL.filter((n) => next.includes(n)).join(" / ")} 移到 packages 末尾`;
|
|
417
424
|
}
|
|
@@ -472,7 +479,7 @@ function healSelfExtension(): Result {
|
|
|
472
479
|
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
473
480
|
const bak = `${SELF_EXT}.bak-overwrite-${stamp}`;
|
|
474
481
|
if (!existsSync(bak)) copyFileSync(SELF_EXT, bak);
|
|
475
|
-
|
|
482
|
+
writeAtomic(SELF_EXT, snap);
|
|
476
483
|
} catch (e) { return { ok: false, detail: `恢复失败:${(e as Error).message}` }; }
|
|
477
484
|
return { ok: true, detail: "疑似被发布精简版覆盖 —— 已从快照恢复(重启后生效)" };
|
|
478
485
|
}
|
package/package.json
CHANGED