principles-disciple 1.7.2 → 1.7.4

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.
Files changed (53) hide show
  1. package/agents/auditor.md +61 -0
  2. package/agents/diagnostician.md +277 -0
  3. package/agents/explorer.md +65 -0
  4. package/agents/implementer.md +68 -0
  5. package/agents/planner.md +65 -0
  6. package/agents/reporter.md +78 -0
  7. package/agents/reviewer.md +66 -0
  8. package/dist/commands/evolution-status.js +4 -2
  9. package/dist/core/config.d.ts +11 -0
  10. package/dist/core/config.js +19 -1
  11. package/dist/core/evolution-logger.d.ts +137 -0
  12. package/dist/core/evolution-logger.js +256 -0
  13. package/dist/core/evolution-reducer.d.ts +23 -0
  14. package/dist/core/evolution-reducer.js +73 -29
  15. package/dist/core/evolution-types.d.ts +6 -0
  16. package/dist/core/focus-history.d.ts +53 -0
  17. package/dist/core/focus-history.js +429 -0
  18. package/dist/core/init.js +24 -0
  19. package/dist/core/risk-calculator.d.ts +15 -0
  20. package/dist/core/risk-calculator.js +48 -0
  21. package/dist/core/trajectory.d.ts +73 -0
  22. package/dist/core/trajectory.js +206 -0
  23. package/dist/hooks/gate.js +127 -17
  24. package/dist/hooks/lifecycle.js +104 -0
  25. package/dist/hooks/pain.js +31 -0
  26. package/dist/hooks/prompt.js +66 -19
  27. package/dist/hooks/subagent.d.ts +1 -0
  28. package/dist/hooks/subagent.js +201 -18
  29. package/dist/http/principles-console-route.js +58 -0
  30. package/dist/service/control-ui-query-service.d.ts +2 -0
  31. package/dist/service/control-ui-query-service.js +4 -0
  32. package/dist/service/empathy-observer-manager.d.ts +8 -0
  33. package/dist/service/empathy-observer-manager.js +40 -0
  34. package/dist/service/evolution-query-service.d.ts +155 -0
  35. package/dist/service/evolution-query-service.js +258 -0
  36. package/dist/service/evolution-worker.d.ts +4 -0
  37. package/dist/service/evolution-worker.js +185 -63
  38. package/dist/service/phase3-input-filter.d.ts +37 -0
  39. package/dist/service/phase3-input-filter.js +106 -0
  40. package/dist/service/runtime-summary-service.d.ts +15 -0
  41. package/dist/service/runtime-summary-service.js +111 -23
  42. package/dist/tools/agent-spawn.js +17 -6
  43. package/dist/tools/deep-reflect.js +9 -2
  44. package/dist/utils/subagent-probe.d.ts +23 -0
  45. package/dist/utils/subagent-probe.js +36 -0
  46. package/openclaw.plugin.json +12 -13
  47. package/package.json +5 -4
  48. package/templates/langs/en/core/AGENTS.md +15 -3
  49. package/templates/langs/en/core/BOOTSTRAP.md +24 -1
  50. package/templates/langs/en/core/TOOLS.md +9 -0
  51. package/templates/langs/zh/core/AGENTS.md +15 -3
  52. package/templates/langs/zh/core/BOOTSTRAP.md +24 -1
  53. package/templates/langs/zh/core/TOOLS.md +9 -0
@@ -5,7 +5,33 @@
5
5
  * - 压缩时备份当前版本到历史目录
6
6
  * - 清理过期历史版本
7
7
  * - 读取历史版本(用于 full 模式)
8
+ * - 工作记忆提取与合并(压缩后恢复上下文)
8
9
  */
10
+ /**
11
+ * 文件输出记录
12
+ */
13
+ export interface FileArtifact {
14
+ path: string;
15
+ action: 'created' | 'modified' | 'deleted';
16
+ description: string;
17
+ }
18
+ /**
19
+ * 工作记忆快照
20
+ */
21
+ export interface WorkingMemorySnapshot {
22
+ lastUpdated: string;
23
+ artifacts: FileArtifact[];
24
+ currentTask?: {
25
+ description: string;
26
+ status: 'in_progress' | 'blocked' | 'reviewing' | 'completed';
27
+ progress: number;
28
+ };
29
+ activeProblems: Array<{
30
+ problem: string;
31
+ approach?: string;
32
+ }>;
33
+ nextActions: string[];
34
+ }
9
35
  /**
10
36
  * 获取历史目录路径
11
37
  */
@@ -63,3 +89,30 @@ export declare function compressFocus(focusPath: string, newContent: string): {
63
89
  * @param maxLines 最大行数
64
90
  */
65
91
  export declare function extractSummary(content: string, maxLines?: number): string;
92
+ /**
93
+ * 从会话消息中提取工作记忆
94
+ *
95
+ * @param messages 会话消息数组(OpenClaw 格式)
96
+ * @param workspaceDir 工作区目录(用于生成相对路径)
97
+ * @returns 提取的工作记忆快照
98
+ */
99
+ export declare function extractWorkingMemory(messages: Array<{
100
+ role?: string;
101
+ content?: string | unknown[];
102
+ }>, workspaceDir?: string): WorkingMemorySnapshot;
103
+ /**
104
+ * 解析 CURRENT_FOCUS.md 中的 Working Memory 章节
105
+ */
106
+ export declare function parseWorkingMemorySection(content: string): WorkingMemorySnapshot | null;
107
+ /**
108
+ * 将工作记忆合并到 CURRENT_FOCUS.md 内容中
109
+ *
110
+ * @param content 原始内容
111
+ * @param snapshot 工作记忆快照
112
+ * @returns 合并后的内容
113
+ */
114
+ export declare function mergeWorkingMemory(content: string, snapshot: WorkingMemorySnapshot): string;
115
+ /**
116
+ * 生成工作记忆注入字符串(用于 prompt 注入)
117
+ */
118
+ export declare function workingMemoryToInjection(snapshot: WorkingMemorySnapshot | null): string;
@@ -5,6 +5,7 @@
5
5
  * - 压缩时备份当前版本到历史目录
6
6
  * - 清理过期历史版本
7
7
  * - 读取历史版本(用于 full 模式)
8
+ * - 工作记忆提取与合并(压缩后恢复上下文)
8
9
  */
9
10
  import * as fs from 'fs';
10
11
  import * as path from 'path';
@@ -264,3 +265,431 @@ export function extractSummary(content, maxLines = 30) {
264
265
  }
265
266
  return trimmed.join('\n');
266
267
  }
268
+ // ============================================================================
269
+ // 工作记忆管理
270
+ // ============================================================================
271
+ /** Working Memory 章节标记 */
272
+ const WORKING_MEMORY_SECTION = '## 🧠 Working Memory';
273
+ /** 最大保留的文件记录数 */
274
+ const MAX_ARTIFACTS = 20;
275
+ /** 最大保留的问题数 */
276
+ const MAX_PROBLEMS = 5;
277
+ /** 最大保留下一步行动数 */
278
+ const MAX_NEXT_ACTIONS = 5;
279
+ /**
280
+ * 从会话消息中提取工作记忆
281
+ *
282
+ * @param messages 会话消息数组(OpenClaw 格式)
283
+ * @param workspaceDir 工作区目录(用于生成相对路径)
284
+ * @returns 提取的工作记忆快照
285
+ */
286
+ export function extractWorkingMemory(messages, workspaceDir) {
287
+ const snapshot = {
288
+ lastUpdated: new Date().toISOString(),
289
+ artifacts: [],
290
+ activeProblems: [],
291
+ nextActions: []
292
+ };
293
+ // 只处理最近的 assistant 消息
294
+ const recentMessages = messages
295
+ .filter(m => m.role === 'assistant')
296
+ .slice(-10);
297
+ for (const msg of recentMessages) {
298
+ let text = '';
299
+ const toolUses = [];
300
+ if (typeof msg.content === 'string') {
301
+ text = msg.content;
302
+ }
303
+ else if (Array.isArray(msg.content)) {
304
+ const textParts = [];
305
+ for (const c of msg.content) {
306
+ if (!c || typeof c !== 'object')
307
+ continue;
308
+ const obj = c;
309
+ // 提取文本内容
310
+ if (obj.type === 'text' && typeof obj.text === 'string') {
311
+ textParts.push(obj.text);
312
+ }
313
+ // 提取工具调用(关键:文件操作在这里!)
314
+ if (obj.type === 'tool_use' && typeof obj.name === 'string' && typeof obj.input === 'object') {
315
+ toolUses.push({
316
+ name: obj.name,
317
+ input: obj.input
318
+ });
319
+ }
320
+ }
321
+ text = textParts.join('\n');
322
+ }
323
+ // 从工具调用中提取文件路径(这是最可靠的方式)
324
+ for (const toolUse of toolUses) {
325
+ if (['write_file', 'replace', 'create_file'].includes(toolUse.name)) {
326
+ const filePath = toolUse.input.file_path || toolUse.input.absolute_path || toolUse.input.path;
327
+ if (typeof filePath === 'string' && filePath.trim()) {
328
+ // 跳过不需要的文件
329
+ if (filePath.includes('node_modules') ||
330
+ filePath.endsWith('.d.ts') ||
331
+ filePath.includes('.config.')) {
332
+ continue;
333
+ }
334
+ // 生成相对路径
335
+ const displayPath = workspaceDir && filePath.startsWith(workspaceDir)
336
+ ? path.relative(workspaceDir, filePath)
337
+ : filePath;
338
+ // 判断操作类型
339
+ const action = toolUse.name === 'write_file' || toolUse.name === 'create_file' ? 'created' : 'modified';
340
+ // 尝试从文本中提取描述
341
+ const description = extractDescription(text, filePath);
342
+ snapshot.artifacts.push({
343
+ path: displayPath,
344
+ action,
345
+ description
346
+ });
347
+ }
348
+ }
349
+ }
350
+ if (!text)
351
+ continue;
352
+ // 从文本中提取文件操作(备用方式)
353
+ extractFileArtifacts(text, snapshot.artifacts, workspaceDir);
354
+ // 提取问题
355
+ extractProblems(text, snapshot.activeProblems);
356
+ // 提取下一步
357
+ extractNextActions(text, snapshot.nextActions);
358
+ }
359
+ // 去重和限制数量
360
+ snapshot.artifacts = deduplicateArtifacts(snapshot.artifacts).slice(-MAX_ARTIFACTS);
361
+ snapshot.activeProblems = snapshot.activeProblems.slice(-MAX_PROBLEMS);
362
+ snapshot.nextActions = snapshot.nextActions.slice(-MAX_NEXT_ACTIONS);
363
+ return snapshot;
364
+ }
365
+ /**
366
+ * 从文本中提取文件操作记录
367
+ */
368
+ function extractFileArtifacts(text, artifacts, workspaceDir) {
369
+ // 匹配 write_file, replace 工具调用
370
+ // 格式: file_path: "/path/to/file" 或 absolute_path: "/path/to/file"
371
+ const filePathRegex = /(?:file_path|absolute_path)["']?\s*[:=]\s*["']([^"']+\.(ts|js|json|md|yaml|yml|py|sh|mjs|cjs))["']/gi;
372
+ let match;
373
+ while ((match = filePathRegex.exec(text)) !== null) {
374
+ const filePath = match[1];
375
+ // 跳过 node_modules 和配置文件
376
+ if (filePath.includes('node_modules') ||
377
+ filePath.endsWith('.d.ts') ||
378
+ filePath.includes('.config.')) {
379
+ continue;
380
+ }
381
+ // 生成相对路径(如果有 workspaceDir)
382
+ const displayPath = workspaceDir && filePath.startsWith(workspaceDir)
383
+ ? path.relative(workspaceDir, filePath)
384
+ : filePath;
385
+ // 判断操作类型 - 根据上下文关键词
386
+ let action = 'modified';
387
+ const contextBefore = text.substring(Math.max(0, match.index - 200), match.index);
388
+ if (contextBefore.toLowerCase().includes('created') ||
389
+ contextBefore.includes('新建') ||
390
+ contextBefore.includes('创建')) {
391
+ action = 'created';
392
+ }
393
+ else if (contextBefore.toLowerCase().includes('deleted') ||
394
+ contextBefore.includes('删除')) {
395
+ action = 'deleted';
396
+ }
397
+ // 尝试提取描述(从附近的文本)
398
+ const description = extractDescription(text, filePath);
399
+ artifacts.push({
400
+ path: displayPath,
401
+ action,
402
+ description
403
+ });
404
+ }
405
+ // 匹配更通用的文件路径格式(如代码块中的路径)
406
+ // 格式: `path/to/file.ts` 或 "path/to/file.ts"
407
+ // 只匹配明确的代码相关路径
408
+ const genericPathRegex = /[`"']([a-zA-Z0-9_\-\/]+\.(ts|js|mjs|cjs|py))[`"']/g;
409
+ while ((match = genericPathRegex.exec(text)) !== null) {
410
+ const filePath = match[1];
411
+ // 跳过太短、node_modules、配置文件
412
+ if (filePath.length < 10 ||
413
+ filePath.includes('node_modules') ||
414
+ filePath.includes('.config.') ||
415
+ filePath.endsWith('.d.ts') ||
416
+ filePath.endsWith('.test.ts') ||
417
+ filePath.endsWith('.spec.ts')) {
418
+ continue;
419
+ }
420
+ // 检查是否已经存在(避免重复)
421
+ if (artifacts.some(a => a.path === filePath || a.path.endsWith(filePath) || filePath.endsWith(a.path))) {
422
+ continue;
423
+ }
424
+ const description = extractDescription(text, filePath);
425
+ artifacts.push({
426
+ path: filePath,
427
+ action: 'modified',
428
+ description
429
+ });
430
+ }
431
+ }
432
+ /**
433
+ * 尝试从文本中提取文件描述
434
+ */
435
+ function extractDescription(text, filePath) {
436
+ // 在文件路径附近查找描述性文字
437
+ const pathIndex = text.indexOf(filePath);
438
+ if (pathIndex === -1)
439
+ return '';
440
+ // 向前查找 100 个字符
441
+ const before = text.substring(Math.max(0, pathIndex - 100), pathIndex);
442
+ // 匹配描述模式
443
+ const descPatterns = [
444
+ /(?:description|说明|描述|功能|purpose)[::]\s*([^\n]{5,50})/i,
445
+ /\/\/\s*(.{5,50})/,
446
+ /\*\s*(.{5,50})\s*$/
447
+ ];
448
+ for (const pattern of descPatterns) {
449
+ const match = before.match(pattern);
450
+ if (match) {
451
+ return match[1].trim().substring(0, 50);
452
+ }
453
+ }
454
+ return '';
455
+ }
456
+ /**
457
+ * 从文本中提取问题
458
+ */
459
+ function extractProblems(text, problems) {
460
+ // 问题模式(匹配问题描述)
461
+ const problemPattern = /(?:问题|problem|error|错误|失败|failed)[::]\s*([^\n]{5,100})/gi;
462
+ let match;
463
+ while ((match = problemPattern.exec(text)) !== null) {
464
+ const content = match[1].trim();
465
+ if (content.length > 5) {
466
+ problems.push({
467
+ problem: content,
468
+ approach: undefined
469
+ });
470
+ }
471
+ }
472
+ // 解决方案模式(匹配问题和解决方案)
473
+ const solutionPattern = /(?:解决|solution|方案|修复|fix)[::]\s*([^\n]{5,100})/gi;
474
+ while ((match = solutionPattern.exec(text)) !== null) {
475
+ const content = match[1].trim();
476
+ if (content.length > 5) {
477
+ // 尝试关联到最近的问题
478
+ const lastProblem = problems[problems.length - 1];
479
+ if (lastProblem && !lastProblem.approach) {
480
+ lastProblem.approach = content;
481
+ }
482
+ else {
483
+ // 作为独立问题记录
484
+ problems.push({
485
+ problem: content,
486
+ approach: content
487
+ });
488
+ }
489
+ }
490
+ }
491
+ }
492
+ /**
493
+ * 从文本中提取下一步行动
494
+ */
495
+ function extractNextActions(text, actions) {
496
+ // 匹配下一步模式
497
+ const patterns = [
498
+ /(?:下一步|next|接下来|todo|待办)[::]?\s*\n?\s*[-\d]+\s*[.)]?\s*([^\n]{5,80})/gi,
499
+ /[-\d]+\s*[.)]\s*([^\n]{5,80})/g
500
+ ];
501
+ for (const pattern of patterns) {
502
+ let match;
503
+ while ((match = pattern.exec(text)) !== null) {
504
+ const action = match[1].trim();
505
+ if (action.length > 5 && !actions.includes(action)) {
506
+ actions.push(action);
507
+ }
508
+ }
509
+ }
510
+ }
511
+ /**
512
+ * 去重文件记录
513
+ */
514
+ function deduplicateArtifacts(artifacts) {
515
+ const seen = new Map();
516
+ for (const artifact of artifacts) {
517
+ const key = artifact.path;
518
+ const existing = seen.get(key);
519
+ if (!existing) {
520
+ seen.set(key, artifact);
521
+ }
522
+ else {
523
+ // 合并描述(保留更长的)
524
+ if (artifact.description.length > existing.description.length) {
525
+ existing.description = artifact.description;
526
+ }
527
+ }
528
+ }
529
+ return Array.from(seen.values());
530
+ }
531
+ /**
532
+ * 解析 CURRENT_FOCUS.md 中的 Working Memory 章节
533
+ */
534
+ export function parseWorkingMemorySection(content) {
535
+ const wmIndex = content.indexOf(WORKING_MEMORY_SECTION);
536
+ if (wmIndex === -1)
537
+ return null;
538
+ const wmContent = content.substring(wmIndex);
539
+ const snapshot = {
540
+ lastUpdated: new Date().toISOString(),
541
+ artifacts: [],
542
+ activeProblems: [],
543
+ nextActions: []
544
+ };
545
+ // 解析 last updated
546
+ const updatedMatch = wmContent.match(/Last updated:\s*([^\n]+)/i);
547
+ if (updatedMatch) {
548
+ snapshot.lastUpdated = updatedMatch[1].trim();
549
+ }
550
+ // 解析文件记录表格
551
+ // | 文件路径 | 操作 | 描述 |
552
+ const tableRegex = /\|\s*`?([^`|\n]+)`?\s*\|\s*(created|modified|deleted)\s*\|\s*([^|\n]*)\s*\|/gi;
553
+ let match;
554
+ while ((match = tableRegex.exec(wmContent)) !== null) {
555
+ snapshot.artifacts.push({
556
+ path: match[1].trim(),
557
+ action: match[2].toLowerCase(),
558
+ description: match[3].trim()
559
+ });
560
+ }
561
+ // 解析问题列表
562
+ const problemRegex = /[-*]\s*(.+?)\s*(?:→|->)\s*(.+)/g;
563
+ while ((match = problemRegex.exec(wmContent)) !== null) {
564
+ snapshot.activeProblems.push({
565
+ problem: match[1].trim(),
566
+ approach: match[2].trim()
567
+ });
568
+ }
569
+ // 解析下一步行动
570
+ const actionRegex = /^\s*[\d]+\.\s*(.+)$/gm;
571
+ while ((match = actionRegex.exec(wmContent)) !== null) {
572
+ snapshot.nextActions.push(match[1].trim());
573
+ }
574
+ return snapshot;
575
+ }
576
+ /**
577
+ * 将工作记忆合并到 CURRENT_FOCUS.md 内容中
578
+ *
579
+ * @param content 原始内容
580
+ * @param snapshot 工作记忆快照
581
+ * @returns 合并后的内容
582
+ */
583
+ export function mergeWorkingMemory(content, snapshot) {
584
+ const wmIndex = content.indexOf(WORKING_MEMORY_SECTION);
585
+ // 生成 Working Memory 章节
586
+ const wmSection = generateWorkingMemorySection(snapshot);
587
+ if (wmIndex === -1) {
588
+ // 没有 Working Memory 章节,追加到末尾
589
+ return content.trimEnd() + '\n\n' + WORKING_MEMORY_SECTION + '\n' + wmSection;
590
+ }
591
+ else {
592
+ // 替换现有的 Working Memory 章节
593
+ const beforeWm = content.substring(0, wmIndex);
594
+ // 查找下一个 ## 标题(如果有的话)
595
+ const afterWm = content.substring(wmIndex);
596
+ const nextSectionMatch = afterWm.substring(WORKING_MEMORY_SECTION.length).match(/\n##\s/);
597
+ if (nextSectionMatch && nextSectionMatch.index !== undefined) {
598
+ const nextSectionStart = WORKING_MEMORY_SECTION.length + nextSectionMatch.index;
599
+ return beforeWm + WORKING_MEMORY_SECTION + '\n' + wmSection + '\n' + afterWm.substring(nextSectionStart);
600
+ }
601
+ else {
602
+ return beforeWm + WORKING_MEMORY_SECTION + '\n' + wmSection;
603
+ }
604
+ }
605
+ }
606
+ /**
607
+ * 生成 Working Memory 章节内容
608
+ */
609
+ function generateWorkingMemorySection(snapshot) {
610
+ const lines = [`> Last updated: ${snapshot.lastUpdated}`, ''];
611
+ // 文件输出记录
612
+ if (snapshot.artifacts.length > 0) {
613
+ lines.push('### 📁 文件输出记录');
614
+ lines.push('');
615
+ lines.push('| 文件路径 | 操作 | 描述 |');
616
+ lines.push('|----------|------|------|');
617
+ for (const artifact of snapshot.artifacts) {
618
+ lines.push(`| \`${artifact.path}\` | ${artifact.action} | ${artifact.description || '-'} |`);
619
+ }
620
+ lines.push('');
621
+ }
622
+ // 当前任务
623
+ if (snapshot.currentTask) {
624
+ lines.push('### 🎯 当前任务');
625
+ lines.push(`- **描述**: ${snapshot.currentTask.description}`);
626
+ lines.push(`- **状态**: ${snapshot.currentTask.status}`);
627
+ lines.push(`- **进度**: ${snapshot.currentTask.progress}%`);
628
+ lines.push('');
629
+ }
630
+ // 活动问题
631
+ if (snapshot.activeProblems.length > 0) {
632
+ lines.push('### ⚠️ 活动问题');
633
+ for (const p of snapshot.activeProblems) {
634
+ if (p.approach) {
635
+ lines.push(`- ${p.problem} → ${p.approach}`);
636
+ }
637
+ else {
638
+ lines.push(`- ${p.problem}`);
639
+ }
640
+ }
641
+ lines.push('');
642
+ }
643
+ // 下一步行动
644
+ if (snapshot.nextActions.length > 0) {
645
+ lines.push('### ➡️ 下一步行动');
646
+ for (let i = 0; i < snapshot.nextActions.length; i++) {
647
+ lines.push(`${i + 1}. ${snapshot.nextActions[i]}`);
648
+ }
649
+ lines.push('');
650
+ }
651
+ return lines.join('\n');
652
+ }
653
+ /**
654
+ * 生成工作记忆注入字符串(用于 prompt 注入)
655
+ */
656
+ export function workingMemoryToInjection(snapshot) {
657
+ if (!snapshot)
658
+ return '';
659
+ if (snapshot.artifacts.length === 0 &&
660
+ snapshot.activeProblems.length === 0 &&
661
+ snapshot.nextActions.length === 0) {
662
+ return '';
663
+ }
664
+ const lines = ['<working_memory preserved="true">'];
665
+ lines.push('以下是你压缩前的工作记忆,请继续完成未完成的任务:');
666
+ lines.push('');
667
+ if (snapshot.artifacts.length > 0) {
668
+ lines.push('### 已输出的文件');
669
+ for (const a of snapshot.artifacts.slice(-10)) {
670
+ lines.push(`- [${a.action.toUpperCase()}] \`${a.path}\`${a.description ? ` - ${a.description}` : ''}`);
671
+ }
672
+ lines.push('');
673
+ }
674
+ if (snapshot.activeProblems.length > 0) {
675
+ lines.push('### 活动问题');
676
+ for (const p of snapshot.activeProblems) {
677
+ if (p.approach) {
678
+ lines.push(`- ${p.problem} → ${p.approach}`);
679
+ }
680
+ else {
681
+ lines.push(`- ${p.problem}`);
682
+ }
683
+ }
684
+ lines.push('');
685
+ }
686
+ if (snapshot.nextActions.length > 0) {
687
+ lines.push('### 下一步行动');
688
+ for (let i = 0; i < snapshot.nextActions.length; i++) {
689
+ lines.push(`${i + 1}. ${snapshot.nextActions[i]}`);
690
+ }
691
+ lines.push('');
692
+ }
693
+ lines.push('</working_memory>');
694
+ return lines.join('\n');
695
+ }
package/dist/core/init.js CHANGED
@@ -11,6 +11,24 @@ const DEFAULT_PROFILE = {
11
11
  version: "1.0.0",
12
12
  contextInjection: defaultContextConfig
13
13
  };
14
+ const CORE_GUIDANCE_VERSION = 'pd-core-guidance-v2';
15
+ const CORE_GUIDANCE_FILES = new Set(['AGENTS.md', 'TOOLS.md']);
16
+ function getCoreGuidanceVersionMarker() {
17
+ return `pd-core-guidance-version: ${CORE_GUIDANCE_VERSION}`;
18
+ }
19
+ function hasOutdatedCoreGuidance(file, content) {
20
+ if (!CORE_GUIDANCE_FILES.has(file))
21
+ return false;
22
+ if (content.includes(getCoreGuidanceVersionMarker()))
23
+ return false;
24
+ if (content.includes('pd_spawn_agent'))
25
+ return true;
26
+ if (!content.includes('subagents'))
27
+ return true;
28
+ if (file === 'AGENTS.md' && !content.includes('pd_run_worker'))
29
+ return true;
30
+ return false;
31
+ }
14
32
  /**
15
33
  * Ensures that the workspace has the necessary template files for Principles Disciple.
16
34
  * This function flattens 'core' templates to the root so OpenClaw can find them.
@@ -45,6 +63,12 @@ export function ensureWorkspaceTemplates(api, workspaceDir, language = 'en') {
45
63
  fs.copyFileSync(srcPath, destPath);
46
64
  api.logger.info(`[PD] Initialized core file: ${file}`);
47
65
  }
66
+ else if (CORE_GUIDANCE_FILES.has(file)) {
67
+ const existingContent = fs.readFileSync(destPath, 'utf8');
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 pd_run_worker routing.`);
70
+ }
71
+ }
48
72
  }
49
73
  }
50
74
  // 3. Copy pain memory seed files
@@ -5,3 +5,18 @@ export interface FileModification {
5
5
  }
6
6
  export declare function estimateLineChanges(modification: FileModification): number;
7
7
  export declare function assessRiskLevel(filePath: string, modification: FileModification, riskPaths: string[]): RiskLevel;
8
+ /**
9
+ * Get the total line count of a target file.
10
+ * @param absoluteFilePath - Absolute path to the file
11
+ * @returns File line count, or null if file doesn't exist or can't be read
12
+ */
13
+ export declare function getTargetFileLineCount(absoluteFilePath: string): number | null;
14
+ /**
15
+ * Calculate the effective line limit based on percentage of target file.
16
+ * @param targetLineCount - Total lines in target file
17
+ * @param percentage - Allowed percentage (0-100)
18
+ * @param minLines - Absolute minimum threshold
19
+ * @param maxLines - Optional upper bound to prevent misconfiguration
20
+ * @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
21
+ */
22
+ export declare function calculatePercentageThreshold(targetLineCount: number, percentage: number, minLines: number, maxLines?: number): number;
@@ -1,3 +1,4 @@
1
+ import * as fs from 'fs';
1
2
  import { isRisky } from '../utils/io.js';
2
3
  export function estimateLineChanges(modification) {
3
4
  const { toolName, params } = modification;
@@ -37,3 +38,50 @@ export function assessRiskLevel(filePath, modification, riskPaths) {
37
38
  return 'LOW';
38
39
  }
39
40
  }
41
+ /**
42
+ * Get the total line count of a target file.
43
+ * @param absoluteFilePath - Absolute path to the file
44
+ * @returns File line count, or null if file doesn't exist or can't be read
45
+ */
46
+ export function getTargetFileLineCount(absoluteFilePath) {
47
+ try {
48
+ if (!fs.existsSync(absoluteFilePath)) {
49
+ return null; // File genuinely doesn't exist
50
+ }
51
+ const stats = fs.statSync(absoluteFilePath);
52
+ if (!stats.isFile()) {
53
+ return null; // Not a regular file (directory, device, etc.)
54
+ }
55
+ const content = fs.readFileSync(absoluteFilePath, 'utf-8');
56
+ return content.split('\n').length;
57
+ }
58
+ catch (e) {
59
+ // Log error before falling back to null - this is intentional for security gates
60
+ const error = e instanceof Error ? e : new Error(String(e));
61
+ const errorCode = e.code;
62
+ console.error(`[PD:RISK_CALC] Failed to read file for line count: ${absoluteFilePath}`, {
63
+ code: errorCode,
64
+ message: error.message,
65
+ });
66
+ return null;
67
+ }
68
+ }
69
+ /**
70
+ * Calculate the effective line limit based on percentage of target file.
71
+ * @param targetLineCount - Total lines in target file
72
+ * @param percentage - Allowed percentage (0-100)
73
+ * @param minLines - Absolute minimum threshold
74
+ * @param maxLines - Optional upper bound to prevent misconfiguration
75
+ * @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
76
+ */
77
+ export function calculatePercentageThreshold(targetLineCount, percentage, minLines, maxLines) {
78
+ // Clamp percentage to valid range [0, 100]
79
+ const clampedPercentage = Math.max(0, Math.min(100, percentage));
80
+ const calculated = Math.round(targetLineCount * (clampedPercentage / 100));
81
+ let effectiveLimit = Math.max(calculated, minLines);
82
+ // Apply optional upper bound
83
+ if (maxLines !== undefined && maxLines > 0) {
84
+ effectiveLimit = Math.min(effectiveLimit, maxLines);
85
+ }
86
+ return effectiveLimit;
87
+ }