principles-disciple 1.5.4 ā 1.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/dist/commands/context.d.ts +5 -0
- package/dist/commands/context.js +312 -0
- package/dist/commands/evolution-status.d.ts +4 -0
- package/dist/commands/evolution-status.js +138 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +45 -0
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +582 -0
- package/dist/commands/pain.js +143 -6
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/rollback.d.ts +19 -0
- package/dist/commands/rollback.js +119 -0
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/core/config.d.ts +37 -0
- package/dist/core/config.js +47 -0
- package/dist/core/control-ui-db.d.ts +68 -0
- package/dist/core/control-ui-db.js +274 -0
- package/dist/core/detection-funnel.d.ts +1 -1
- package/dist/core/detection-funnel.js +4 -0
- package/dist/core/dictionary.d.ts +2 -0
- package/dist/core/dictionary.js +13 -0
- package/dist/core/event-log.d.ts +22 -1
- package/dist/core/event-log.js +319 -0
- package/dist/core/evolution-engine.d.ts +5 -5
- package/dist/core/evolution-engine.js +18 -18
- package/dist/core/evolution-migration.d.ts +5 -0
- package/dist/core/evolution-migration.js +65 -0
- package/dist/core/evolution-reducer.d.ts +69 -0
- package/dist/core/evolution-reducer.js +369 -0
- package/dist/core/evolution-types.d.ts +103 -0
- package/dist/core/focus-history.d.ts +65 -0
- package/dist/core/focus-history.js +266 -0
- package/dist/core/init.js +30 -7
- package/dist/core/migration.js +0 -2
- package/dist/core/path-resolver.d.ts +3 -0
- package/dist/core/path-resolver.js +90 -31
- package/dist/core/paths.d.ts +7 -8
- package/dist/core/paths.js +48 -40
- package/dist/core/profile.js +1 -1
- package/dist/core/session-tracker.d.ts +4 -0
- package/dist/core/session-tracker.js +15 -0
- package/dist/core/thinking-models.d.ts +38 -0
- package/dist/core/thinking-models.js +170 -0
- package/dist/core/trajectory.d.ts +184 -0
- package/dist/core/trajectory.js +817 -0
- package/dist/core/trust-engine.d.ts +2 -0
- package/dist/core/trust-engine.js +30 -4
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +301 -30
- package/dist/hooks/llm.d.ts +8 -0
- package/dist/hooks/llm.js +347 -69
- package/dist/hooks/message-sanitize.d.ts +3 -0
- package/dist/hooks/message-sanitize.js +37 -0
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +20 -11
- package/dist/hooks/prompt.js +558 -158
- package/dist/hooks/subagent.d.ts +9 -2
- package/dist/hooks/subagent.js +40 -3
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +48 -20
- package/dist/index.js +264 -8
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/empathy-observer-manager.d.ts +42 -0
- package/dist/service/empathy-observer-manager.js +147 -0
- package/dist/service/evolution-worker.d.ts +10 -0
- package/dist/service/evolution-worker.js +156 -24
- package/dist/service/trajectory-service.d.ts +2 -0
- package/dist/service/trajectory-service.js +15 -0
- package/dist/tools/agent-spawn.d.ts +27 -6
- package/dist/tools/agent-spawn.js +339 -87
- package/dist/tools/deep-reflect.d.ts +27 -7
- package/dist/tools/deep-reflect.js +282 -113
- package/dist/types/event-types.d.ts +84 -2
- package/dist/types/event-types.js +33 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +24 -1
- package/openclaw.plugin.json +43 -11
- package/package.json +16 -6
- package/templates/langs/zh/core/HEARTBEAT.md +28 -4
- package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
- package/templates/pain_settings.json +54 -2
- package/templates/workspace/.principles/PROFILE.json +2 -0
- package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /pd-focus å½ä»¤ - ē®”ē CURRENT_FOCUS.md
|
|
3
|
+
*
|
|
4
|
+
* åč½ļ¼
|
|
5
|
+
* - status: ę„ēå½åē¶ęååå²ēę¬
|
|
6
|
+
* - compress: ęåØå缩并å¤ä»½
|
|
7
|
+
* - history: ę„ēåå²ēę¬å蔨
|
|
8
|
+
* - rollback: åę»å°ęå®åå²ēę¬
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
14
|
+
import { getHistoryDir, backupToHistory, cleanupHistory, extractVersion, extractDate, } from '../core/focus-history.js';
|
|
15
|
+
import { createAgentSpawnTool } from '../tools/agent-spawn.js';
|
|
16
|
+
/**
|
|
17
|
+
* ęø
ē Markdown 代ē åå“ę
|
|
18
|
+
* ē§»é¤å¼å¤“ē ```lang åē»å°¾ē ```
|
|
19
|
+
*/
|
|
20
|
+
function stripMarkdownFence(content) {
|
|
21
|
+
let result = content.trim();
|
|
22
|
+
// ē§»é¤å¼å¤“ē代ē åę č®°ļ¼å¦ ```markdown, ```text ēļ¼
|
|
23
|
+
result = result.replace(/^```[\w]*\n?/, '');
|
|
24
|
+
// ē§»é¤ē»å°¾ē代ē åę č®°
|
|
25
|
+
result = result.replace(/\n?```$/, '');
|
|
26
|
+
return result.trim();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* č·åå·„ä½åŗē®å½
|
|
30
|
+
*/
|
|
31
|
+
function getWorkspaceDir(ctx) {
|
|
32
|
+
const workspaceDir = ctx.config?.workspaceDir;
|
|
33
|
+
if (!workspaceDir) {
|
|
34
|
+
throw new Error('[PD:Focus] workspaceDir is required but not provided');
|
|
35
|
+
}
|
|
36
|
+
return workspaceDir;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* å缩 CURRENT_FOCUSå
容
|
|
40
|
+
*
|
|
41
|
+
* č§åļ¼
|
|
42
|
+
* - äæēļ¼ę é¢ćå
ę°ę®ćē¶ęåæ«ē
§
|
|
43
|
+
* - äæēļ¼äøäøę„ē« čļ¼å®ę“ļ¼
|
|
44
|
+
* - äæēļ¼å½åä»»å”äøęŖå®ęē锹ļ¼- [ ]ļ¼
|
|
45
|
+
* - ē§»é¤ļ¼å½åä»»å”äøå·²å®ęē锹ļ¼- [x]ļ¼č¶
čæ 3 äøŖę¶
|
|
46
|
+
* - ē§»é¤ļ¼P0 ē« čå¦ęå
ØéØå®ę
|
|
47
|
+
* - äæēļ¼åčē« č
|
|
48
|
+
*/
|
|
49
|
+
function compressFocusContent(content) {
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
const result = [];
|
|
52
|
+
let currentSection = '';
|
|
53
|
+
let inP0Section = false;
|
|
54
|
+
let p0AllCompleted = true;
|
|
55
|
+
let p0Lines = [];
|
|
56
|
+
let completedCount = 0;
|
|
57
|
+
// č¾
å©å½ę°ļ¼å·ę° P0 ē« čē¼å
|
|
58
|
+
const flushP0Lines = (skipIfCompleted) => {
|
|
59
|
+
if (inP0Section && p0Lines.length > 0) {
|
|
60
|
+
if (!skipIfCompleted || !p0AllCompleted) {
|
|
61
|
+
// P0 ęęŖå®ęä»»å”ļ¼äæē P0 å
容
|
|
62
|
+
result.push(...p0Lines);
|
|
63
|
+
}
|
|
64
|
+
// éē½®ē¶ę
|
|
65
|
+
p0Lines = [];
|
|
66
|
+
inP0Section = false;
|
|
67
|
+
p0AllCompleted = true;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
const trimmedLine = line.trim();
|
|
73
|
+
// čÆå«ē« č
|
|
74
|
+
if (/^#{1,3}\s*.*ē¶ęåæ«ē
§|š/.test(trimmedLine)) {
|
|
75
|
+
flushP0Lines(true); // P0 å®ęę¶č·³čæ
|
|
76
|
+
currentSection = 'snapshot';
|
|
77
|
+
}
|
|
78
|
+
else if (/^###\s*P0/i.test(trimmedLine)) {
|
|
79
|
+
currentSection = 'current_p0';
|
|
80
|
+
inP0Section = true;
|
|
81
|
+
p0AllCompleted = true;
|
|
82
|
+
p0Lines = [line];
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
else if (/^###\s*P[1-9]/i.test(trimmedLine)) {
|
|
86
|
+
// ē¦»å¼ P0 ē« č
|
|
87
|
+
flushP0Lines(true); // P0 å®ęę¶č·³čæļ¼ęŖå®ęę¶äæē
|
|
88
|
+
currentSection = 'current';
|
|
89
|
+
result.push(line);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
else if (/^#{1,3}\s*.*å½åä»»å”|š/.test(trimmedLine)) {
|
|
93
|
+
flushP0Lines(true); // P0 å®ęę¶č·³čæ
|
|
94
|
+
currentSection = 'current';
|
|
95
|
+
}
|
|
96
|
+
else if (/^#{1,3}\s*.*äøäøę„|ā”ļø/.test(trimmedLine)) {
|
|
97
|
+
flushP0Lines(false); // äæēęŖå®ęē P0
|
|
98
|
+
currentSection = 'nextSteps';
|
|
99
|
+
}
|
|
100
|
+
else if (/^#{1,3}\s*.*åč|š/.test(trimmedLine)) {
|
|
101
|
+
flushP0Lines(false); // äæēęŖå®ęē P0
|
|
102
|
+
currentSection = 'reference';
|
|
103
|
+
}
|
|
104
|
+
// å¤ēP0ē« č
|
|
105
|
+
if (inP0Section) {
|
|
106
|
+
p0Lines.push(line);
|
|
107
|
+
// ę£ę„ęÆå¦ęęŖå®ęä»»å”
|
|
108
|
+
if (/^-\s*\[\s*\]/.test(trimmedLine)) {
|
|
109
|
+
p0AllCompleted = false;
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// å¤ēå½åä»»å”ē« čäøå·²å®ęē锹
|
|
114
|
+
if (currentSection === 'current') {
|
|
115
|
+
if (/^-\s*\[x\]/i.test(trimmedLine)) {
|
|
116
|
+
completedCount++;
|
|
117
|
+
// å¦ęå·²å®ę锹č¶
čæ 3 äøŖļ¼č·³čæ
|
|
118
|
+
if (completedCount > 3) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
result.push(line);
|
|
124
|
+
}
|
|
125
|
+
// å¾ŖēÆē»ęåļ¼å·ę°å©ä½ē P0 ē« č
|
|
126
|
+
flushP0Lines(false); // äæēęŖå®ęē P0
|
|
127
|
+
return result.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* ę¾ē¤ŗ CURRENT_FOCUS ē¶ę
|
|
131
|
+
*/
|
|
132
|
+
function showStatus(workspaceDir, isZh) {
|
|
133
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
134
|
+
const focusPath = wctx.resolve('CURRENT_FOCUS');
|
|
135
|
+
const historyDir = getHistoryDir(focusPath);
|
|
136
|
+
if (!fs.existsSync(focusPath)) {
|
|
137
|
+
return isZh
|
|
138
|
+
? 'ā ļø CURRENT_FOCUS.md äøååØ\n\nš” 请å
čæč” `/pd-init` åå§åå·„ä½åŗ'
|
|
139
|
+
: 'ā ļø CURRENT_FOCUS.md does not exist\n\nš” Run `/pd-init` to initialize workspace first';
|
|
140
|
+
}
|
|
141
|
+
const content = fs.readFileSync(focusPath, 'utf-8');
|
|
142
|
+
const version = extractVersion(content);
|
|
143
|
+
const date = extractDate(content);
|
|
144
|
+
const lines = content.split('\n').length;
|
|
145
|
+
// ē»č®”åå²ēę¬
|
|
146
|
+
let historyCount = 0;
|
|
147
|
+
if (fs.existsSync(historyDir)) {
|
|
148
|
+
historyCount = fs.readdirSync(historyDir).filter(f => f.startsWith('CURRENT_FOCUS.v')).length;
|
|
149
|
+
}
|
|
150
|
+
if (isZh) {
|
|
151
|
+
return `š **CURRENT_FOCUS.md ē¶ę**
|
|
152
|
+
|
|
153
|
+
| å±ę§ | å¼ |
|
|
154
|
+
|------|-----|
|
|
155
|
+
| ēę¬ | v${version} |
|
|
156
|
+
| ę“ę°ę„ę | ${date} |
|
|
157
|
+
| č”ę° | ${lines} č” |
|
|
158
|
+
| åå²ēę¬ | ${historyCount} äøŖ |
|
|
159
|
+
|
|
160
|
+
š” č¾å
„ \`/pd-focus history\` ę„ēåå²ēę¬
|
|
161
|
+
š” č¾å
„ \`/pd-focus compress\` ęåØå缩`;
|
|
162
|
+
}
|
|
163
|
+
return `š **CURRENT_FOCUS.md Status**
|
|
164
|
+
|
|
165
|
+
| Property | Value |
|
|
166
|
+
|----------|-------|
|
|
167
|
+
| Version | v${version} |
|
|
168
|
+
| Updated | ${date} |
|
|
169
|
+
| Lines | ${lines} lines |
|
|
170
|
+
| History | ${historyCount} versions |
|
|
171
|
+
|
|
172
|
+
š” Type \`/pd-focus history\` to view history
|
|
173
|
+
š” Type \`/pd-focus compress\` to compress manually`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* ę¾ē¤ŗåå²ēę¬å蔨
|
|
177
|
+
*/
|
|
178
|
+
function showHistory(workspaceDir, isZh) {
|
|
179
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
180
|
+
const focusPath = wctx.resolve('CURRENT_FOCUS');
|
|
181
|
+
const historyDir = getHistoryDir(focusPath);
|
|
182
|
+
if (!fs.existsSync(historyDir)) {
|
|
183
|
+
return isZh
|
|
184
|
+
? 'š ęę åå²ēę¬\n\nš” åå²ēę¬åØå缩 CURRENT_FOCUS.md ę¶čŖåØå建'
|
|
185
|
+
: 'š No history versions yet\n\nš” History is created when CURRENT_FOCUS.md is compressed';
|
|
186
|
+
}
|
|
187
|
+
const files = fs.readdirSync(historyDir)
|
|
188
|
+
.filter(f => f.startsWith('CURRENT_FOCUS.v') && f.endsWith('.md'))
|
|
189
|
+
.map(f => {
|
|
190
|
+
const filePath = path.join(historyDir, f);
|
|
191
|
+
const stat = fs.statSync(filePath);
|
|
192
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
193
|
+
return {
|
|
194
|
+
name: f,
|
|
195
|
+
version: extractVersion(content),
|
|
196
|
+
date: extractDate(content),
|
|
197
|
+
mtime: stat.mtime,
|
|
198
|
+
lines: content.split('\n').length,
|
|
199
|
+
};
|
|
200
|
+
})
|
|
201
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
202
|
+
if (files.length === 0) {
|
|
203
|
+
return isZh ? 'š ęę åå²ēę¬' : 'š No history versions';
|
|
204
|
+
}
|
|
205
|
+
const lines = files.slice(0, 10).map((f, i) => {
|
|
206
|
+
const num = i + 1;
|
|
207
|
+
return isZh
|
|
208
|
+
? `${num}. \`${f.name}\` - v${f.version} (${f.date}, ${f.lines} č”)`
|
|
209
|
+
: `${num}. \`${f.name}\` - v${f.version} (${f.date}, ${f.lines} lines)`;
|
|
210
|
+
});
|
|
211
|
+
const header = isZh
|
|
212
|
+
? `š **åå²ēę¬å蔨** (ęčæ ${Math.min(files.length, 10)} äøŖ)\n`
|
|
213
|
+
: `š **History Versions** (Last ${Math.min(files.length, 10)})\n`;
|
|
214
|
+
const footer = isZh
|
|
215
|
+
? `\n\nš” č¾å
„ \`/pd-focus rollback <åŗå·>\` åę»å°ęå®ēę¬`
|
|
216
|
+
: `\n\nš” Type \`/pd-focus rollback <number>\` to rollback`;
|
|
217
|
+
return header + '\n' + lines.join('\n') + footer;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* ęåØå缩 CURRENT_FOCUS.mdļ¼ä½æēØåęŗč½ä½ļ¼
|
|
221
|
+
*/
|
|
222
|
+
async function compressFocus(workspaceDir, isZh, api) {
|
|
223
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
224
|
+
const focusPath = wctx.resolve('CURRENT_FOCUS');
|
|
225
|
+
if (!fs.existsSync(focusPath)) {
|
|
226
|
+
return isZh
|
|
227
|
+
? 'ā CURRENT_FOCUS.md äøååØ'
|
|
228
|
+
: 'ā CURRENT_FOCUS.md does not exist';
|
|
229
|
+
}
|
|
230
|
+
const oldContent = fs.readFileSync(focusPath, 'utf-8');
|
|
231
|
+
const oldVersion = extractVersion(oldContent);
|
|
232
|
+
const oldDate = extractDate(oldContent);
|
|
233
|
+
const oldLines = oldContent.split('\n').length;
|
|
234
|
+
// ę£ę„ęÆå¦éč¦å缩
|
|
235
|
+
if (oldLines <= 40) {
|
|
236
|
+
return isZh
|
|
237
|
+
? `ā
å½åęä»¶ä»
${oldLines} č”ļ¼ę éå缩\n\nš” ęä»¶å°äŗ 40 č”ę¶äøå»ŗč®®å缩`
|
|
238
|
+
: `ā
Current file has only ${oldLines} lines, no need to compress\n\nš” Compression not recommended for files under 40 lines`;
|
|
239
|
+
}
|
|
240
|
+
// å¤ä»½å½åēę¬
|
|
241
|
+
const backupPath = backupToHistory(focusPath, oldContent);
|
|
242
|
+
// ęø
ēčæęåå²
|
|
243
|
+
cleanupHistory(focusPath);
|
|
244
|
+
// 使ēØåęŗč½ä½čæč”ęŗč½å缩
|
|
245
|
+
// č·å MEMORY.md č·Æå¾
|
|
246
|
+
const memoryPath = wctx.resolve('MEMORY_MD');
|
|
247
|
+
const compressPrompt = isZh
|
|
248
|
+
? `ä½ ęÆäøäøŖäøäøē锹ē®ę攣å缩å©ęć请åē¼©ä»„äø CURRENT_FOCUS.md ęä»¶å
容ć
|
|
249
|
+
|
|
250
|
+
**éč¦ļ¼åØå缩åļ¼ä½ éč¦ęåéč¦éēØē¢äæ”ęÆļ¼**
|
|
251
|
+
|
|
252
|
+
**第äøę„ļ¼ęåéēØē¢**
|
|
253
|
+
ä»åå§å
容äøčÆå«å·²å®ęēéēØē¢ļ¼čæäŗäæ”ęÆéč¦äæåå°č®°åæęä»¶äøć
|
|
254
|
+
|
|
255
|
+
**第äŗę„ļ¼å缩ęä»¶**
|
|
256
|
+
å缩č§åļ¼
|
|
257
|
+
1. äæēę é¢ćå
ę°ę®č”ļ¼ēę¬ćē¶ęćę„ęļ¼
|
|
258
|
+
2. äæē"š ē¶ęåæ«ē
§"ē« čļ¼å®ę“ļ¼
|
|
259
|
+
3. äæē"ā”ļø äøäøę„"ē« čļ¼å®ę“ļ¼čæęÆęéč¦ēäæ”ęÆļ¼
|
|
260
|
+
4. äæē"š åč"ē« čļ¼å®ę“ļ¼
|
|
261
|
+
5. 对äŗ"š å½åä»»å”"ē« čļ¼
|
|
262
|
+
- å¦ę P0/P1 ēåē« čå
ØéØå®ęļ¼åå¹¶äøŗē®ē"å·²å®ęéēØē¢"å蔨ļ¼ęå¤5锹ļ¼
|
|
263
|
+
- äæēęęęŖå®ęä»»å”ļ¼- [ ]ļ¼
|
|
264
|
+
- å·²å®ęä»»å”ęå¤äæē 3 äøŖęčæēļ¼å
¶ä½ē§»é¤
|
|
265
|
+
6. ē§»é¤éå¤äæ”ęÆååä½ęčæ°
|
|
266
|
+
7. äæę Markdown ę ¼å¼åčÆä¹čæč“Æ
|
|
267
|
+
|
|
268
|
+
**ē®ę ļ¼** å°ęä»¶åē¼©å° 40 č”仄å
ć
|
|
269
|
+
|
|
270
|
+
**åå§å
容ļ¼**
|
|
271
|
+
\`\`\`markdown
|
|
272
|
+
${oldContent}
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
**č¾åŗę ¼å¼ļ¼åæ
é”»äø„ę ¼éµå¾Ŗļ¼ļ¼**
|
|
276
|
+
\`\`\`
|
|
277
|
+
===MEMORY===
|
|
278
|
+
[éč¦čæ½å å° memory/MEMORY.md ēéēØē¢å
容ļ¼ę ¼å¼ļ¼]
|
|
279
|
+
## {YYYY-MM-DD} éēØē¢
|
|
280
|
+
- [éēØē¢1]
|
|
281
|
+
- [éēØē¢2]
|
|
282
|
+
...
|
|
283
|
+
===COMPRESSED===
|
|
284
|
+
[å缩åē CURRENT_FOCUS.md å
容]
|
|
285
|
+
\`\`\`
|
|
286
|
+
|
|
287
|
+
å¦ęę²”ęéč¦č®°å½ēéēØē¢ļ¼===MEMORY=== éØåē空ć`
|
|
288
|
+
: `You are a professional project document compression assistant. Please compress the following CURRENT_FOCUS.md file.
|
|
289
|
+
|
|
290
|
+
**IMPORTANT: Extract milestones before compression!**
|
|
291
|
+
|
|
292
|
+
**Step 1: Extract Milestones**
|
|
293
|
+
Identify completed milestones from the original content that should be saved to memory.
|
|
294
|
+
|
|
295
|
+
**Step 2: Compress File**
|
|
296
|
+
Compression Rules:
|
|
297
|
+
1. Keep title, metadata lines (version, status, date)
|
|
298
|
+
2. Keep "š Status Snapshot" section (complete)
|
|
299
|
+
3. Keep "ā”ļø Next Steps" section (complete, this is the most important)
|
|
300
|
+
4. Keep "š References" section (complete)
|
|
301
|
+
5. For "š Current Tasks" section:
|
|
302
|
+
- If P0/P1 subsections are all completed, merge into a short "Completed Milestones" list (max 5 items)
|
|
303
|
+
- Keep all incomplete tasks (- [ ])
|
|
304
|
+
- Keep at most 3 recent completed tasks, remove the rest
|
|
305
|
+
6. Remove duplicate info and redundant descriptions
|
|
306
|
+
7. Maintain Markdown format and semantic coherence
|
|
307
|
+
|
|
308
|
+
**Goal:** Compress the file to under 40 lines.
|
|
309
|
+
|
|
310
|
+
**Original Content:**
|
|
311
|
+
\`\`\`markdown
|
|
312
|
+
${oldContent}
|
|
313
|
+
\`\`\`
|
|
314
|
+
|
|
315
|
+
**Output Format (must follow strictly):**
|
|
316
|
+
\`\`\`
|
|
317
|
+
===MEMORY===
|
|
318
|
+
[Content to append to memory/MEMORY.md, format:]
|
|
319
|
+
## {YYYY-MM-DD} Milestones
|
|
320
|
+
- [milestone 1]
|
|
321
|
+
- [milestone 2]
|
|
322
|
+
...
|
|
323
|
+
===COMPRESSED===
|
|
324
|
+
[Compressed CURRENT_FOCUS.md content]
|
|
325
|
+
\`\`\`
|
|
326
|
+
|
|
327
|
+
If no milestones to record, leave ===MEMORY=== section empty.`;
|
|
328
|
+
let compressedContent;
|
|
329
|
+
let usedAI = false;
|
|
330
|
+
let memoryUpdated = false;
|
|
331
|
+
try {
|
|
332
|
+
// č°ēØåęŗč½ä½čæč”å缩
|
|
333
|
+
const tool = createAgentSpawnTool(api);
|
|
334
|
+
const result = await tool.execute(`focus-compress-${randomUUID()}`, {
|
|
335
|
+
agentType: 'reporter', // ä½æēØ reporter ē±»åļ¼éåę»ē»åå缩
|
|
336
|
+
task: compressPrompt,
|
|
337
|
+
});
|
|
338
|
+
// č§£ęč¾åŗļ¼ęå MEMORY å COMPRESSED éØå
|
|
339
|
+
const resultText = result?.content?.[0]?.text || '';
|
|
340
|
+
if (resultText.trim()) {
|
|
341
|
+
const memoryMatch = resultText.match(/===MEMORY===([\s\S]*?)===COMPRESSED===/);
|
|
342
|
+
const compressedMatch = resultText.match(/===COMPRESSED===([\s\S]*?)$/);
|
|
343
|
+
if (compressedMatch && compressedMatch[1].trim()) {
|
|
344
|
+
// ęø
ē Markdown 代ē åå“ę
|
|
345
|
+
compressedContent = stripMarkdownFence(compressedMatch[1]);
|
|
346
|
+
usedAI = true;
|
|
347
|
+
// åå
„č®°åæęä»¶ļ¼MEMORY.md åØę ¹ē®å½ļ¼ę éå建ē®å½ļ¼
|
|
348
|
+
if (memoryMatch && memoryMatch[1].trim()) {
|
|
349
|
+
// ęø
ē Markdown 代ē åå“ę
|
|
350
|
+
const memoryContent = stripMarkdownFence(memoryMatch[1]);
|
|
351
|
+
// čæ½å å° MEMORY.md
|
|
352
|
+
const existingMemory = fs.existsSync(memoryPath)
|
|
353
|
+
? fs.readFileSync(memoryPath, 'utf-8')
|
|
354
|
+
: '';
|
|
355
|
+
const newMemory = existingMemory
|
|
356
|
+
? `${existingMemory}\n\n${memoryContent}`
|
|
357
|
+
: memoryContent;
|
|
358
|
+
fs.writeFileSync(memoryPath, newMemory, 'utf-8');
|
|
359
|
+
memoryUpdated = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// ę ę³č§£ęč¾åŗļ¼åéå°ē®åå缩
|
|
364
|
+
compressedContent = compressFocusContent(oldContent);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// åęŗč½ä½čæå空ļ¼åéå°ē®åå缩
|
|
369
|
+
compressedContent = compressFocusContent(oldContent);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
// åęŗč½ä½å¤±č“„ļ¼åéå°ē®åå缩
|
|
374
|
+
api.logger?.error(`[PD:Focus] AI compression failed, falling back to simple compression: ${String(error)}`);
|
|
375
|
+
compressedContent = compressFocusContent(oldContent);
|
|
376
|
+
}
|
|
377
|
+
// ę“ę°ēę¬å·åę„ę
|
|
378
|
+
const versionParts = oldVersion.split('.');
|
|
379
|
+
const majorVersion = parseInt(versionParts[0], 10) || 1;
|
|
380
|
+
const newVersion = `${majorVersion + 1}`;
|
|
381
|
+
const today = new Date().toISOString().split('T')[0];
|
|
382
|
+
const newContent = compressedContent
|
|
383
|
+
.replace(/\*\*ēę¬\*\*:\s*v[\d.]+/i, `**ēę¬**: v${newVersion}`)
|
|
384
|
+
.replace(/\*\*ę“ę°\*\*:\s*\d{4}-\d{2}-\d{2}/, `**ę“ę°**: ${today}`);
|
|
385
|
+
const newLines = newContent.split('\n').length;
|
|
386
|
+
const savedLines = oldLines - newLines;
|
|
387
|
+
fs.writeFileSync(focusPath, newContent, 'utf-8');
|
|
388
|
+
const methodNote = usedAI
|
|
389
|
+
? isZh
|
|
390
|
+
? 'š¤ ä½æēØ AI ęŗč½å缩'
|
|
391
|
+
: 'š¤ AI-powered compression'
|
|
392
|
+
: isZh
|
|
393
|
+
? 'š 使ēØč§åå缩'
|
|
394
|
+
: 'š Rule-based compression';
|
|
395
|
+
const memoryNote = memoryUpdated
|
|
396
|
+
? isZh
|
|
397
|
+
? 'š å·²å°éēØē¢åå
„ MEMORY.md'
|
|
398
|
+
: 'š Milestones saved to MEMORY.md'
|
|
399
|
+
: '';
|
|
400
|
+
if (isZh) {
|
|
401
|
+
return `ā
**å缩å®ę**
|
|
402
|
+
|
|
403
|
+
| ęä½ | 详ę
|
|
|
404
|
+
|------|------|
|
|
405
|
+
| ę§ēę¬ | v${oldVersion} (${oldDate}) |
|
|
406
|
+
| ę°ēę¬ | v${newVersion} (${today}) |
|
|
407
|
+
| å缩å | ${oldLines} č” |
|
|
408
|
+
| å缩å | ${newLines} č” |
|
|
409
|
+
| čē | ${savedLines} č” |
|
|
410
|
+
| å¤ä»½ęä»¶ | ${backupPath ? path.basename(backupPath) : 'å·²ååØ'} |
|
|
411
|
+
|
|
412
|
+
${methodNote}${memoryNote ? `\n${memoryNote}` : ''}
|
|
413
|
+
|
|
414
|
+
š” å·²å缩ēę¬å·²å¤ä»½å°åå²ē®å½
|
|
415
|
+
š” č¾å
„ \`/pd-focus history\` ę„ēęęåå²ēę¬`;
|
|
416
|
+
}
|
|
417
|
+
return `ā
**Compression Complete**
|
|
418
|
+
|
|
419
|
+
| Action | Details |
|
|
420
|
+
|--------|---------|
|
|
421
|
+
| Old Version | v${oldVersion} (${oldDate}) |
|
|
422
|
+
| New Version | v${newVersion} (${today}) |
|
|
423
|
+
| Before | ${oldLines} lines |
|
|
424
|
+
| After | ${newLines} lines |
|
|
425
|
+
| Saved | ${savedLines} lines |
|
|
426
|
+
| Backup File | ${backupPath ? path.basename(backupPath) : 'exists'} |
|
|
427
|
+
|
|
428
|
+
${methodNote}${memoryNote ? `\n${memoryNote}` : ''}
|
|
429
|
+
|
|
430
|
+
š” Compressed version backed up to history
|
|
431
|
+
š” Type \`/pd-focus history\` to view all versions`;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* åę»å°åå²ēę¬
|
|
435
|
+
*/
|
|
436
|
+
function rollbackFocus(workspaceDir, index, isZh) {
|
|
437
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
438
|
+
const focusPath = wctx.resolve('CURRENT_FOCUS');
|
|
439
|
+
const historyDir = getHistoryDir(focusPath);
|
|
440
|
+
if (!fs.existsSync(historyDir)) {
|
|
441
|
+
return isZh ? 'ā ęę åå²ēę¬åÆåę»' : 'ā No history versions to rollback';
|
|
442
|
+
}
|
|
443
|
+
const files = fs.readdirSync(historyDir)
|
|
444
|
+
.filter(f => f.startsWith('CURRENT_FOCUS.v') && f.endsWith('.md'))
|
|
445
|
+
.map(f => ({
|
|
446
|
+
name: f,
|
|
447
|
+
path: path.join(historyDir, f),
|
|
448
|
+
mtime: fs.statSync(path.join(historyDir, f)).mtime.getTime(),
|
|
449
|
+
}))
|
|
450
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
451
|
+
if (index < 1 || index > files.length) {
|
|
452
|
+
return isZh
|
|
453
|
+
? `ā ę ęēåŗå·: ${index}\n\nš” 请č¾å
„ 1-${files.length} ä¹é“ēę°å`
|
|
454
|
+
: `ā Invalid index: ${index}\n\nš” Please enter a number between 1-${files.length}`;
|
|
455
|
+
}
|
|
456
|
+
const targetFile = files[index - 1];
|
|
457
|
+
const historyContent = fs.readFileSync(targetFile.path, 'utf-8');
|
|
458
|
+
// å¤ä»½å½åēę¬
|
|
459
|
+
const currentContent = fs.existsSync(focusPath)
|
|
460
|
+
? fs.readFileSync(focusPath, 'utf-8')
|
|
461
|
+
: '';
|
|
462
|
+
if (currentContent) {
|
|
463
|
+
backupToHistory(focusPath, currentContent);
|
|
464
|
+
}
|
|
465
|
+
// ę¢å¤åå²ēę¬
|
|
466
|
+
const restoredVersion = extractVersion(historyContent);
|
|
467
|
+
const restoredDate = extractDate(historyContent);
|
|
468
|
+
const today = new Date().toISOString().split('T')[0];
|
|
469
|
+
// č·åę大ēę¬å·ļ¼ä»å½åęä»¶ęåå²ęä»¶äøļ¼
|
|
470
|
+
let maxVersion = parseFloat(restoredVersion) || 1;
|
|
471
|
+
if (currentContent) {
|
|
472
|
+
const currentVersion = parseFloat(extractVersion(currentContent)) || 1;
|
|
473
|
+
if (currentVersion > maxVersion) {
|
|
474
|
+
maxVersion = currentVersion;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ę£åøøéå¢ēę¬å·
|
|
478
|
+
const newVersion = `${maxVersion + 1}`;
|
|
479
|
+
// ę·»å åę»ę č®°å°ē¶ęåꮵ
|
|
480
|
+
const restoredContent = historyContent
|
|
481
|
+
.replace(/\*\*ēę¬\*\*:\s*v[\d.]+/i, `**ēę¬**: v${newVersion}`)
|
|
482
|
+
.replace(/\*\*ę“ę°\*\*:\s*\d{4}-\d{2}-\d{2}/, `**ę“ę°**: ${today}`)
|
|
483
|
+
.replace(/\*\*ē¶ę\*\*:\s*[A-Z]+/i, `**ē¶ę**: ROLLBACK (from v${restoredVersion})`);
|
|
484
|
+
fs.writeFileSync(focusPath, restoredContent, 'utf-8');
|
|
485
|
+
if (isZh) {
|
|
486
|
+
return `ā
**åę»ęå**
|
|
487
|
+
|
|
488
|
+
| ęä½ | 详ę
|
|
|
489
|
+
|------|------|
|
|
490
|
+
| ę¢å¤čŖ | ${targetFile.name} |
|
|
491
|
+
| åēę¬ | v${restoredVersion} (${restoredDate}) |
|
|
492
|
+
| ę°ēę¬ | v${newVersion} (${today}) |
|
|
493
|
+
|
|
494
|
+
š” å½åēę¬å·²å¤ä»½ļ¼åÆåꬔåę»`;
|
|
495
|
+
}
|
|
496
|
+
return `ā
**Rollback Complete**
|
|
497
|
+
|
|
498
|
+
| Action | Details |
|
|
499
|
+
|--------|---------|
|
|
500
|
+
| Restored From | ${targetFile.name} |
|
|
501
|
+
| Original Version | v${restoredVersion} (${restoredDate}) |
|
|
502
|
+
| New Version | v${newVersion} (${today}) |
|
|
503
|
+
|
|
504
|
+
š” Current version backed up, you can rollback again`;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* ę¾ē¤ŗåø®å©
|
|
508
|
+
*/
|
|
509
|
+
function showHelp(isZh) {
|
|
510
|
+
if (isZh) {
|
|
511
|
+
return `š **/pd-focus å½ä»¤åø®å©**
|
|
512
|
+
|
|
513
|
+
\`/pd-focus status\` - ę„ē CURRENT_FOCUS.md ē¶ę
|
|
514
|
+
\`/pd-focus history\` - ę„ēåå²ēę¬å蔨
|
|
515
|
+
\`/pd-focus compress\` - ęåØå缩并å¤ä»½
|
|
516
|
+
\`/pd-focus rollback <åŗå·>\` - åę»å°ęå®åå²ēę¬
|
|
517
|
+
|
|
518
|
+
**åč½čÆ“ęļ¼**
|
|
519
|
+
- åå²ēę¬åØå缩ę¶čŖåØå建
|
|
520
|
+
- ęå¤äæē 10 äøŖåå²ēę¬
|
|
521
|
+
- Full 樔å¼ä¼čÆ»åå½åēę¬ + ęčæ 3 äøŖåå²ēę¬`;
|
|
522
|
+
}
|
|
523
|
+
return `š **/pd-focus Command Help**
|
|
524
|
+
|
|
525
|
+
\`/pd-focus status\` - Show CURRENT_FOCUS.md status
|
|
526
|
+
\`/pd-focus history\` - View history versions list
|
|
527
|
+
\`/pd-focus compress\` - Manually compress and backup
|
|
528
|
+
\`/pd-focus rollback <number>\` - Rollback to specified version
|
|
529
|
+
|
|
530
|
+
**Features:**
|
|
531
|
+
- History versions created during compression
|
|
532
|
+
- Maximum 10 history versions retained
|
|
533
|
+
- Full mode reads current + last 3 history versions`;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* å¤ē /pd-focus å½ä»¤
|
|
537
|
+
*/
|
|
538
|
+
export async function handleFocusCommand(ctx, api) {
|
|
539
|
+
const workspaceDir = getWorkspaceDir(ctx);
|
|
540
|
+
const args = ctx.args || [];
|
|
541
|
+
const subCommand = args[0]?.toLowerCase() || 'status';
|
|
542
|
+
// ę£ęµčÆčØļ¼äø context.ts äæęäøč“ļ¼
|
|
543
|
+
const isZh = ctx.config?.language === 'zh';
|
|
544
|
+
let result;
|
|
545
|
+
switch (subCommand) {
|
|
546
|
+
case 'status':
|
|
547
|
+
result = showStatus(workspaceDir, isZh);
|
|
548
|
+
break;
|
|
549
|
+
case 'history':
|
|
550
|
+
case 'hist':
|
|
551
|
+
result = showHistory(workspaceDir, isZh);
|
|
552
|
+
break;
|
|
553
|
+
case 'compress':
|
|
554
|
+
case 'cp':
|
|
555
|
+
result = await compressFocus(workspaceDir, isZh, api);
|
|
556
|
+
break;
|
|
557
|
+
case 'rollback':
|
|
558
|
+
case 'rb':
|
|
559
|
+
const index = parseInt(args[1], 10);
|
|
560
|
+
if (isNaN(index)) {
|
|
561
|
+
result = isZh
|
|
562
|
+
? 'ā 请ęå®č¦åę»ēēę¬åŗå·\n\nš” č¾å
„ `/pd-focus history` ę„ēåÆēØēę¬'
|
|
563
|
+
: 'ā Please specify version number to rollback\n\nš” Type `/pd-focus history` to see available versions';
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
result = rollbackFocus(workspaceDir, index, isZh);
|
|
567
|
+
}
|
|
568
|
+
break;
|
|
569
|
+
case 'help':
|
|
570
|
+
case '--help':
|
|
571
|
+
case '-h':
|
|
572
|
+
result = showHelp(isZh);
|
|
573
|
+
break;
|
|
574
|
+
default:
|
|
575
|
+
result = isZh
|
|
576
|
+
? `ā ęŖē„å½ä»¤: ${subCommand}\n\n${showHelp(isZh)}`
|
|
577
|
+
: `ā Unknown command: ${subCommand}\n\n${showHelp(isZh)}`;
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
text: result,
|
|
581
|
+
};
|
|
582
|
+
}
|