principles-disciple 1.65.0 → 1.67.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.
@@ -6,49 +6,6 @@
6
6
  - **工具偏好**:优先使用 `rg` (ripgrep) 进行高性能检索,严禁盲目遍历。
7
7
 
8
8
 
9
- ## 3. 深度反思工具 (Deep Reflection)
10
- `deep_reflect` 是**认知分析工具**——在执行复杂任务前,进行批判性分析,识别盲点、风险和替代方案。
11
-
12
- ### 何时应该调用
13
- - **复杂任务**:规划、设计、决策、分析等需要深思熟虑的场景
14
- - **信息不足**:需求模糊、约束不明确、缺少关键信息
15
- - **高风险决策**:重要决策、不可逆操作、影响范围大
16
- - **不确定时**:对最佳方案存疑,需要多角度思考
17
-
18
- ### 使用场景示例
19
- - 营销方案设计:分析目标受众、渠道选择、风险预案
20
- - 产品功能规划:评估用户需求、技术可行性、资源投入
21
- - 架构设计决策:权衡方案优劣、识别潜在风险
22
- - 问题分析诊断:多角度分析根因、避免遗漏关键因素
23
-
24
- ### 带来的好处
25
- - 识别可能遗漏的盲点
26
- - 发现潜在风险和失败模式
27
- - 提供替代方案及权衡分析
28
- - 应用结构化思维模型深化洞察
29
-
30
- ### 如何调用
31
- ```
32
- deep_reflect(
33
- model_id: "T-01" | "T-02" | ... | "T-09", // 推荐 T-01 或 T-05
34
- context: "描述你的计划和担忧...",
35
- depth: 1 | 2 | 3 // 1=快速, 2=平衡, 3=详尽
36
- )
37
- ```
38
-
39
- ### 思维模型选择
40
- | 模型 | 名称 | 适用场景 |
41
- |------|------|----------|
42
- | T-01 | 地图先于领土 | 规划、设计、理解系统 |
43
- | T-05 | 否定优于肯定 | 风险分析、找漏洞 |
44
- | T-07 | 系统优于组件 | 架构决策、集成问题 |
45
-
46
- ### 输出结构
47
- 工具返回:盲点分析 → 风险警告 → 替代方案 → 建议 → 置信度
48
-
49
- **注意**:这是批判性反馈,最终决策权在你。认真考虑建议,但不必盲目遵循。
50
-
51
- ---
52
9
 
53
10
  ## 4. 智能体路由澄清
54
11
 
@@ -60,27 +60,6 @@
60
60
  "stage_3_max_lines": 300
61
61
  }
62
62
  },
63
- "deep_reflection": {
64
- "enabled": true,
65
- "mode": "auto",
66
- "force_checkpoint": true,
67
- "checkpoint_message": "Before responding, quick self-check: 1. Task complexity (simple/medium/complex) 2. Information sufficiency (sufficient/need more) 3. If complex or insufficient info, call deep_reflect tool",
68
- "auto_trigger_conditions": {
69
- "min_tool_calls": 5,
70
- "error_rate_threshold": 0.3,
71
- "complexity_keywords": [
72
- "refactor",
73
- "architecture",
74
- "design",
75
- "optimize",
76
- "security",
77
- "critical"
78
- ]
79
- },
80
- "default_model": "T-01",
81
- "default_depth": 2,
82
- "timeout_ms": 60000
83
- },
84
63
  "empathy_engine": {
85
64
  "enabled": true,
86
65
  "dedupe_window_ms": 60000,
@@ -8,6 +8,8 @@ import { appendCandidateArtifactLineageRecord } from '../../src/core/nocturnal-a
8
8
  import { getImplementationAssetRoot } from '../../src/core/code-implementation-storage.js';
9
9
  import { EvolutionReducerImpl } from '../../src/core/evolution-reducer.js';
10
10
  import { saveLedger } from '../../src/core/principle-tree-ledger.js';
11
+ import { WorkflowFunnelLoader } from '../../src/core/workflow-funnel-loader.js';
12
+ import { RuntimeSummaryService } from '../../src/service/runtime-summary-service.js';
11
13
 
12
14
  const tempDirs: string[] = [];
13
15
 
@@ -338,4 +340,290 @@ describe('evolution commands', () => {
338
340
 
339
341
  expect(result.text).toContain('internalization routes: P-001:skill@');
340
342
  });
343
+
344
+ it('renders workflowFunnel blocks when YAML-driven funnels are present', () => {
345
+ const workspace = makeTempDir();
346
+ const stateDir = path.join(workspace, '.state');
347
+
348
+ // Write workflows.yaml with two funnels matching actual schema
349
+ const workflowsYaml = `
350
+ version: "1.0"
351
+ funnels:
352
+ - workflowId: nocturnal
353
+ stages:
354
+ - name: dreamer_completed
355
+ eventType: nocturnal_dreamer_completed
356
+ eventCategory: completed
357
+ statsField: evolution.nocturnalDreamerCompleted
358
+ - name: artifact_persisted
359
+ eventType: nocturnal_artifact_persisted
360
+ eventCategory: completed
361
+ statsField: evolution.nocturnalArtifactPersisted
362
+ - workflowId: rulehost
363
+ stages:
364
+ - name: evaluated
365
+ eventType: rulehost_evaluated
366
+ eventCategory: evaluated
367
+ statsField: evolution.rulehostEvaluated
368
+ `;
369
+ fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
370
+
371
+ // Write daily stats with matching event counts (must be in logs/ subdirectory)
372
+ const today = new Date().toISOString().slice(0, 10);
373
+ writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
374
+ [today]: {
375
+ evolution: {
376
+ nocturnalDreamerCompleted: 3,
377
+ nocturnalArtifactPersisted: 2,
378
+ rulehostEvaluated: 15,
379
+ },
380
+ },
381
+ });
382
+
383
+ const result = handleEvolutionStatusCommand({
384
+ config: { workspaceDir: workspace, language: 'en' },
385
+ } as any);
386
+
387
+ expect(result.text).toContain('Workflow Funnel: nocturnal');
388
+ expect(result.text).toMatch(/dreamer_completed: 3/);
389
+ expect(result.text).toMatch(/artifact_persisted: 2/);
390
+ expect(result.text).toContain('Workflow Funnel: rulehost');
391
+ expect(result.text).toMatch(/evaluated: 15/);
392
+ });
393
+
394
+ it('skips funnel block when workflowFunnels is empty array', () => {
395
+ const workspace = makeTempDir();
396
+
397
+ // Write empty funnels array — valid YAML but no funnels defined
398
+ const workflowsYaml = `version: "1.0"\nfunnels: []`;
399
+ fs.writeFileSync(path.join(workspace, '.state', 'workflows.yaml'), workflowsYaml, 'utf8');
400
+
401
+ const result = handleEvolutionStatusCommand({
402
+ config: { workspaceDir: workspace, language: 'en' },
403
+ } as any);
404
+
405
+ expect(result.text).not.toContain('Workflow Funnel:');
406
+ });
407
+
408
+ it('shows degraded status and warning when YAML load has warnings', () => {
409
+ const workspace = makeTempDir();
410
+ const stateDir = path.join(workspace, '.state');
411
+
412
+ // Write malformed YAML — loader emits a parse warning
413
+ const badYaml = `
414
+ version: "1.0"
415
+ funnels:
416
+ - workflowId: nocturnal
417
+ stages:
418
+ - name: "unclosed string
419
+ `;
420
+ fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), badYaml, 'utf8');
421
+
422
+ const result = handleEvolutionStatusCommand({
423
+ config: { workspaceDir: workspace, language: 'en' },
424
+ } as any);
425
+
426
+ // Should still render — degraded but not crashed
427
+ expect(result.text).toContain('Evolution Status');
428
+ // Loader warning should appear in output
429
+ expect(result.text).toMatch(/YAML load warning|YAML parse error|workflows\.yaml/);
430
+ });
431
+
432
+ it('renders Chinese workflow funnel labels from YAML', () => {
433
+ const workspace = makeTempDir();
434
+ const stateDir = path.join(workspace, '.state');
435
+
436
+ const workflowsYaml = `
437
+ version: "1.0"
438
+ funnels:
439
+ - workflowId: nocturnal
440
+ stages:
441
+ - name: 做梦完成
442
+ eventType: nocturnal_dreamer_completed
443
+ eventCategory: completed
444
+ statsField: evolution.nocturnalDreamerCompleted
445
+ `;
446
+ fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
447
+
448
+ const today = new Date().toISOString().slice(0, 10);
449
+ writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
450
+ [today]: {
451
+ evolution: { nocturnalDreamerCompleted: 7 },
452
+ },
453
+ });
454
+
455
+ const result = handleEvolutionStatusCommand({
456
+ config: { workspaceDir: workspace, language: 'zh' },
457
+ } as any);
458
+
459
+ expect(result.text).toContain('Workflow 漏斗: nocturnal');
460
+ expect(result.text).toMatch(/做梦完成: 7/);
461
+ });
462
+ });
463
+
464
+ describe('YAML funnel E2E integration tests', () => {
465
+ // TEST-01: Full YAML-driven flow with real WorkflowFunnelLoader
466
+ it('e2e_test_full_yaml_driven_flow', () => {
467
+ const workspace = makeTempDir();
468
+ const stateDir = path.join(workspace, '.state');
469
+
470
+ // Create real WorkflowFunnelLoader (not mocked)
471
+ const loader = new WorkflowFunnelLoader(stateDir);
472
+ loader.watch();
473
+
474
+ // Write valid workflows.yaml
475
+ const workflowsYaml = `
476
+ version: "1.0"
477
+ funnels:
478
+ - workflowId: nocturnal
479
+ stages:
480
+ - name: dreamer_completed
481
+ eventType: nocturnal_dreamer_completed
482
+ eventCategory: completed
483
+ statsField: evolution.nocturnalDreamerCompleted
484
+ - name: artifact_persisted
485
+ eventType: nocturnal_artifact_persisted
486
+ eventCategory: completed
487
+ statsField: evolution.nocturnalArtifactPersisted
488
+ - workflowId: rulehost
489
+ stages:
490
+ - name: evaluated
491
+ eventType: rulehost_evaluated
492
+ eventCategory: evaluated
493
+ statsField: evolution.rulehostEvaluated
494
+ `;
495
+ fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
496
+ loader.load(); // reload after writing file
497
+
498
+ // Write daily-stats.json
499
+ const today = new Date().toISOString().slice(0, 10);
500
+ writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
501
+ [today]: {
502
+ evolution: {
503
+ nocturnalDreamerCompleted: 3,
504
+ nocturnalArtifactPersisted: 2,
505
+ rulehostEvaluated: 15,
506
+ },
507
+ },
508
+ });
509
+
510
+ // Call getSummary directly with real loader data
511
+ const summary = RuntimeSummaryService.getSummary(workspace, {
512
+ funnels: loader.getAllFunnels(),
513
+ loaderWarnings: loader.getWarnings(),
514
+ });
515
+
516
+ // Assert workflowFunnels structure
517
+ expect(summary.workflowFunnels).toBeDefined();
518
+ expect(summary.workflowFunnels!.length).toBe(2);
519
+ expect(summary.workflowFunnels![0].funnelKey).toBe('nocturnal');
520
+ expect(summary.workflowFunnels![0].stages[0].label).toBe('dreamer_completed');
521
+ expect(summary.workflowFunnels![0].stages[0].count).toBe(3);
522
+ expect(summary.workflowFunnels![0].stages[1].label).toBe('artifact_persisted');
523
+ expect(summary.workflowFunnels![0].stages[1].count).toBe(2);
524
+ expect(summary.workflowFunnels![1].funnelKey).toBe('rulehost');
525
+ expect(summary.workflowFunnels![1].stages[0].label).toBe('evaluated');
526
+ expect(summary.workflowFunnels![1].stages[0].count).toBe(15);
527
+ expect(summary.workflowFunnels![0].funnelLabel).toBe('nocturnal');
528
+
529
+ // DEGRADED-01: valid YAML + valid stats → status ok, no funnel-related warnings
530
+ expect(summary.metadata.status).toBe('ok');
531
+ // Note: warnings array may contain non-funnel warnings (GFI/Daily stats defaults); funnel warnings are checked separately
532
+ const funnelWarnings = summary.metadata.warnings.filter(w => w.includes('statsField') || w.includes('YAML load'));
533
+ expect(funnelWarnings).toHaveLength(0);
534
+
535
+ loader.dispose();
536
+ });
537
+
538
+ // TEST-02: Degraded fallback when YAML is missing
539
+ it('e2e_test_degraded_fallback_on_missing_yaml', () => {
540
+ const workspace = makeTempDir();
541
+ const stateDir = path.join(workspace, '.state');
542
+
543
+ // Create real loader pointing at stateDir with NO workflows.yaml
544
+ const loader = new WorkflowFunnelLoader(stateDir);
545
+ loader.watch();
546
+
547
+ // Write daily-stats.json (so the file exists, but no YAML)
548
+ const today = new Date().toISOString().slice(0, 10);
549
+ writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
550
+ [today]: { evolution: {} },
551
+ });
552
+
553
+ // Call getSummary — should not crash
554
+ const summary = RuntimeSummaryService.getSummary(workspace, {
555
+ funnels: loader.getAllFunnels(),
556
+ loaderWarnings: loader.getWarnings(),
557
+ });
558
+
559
+ // Assert degraded status
560
+ expect(summary.metadata.status).toBe('degraded');
561
+ // loaderWarnings are prefixed with "YAML load warning: " when propagated to metadata.warnings
562
+ expect(summary.metadata.warnings).toContain('YAML load warning: workflows.yaml file not found.');
563
+ // DEGRADED-02: funnels absent/empty when YAML missing — empty array is acceptable, just not rendered
564
+ expect(summary.workflowFunnels == null || summary.workflowFunnels.length === 0).toBe(true);
565
+
566
+ loader.dispose();
567
+ });
568
+
569
+ // TEST-03: Hot-reload — YAML changes reflected after loader.load()
570
+ it('e2e_test_hot_reload_reflects_yaml_changes', () => {
571
+ const workspace = makeTempDir();
572
+ const stateDir = path.join(workspace, '.state');
573
+
574
+ const loader = new WorkflowFunnelLoader(stateDir);
575
+ loader.watch();
576
+
577
+ // Write initial workflows.yaml with original_label
578
+ const workflowsYamlV1 = `
579
+ version: "1.0"
580
+ funnels:
581
+ - workflowId: nocturnal
582
+ stages:
583
+ - name: original_label
584
+ eventType: nocturnal_dreamer_completed
585
+ eventCategory: completed
586
+ statsField: evolution.nocturnalDreamerCompleted
587
+ `;
588
+ const yamlPath = path.join(stateDir, 'workflows.yaml');
589
+ fs.writeFileSync(yamlPath, workflowsYamlV1, 'utf8');
590
+ loader.load();
591
+
592
+ const today = new Date().toISOString().slice(0, 10);
593
+ writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
594
+ [today]: { evolution: { nocturnalDreamerCompleted: 5 } },
595
+ });
596
+
597
+ // First call — should show original_label
598
+ const summary1 = RuntimeSummaryService.getSummary(workspace, {
599
+ funnels: loader.getAllFunnels(),
600
+ loaderWarnings: loader.getWarnings(),
601
+ });
602
+ expect(summary1.workflowFunnels![0].stages[0].label).toBe('original_label');
603
+ expect(summary1.workflowFunnels![0].stages[0].count).toBe(5);
604
+
605
+ // Modify workflows.yaml to use modified_label
606
+ const workflowsYamlV2 = `
607
+ version: "1.0"
608
+ funnels:
609
+ - workflowId: nocturnal
610
+ stages:
611
+ - name: modified_label
612
+ eventType: nocturnal_dreamer_completed
613
+ eventCategory: completed
614
+ statsField: evolution.nocturnalDreamerCompleted
615
+ `;
616
+ fs.writeFileSync(yamlPath, workflowsYamlV2, 'utf8');
617
+ loader.load(); // trigger hot-reload manually
618
+
619
+ // Second call — should show modified_label
620
+ const summary2 = RuntimeSummaryService.getSummary(workspace, {
621
+ funnels: loader.getAllFunnels(),
622
+ loaderWarnings: loader.getWarnings(),
623
+ });
624
+ expect(summary2.workflowFunnels![0].stages[0].label).toBe('modified_label');
625
+ expect(summary2.workflowFunnels![0].stages[0].count).toBe(5);
626
+
627
+ loader.dispose();
628
+ });
341
629
  });
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { EventLogService, EventLog } from '../../src/core/event-log.js';
3
- import type { DailyStats, DeepReflectionEventData, DiagnosticianReportEventData } from '../../src/types/event-types.js';
3
+ import type { DailyStats, DiagnosticianReportEventData } from '../../src/types/event-types.js';
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import * as os from 'os';
@@ -19,32 +19,6 @@ describe('EventLog', () => {
19
19
  fs.rmSync(tempDir, { recursive: true, force: true });
20
20
  });
21
21
 
22
- describe('recordDeepReflection', () => {
23
- it('should record deep reflection event', () => {
24
- const data: DeepReflectionEventData = {
25
- modelId: 'claude-sonnet-4',
26
- modelSelectionMode: 'auto',
27
- confidenceScore: 0.85,
28
- insightsGenerated: 3,
29
- durationMs: 1500,
30
- passed: true
31
- };
32
-
33
- eventLog.recordDeepReflection('session-1', data);
34
- eventLog.flush();
35
-
36
- const today = new Date().toISOString().slice(0, 10);
37
- const eventsFile = path.join(tempDir, 'logs', `events_${today}.jsonl`);
38
- const content = fs.readFileSync(eventsFile, 'utf-8');
39
- const event = JSON.parse(content.trim());
40
-
41
- expect(event.type).toBe('deep_reflection');
42
- expect(event.category).toBe('passed');
43
- expect(event.data.modelId).toBe('claude-sonnet-4');
44
- expect(event.data.modelSelectionMode).toBe('auto');
45
- });
46
- });
47
-
48
22
  describe('DailyStats', () => {
49
23
  it('should aggregate tool call statistics correctly', () => {
50
24
  // Record multiple tool calls
@@ -97,8 +71,6 @@ describe('EventLog', () => {
97
71
  // Hooks field
98
72
  expect(stats.hooks).toBeDefined();
99
73
 
100
- // Deep Reflection field
101
- expect(stats.deepReflection).toBeDefined();
102
74
  });
103
75
 
104
76
  it('should increment tools.failure on error', () => {
@@ -678,7 +678,7 @@ describe('RuntimeSummaryService', () => {
678
678
  last_updated: '2026-03-20T10:00:00Z',
679
679
  });
680
680
  writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
681
- '2026-03-20': {
681
+ [new Date().toISOString().slice(0, 10)]: {
682
682
  toolCalls: 120,
683
683
  painSignals: 15,
684
684
  evolutionTasks: 5,