principles-disciple 1.66.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.
@@ -44,10 +44,6 @@
44
44
  * `success_base` (默认: 1): 成功干完一个小任务,加 1 分。
45
45
  * `streak_bonus` (默认: 5): 连续 5 次成功(连杀奖励),额外加 5 分。
46
46
 
47
- ### 5. 元认知深度反思 (`deep_reflection`)
48
- * `enabled`: 是否允许 AI 停下来深思熟虑。
49
- * `auto_trigger_conditions.error_rate_threshold` (默认: 0.3): 当近期操作的错误率超过 30% 时,强制让 AI 停下手头的活,调用 `deep_reflect` 工具分析自己是不是大方向搞错了。
50
-
51
47
  ---
52
48
 
53
49
  ### ⚠️ 高风险操作警告!
package/README.md CHANGED
@@ -44,15 +44,6 @@ All commands support **short aliases** for easier input:
44
44
  | `/nocturnal-rollout` | Nocturnal rollout and promotion |
45
45
  | `/pd-workflow-debug` | Debug workflow state |
46
46
 
47
- ### Tools
48
-
49
- **`deep_reflect`** - Executes deep meta-cognitive reflection to analyze potential risks, logical gaps, or architectural improvements in the current task.
50
-
51
- Parameters:
52
- - `context` (required): Task context, code snippet, or current difficulty
53
- - `depth` (optional): Reflection depth 1-3 (default: 2)
54
- - `model_id` (optional): Force specific thinking model
55
-
56
47
  ### Configuration
57
48
 
58
49
  The plugin accepts the following configuration options:
@@ -62,8 +53,6 @@ The plugin accepts the following configuration options:
62
53
  | `language` | `zh` | Interaction language (`en` or `zh`) |
63
54
  | `auditLevel` | `medium` | Security guardrail level (`low`, `medium`, `high`) |
64
55
  | `riskPaths` | `[]` | High-risk directories requiring explicit authorization |
65
- | `deep_reflection.enabled` | `true` | Enable AI deep reflection |
66
- | `deep_reflection.mode` | `auto` | Reflection trigger mode (`auto` or `forced`) |
67
56
 
68
57
  ## Part of the principles monorepo
69
58
 
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.66.0",
5
+ "version": "1.67.0",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -37,27 +37,6 @@
37
37
  "default": [],
38
38
  "description": "自定义高危目录(例如 .git/, prod_db/)。AI 试图修改这些目录前,将被强制拦截并要求出具安全计划。"
39
39
  },
40
- "deep_reflection": {
41
- "type": "object",
42
- "additionalProperties": false,
43
- "description": "当 AI 遇到复杂问题或连续报错时,是否允许它停下来进行深度自我反思?",
44
- "properties": {
45
- "enabled": {
46
- "type": "boolean",
47
- "default": true,
48
- "description": "开启 AI 深度反思功能"
49
- },
50
- "mode": {
51
- "type": "string",
52
- "enum": [
53
- "auto",
54
- "forced"
55
- ],
56
- "default": "auto",
57
- "description": "auto: 遇到困难自动触发; forced: 每次回答前都强制反思 (极耗时间,不推荐)"
58
- }
59
- }
60
- }
61
40
  }
62
41
  },
63
42
  "uiHints": {
@@ -70,9 +49,6 @@
70
49
  },
71
50
  "riskPaths": {
72
51
  "label": "⚠️ 绝对高危目录 (空表示不设限)"
73
- },
74
- "deep_reflection": {
75
- "label": "💡 AI 深度反思功能"
76
52
  }
77
53
  },
78
54
  "buildFingerprint": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.66.0",
3
+ "version": "1.67.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -74,7 +74,6 @@ function showStatus(workspaceDir: string, isZh: boolean): string {
74
74
  |------|------|------|
75
75
  | 核心原则 | ✅ 始终开启 | 不可关闭 |
76
76
  | 思维模型 | ${config.thinkingOs ? '✅ 开启' : '❌ 关闭'} | /pd-context thinking on/off |
77
- | 反思日志 | ${config.reflectionLog ? '✅ 开启' : '❌ 关闭'} | /pd-context reflection on/off |
78
77
  | 项目上下文 | ${formatProjectFocus(config.projectFocus, isZh)} | /pd-context focus full/summary/off |
79
78
 
80
79
  💡 输入 \`/pd-context help\` 查看更多选项
@@ -87,7 +86,6 @@ function showStatus(workspaceDir: string, isZh: boolean): string {
87
86
  |---------|--------|---------|
88
87
  | Core Principles | ✅ Always ON | Not configurable |
89
88
  | Thinking OS | ${config.thinkingOs ? '✅ ON' : '❌ OFF'} | /pd-context thinking on/off |
90
- | Reflection Log | ${config.reflectionLog ? '✅ ON' : '❌ OFF'} | /pd-context reflection on/off |
91
89
  | Project Context | ${formatProjectFocus(config.projectFocus, isZh)} | /pd-context focus full/summary/off |
92
90
 
93
91
  💡 Type \`/pd-context help\` for more options
@@ -102,26 +100,26 @@ function showStatus(workspaceDir: string, isZh: boolean): string {
102
100
 
103
101
  function toggleSetting(
104
102
  workspaceDir: string,
105
- key: 'thinkingOs' | 'reflectionLog',
103
+ key: 'thinkingOs',
106
104
  value: string,
107
105
  isZh: boolean
108
106
  ): string {
109
107
  const config = loadContextInjectionConfig(workspaceDir);
110
108
  const oldValue = config[key];
111
-
109
+
112
110
  if (value === 'on') {
113
111
  config[key] = true;
114
112
  } else if (value === 'off') {
115
113
  config[key] = false;
116
114
  } else {
117
- return isZh
115
+ return isZh
118
116
  ? `❌ 无效值: "${value}"。使用 "on" 或 "off"。`
119
117
  : `❌ Invalid value: "${value}". Use "on" or "off".`;
120
118
  }
121
-
119
+
122
120
  const newValue = config[key];
123
- const keyName = isZh
124
- ? { thinkingOs: '思维模型', reflectionLog: '反思日志' }[key]
121
+ const keyName = isZh
122
+ ? { thinkingOs: '思维模型' }[key]
125
123
  : key;
126
124
 
127
125
  // No change needed
@@ -222,7 +220,6 @@ function applyPreset(
222
220
  case 'minimal':
223
221
  config = {
224
222
  thinkingOs: false,
225
- reflectionLog: false,
226
223
  projectFocus: 'off',
227
224
  evolutionContext: { ...defaultContextConfig.evolutionContext }
228
225
  };
@@ -230,7 +227,6 @@ function applyPreset(
230
227
  case 'standard':
231
228
  config = {
232
229
  thinkingOs: true,
233
- reflectionLog: false,
234
230
  projectFocus: 'off',
235
231
  evolutionContext: { ...defaultContextConfig.evolutionContext }
236
232
  };
@@ -238,7 +234,6 @@ function applyPreset(
238
234
  case 'full':
239
235
  config = {
240
236
  thinkingOs: true,
241
- reflectionLog: true,
242
237
  projectFocus: 'summary',
243
238
  evolutionContext: { ...defaultContextConfig.evolutionContext }
244
239
  };
@@ -328,9 +323,6 @@ export function handleContextCommand(ctx: PluginCommandContext): PluginCommandRe
328
323
  case 'thinking':
329
324
  result = toggleSetting(workspaceDir, 'thinkingOs', value, isZh);
330
325
  break;
331
- case 'reflection':
332
- result = toggleSetting(workspaceDir, 'reflectionLog', value, isZh);
333
- break;
334
326
  case 'focus':
335
327
  result = setProjectFocus(workspaceDir, value, isZh);
336
328
  break;
@@ -100,6 +100,17 @@ function buildEnglishOutput(
100
100
  `- generatedAt: ${summary.metadata.generatedAt}`,
101
101
  ];
102
102
 
103
+ // E: YAML-driven Workflow Funnels (Phase 6)
104
+ if (summary.workflowFunnels && summary.workflowFunnels.length > 0) {
105
+ lines.push('');
106
+ for (const funnel of summary.workflowFunnels) {
107
+ lines.push(`Workflow Funnel: ${funnel.funnelLabel}`);
108
+ for (const stage of funnel.stages) {
109
+ lines.push(` - ${stage.label}: ${stage.count}`);
110
+ }
111
+ }
112
+ }
113
+
103
114
  if (warnings.length > 0) {
104
115
  lines.push('', 'Warnings');
105
116
  for (const warning of warnings) {
@@ -110,8 +121,8 @@ function buildEnglishOutput(
110
121
  return lines.join('\n');
111
122
  }
112
123
 
113
-
114
-
124
+
125
+
115
126
  function buildChineseOutput(
116
127
  workspaceDir: string,
117
128
  sessionId: string | null,
@@ -161,6 +172,17 @@ function buildChineseOutput(
161
172
  `- 生成时间: ${summary.metadata.generatedAt}`,
162
173
  ];
163
174
 
175
+ // E: YAML驱动的Workflow Funnels (Phase 6)
176
+ if (summary.workflowFunnels && summary.workflowFunnels.length > 0) {
177
+ lines.push('');
178
+ for (const funnel of summary.workflowFunnels) {
179
+ lines.push(`Workflow 漏斗: ${funnel.funnelLabel}`);
180
+ for (const stage of funnel.stages) {
181
+ lines.push(` - ${stage.label}: ${stage.count}`);
182
+ }
183
+ }
184
+ }
185
+
164
186
  if (warnings.length > 0) {
165
187
  lines.push('', '警告');
166
188
  for (const warning of warnings) {
@@ -183,7 +205,11 @@ export function handleEvolutionStatusCommand(ctx: PluginCommandContext): { text:
183
205
  const loader = new WorkflowFunnelLoader(stateDir);
184
206
  loader.watch();
185
207
  try {
186
- const summary = RuntimeSummaryService.getSummary(workspaceDir, { sessionId, loaderWarnings: loader.getWarnings() });
208
+ const summary = RuntimeSummaryService.getSummary(workspaceDir, {
209
+ sessionId,
210
+ loaderWarnings: loader.getWarnings(),
211
+ funnels: loader.getAllFunnels(),
212
+ });
187
213
  const recommendations = WorkspaceContext.fromHookContext({ workspaceDir })
188
214
  .principleLifecycle
189
215
  .recomputeAll()
@@ -156,8 +156,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
156
156
  suggestionText = `
157
157
  💡 **建议 (系统检测到您当前遇到较大阻力)**:
158
158
  1. 执行 \`/pd-status reset\` 清零疲劳值。
159
- 2. AI 调用 \`deep_reflect\` 工具进行深度反思。
160
- 3. 如果当前上下文太乱,考虑使用 \`/clear\` 开启新会话。`;
159
+ 2. 如果当前上下文太乱,考虑使用 \`/clear\` 开启新会话。`;
161
160
  }
162
161
  else if (gfi > 50) healthLabel = '遇到阻力 🟡';
163
162
  else if (gfi > 20) healthLabel = '轻微受挫 🟢';
@@ -168,8 +167,8 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
168
167
  suggestionText = `
169
168
  💡 **Suggestion (High friction detected)**:
170
169
  1. Run \`/pd-status reset\` to clear friction.
171
- 2. Ask the AI to use the \`deep_reflect\` tool.
172
- 4. Consider starting a new session with \`/clear\`.`;
170
+ 2. Ask the AI to reflect deeply before continuing.
171
+ 3. Consider starting a new session with \`/clear\`.`;
173
172
  }
174
173
  else if (gfi > 50) healthLabel = 'High Friction 🟡';
175
174
  else if (gfi > 20) healthLabel = 'Minor Issues 🟢';
@@ -115,8 +115,6 @@ export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
115
115
 
116
116
  // ── Workflow TTL Settings ───────────────────────────────────────────────────────
117
117
 
118
- /** Deep-reflect workflow TTL (10 minutes) */
119
- export const DEEP_REFLECT_TTL_MS = 10 * ONE_MINUTE_MS;
120
118
 
121
119
  // ── Time Window Constants ───────────────────────────────────────────────────────
122
120
 
@@ -6,7 +6,6 @@ export const READ_ONLY_TOOL_NAMES = [
6
6
  'resolve-library-id', 'get-library-docs',
7
7
  'memory_recall', 'save_memory', 'todo_read', 'todo_write',
8
8
  'ask_user', 'ask_user_question',
9
- 'deep_reflect',
10
9
  'pd-status', 'report',
11
10
  ] as const;
12
11
 
@@ -3,21 +3,6 @@ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { atomicWriteFileSync } from '../utils/io.js';
5
5
 
6
- export interface DeepReflectionSettings {
7
- enabled: boolean;
8
- mode: 'auto' | 'forced' | 'disabled';
9
- force_checkpoint?: boolean;
10
- checkpoint_message?: string;
11
- auto_trigger_conditions?: {
12
- min_tool_calls?: number;
13
- error_rate_threshold?: number;
14
- complexity_keywords?: string[];
15
- };
16
- default_model?: string;
17
- default_depth?: number;
18
- timeout_ms?: number;
19
- modelsDir?: string;
20
- }
21
6
 
22
7
  export interface GfiGateSettings {
23
8
  enabled: boolean;
@@ -83,7 +68,6 @@ export interface PainSettings {
83
68
  initial_delay_ms: number;
84
69
  task_timeout_ms: number;
85
70
  };
86
- deep_reflection?: DeepReflectionSettings;
87
71
  empathy_engine?: {
88
72
  enabled?: boolean;
89
73
  dedupe_window_ms?: number;
@@ -159,20 +143,6 @@ export const DEFAULT_SETTINGS: PainSettings = {
159
143
  initial_delay_ms: 5000,
160
144
  task_timeout_ms: 60 * 60 * 1000 // 1 hour, matching evolution-worker.ts default
161
145
  },
162
- deep_reflection: {
163
- enabled: true,
164
- mode: 'auto',
165
- force_checkpoint: true,
166
- 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',
167
- auto_trigger_conditions: {
168
- min_tool_calls: 5,
169
- error_rate_threshold: 0.3,
170
- complexity_keywords: ['refactor', 'architecture', 'design', 'optimize', 'security', 'critical']
171
- },
172
- default_model: 'T-01',
173
- default_depth: 2,
174
- timeout_ms: 60000
175
- },
176
146
  empathy_engine: {
177
147
  enabled: true,
178
148
  dedupe_window_ms: 60000,
@@ -16,7 +16,6 @@ import type {
16
16
  GateBypassEventData,
17
17
  PlanApprovalEventData,
18
18
  EvolutionTaskEventData,
19
- DeepReflectionEventData,
20
19
  EmpathyRollbackEventData,
21
20
  // C: New event data types
22
21
  DiagnosisTaskEventData,
@@ -178,11 +177,6 @@ export class EventLog {
178
177
  this.record('evolution_task', 'completed', undefined, data);
179
178
  }
180
179
 
181
- recordDeepReflection(sessionId: string | undefined, data: DeepReflectionEventData): void {
182
- const category = data.passed ? 'passed' : data.timeout ? 'failure' : 'completed';
183
- this.record('deep_reflection', category, sessionId, data);
184
- }
185
-
186
180
  recordEmpathyRollback(sessionId: string | undefined, data: EmpathyRollbackEventData): void {
187
181
  this.record('empathy_rollback', 'rolled_back', sessionId, data);
188
182
  }
@@ -789,7 +789,6 @@ ${taskBlocks}${processingNote}
789
789
  - **STOP** aggressive file modifications.
790
790
  - **START** every response with a sincere, non-defensive apology.
791
791
  - **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
792
- - Use 'deep_reflect' to analyze the root cause before proceeding with code changes.
793
792
  `;
794
793
  } else if (currentGfi >= 40) {
795
794
  attitudeDirective = `
@@ -839,18 +838,6 @@ ${taskBlocks}${processingNote}
839
838
  }
840
839
  }
841
840
 
842
- // Reflection Log (configurable) - moved to appendSystemContext for WebUI UX
843
- let reflectionLogContent = '';
844
- if (contextConfig.reflectionLog) {
845
- const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
846
- try {
847
- const cached = cachedReadFile(reflectionLogPath);
848
- if (cached) reflectionLogContent = cached.trim();
849
- } catch (e) {
850
- logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
851
- }
852
- }
853
-
854
841
  // Project Context (configurable: full/summary/off) - moved to appendSystemContext for WebUI UX
855
842
  let projectContextContent = '';
856
843
  let workingMemoryContent = '';
@@ -988,17 +975,12 @@ ${empathySilenceConstraint}
988
975
  appendParts.push(workingMemoryContent);
989
976
  }
990
977
 
991
- // 2. Reflection Log
992
- if (reflectionLogContent) {
993
- appendParts.push(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`);
994
- }
995
-
996
- // 3. Thinking OS (configurable)
978
+ // 2. Thinking OS (configurable)
997
979
  if (thinkingOsContent) {
998
980
  appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
999
981
  }
1000
982
 
1001
- // 4. Evolution Loop principles (active/probation)
983
+ // 3. Evolution Loop principles (active/probation)
1002
984
  if (evolutionPrinciplesContent) {
1003
985
  appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
1004
986
  }
@@ -1163,22 +1145,8 @@ ${attitudeDirective}
1163
1145
  }
1164
1146
  }
1165
1147
 
1166
- // 2. Truncate reflection_log if still over limit
1148
+ // 2. Final check
1167
1149
  let newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
1168
- if (newSize > MAX_SIZE && reflectionLogContent && appendSystemContext.includes('<reflection_log>')) {
1169
- const lines = reflectionLogContent.split('\n');
1170
- if (lines.length > 30) {
1171
- const truncated = lines.slice(0, 30).join('\n') + '\n...[truncated]';
1172
- appendSystemContext = appendSystemContext.replace(
1173
- `<reflection_log>\n${reflectionLogContent}\n</reflection_log>`,
1174
- `<reflection_log>\n${truncated}\n</reflection_log>`
1175
- );
1176
- truncationLog.push('reflection_log');
1177
- }
1178
- }
1179
-
1180
- // 3. Final check
1181
- newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
1182
1150
  if (newSize > MAX_SIZE) {
1183
1151
  // NOTE: We still return the content even if over limit, as truncating more
1184
1152
  // could lose critical context like principles or evolution directives.
@@ -6,7 +6,6 @@ import { extractAgentIdFromSessionKey } from '../utils/session-key.js';
6
6
  import { recordEvolutionSuccess } from '../core/evolution-engine.js';
7
7
  import { WorkflowStore } from '../service/subagent-workflow/workflow-store.js';
8
8
  import { EmpathyObserverWorkflowManager } from '../service/subagent-workflow/empathy-observer-workflow-manager.js';
9
- import { DeepReflectWorkflowManager } from '../service/subagent-workflow/deep-reflect-workflow-manager.js';
10
9
  import type { WorkflowManager } from '../service/subagent-workflow/types.js';
11
10
 
12
11
  /**
@@ -35,12 +34,6 @@ function createWorkflowManagerForType(
35
34
  logger: loggerAdapter,
36
35
  subagent,
37
36
  });
38
- case 'deep-reflect':
39
- return new DeepReflectWorkflowManager({
40
- workspaceDir,
41
- logger: loggerAdapter,
42
- subagent,
43
- });
44
37
  default:
45
38
  return null;
46
39
  }
package/src/index.ts CHANGED
@@ -55,7 +55,6 @@ import { CentralSyncService } from './service/central-sync-service.js';
55
55
  import { ensureWorkspaceTemplates } from './core/init.js';
56
56
  import { migrateDirectoryStructure } from './core/migration.js';
57
57
  import { SystemLogger } from './core/system-logger.js';
58
- import { createDeepReflectTool } from './tools/deep-reflect.js';
59
58
  import { createWritePainFlagTool } from './tools/write-pain-flag.js';
60
59
  import { PathResolver } from './core/path-resolver.js';
61
60
  import { createPrinciplesConsoleRoute } from './http/principles-console-route.js';
@@ -750,7 +749,6 @@ const plugin = {
750
749
  }
751
750
  });
752
751
 
753
- api.registerTool(createDeepReflectTool(api));
754
752
  api.registerTool(createWritePainFlagTool(api));
755
753
  }
756
754
  };
@@ -31,7 +31,6 @@ import { checkWorkspaceIdle, checkCooldown, recordCooldown } from './nocturnal-r
31
31
  import { loadCooldownEscalationConfig, loadNocturnalConfigMerged } from './nocturnal-config.js';
32
32
  import { WorkflowStore } from './subagent-workflow/workflow-store.js';
33
33
  import { EmpathyObserverWorkflowManager } from './subagent-workflow/empathy-observer-workflow-manager.js';
34
- import { DeepReflectWorkflowManager } from './subagent-workflow/deep-reflect-workflow-manager.js';
35
34
  import { NocturnalWorkflowManager, nocturnalWorkflowSpec } from './subagent-workflow/nocturnal-workflow-manager.js';
36
35
  import {
37
36
  createNocturnalTrajectoryExtractor,
@@ -2443,18 +2442,6 @@ export const EvolutionWorkerService: ExtendedEvolutionWorkerService = {
2443
2442
  empathyMgr.dispose();
2444
2443
  }
2445
2444
 
2446
- const deepReflectMgr = new DeepReflectWorkflowManager({
2447
- workspaceDir: wctx.workspaceDir,
2448
- logger: api.logger,
2449
- subagent: subagentRuntime,
2450
- agentSession,
2451
- });
2452
- try {
2453
- swept += await deepReflectMgr.sweepExpiredWorkflows(WORKFLOW_TTL_MS);
2454
- } finally {
2455
- deepReflectMgr.dispose();
2456
- }
2457
-
2458
2445
  // #183 + #188: Sweep Nocturnal workflows too (with gateway-safe fallback)
2459
2446
  try {
2460
2447
  const nocturnalMgr = new NocturnalWorkflowManager({
@@ -6,6 +6,7 @@ import { WorkspaceContext } from '../core/workspace-context.js';
6
6
  import { evaluatePhase3Inputs } from './phase3-input-filter.js';
7
7
  import { TrajectoryRegistry } from '../core/trajectory.js';
8
8
  import { getPendingDiagnosticianTasks } from '../core/diagnostician-task-store.js';
9
+ import type { WorkflowStage } from '../core/workflow-funnel-loader.js';
9
10
  import type { RuntimeTruth, AnalyticsTruth } from '../types/runtime-summary.js';
10
11
 
11
12
  export type RuntimeDataQuality = 'authoritative' | 'partial';
@@ -106,11 +107,15 @@ export interface RuntimeSummary {
106
107
  recentBypasses: number | null;
107
108
  dataQuality: RuntimeDataQuality;
108
109
  };
110
+ /** YAML-driven funnel counts — only present when funnels param is provided (YAML-SSOT-01) */
111
+ workflowFunnels?: WorkflowFunnelOutput[];
109
112
  metadata: {
110
113
  generatedAt: string;
111
114
  workspaceDir: string;
112
115
  sessionId: string | null;
113
116
  selectedSessionReason: 'explicit' | 'latest_active' | 'none';
117
+ /** 'degraded' when YAML load errors or unresolved statsField paths exist; 'ok' otherwise */
118
+ status: 'ok' | 'degraded';
114
119
  warnings: string[];
115
120
  };
116
121
  }
@@ -153,6 +158,23 @@ interface EventLogEntry {
153
158
  data?: Record<string, unknown>;
154
159
  }
155
160
 
161
+ // ─────────────────────────────────────────────────────────────────────────────
162
+ // Workflow Funnel Types (YAML-SSOT-01 / YAML-SSOT-02)
163
+ // ─────────────────────────────────────────────────────────────────────────────
164
+
165
+ export interface WorkflowFunnelStageOutput {
166
+ key: string;
167
+ label: string;
168
+ statsField: string;
169
+ count: number;
170
+ }
171
+
172
+ export interface WorkflowFunnelOutput {
173
+ funnelKey: string;
174
+ funnelLabel: string;
175
+ stages: WorkflowFunnelStageOutput[];
176
+ }
177
+
156
178
  const MAX_SOURCE_EVENTS = 5;
157
179
  const GFI_PARTIAL_WARNING =
158
180
  'GFI source attribution remains partial in Phase 2b because only the empathy slice is source-attributed; most non-empathy friction still lacks full per-source attribution.';
@@ -167,17 +189,30 @@ function pushWarning(warnings: string[], message: string): void {
167
189
  }
168
190
  }
169
191
 
192
+ /**
193
+ * YAML-SSOT-03: resolve a dot-path (e.g. 'evolution.nocturnalDreamerCompleted') from dailyStats.
194
+ * Returns { count, resolvable } to distinguish "field not found / non-numeric" from "legitimate zero".
195
+ */
196
+ function resolveStatsField(stats: unknown, dotPath: string): { count: number; resolvable: boolean } {
197
+ const parts = dotPath.split('.');
198
+ let current: unknown = stats;
199
+ for (const part of parts) {
200
+ if (current == null || typeof current !== 'object') return { count: 0, resolvable: false };
201
+ current = (current as Record<string, unknown>)[part];
202
+ }
203
+ if (typeof current === 'number') {
204
+ return { count: current, resolvable: true };
205
+ }
206
+ return { count: 0, resolvable: false };
207
+ }
208
+
170
209
  export class RuntimeSummaryService {
171
210
  static getSummary(
172
211
  workspaceDir: string,
173
- options?: { sessionId?: string | null; loaderWarnings?: string[] }
212
+ options?: { sessionId?: string | null; loaderWarnings?: string[]; funnels?: Map<string, WorkflowStage[]> }
174
213
  ): RuntimeSummary {
175
214
  const generatedAt = new Date().toISOString();
176
215
  const warnings: string[] = [];
177
- // ERR-01: surface loader warnings (YAML parse failures) into metadata.warnings
178
- if (options?.loaderWarnings) {
179
- warnings.push(...options.loaderWarnings);
180
- }
181
216
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
182
217
 
183
218
  const sessions = this.mergeSessionSnapshots(
@@ -215,11 +250,54 @@ export class RuntimeSummaryService {
215
250
  false
216
251
  );
217
252
 
218
- // Get most recent date from daily stats, fallback to today
219
- const today = generatedAt.slice(0, 10);
220
- const availableDates = Object.keys(dailyStats || {}).sort().reverse();
221
- const statsDate = availableDates.length > 0 ? availableDates[0] : today;
222
- const dailyGfiPeak = dailyStats?.[statsDate]?.gfi?.peak;
253
+ // Unified date for all "today"-scope reads in this summary — ensures funnel counts,
254
+ // heartbeat stats, and dailyStats all agree on the same date and never contradict.
255
+ const todayStr = generatedAt.slice(0, 10);
256
+
257
+ // YAML-SSOT-02/03: build workflowFunnels from funnels Map if provided
258
+ let workflowFunnelsOutput: WorkflowFunnelOutput[] | undefined;
259
+ if (options?.funnels) {
260
+ workflowFunnelsOutput = [];
261
+ for (const [funnelKey, stages] of options.funnels) {
262
+ const stageOutputs: WorkflowFunnelStageOutput[] = stages.map(stage => {
263
+ const { count, resolvable } = resolveStatsField(dailyStats?.[todayStr], stage.statsField);
264
+ return {
265
+ key: stage.name,
266
+ label: stage.name,
267
+ statsField: stage.statsField,
268
+ count,
269
+ _resolvable: resolvable, // internal tag for degraded detection
270
+ };
271
+ });
272
+ workflowFunnelsOutput.push({ funnelKey, funnelLabel: funnelKey, stages: stageOutputs });
273
+ }
274
+ }
275
+
276
+ // YAML-SSOT-04: warn for any unresolvable statsField paths
277
+ let hasUnresolvableStage = false;
278
+ if (workflowFunnelsOutput) {
279
+ for (const funnel of workflowFunnelsOutput) {
280
+ for (const stage of funnel.stages) {
281
+ if (!(stage as { _resolvable?: boolean })._resolvable) {
282
+ hasUnresolvableStage = true;
283
+ pushWarning(warnings, `statsField not resolvable: ${stage.statsField}`);
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ // DEGRADED-01/02: status is degraded when YAML load errors or unresolvable statsField paths exist
290
+ const loaderWarnings = options?.loaderWarnings ?? [];
291
+ if (loaderWarnings.length > 0) {
292
+ for (const w of loaderWarnings) {
293
+ pushWarning(warnings, `YAML load warning: ${w}`);
294
+ }
295
+ }
296
+ const status: 'ok' | 'degraded' =
297
+ (loaderWarnings.length > 0 || hasUnresolvableStage) ? 'degraded' : 'ok';
298
+
299
+ // GFI peak — use today's data (consistent with funnel/heartbeat)
300
+ const dailyGfiPeak = dailyStats?.[todayStr]?.gfi?.peak;
223
301
 
224
302
  const gfiCurrent =
225
303
  selectedSession.session && Number.isFinite(selectedSession.session.currentGfi)
@@ -269,7 +347,6 @@ export class RuntimeSummaryService {
269
347
  // Read pending tasks from the diagnostician task store
270
348
  const pendingDiagTasks = getPendingDiagnosticianTasks(wctx.stateDir);
271
349
  // Read heartbeat diagnosis stats from daily event log
272
- const todayStr = generatedAt.slice(0, 10);
273
350
  const diagDailyStats = dailyStats?.[todayStr]?.evolution;
274
351
  const heartbeatDiagnosis = {
275
352
  pendingTasks: pendingDiagTasks.length,
@@ -310,10 +387,10 @@ export class RuntimeSummaryService {
310
387
  lastUpdated: trajectoryStats.lastIngestAt ?? generatedAt,
311
388
  },
312
389
  dailyStats: {
313
- toolCalls: dailyStats?.[statsDate]?.toolCalls ?? 0,
314
- painSignals: dailyStats?.[statsDate]?.painSignals ?? 0,
315
- evolutionTasks: dailyStats?.[statsDate]?.evolutionTasks ?? 0,
316
- lastUpdated: statsDate,
390
+ toolCalls: dailyStats?.[todayStr]?.toolCalls ?? 0,
391
+ painSignals: dailyStats?.[todayStr]?.painSignals ?? 0,
392
+ evolutionTasks: dailyStats?.[todayStr]?.evolutionTasks ?? 0,
393
+ lastUpdated: todayStr,
317
394
  },
318
395
  trends: {
319
396
  sevenDay: { successRateChange: 0, toolCallVolumeChange: 0, painSignalRateChange: 0 },
@@ -360,11 +437,13 @@ export class RuntimeSummaryService {
360
437
  gate: gateStats,
361
438
  // D: Heartbeat Diagnostician chain — separate from evolution/nocturnal
362
439
  heartbeatDiagnosis,
440
+ ...(workflowFunnelsOutput && { workflowFunnels: workflowFunnelsOutput }),
363
441
  metadata: {
364
442
  generatedAt,
365
443
  workspaceDir,
366
444
  sessionId: selectedSessionId,
367
445
  selectedSessionReason: selectedSession.reason,
446
+ status,
368
447
  warnings,
369
448
  },
370
449
  };