pi-patch-keeper 1.2.11 → 1.3.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 +32 -23
- package/index.ts +30 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ pi 配置守卫:codemode 一致性、force 顺序、环境指纹漂移、注
|
|
|
13
13
|
|
|
14
14
|
| 项 | 说明 |
|
|
15
15
|
| --- | --- |
|
|
16
|
-
| 配置文件 |
|
|
16
|
+
| 配置文件 | `~/.pi/patch-keeper.json`(可省略;`forceTail` 数组) |
|
|
17
17
|
| 命令行 | /patch-keeper [fix] |
|
|
18
18
|
| 调用方式 | **全自动**:启动自检并自动修复 force 顺序;fix 手动重跑 |
|
|
19
19
|
|
|
@@ -25,11 +25,23 @@ pi install npm:pi-patch-keeper
|
|
|
25
25
|
|
|
26
26
|
## 配置
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
配置文件 `~/.pi/patch-keeper.json`(可省略);无环境变量。
|
|
29
|
+
|
|
30
|
+
| 键 | 类型 | 默认 | 说明 |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| `forceTail` | string[] | `["npm:pi-rtk-optimizer", "npm:pi-cache-optimizer"]` | 视为 force 型的包名(须排在 packages 末尾);配了非空数组则完全覆盖默认 |
|
|
33
|
+
|
|
34
|
+
完整配置:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"forceTail": ["npm:pi-rtk-optimizer", "npm:pi-cache-optimizer"]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
29
41
|
|
|
30
42
|
| 状态文件 | 说明 |
|
|
31
43
|
| --- | --- |
|
|
32
|
-
| `~/.pi/patch-keeper.json` | 上次启动的配置指纹(漂移对比) |
|
|
44
|
+
| `~/.pi/patch-keeper-state.json` | 上次启动的配置指纹(漂移对比) |
|
|
33
45
|
| `~/.pi/patch-keeper-manual.json` | 手册版本 + 内容签名 |
|
|
34
46
|
| `~/.pi/agent/settings.json.bak-force-order-*` | force 顺序修复前的备份 |
|
|
35
47
|
|
|
@@ -51,14 +63,11 @@ pi install npm:pi-patch-keeper
|
|
|
51
63
|
|
|
52
64
|
```mermaid
|
|
53
65
|
flowchart TD
|
|
54
|
-
A[factory
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
C
|
|
59
|
-
F -->|是| G[备份 + 重排 packages]
|
|
60
|
-
C --> H[before_agent_start<br/>比对注入手册版本与磁盘]
|
|
61
|
-
H -->|不一致| I[通知 + nextTurn 尾部消息<br/>以磁盘为准]
|
|
66
|
+
A[factory:读 settings.json 建指纹] --> B[session_start]
|
|
67
|
+
A --> C[before_agent_start]
|
|
68
|
+
B -->|指纹变化| D[通知:手册 / 结论可能过期]
|
|
69
|
+
B -->|force 顺序错| E[备份 + 重排 packages]
|
|
70
|
+
C -->|注入版本 vs 磁盘不一致| F[通知 + nextTurn 尾部消息<br/>以磁盘为准]
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
## 原理(小白)
|
|
@@ -92,7 +101,7 @@ pi configuration guards: codemode consistency, force order, settings fingerprint
|
|
|
92
101
|
|
|
93
102
|
| Item | Description |
|
|
94
103
|
| --- | --- |
|
|
95
|
-
| Config file |
|
|
104
|
+
| Config file | `~/.pi/patch-keeper.json` (optional; `forceTail` array) |
|
|
96
105
|
| Commands | /patch-keeper [fix] |
|
|
97
106
|
| Automation | **Fully automatic**: self-checks at startup and repairs force order; fix re-runs it |
|
|
98
107
|
|
|
@@ -104,11 +113,15 @@ pi install npm:pi-patch-keeper
|
|
|
104
113
|
|
|
105
114
|
## Configuration
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
Config file `~/.pi/patch-keeper.json` (optional); no environment variables.
|
|
117
|
+
|
|
118
|
+
| Key | Type | Default | Description |
|
|
119
|
+
| --- | --- | --- | --- |
|
|
120
|
+
| `forceTail` | string[] | `["npm:pi-rtk-optimizer", "npm:pi-cache-optimizer"]` | Packages treated as force-type (must sit last in packages); a non-empty array replaces the default |
|
|
108
121
|
|
|
109
122
|
| State file | Description |
|
|
110
123
|
| --- | --- |
|
|
111
|
-
| `~/.pi/patch-keeper.json` | Settings fingerprint from the previous start (drift compare) |
|
|
124
|
+
| `~/.pi/patch-keeper-state.json` | Settings fingerprint from the previous start (drift compare) |
|
|
112
125
|
| `~/.pi/patch-keeper-manual.json` | Manual version + content signature |
|
|
113
126
|
| `~/.pi/agent/settings.json.bak-force-order-*` | Backup before a force-order repair |
|
|
114
127
|
|
|
@@ -130,15 +143,11 @@ No config file (only reads `settings.json` and `APPEND_SYSTEM.md`); no environme
|
|
|
130
143
|
|
|
131
144
|
```mermaid
|
|
132
145
|
flowchart TD
|
|
133
|
-
A[factory
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
C
|
|
138
|
-
F -->|yes| G[backup + reorder packages]
|
|
139
|
-
C --> H[before_agent_start<br/>compare injected manual vs disk]
|
|
140
|
-
H -->|mismatch| I[notify + nextTurn message<br/>trust disk]
|
|
141
|
-
|
|
146
|
+
A[factory: build fingerprint from settings.json] --> B[session_start]
|
|
147
|
+
A --> C[before_agent_start]
|
|
148
|
+
B -->|fingerprint changed| D[notify: manuals may be stale]
|
|
149
|
+
B -->|force order wrong| E[backup + reorder packages]
|
|
150
|
+
C -->|injected vs disk mismatch| F[notify + nextTurn message<br/>trust disk]
|
|
142
151
|
```
|
|
143
152
|
|
|
144
153
|
## How it works (plain words)
|
package/index.ts
CHANGED
|
@@ -15,7 +15,8 @@ import * as path from "node:path";
|
|
|
15
15
|
|
|
16
16
|
const HOME = os.homedir();
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const CONFIG = path.join(HOME, ".pi", "patch-keeper.json"); // 用户配置(可编辑)
|
|
19
|
+
const FINGERPRINT = path.join(HOME, ".pi", "patch-keeper-state.json"); // 指纹状态(自动维护)
|
|
19
20
|
|
|
20
21
|
const SETTINGS = path.join(HOME, ".pi", "agent", "settings.json");
|
|
21
22
|
// 手册(注入对齐检测:本轮注入文本的版本行 vs 磁盘版)
|
|
@@ -121,25 +122,42 @@ function manualDrift(): string | null {
|
|
|
121
122
|
return null;
|
|
122
123
|
}
|
|
123
124
|
// 已知盲区:caveman-think-zh 是 user 扩展(不在 packages 数组),它在检测到原版英文段时
|
|
124
|
-
// 也会 force;
|
|
125
|
+
// 也会 force;forceTail 覆盖不到,触发时本扩展看不到。
|
|
125
126
|
/** force/条件 force 型必须排在 packages 末尾(其后 section 写入会被静默丢弃)。触发概率升序:rtk → cache-optimizer。 */
|
|
126
|
-
const
|
|
127
|
+
const DEFAULT_FORCE_TAIL = ["npm:pi-rtk-optimizer", "npm:pi-cache-optimizer"];
|
|
128
|
+
|
|
129
|
+
/** 用户配置(~/.pi/patch-keeper.json,可省略):forceTail 决定哪些包被视为 force 型。 */
|
|
130
|
+
export type PkConfig = { forceTail?: string[] };
|
|
131
|
+
export function loadConfig(): PkConfig {
|
|
132
|
+
const raw = safeRead(CONFIG);
|
|
133
|
+
if (raw === null) return {};
|
|
134
|
+
try { const j = JSON.parse(raw) as PkConfig; return j && typeof j === "object" ? j : {}; } catch { return {}; }
|
|
135
|
+
}
|
|
136
|
+
/** force 型包列表(配置优先,缺省用内置两项)。 */
|
|
137
|
+
export function forceTail(): string[] {
|
|
138
|
+
const c = loadConfig();
|
|
139
|
+
const list = Array.isArray(c.forceTail) ? c.forceTail.filter((x): x is string => typeof x === "string" && x.length > 0) : [];
|
|
140
|
+
return list.length ? list : DEFAULT_FORCE_TAIL;
|
|
141
|
+
}
|
|
127
142
|
export function forceOrderIssues(packages: string[]): string[] {
|
|
128
143
|
const out: string[] = [];
|
|
144
|
+
const FT = forceTail();
|
|
129
145
|
const idx = (n: string) => packages.indexOf(n);
|
|
130
|
-
const present =
|
|
146
|
+
const present = FT.filter((n) => idx(n) >= 0);
|
|
131
147
|
if (!present.length) return out;
|
|
132
148
|
const minForce = Math.min(...present.map(idx));
|
|
133
|
-
const later = packages.slice(minForce + 1).filter((n) => !
|
|
134
|
-
if (later.length) out.push(`force 型之后仍有 ${later.length} 个包(${later.slice(0, 2).join(" / ")})—— 它们的 section 写入会被丢弃;把 ${
|
|
135
|
-
|
|
149
|
+
const later = packages.slice(minForce + 1).filter((n) => !FT.includes(n));
|
|
150
|
+
if (later.length) out.push(`force 型之后仍有 ${later.length} 个包(${later.slice(0, 2).join(" / ")})—— 它们的 section 写入会被丢弃;把 ${FT.join(" / ")} 移到末尾`);
|
|
151
|
+
const actual = present.slice().sort((a, b) => idx(a) - idx(b));
|
|
152
|
+
if (actual.join("|") !== present.join("|")) out.push("force 顺序应为 " + FT.join(" → ") + "(最可能触发者最后)");
|
|
136
153
|
return out;
|
|
137
154
|
}
|
|
138
|
-
/** 把
|
|
155
|
+
/** 把 forceTail 成员移到末尾(其余相对顺序不变);已正确时返回 null。 */
|
|
139
156
|
export function normalizeForceOrder(packages: string[]): string[] | null {
|
|
140
|
-
const
|
|
157
|
+
const FT = forceTail();
|
|
158
|
+
const present = FT.filter((n) => packages.includes(n));
|
|
141
159
|
if (!present.length) return null;
|
|
142
|
-
const rest = packages.filter((n) => !
|
|
160
|
+
const rest = packages.filter((n) => !FT.includes(n));
|
|
143
161
|
const next = [...rest, ...present];
|
|
144
162
|
return next.join("\n") === packages.join("\n") ? null : next;
|
|
145
163
|
}
|
|
@@ -158,7 +176,7 @@ function fixForceOrder(): string | null {
|
|
|
158
176
|
s.packages = next;
|
|
159
177
|
writeAtomic(SETTINGS, JSON.stringify(s, null, 2));
|
|
160
178
|
} catch (e) { return `force 顺序自动修复失败:${(e as Error).message}`; }
|
|
161
|
-
return `force 顺序已自动修复:${
|
|
179
|
+
return `force 顺序已自动修复:${forceTail().filter((n) => next.includes(n)).join(" / ")} 移到 packages 末尾`;
|
|
162
180
|
}
|
|
163
181
|
/** 静态扫描:packages 中疑似 force 型扩展(入口源码含 return { systemPrompt } / forceSystemPrompt)。 */
|
|
164
182
|
const FORCE_RE = /return\s*\{\s*systemPrompt|forceSystemPrompt/;
|
|
@@ -169,7 +187,7 @@ function classifyPackages(packages: string[]): { force: string[]; fallback: stri
|
|
|
169
187
|
const force: string[] = [];
|
|
170
188
|
const fallback: string[] = [];
|
|
171
189
|
for (const name of packages) {
|
|
172
|
-
if (!name.startsWith("npm:") ||
|
|
190
|
+
if (!name.startsWith("npm:") || forceTail().includes(name)) continue;
|
|
173
191
|
const dir = path.join(HOME, ".pi", "agent", "npm", "node_modules", name.slice(4));
|
|
174
192
|
if (!existsSync(dir)) continue;
|
|
175
193
|
let entries: string[] = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-patch-keeper",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "pi config guards: codemode single-source check, force-order guard (auto-fix), settings fingerprint drift, injected-manual alignment. (PiDeck / third-party source patches are a local-only module, not bundled.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|