principles-disciple 1.7.4 → 1.7.5
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/focus.js +30 -155
- package/dist/constants/diagnostician.d.ts +16 -0
- package/dist/constants/diagnostician.js +60 -0
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +1 -1
- package/dist/core/config.d.ts +12 -0
- package/dist/core/config.js +7 -0
- package/dist/core/evolution-engine.js +1 -1
- package/dist/core/focus-history.d.ts +92 -0
- package/dist/core/focus-history.js +490 -0
- package/dist/core/init.js +2 -2
- package/dist/core/profile.js +1 -1
- package/dist/hooks/gate.js +3 -3
- package/dist/hooks/prompt.js +73 -22
- package/dist/hooks/subagent.js +1 -2
- package/dist/http/principles-console-route.d.ts +7 -0
- package/dist/http/principles-console-route.js +243 -1
- package/dist/index.js +0 -2
- package/dist/service/central-database.d.ts +104 -0
- package/dist/service/central-database.js +648 -0
- package/dist/service/evolution-worker.js +3 -3
- package/dist/tools/deep-reflect.js +1 -2
- package/dist/utils/subagent-probe.d.ts +11 -0
- package/dist/utils/subagent-probe.js +46 -1
- package/package.json +2 -1
- package/templates/langs/en/core/AGENTS.md +1 -1
- package/templates/langs/en/core/TOOLS.md +1 -1
- package/templates/langs/zh/core/AGENTS.md +1 -1
- package/templates/langs/zh/core/TOOLS.md +1 -1
- package/{agents/auditor.md → templates/langs/zh/skills/pd-auditor/SKILL.md} +3 -3
- package/{agents/diagnostician.md → templates/langs/zh/skills/pd-diagnostician/SKILL.md} +39 -29
- package/{agents/explorer.md → templates/langs/zh/skills/pd-explorer/SKILL.md} +3 -3
- package/{agents/implementer.md → templates/langs/zh/skills/pd-implementer/SKILL.md} +3 -3
- package/{agents/planner.md → templates/langs/zh/skills/pd-planner/SKILL.md} +3 -3
- package/{agents/reporter.md → templates/langs/zh/skills/pd-reporter/SKILL.md} +3 -3
- package/{agents/reviewer.md → templates/langs/zh/skills/pd-reviewer/SKILL.md} +3 -3
- package/dist/core/agent-loader.d.ts +0 -44
- package/dist/core/agent-loader.js +0 -147
- package/dist/tools/agent-spawn.d.ts +0 -54
- package/dist/tools/agent-spawn.js +0 -456
|
@@ -693,3 +693,493 @@ export function workingMemoryToInjection(snapshot) {
|
|
|
693
693
|
lines.push('</working_memory>');
|
|
694
694
|
return lines.join('\n');
|
|
695
695
|
}
|
|
696
|
+
// ============================================================================
|
|
697
|
+
// 自动压缩与维护
|
|
698
|
+
// ============================================================================
|
|
699
|
+
/** 默认压缩配置 */
|
|
700
|
+
const DEFAULT_COMPRESSION_CONFIG = {
|
|
701
|
+
lineThreshold: 100,
|
|
702
|
+
sizeThreshold: 15 * 1024, // 15KB
|
|
703
|
+
intervalMs: 24 * 60 * 60 * 1000, // 24 hours
|
|
704
|
+
keepCompletedTasks: 3,
|
|
705
|
+
maxWorkingMemoryArtifacts: 10,
|
|
706
|
+
};
|
|
707
|
+
/** 压缩时间记录文件名 */
|
|
708
|
+
const LAST_COMPRESS_FILE = '.last_compress';
|
|
709
|
+
/**
|
|
710
|
+
* 从 pain_settings.json 加载压缩配置
|
|
711
|
+
*
|
|
712
|
+
* @param stateDir state 目录路径
|
|
713
|
+
* @returns 压缩配置
|
|
714
|
+
*/
|
|
715
|
+
function loadCompressionConfig(stateDir) {
|
|
716
|
+
if (!stateDir) {
|
|
717
|
+
return DEFAULT_COMPRESSION_CONFIG;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const configPath = path.join(stateDir, 'pain_settings.json');
|
|
721
|
+
if (!fs.existsSync(configPath)) {
|
|
722
|
+
return DEFAULT_COMPRESSION_CONFIG;
|
|
723
|
+
}
|
|
724
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
725
|
+
const compression = config?.compression || {};
|
|
726
|
+
return {
|
|
727
|
+
lineThreshold: compression.line_threshold ?? DEFAULT_COMPRESSION_CONFIG.lineThreshold,
|
|
728
|
+
sizeThreshold: (compression.size_threshold_kb ?? 15) * 1024,
|
|
729
|
+
intervalMs: (compression.interval_hours ?? 24) * 60 * 60 * 1000,
|
|
730
|
+
keepCompletedTasks: compression.keep_completed_tasks ?? DEFAULT_COMPRESSION_CONFIG.keepCompletedTasks,
|
|
731
|
+
maxWorkingMemoryArtifacts: compression.max_working_memory_artifacts ?? DEFAULT_COMPRESSION_CONFIG.maxWorkingMemoryArtifacts,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return DEFAULT_COMPRESSION_CONFIG;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* 检查是否可以进行自动压缩(频率限制)
|
|
740
|
+
*
|
|
741
|
+
* @param stateDir state 目录路径
|
|
742
|
+
* @returns 是否可以进行压缩
|
|
743
|
+
*/
|
|
744
|
+
function canAutoCompress(stateDir) {
|
|
745
|
+
const lastCompressPath = path.join(stateDir, LAST_COMPRESS_FILE);
|
|
746
|
+
if (!fs.existsSync(lastCompressPath)) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
const config = loadCompressionConfig(stateDir);
|
|
751
|
+
const lastCompressTime = parseInt(fs.readFileSync(lastCompressPath, 'utf-8'), 10);
|
|
752
|
+
const now = Date.now();
|
|
753
|
+
return (now - lastCompressTime) >= config.intervalMs;
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* 记录压缩时间
|
|
761
|
+
*
|
|
762
|
+
* @param stateDir state 目录路径
|
|
763
|
+
*/
|
|
764
|
+
function recordCompressTime(stateDir) {
|
|
765
|
+
try {
|
|
766
|
+
if (!fs.existsSync(stateDir)) {
|
|
767
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
768
|
+
}
|
|
769
|
+
fs.writeFileSync(path.join(stateDir, LAST_COMPRESS_FILE), Date.now().toString(), 'utf-8');
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
logError('Failed to record compress time', error);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* 提取已完成任务作为里程碑
|
|
777
|
+
*/
|
|
778
|
+
export function extractMilestones(content) {
|
|
779
|
+
const completedTasks = [];
|
|
780
|
+
const fileArtifacts = [];
|
|
781
|
+
const lines = content.split('\n');
|
|
782
|
+
let inTaskSection = false;
|
|
783
|
+
let inWorkingMemory = false;
|
|
784
|
+
for (const line of lines) {
|
|
785
|
+
const trimmed = line.trim();
|
|
786
|
+
// 识别章节
|
|
787
|
+
if (/^#{1,3}\s*.*当前任务|🔄/.test(trimmed)) {
|
|
788
|
+
inTaskSection = true;
|
|
789
|
+
inWorkingMemory = false;
|
|
790
|
+
}
|
|
791
|
+
else if (/^#{1,3}\s*.*下一步|➡️/.test(trimmed)) {
|
|
792
|
+
inTaskSection = false;
|
|
793
|
+
inWorkingMemory = false;
|
|
794
|
+
}
|
|
795
|
+
else if (/^##\s*🧠\s*Working Memory/.test(trimmed)) {
|
|
796
|
+
inWorkingMemory = true;
|
|
797
|
+
inTaskSection = false;
|
|
798
|
+
}
|
|
799
|
+
// 提取已完成任务
|
|
800
|
+
if (inTaskSection && /^-\s*\[x\]/i.test(trimmed)) {
|
|
801
|
+
completedTasks.push(trimmed.replace(/^-\s*\[x\]\s*/i, ''));
|
|
802
|
+
}
|
|
803
|
+
// 提取文件引用(从工作记忆)
|
|
804
|
+
if (inWorkingMemory) {
|
|
805
|
+
const fileMatch = trimmed.match(/^\|\s*`?([^`|\n]+)`?\s*\|/);
|
|
806
|
+
if (fileMatch && !fileMatch[1].includes('文件路径')) {
|
|
807
|
+
fileArtifacts.push(fileMatch[1].trim());
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
completedTasks: completedTasks.slice(-10), // 最多 10 个
|
|
813
|
+
fileArtifacts: fileArtifacts.slice(-10)
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* 归档里程碑到 daily memory 文件
|
|
818
|
+
*/
|
|
819
|
+
export function archiveMilestonesToDaily(workspaceDir, milestones, version) {
|
|
820
|
+
if (milestones.completedTasks.length === 0 && milestones.fileArtifacts.length === 0) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
const dateStr = new Date().toISOString().split('T')[0];
|
|
824
|
+
const memoryDir = path.join(workspaceDir, 'memory');
|
|
825
|
+
const dailyLogPath = path.join(memoryDir, `${dateStr}.md`);
|
|
826
|
+
const timestamp = new Date().toISOString();
|
|
827
|
+
// 确保目录存在
|
|
828
|
+
if (!fs.existsSync(memoryDir)) {
|
|
829
|
+
fs.mkdirSync(memoryDir, { recursive: true });
|
|
830
|
+
}
|
|
831
|
+
// 构建里程碑内容
|
|
832
|
+
const lines = [];
|
|
833
|
+
lines.push(`\n## 🏆 里程碑 [CURRENT_FOCUS v${version} 压缩]`);
|
|
834
|
+
lines.push(`> 时间: ${timestamp}`);
|
|
835
|
+
lines.push('');
|
|
836
|
+
if (milestones.completedTasks.length > 0) {
|
|
837
|
+
lines.push('### 已完成任务');
|
|
838
|
+
for (const task of milestones.completedTasks) {
|
|
839
|
+
lines.push(`- [x] ${task}`);
|
|
840
|
+
}
|
|
841
|
+
lines.push('');
|
|
842
|
+
}
|
|
843
|
+
if (milestones.fileArtifacts.length > 0) {
|
|
844
|
+
lines.push('### 相关文件');
|
|
845
|
+
for (const file of milestones.fileArtifacts) {
|
|
846
|
+
lines.push(`- \`${file}\``);
|
|
847
|
+
}
|
|
848
|
+
lines.push('');
|
|
849
|
+
}
|
|
850
|
+
lines.push('---');
|
|
851
|
+
lines.push('');
|
|
852
|
+
// 追加到 daily log
|
|
853
|
+
try {
|
|
854
|
+
fs.appendFileSync(dailyLogPath, lines.join('\n'), 'utf-8');
|
|
855
|
+
return dailyLogPath;
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
logError(`Failed to archive milestones to ${dailyLogPath}`, error);
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* 清理过期信息和验证文件引用
|
|
864
|
+
*/
|
|
865
|
+
export function cleanupStaleInfo(content, workspaceDir, config) {
|
|
866
|
+
const effectiveConfig = config || DEFAULT_COMPRESSION_CONFIG;
|
|
867
|
+
const lines = content.split('\n');
|
|
868
|
+
const result = [];
|
|
869
|
+
let inWorkingMemory = false;
|
|
870
|
+
let inFileTable = false;
|
|
871
|
+
let completedCount = 0;
|
|
872
|
+
let artifactCount = 0;
|
|
873
|
+
for (let i = 0; i < lines.length; i++) {
|
|
874
|
+
const line = lines[i];
|
|
875
|
+
const trimmed = line.trim();
|
|
876
|
+
// 检测 Working Memory 章节
|
|
877
|
+
if (/^##\s*🧠\s*Working Memory/.test(trimmed)) {
|
|
878
|
+
inWorkingMemory = true;
|
|
879
|
+
inFileTable = false;
|
|
880
|
+
}
|
|
881
|
+
else if (/^##\s/.test(trimmed) && !trimmed.includes('Working Memory')) {
|
|
882
|
+
inWorkingMemory = false;
|
|
883
|
+
inFileTable = false;
|
|
884
|
+
}
|
|
885
|
+
// 检测文件表格
|
|
886
|
+
if (inWorkingMemory && /^\|\s*文件路径/.test(trimmed)) {
|
|
887
|
+
inFileTable = true;
|
|
888
|
+
result.push(line);
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (inFileTable && /^\|[^|]+\|[^|]+\|[^|]+\|/.test(trimmed)) {
|
|
892
|
+
// 检查是否是表格分隔行
|
|
893
|
+
if (/^\|[-\s|:]+\|$/.test(trimmed)) {
|
|
894
|
+
result.push(line);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
// 提取文件路径
|
|
898
|
+
const match = trimmed.match(/^\|\s*`?([^`|\n]+)`?\s*\|/);
|
|
899
|
+
if (match) {
|
|
900
|
+
const filePath = match[1].trim();
|
|
901
|
+
artifactCount++;
|
|
902
|
+
// 限制条数
|
|
903
|
+
if (artifactCount > effectiveConfig.maxWorkingMemoryArtifacts) {
|
|
904
|
+
continue; // 跳过超出限制的条目
|
|
905
|
+
}
|
|
906
|
+
// 可选:验证文件是否存在
|
|
907
|
+
if (workspaceDir) {
|
|
908
|
+
const fullPath = path.join(workspaceDir, filePath);
|
|
909
|
+
if (!fs.existsSync(fullPath)) {
|
|
910
|
+
continue; // 文件不存在,跳过
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
result.push(line);
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// 处理已完成任务
|
|
918
|
+
if (/^-\s*\[x\]/i.test(trimmed)) {
|
|
919
|
+
completedCount++;
|
|
920
|
+
if (completedCount > effectiveConfig.keepCompletedTasks) {
|
|
921
|
+
continue; // 跳过超出限制的已完成任务
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
result.push(line);
|
|
925
|
+
}
|
|
926
|
+
return result.join('\n');
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* 自动压缩 CURRENT_FOCUS.md
|
|
930
|
+
*
|
|
931
|
+
* @param focusPath CURRENT_FOCUS.md 的完整路径
|
|
932
|
+
* @param workspaceDir 工作区目录(可选,用于验证文件引用)
|
|
933
|
+
* @param stateDir state 目录路径(可选,用于频率限制)
|
|
934
|
+
* @returns 压缩结果信息,如果不需要压缩则返回 null
|
|
935
|
+
*/
|
|
936
|
+
export function autoCompressFocus(focusPath, workspaceDir, stateDir) {
|
|
937
|
+
// 检查文件是否存在
|
|
938
|
+
if (!fs.existsSync(focusPath)) {
|
|
939
|
+
return {
|
|
940
|
+
compressed: false,
|
|
941
|
+
oldLines: 0,
|
|
942
|
+
newLines: 0,
|
|
943
|
+
milestonesArchived: false,
|
|
944
|
+
backupPath: null,
|
|
945
|
+
reason: 'File not found'
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
// 加载配置
|
|
949
|
+
const config = loadCompressionConfig(stateDir);
|
|
950
|
+
const oldContent = fs.readFileSync(focusPath, 'utf-8');
|
|
951
|
+
const oldLines = oldContent.split('\n').length;
|
|
952
|
+
const oldSize = Buffer.byteLength(oldContent, 'utf-8');
|
|
953
|
+
// 检查是否需要压缩(行数或大小阈值)
|
|
954
|
+
const needsCompression = oldLines > config.lineThreshold ||
|
|
955
|
+
oldSize > config.sizeThreshold;
|
|
956
|
+
if (!needsCompression) {
|
|
957
|
+
return {
|
|
958
|
+
compressed: false,
|
|
959
|
+
oldLines,
|
|
960
|
+
newLines: oldLines,
|
|
961
|
+
milestonesArchived: false,
|
|
962
|
+
backupPath: null,
|
|
963
|
+
reason: 'Below threshold'
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
// 检查频率限制
|
|
967
|
+
if (stateDir && !canAutoCompress(stateDir)) {
|
|
968
|
+
return {
|
|
969
|
+
compressed: false,
|
|
970
|
+
oldLines,
|
|
971
|
+
newLines: oldLines,
|
|
972
|
+
milestonesArchived: false,
|
|
973
|
+
backupPath: null,
|
|
974
|
+
reason: 'Rate limited (24h interval)'
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
// 1. 提取里程碑
|
|
978
|
+
const version = extractVersion(oldContent);
|
|
979
|
+
const milestones = extractMilestones(oldContent);
|
|
980
|
+
// 2. 归档里程碑到 daily memory
|
|
981
|
+
let milestonesArchived = false;
|
|
982
|
+
if (workspaceDir) {
|
|
983
|
+
const archivePath = archiveMilestonesToDaily(workspaceDir, milestones, version);
|
|
984
|
+
milestonesArchived = archivePath !== null;
|
|
985
|
+
}
|
|
986
|
+
// 3. 清理过期信息(传入配置)
|
|
987
|
+
let newContent = cleanupStaleInfo(oldContent, workspaceDir, config);
|
|
988
|
+
// 4. 压缩内容(使用 extractSummary)
|
|
989
|
+
newContent = extractSummary(newContent, 50);
|
|
990
|
+
// 5. 递增版本号和日期
|
|
991
|
+
const newVersion = `${parseInt(version, 10) + 1}`;
|
|
992
|
+
const today = new Date().toISOString().split('T')[0];
|
|
993
|
+
newContent = newContent
|
|
994
|
+
.replace(/\*\*版本\*\*:\s*v[\d.]+/i, `**版本**: v${newVersion}`)
|
|
995
|
+
.replace(/\*\*更新\*\*:\s*\d{4}-\d{2}-\d{2}/, `**更新**: ${today}`);
|
|
996
|
+
// 6. 备份原版本
|
|
997
|
+
const backupPath = backupToHistory(focusPath, oldContent);
|
|
998
|
+
// 7. 清理过期历史
|
|
999
|
+
cleanupHistory(focusPath);
|
|
1000
|
+
// 8. 写入新内容
|
|
1001
|
+
fs.writeFileSync(focusPath, newContent, 'utf-8');
|
|
1002
|
+
// 9. 记录压缩时间
|
|
1003
|
+
if (stateDir) {
|
|
1004
|
+
recordCompressTime(stateDir);
|
|
1005
|
+
}
|
|
1006
|
+
const newLines = newContent.split('\n').length;
|
|
1007
|
+
return {
|
|
1008
|
+
compressed: true,
|
|
1009
|
+
oldLines,
|
|
1010
|
+
newLines,
|
|
1011
|
+
milestonesArchived,
|
|
1012
|
+
backupPath,
|
|
1013
|
+
reason: `Auto-compressed: ${oldLines} → ${newLines} lines`
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* 检查是否需要自动压缩
|
|
1018
|
+
*/
|
|
1019
|
+
export function needsAutoCompression(focusPath, stateDir) {
|
|
1020
|
+
if (!fs.existsSync(focusPath)) {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
const config = stateDir ? loadCompressionConfig(stateDir) : DEFAULT_COMPRESSION_CONFIG;
|
|
1025
|
+
const content = fs.readFileSync(focusPath, 'utf-8');
|
|
1026
|
+
const lines = content.split('\n').length;
|
|
1027
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
1028
|
+
return lines > config.lineThreshold || size > config.sizeThreshold;
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
// ============================================================================
|
|
1035
|
+
// 格式验证与模板恢复
|
|
1036
|
+
// ============================================================================
|
|
1037
|
+
/** CURRENT_FOCUS 模板路径(相对于插件根目录) */
|
|
1038
|
+
const CURRENT_FOCUS_TEMPLATE_PATH = 'templates/workspace/okr/CURRENT_FOCUS.md';
|
|
1039
|
+
/**
|
|
1040
|
+
* 验证 CURRENT_FOCUS.md 格式
|
|
1041
|
+
*
|
|
1042
|
+
* 仅校验会导致程序崩溃的核心问题,不过度校验
|
|
1043
|
+
*
|
|
1044
|
+
* @param content 文件内容
|
|
1045
|
+
* @returns 验证结果
|
|
1046
|
+
*/
|
|
1047
|
+
export function validateCurrentFocus(content) {
|
|
1048
|
+
const errors = [];
|
|
1049
|
+
const warnings = [];
|
|
1050
|
+
// 仅检查会导致程序崩溃的核心问题
|
|
1051
|
+
// 1. 文件为空
|
|
1052
|
+
if (!content || !content.trim()) {
|
|
1053
|
+
errors.push('文件为空');
|
|
1054
|
+
return { valid: false, errors, warnings };
|
|
1055
|
+
}
|
|
1056
|
+
// 2. 检查是否是有效的文本(排除二进制乱码)
|
|
1057
|
+
// 如果有超过 50% 的非打印字符,认为是乱码
|
|
1058
|
+
const nonPrintable = content.split('').filter(c => {
|
|
1059
|
+
const code = c.charCodeAt(0);
|
|
1060
|
+
// 允许:换行、制表符、中文、英文、数字、标点
|
|
1061
|
+
return code < 32 && code !== 10 && code !== 13 && code !== 9;
|
|
1062
|
+
}).length;
|
|
1063
|
+
if (nonPrintable > content.length * 0.5) {
|
|
1064
|
+
errors.push('文件内容损坏(可能是二进制乱码)');
|
|
1065
|
+
return { valid: false, errors, warnings };
|
|
1066
|
+
}
|
|
1067
|
+
// 以下仅作为警告,不触发恢复
|
|
1068
|
+
// 提示缺少建议的章节(不影响程序运行)
|
|
1069
|
+
if (!content.includes('下一步') && !content.includes('Next')) {
|
|
1070
|
+
warnings.push('缺少下一步章节(建议保留)');
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
valid: true, // 只要不崩溃就认为是 valid
|
|
1074
|
+
errors,
|
|
1075
|
+
warnings
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* 从模板恢复 CURRENT_FOCUS.md
|
|
1080
|
+
*
|
|
1081
|
+
* @param focusPath CURRENT_FOCUS.md 路径
|
|
1082
|
+
* @param extensionRoot 插件根目录
|
|
1083
|
+
* @returns 恢复是否成功
|
|
1084
|
+
*/
|
|
1085
|
+
export function recoverFromTemplate(focusPath, extensionRoot) {
|
|
1086
|
+
try {
|
|
1087
|
+
// 查找模板文件
|
|
1088
|
+
const templatePath = path.join(extensionRoot, CURRENT_FOCUS_TEMPLATE_PATH);
|
|
1089
|
+
if (!fs.existsSync(templatePath)) {
|
|
1090
|
+
return {
|
|
1091
|
+
success: false,
|
|
1092
|
+
error: `Template not found: ${templatePath}`
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
// 读取模板
|
|
1096
|
+
let template = fs.readFileSync(templatePath, 'utf-8');
|
|
1097
|
+
// 替换日期占位符
|
|
1098
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1099
|
+
template = template.replace(/{YYYY-MM-DD}/g, today);
|
|
1100
|
+
// 备份损坏的文件(如果存在)
|
|
1101
|
+
if (fs.existsSync(focusPath)) {
|
|
1102
|
+
const backupPath = `${focusPath}.corrupted.${Date.now()}.md`;
|
|
1103
|
+
fs.copyFileSync(focusPath, backupPath);
|
|
1104
|
+
}
|
|
1105
|
+
// 确保目录存在
|
|
1106
|
+
const focusDir = path.dirname(focusPath);
|
|
1107
|
+
if (!fs.existsSync(focusDir)) {
|
|
1108
|
+
fs.mkdirSync(focusDir, { recursive: true });
|
|
1109
|
+
}
|
|
1110
|
+
// 写入恢复的内容
|
|
1111
|
+
fs.writeFileSync(focusPath, template, 'utf-8');
|
|
1112
|
+
return {
|
|
1113
|
+
success: true,
|
|
1114
|
+
templatePath
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
return {
|
|
1119
|
+
success: false,
|
|
1120
|
+
error: String(error)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* 安全读取 CURRENT_FOCUS.md(自动验证 + 恢复)
|
|
1126
|
+
*
|
|
1127
|
+
* 仅在文件为空或损坏时才恢复,其他情况正常使用
|
|
1128
|
+
*
|
|
1129
|
+
* @param focusPath CURRENT_FOCUS.md 路径
|
|
1130
|
+
* @param extensionRoot 插件根目录
|
|
1131
|
+
* @param logger 日志记录器
|
|
1132
|
+
* @returns 文件内容和恢复状态
|
|
1133
|
+
*/
|
|
1134
|
+
export function safeReadCurrentFocus(focusPath, extensionRoot, logger) {
|
|
1135
|
+
// 文件不存在,从模板创建
|
|
1136
|
+
if (!fs.existsSync(focusPath)) {
|
|
1137
|
+
const result = recoverFromTemplate(focusPath, extensionRoot);
|
|
1138
|
+
if (result.success) {
|
|
1139
|
+
logger?.info?.(`[PD:Focus] Created CURRENT_FOCUS.md from template`);
|
|
1140
|
+
return {
|
|
1141
|
+
content: fs.readFileSync(focusPath, 'utf-8'),
|
|
1142
|
+
recovered: true,
|
|
1143
|
+
validationErrors: []
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
content: '',
|
|
1148
|
+
recovered: false,
|
|
1149
|
+
validationErrors: [`Failed to create from template: ${result.error}`]
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
// 读取并验证
|
|
1153
|
+
const content = fs.readFileSync(focusPath, 'utf-8');
|
|
1154
|
+
const validation = validateCurrentFocus(content);
|
|
1155
|
+
// 记录警告(不触发恢复)
|
|
1156
|
+
if (validation.warnings.length > 0) {
|
|
1157
|
+
logger?.warn?.(`[PD:Focus] CURRENT_FOCUS.md warnings: ${validation.warnings.join(', ')}`);
|
|
1158
|
+
}
|
|
1159
|
+
// 仅在有严重错误时才恢复
|
|
1160
|
+
if (!validation.valid) {
|
|
1161
|
+
logger?.warn?.(`[PD:Focus] CURRENT_FOCUS.md corrupted: ${validation.errors.join(', ')}`);
|
|
1162
|
+
const result = recoverFromTemplate(focusPath, extensionRoot);
|
|
1163
|
+
if (result.success) {
|
|
1164
|
+
logger?.info?.(`[PD:Focus] Recovered CURRENT_FOCUS.md from template`);
|
|
1165
|
+
return {
|
|
1166
|
+
content: fs.readFileSync(focusPath, 'utf-8'),
|
|
1167
|
+
recovered: true,
|
|
1168
|
+
validationErrors: validation.errors
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
// 恢复失败,返回原始内容(让系统继续运行)
|
|
1172
|
+
logger?.warn?.(`[PD:Focus] Failed to recover: ${result.error}`);
|
|
1173
|
+
return {
|
|
1174
|
+
content,
|
|
1175
|
+
recovered: false,
|
|
1176
|
+
validationErrors: validation.errors
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
// 正常情况:文件有效
|
|
1180
|
+
return {
|
|
1181
|
+
content,
|
|
1182
|
+
recovered: false,
|
|
1183
|
+
validationErrors: []
|
|
1184
|
+
};
|
|
1185
|
+
}
|
package/dist/core/init.js
CHANGED
|
@@ -25,7 +25,7 @@ function hasOutdatedCoreGuidance(file, content) {
|
|
|
25
25
|
return true;
|
|
26
26
|
if (!content.includes('subagents'))
|
|
27
27
|
return true;
|
|
28
|
-
if (
|
|
28
|
+
if (!content.includes('sessions_spawn'))
|
|
29
29
|
return true;
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
@@ -66,7 +66,7 @@ export function ensureWorkspaceTemplates(api, workspaceDir, language = 'en') {
|
|
|
66
66
|
else if (CORE_GUIDANCE_FILES.has(file)) {
|
|
67
67
|
const existingContent = fs.readFileSync(destPath, 'utf8');
|
|
68
68
|
if (hasOutdatedCoreGuidance(file, existingContent)) {
|
|
69
|
-
api.logger.warn(`[PD] Outdated core guidance detected in ${file}. Review the latest template guidance for peer sessions, subagents, and
|
|
69
|
+
api.logger.warn(`[PD] Outdated core guidance detected in ${file}. Review the latest template guidance for peer sessions, subagents, and sessions_spawn routing.`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
package/dist/core/profile.js
CHANGED
|
@@ -51,7 +51,7 @@ export const PROFILE_DEFAULTS = {
|
|
|
51
51
|
thinking_checkpoint: {
|
|
52
52
|
enabled: false, // Default OFF to avoid blocking new users
|
|
53
53
|
window_ms: 5 * 60 * 1000, // 5 minute window
|
|
54
|
-
high_risk_tools: ['run_shell_command', 'delete_file', 'move_file'
|
|
54
|
+
high_risk_tools: ['run_shell_command', 'delete_file', 'move_file'],
|
|
55
55
|
},
|
|
56
56
|
custom_guards: [],
|
|
57
57
|
};
|
package/dist/hooks/gate.js
CHANGED
|
@@ -14,8 +14,8 @@ const TRAJECTORY_GATE_BLOCK_MAX_RETRIES = 3;
|
|
|
14
14
|
// ═══ GFI Gate Tool Tiers ═══
|
|
15
15
|
// TIER 0: 只读工具 - 永不拦截
|
|
16
16
|
// TIER 1: 低风险修改 - GFI >= low_risk_block 时拦截
|
|
17
|
-
// 注意:
|
|
18
|
-
//
|
|
17
|
+
// 注意:sessions_spawn、task 是 Agent 派生工具,常规阈值不拦截
|
|
18
|
+
// 但极高 GFI (>=90) 时仍会拦截,防止极端情况下失控
|
|
19
19
|
// TIER 2: 高风险操作 - GFI >= high_risk_block 时拦截
|
|
20
20
|
// TIER 3: Bash 命令 - 根据内容判断
|
|
21
21
|
/**
|
|
@@ -134,7 +134,7 @@ export function handleBeforeToolCall(event, ctx) {
|
|
|
134
134
|
thinking_checkpoint: {
|
|
135
135
|
enabled: false, // Default OFF
|
|
136
136
|
window_ms: 5 * 60 * 1000,
|
|
137
|
-
high_risk_tools: ['run_shell_command', 'delete_file', 'move_file'
|
|
137
|
+
high_risk_tools: ['run_shell_command', 'delete_file', 'move_file'],
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
if (fs.existsSync(profilePath)) {
|