principles-disciple 1.60.0 → 1.61.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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/core/diagnostician-task-store.ts +38 -1
- package/src/core/event-log.ts +21 -3
- package/src/service/evolution-worker.ts +89 -9
- package/src/service/runtime-summary-service.ts +8 -0
- package/src/types/event-types.ts +10 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +98 -15
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -37,6 +37,8 @@ export interface DiagnosticianTask {
|
|
|
37
37
|
prompt: string;
|
|
38
38
|
createdAt: string;
|
|
39
39
|
status: 'pending' | 'completed';
|
|
40
|
+
/** Number of times task was retried due to marker exists but JSON report missing (#366) */
|
|
41
|
+
reportMissingRetries?: number;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export interface DiagnosticianTaskStore {
|
|
@@ -86,10 +88,12 @@ export async function addDiagnosticianTask(
|
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
const store = readTaskStoreSync(filePath);
|
|
91
|
+
const existing = store.tasks[taskId];
|
|
89
92
|
store.tasks[taskId] = {
|
|
90
93
|
prompt,
|
|
91
|
-
createdAt: new Date().toISOString(),
|
|
94
|
+
createdAt: existing?.createdAt ?? new Date().toISOString(),
|
|
92
95
|
status: 'pending',
|
|
96
|
+
reportMissingRetries: existing?.reportMissingRetries ?? 0,
|
|
93
97
|
};
|
|
94
98
|
atomicWriteFileSync(filePath, JSON.stringify(store, null, 2));
|
|
95
99
|
});
|
|
@@ -153,3 +157,36 @@ export function hasPendingDiagnosticianTasks(stateDir: string): boolean {
|
|
|
153
157
|
const store = readTaskStore(stateDir);
|
|
154
158
|
return Object.values(store.tasks).some(t => t.status === 'pending');
|
|
155
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Re-queue a diagnostician task with an incremented reportMissingRetries counter.
|
|
163
|
+
* Used when a task has a marker file but no JSON report — the worker re-injects
|
|
164
|
+
* the task for the LLM to retry (up to MAX_REPORT_MISSING_RETRIES times).
|
|
165
|
+
*
|
|
166
|
+
* Idempotent: if the task doesn't exist, does nothing.
|
|
167
|
+
*/
|
|
168
|
+
export async function requeueDiagnosticianTask(
|
|
169
|
+
stateDir: string,
|
|
170
|
+
taskId: string,
|
|
171
|
+
maxRetries = 3,
|
|
172
|
+
): Promise<{ requeued: boolean; maxRetriesReached: boolean }> {
|
|
173
|
+
const filePath = resolveTasksPath(stateDir);
|
|
174
|
+
return withLockAsync(filePath, async () => {
|
|
175
|
+
const store = readTaskStoreSync(filePath);
|
|
176
|
+
const existing = store.tasks[taskId];
|
|
177
|
+
if (!existing) {
|
|
178
|
+
return { requeued: false, maxRetriesReached: false };
|
|
179
|
+
}
|
|
180
|
+
const retries = (existing.reportMissingRetries ?? 0) + 1;
|
|
181
|
+
if (retries > maxRetries) {
|
|
182
|
+
return { requeued: false, maxRetriesReached: true };
|
|
183
|
+
}
|
|
184
|
+
store.tasks[taskId] = {
|
|
185
|
+
...existing,
|
|
186
|
+
status: 'pending',
|
|
187
|
+
reportMissingRetries: retries,
|
|
188
|
+
};
|
|
189
|
+
atomicWriteFileSync(filePath, JSON.stringify(store, null, 2));
|
|
190
|
+
return { requeued: true, maxRetriesReached: false };
|
|
191
|
+
});
|
|
192
|
+
}
|
package/src/core/event-log.ts
CHANGED
|
@@ -197,7 +197,14 @@ export class EventLog {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
recordDiagnosticianReport(data: DiagnosticianReportEventData): void {
|
|
200
|
-
|
|
200
|
+
// Map three-state category to EventCategory
|
|
201
|
+
// Both missing_json and incomplete_fields map to 'failure' in EventCategory
|
|
202
|
+
const categoryMap: Record<DiagnosticianReportEventData['category'], EventCategory> = {
|
|
203
|
+
success: 'completed',
|
|
204
|
+
missing_json: 'failure',
|
|
205
|
+
incomplete_fields: 'failure',
|
|
206
|
+
};
|
|
207
|
+
this.record('diagnostician_report', categoryMap[data.category], undefined, data);
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
recordPrincipleCandidate(data: PrincipleCandidateEventData): void {
|
|
@@ -356,8 +363,19 @@ export class EventLog {
|
|
|
356
363
|
} else if (entry.type === 'heartbeat_diagnosis') {
|
|
357
364
|
stats.evolution.heartbeatsInjected++;
|
|
358
365
|
} else if (entry.type === 'diagnostician_report') {
|
|
359
|
-
|
|
360
|
-
|
|
366
|
+
const data = entry.data as unknown as DiagnosticianReportEventData;
|
|
367
|
+
// Backward compat: handle old events with success:boolean and new events with category:string
|
|
368
|
+
if ('category' in data) {
|
|
369
|
+
// New format: category is 'success' | 'missing_json' | 'incomplete_fields'
|
|
370
|
+
if (data.category === 'success' || data.category === 'incomplete_fields') {
|
|
371
|
+
stats.evolution.diagnosticianReportsWritten++;
|
|
372
|
+
}
|
|
373
|
+
if (data.category === 'missing_json') {
|
|
374
|
+
stats.evolution.reportsMissingJson++;
|
|
375
|
+
}
|
|
376
|
+
if (data.category === 'incomplete_fields') {
|
|
377
|
+
stats.evolution.reportsIncompleteFields++;
|
|
378
|
+
}
|
|
361
379
|
}
|
|
362
380
|
} else if (entry.type === 'principle_candidate') {
|
|
363
381
|
stats.evolution.principleCandidatesCreated++;
|
|
@@ -12,7 +12,7 @@ import { SystemLogger } from '../core/system-logger.js';
|
|
|
12
12
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
13
13
|
import type { EventLog } from '../core/event-log.js';
|
|
14
14
|
import { initPersistence, flushAllSessions } from '../core/session-tracker.js';
|
|
15
|
-
import { addDiagnosticianTask, completeDiagnosticianTask } from '../core/diagnostician-task-store.js';
|
|
15
|
+
import { addDiagnosticianTask, completeDiagnosticianTask, requeueDiagnosticianTask } from '../core/diagnostician-task-store.js';
|
|
16
16
|
import { getEvolutionLogger } from '../core/evolution-logger.js';
|
|
17
17
|
import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
|
|
18
18
|
import type { PrincipleEvaluability } from '../types/principle-tree-schema.js';
|
|
@@ -923,12 +923,49 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
923
923
|
|
|
924
924
|
let principlesGenerated = 0;
|
|
925
925
|
// C: Track report success for event recording
|
|
926
|
+
// FIX: Use reportParsed flag so reportSuccess=false when JSON is missing/garbled
|
|
926
927
|
let reportSuccess = false;
|
|
928
|
+
let reportParsed = false;
|
|
927
929
|
// Create principle from the diagnostician's JSON report.
|
|
928
930
|
const reportPath = path.join(wctx.stateDir, `.diagnostician_report_${task.id}.json`);
|
|
929
931
|
if (fs.existsSync(reportPath)) {
|
|
930
932
|
try {
|
|
931
|
-
const
|
|
933
|
+
const raw = fs.readFileSync(reportPath, 'utf8');
|
|
934
|
+
if (!raw || raw.trim().length === 0) {
|
|
935
|
+
throw new Error('Report file is empty');
|
|
936
|
+
}
|
|
937
|
+
const reportData = JSON.parse(raw);
|
|
938
|
+
if (!reportData) {
|
|
939
|
+
throw new Error('JSON parsed but content is null/undefined');
|
|
940
|
+
}
|
|
941
|
+
// Report is valid JSON — mark as parsed
|
|
942
|
+
reportParsed = true;
|
|
943
|
+
|
|
944
|
+
// FIX: Validate phase completeness before accepting the report
|
|
945
|
+
// A report missing critical phases is considered failed (not silently accepted).
|
|
946
|
+
// The diagnostician must produce all 4 diagnostic phases.
|
|
947
|
+
const phases = reportData?.phases || reportData?.diagnosis_report?.phases || {};
|
|
948
|
+
const requiredPhases = [
|
|
949
|
+
'evidence_gathering',
|
|
950
|
+
'causal_chain',
|
|
951
|
+
'root_cause_classification',
|
|
952
|
+
'principle_extraction',
|
|
953
|
+
];
|
|
954
|
+
const presentPhases = requiredPhases.filter(p =>
|
|
955
|
+
phases && Object.keys(phases).length > 0 && phases[p]
|
|
956
|
+
);
|
|
957
|
+
if (presentPhases.length < requiredPhases.length) {
|
|
958
|
+
const missing = requiredPhases.filter(p => !phases[p]);
|
|
959
|
+
if (logger) logger.warn(`[PD:EvolutionWorker] Report for task ${task.id} incomplete — missing phases: ${missing.join(', ')} (present: ${presentPhases.length}/${requiredPhases.length})`);
|
|
960
|
+
// Treat as retryable failure: don't mark success, let retry logic kick in
|
|
961
|
+
reportParsed = false;
|
|
962
|
+
// Also delete the incomplete marker so next heartbeat re-runs the diagnostician
|
|
963
|
+
try { fs.unlinkSync(completeMarker); } catch { /* ignore if already gone */ }
|
|
964
|
+
task.status = 'pending';
|
|
965
|
+
task.resolution = undefined;
|
|
966
|
+
queueChanged = true;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
932
969
|
|
|
933
970
|
// ── Step 3: Noise Classification Filter ──
|
|
934
971
|
// Skip principle creation for low-value noise categories that don't represent
|
|
@@ -1048,15 +1085,52 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1048
1085
|
} catch (err) {
|
|
1049
1086
|
logger.warn(`[PD:EvolutionWorker] Failed to parse diagnostician report for task ${task.id}: ${String(err)}`);
|
|
1050
1087
|
}
|
|
1051
|
-
//
|
|
1052
|
-
reportSuccess
|
|
1088
|
+
// FIX: Only mark success if JSON was actually parsed and non-empty
|
|
1089
|
+
// If JSON was missing, garbled, or empty — reportSuccess stays false
|
|
1090
|
+
reportSuccess = reportParsed;
|
|
1053
1091
|
} else {
|
|
1054
|
-
|
|
1092
|
+
// ── #366: Marker exists but JSON report missing — retry logic ──
|
|
1093
|
+
// Do NOT mark completed yet. Re-inject the task for the next heartbeat cycle.
|
|
1094
|
+
// Read retry count from marker file content.
|
|
1095
|
+
const MAX_REPORT_MISSING_RETRIES = 3;
|
|
1096
|
+
let markerRetries = 0;
|
|
1097
|
+
try {
|
|
1098
|
+
const markerContent = fs.readFileSync(completeMarker, 'utf8');
|
|
1099
|
+
const match = markerContent.match(/report_missing_retries:(\d+)/);
|
|
1100
|
+
if (match) markerRetries = parseInt(match[1], 10);
|
|
1101
|
+
} catch { /* marker may not be readable, use 0 */ }
|
|
1102
|
+
|
|
1103
|
+
if (markerRetries < MAX_REPORT_MISSING_RETRIES) {
|
|
1104
|
+
// Re-inject: keep task in queue (don't mark completed), update marker with incremented count
|
|
1105
|
+
const newRetries = markerRetries + 1;
|
|
1106
|
+
if (logger) logger.info(`[PD:EvolutionWorker] Task ${task.id}: marker found but report missing — re-queuing (retry ${newRetries}/${MAX_REPORT_MISSING_RETRIES})`);
|
|
1107
|
+
// FIX: Update store's reportMissingRetries BEFORE deleting the marker.
|
|
1108
|
+
// This ensures the store's retry count is persisted even if the
|
|
1109
|
+
// diagnostician session crashes before re-adding the task.
|
|
1110
|
+
await requeueDiagnosticianTask(wctx.stateDir, task.id, MAX_REPORT_MISSING_RETRIES);
|
|
1111
|
+
// Also update the task in the main queue to keep it alive
|
|
1112
|
+
task.status = 'pending';
|
|
1113
|
+
task.resolution = undefined;
|
|
1114
|
+
queueChanged = true;
|
|
1115
|
+
// Delete the marker so the next heartbeat sees no marker
|
|
1116
|
+
// and re-processes the task as a fresh diagnostician run.
|
|
1117
|
+
try {
|
|
1118
|
+
fs.unlinkSync(completeMarker);
|
|
1119
|
+
} catch { /* ignore if already deleted */ }
|
|
1120
|
+
// Skip the completion/unlink block below — task is still pending
|
|
1121
|
+
continue;
|
|
1122
|
+
} else {
|
|
1123
|
+
// Max retries reached — accept that no report was produced
|
|
1124
|
+
if (logger) logger.warn(`[PD:EvolutionWorker] Task ${task.id}: max retries (${MAX_REPORT_MISSING_RETRIES}) reached — marking as failed_max_retries`);
|
|
1125
|
+
task.status = 'completed';
|
|
1126
|
+
task.completed_at = new Date().toISOString();
|
|
1127
|
+
task.resolution = 'failed_max_retries';
|
|
1128
|
+
}
|
|
1055
1129
|
}
|
|
1056
1130
|
|
|
1057
|
-
|
|
1058
|
-
task.
|
|
1059
|
-
|
|
1131
|
+
// Only reached if JSON existed or max retries reached:
|
|
1132
|
+
task.status = task.status || 'completed';
|
|
1133
|
+
task.completed_at = task.completed_at || new Date().toISOString();
|
|
1060
1134
|
if (!task.resolution) task.resolution = 'marker_detected';
|
|
1061
1135
|
try {
|
|
1062
1136
|
fs.unlinkSync(completeMarker);
|
|
@@ -1073,10 +1147,16 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1073
1147
|
|
|
1074
1148
|
// C: Record diagnostician_report event for observability
|
|
1075
1149
|
if (eventLog) {
|
|
1150
|
+
// Map to three-state category:
|
|
1151
|
+
// - reportSuccess=true → 'success' (JSON exists, parsed, principle found)
|
|
1152
|
+
// - reportSuccess=false, reportParsed=true → 'incomplete_fields' (JSON existed but principle missing)
|
|
1153
|
+
// - reportSuccess=false, reportParsed=false → 'missing_json' (JSON never existed)
|
|
1154
|
+
const reportCategory: 'success' | 'missing_json' | 'incomplete_fields' =
|
|
1155
|
+
reportSuccess ? 'success' : reportParsed ? 'incomplete_fields' : 'missing_json';
|
|
1076
1156
|
eventLog.recordDiagnosticianReport({
|
|
1077
1157
|
taskId: task.id,
|
|
1078
1158
|
reportPath,
|
|
1079
|
-
|
|
1159
|
+
category: reportCategory,
|
|
1080
1160
|
});
|
|
1081
1161
|
}
|
|
1082
1162
|
|
|
@@ -69,6 +69,10 @@ export interface RuntimeSummary {
|
|
|
69
69
|
tasksWrittenToday: number;
|
|
70
70
|
/** Total diagnostician reports written (today from event log) */
|
|
71
71
|
reportsWrittenToday: number;
|
|
72
|
+
/** Total diagnostician reports that were missing JSON (category=missing_json) */
|
|
73
|
+
reportsMissingJsonToday: number;
|
|
74
|
+
/** Total diagnostician reports with incomplete fields (category=incomplete_fields) */
|
|
75
|
+
reportsIncompleteFieldsToday: number;
|
|
72
76
|
/** Total principle candidates created from heartbeat chain (today from event log) */
|
|
73
77
|
candidatesCreatedToday: number;
|
|
74
78
|
/** Heartbeats that injected diagnostician tasks (today from event log) */
|
|
@@ -194,6 +198,8 @@ export class RuntimeSummaryService {
|
|
|
194
198
|
evolution?: {
|
|
195
199
|
diagnosisTasksWritten?: number;
|
|
196
200
|
diagnosticianReportsWritten?: number;
|
|
201
|
+
reportsMissingJson?: number;
|
|
202
|
+
reportsIncompleteFields?: number;
|
|
197
203
|
principleCandidatesCreated?: number;
|
|
198
204
|
heartbeatsInjected?: number;
|
|
199
205
|
[key: string]: unknown;
|
|
@@ -265,6 +271,8 @@ export class RuntimeSummaryService {
|
|
|
265
271
|
pendingTasks: pendingDiagTasks.length,
|
|
266
272
|
tasksWrittenToday: diagDailyStats?.diagnosisTasksWritten ?? 0,
|
|
267
273
|
reportsWrittenToday: diagDailyStats?.diagnosticianReportsWritten ?? 0,
|
|
274
|
+
reportsMissingJsonToday: diagDailyStats?.reportsMissingJson ?? 0,
|
|
275
|
+
reportsIncompleteFieldsToday: diagDailyStats?.reportsIncompleteFields ?? 0,
|
|
268
276
|
candidatesCreatedToday: diagDailyStats?.principleCandidatesCreated ?? 0,
|
|
269
277
|
heartbeatsInjectedToday: diagDailyStats?.heartbeatsInjected ?? 0,
|
|
270
278
|
};
|
package/src/types/event-types.ts
CHANGED
|
@@ -209,7 +209,12 @@ export interface DiagnosisTaskEventData {
|
|
|
209
209
|
export interface DiagnosticianReportEventData {
|
|
210
210
|
taskId: string;
|
|
211
211
|
reportPath: string;
|
|
212
|
-
success
|
|
212
|
+
/** Three-state category replacing boolean success field.
|
|
213
|
+
* - 'success': JSON exists and has principle field
|
|
214
|
+
* - 'missing_json': marker exists but JSON does not (Issue #366, LLM output truncation)
|
|
215
|
+
* - 'incomplete_fields': JSON exists but missing principle field
|
|
216
|
+
*/
|
|
217
|
+
category: 'success' | 'missing_json' | 'incomplete_fields';
|
|
213
218
|
}
|
|
214
219
|
|
|
215
220
|
/**
|
|
@@ -326,6 +331,8 @@ export interface EvolutionStats {
|
|
|
326
331
|
diagnosisTasksWritten: number;
|
|
327
332
|
heartbeatsInjected: number;
|
|
328
333
|
diagnosticianReportsWritten: number;
|
|
334
|
+
reportsMissingJson: number;
|
|
335
|
+
reportsIncompleteFields: number;
|
|
329
336
|
principleCandidatesCreated: number;
|
|
330
337
|
rulesEnforced: number;
|
|
331
338
|
}
|
|
@@ -490,6 +497,8 @@ export function createEmptyDailyStats(date: string): DailyStats {
|
|
|
490
497
|
diagnosisTasksWritten: 0,
|
|
491
498
|
heartbeatsInjected: 0,
|
|
492
499
|
diagnosticianReportsWritten: 0,
|
|
500
|
+
reportsMissingJson: 0,
|
|
501
|
+
reportsIncompleteFields: 0,
|
|
493
502
|
principleCandidatesCreated: 0,
|
|
494
503
|
rulesEnforced: 0,
|
|
495
504
|
},
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Diagnostician - Root Cause Analysis Agent
|
|
8
8
|
|
|
9
|
-
You are a professional root cause analysis expert. You MUST strictly follow the **
|
|
9
|
+
You are a professional root cause analysis expert. You MUST strictly follow the **six-phase protocol** (Phase 0 optional + Phase 1-5 mandatory) below to execute analysis and **immediately write results to the report file after each Phase completes**.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -106,6 +106,20 @@ You are a professional root cause analysis expert. You MUST strictly follow the
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
**⚠️ Write Report File Immediately After Phase 1**:
|
|
110
|
+
Once Phase 1 is complete, **immediately** write the result to the report file (do NOT wait until the end):
|
|
111
|
+
```
|
|
112
|
+
write: .state/.diagnostician_report_<TASK_ID>.json
|
|
113
|
+
content: {
|
|
114
|
+
"taskId": "<TASK_ID>",
|
|
115
|
+
"completedAt": "<ISO timestamp>",
|
|
116
|
+
"phases": {
|
|
117
|
+
"evidence_gathering": { ...Phase 1 result... }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
If the file already exists (a previous Phase was already written), read the existing content, merge the new Phase result into it, then overwrite.
|
|
122
|
+
|
|
109
123
|
---
|
|
110
124
|
|
|
111
125
|
### Phase 2: Causal Chain Construction [Required]
|
|
@@ -145,6 +159,9 @@ You are a professional root cause analysis expert. You MUST strictly follow the
|
|
|
145
159
|
}
|
|
146
160
|
```
|
|
147
161
|
|
|
162
|
+
**⚠️ Write Report File Immediately After Phase 2**:
|
|
163
|
+
Once Phase 2 is complete, **immediately** merge the result into the report file (overwrite, do not lose Phase 1 content).
|
|
164
|
+
|
|
148
165
|
---
|
|
149
166
|
|
|
150
167
|
### Phase 3: Root Cause Classification [Required]
|
|
@@ -178,6 +195,9 @@ You are a professional root cause analysis expert. You MUST strictly follow the
|
|
|
178
195
|
}
|
|
179
196
|
```
|
|
180
197
|
|
|
198
|
+
**⚠️ Write Report File Immediately After Phase 3**:
|
|
199
|
+
Once Phase 3 is complete, **immediately** merge the result into the report file.
|
|
200
|
+
|
|
181
201
|
---
|
|
182
202
|
|
|
183
203
|
### Phase 4: Principle Extraction [Required]
|
|
@@ -264,9 +284,32 @@ You are a professional root cause analysis expert. You MUST strictly follow the
|
|
|
264
284
|
- "External dependency availability must be validated before invocation"
|
|
265
285
|
- "Code modifications must go through Issue process, ensuring traceability and rollback"
|
|
266
286
|
|
|
267
|
-
**
|
|
268
|
-
|
|
269
|
-
|
|
287
|
+
**Phase 4 Output Fields** (also write immediately after completing Phase 4 — merge with previous Phases):
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"taskId": "<TASK_ID>",
|
|
291
|
+
"completedAt": "<ISO timestamp>",
|
|
292
|
+
"phases": {
|
|
293
|
+
"context_extraction": { ... Phase 0 result ... },
|
|
294
|
+
"evidence_gathering": { ... Phase 1 result ... },
|
|
295
|
+
"causal_chain": { ... Phase 2 result ... },
|
|
296
|
+
"root_cause_classification": { ... Phase 3 result ... },
|
|
297
|
+
"principle_extraction": {
|
|
298
|
+
"phase": "principle_extraction",
|
|
299
|
+
"classification": {
|
|
300
|
+
"category": "development_transient|user_error|Design|Tooling|...",
|
|
301
|
+
"confidence": "high|medium|low",
|
|
302
|
+
"reproducible": true|false,
|
|
303
|
+
"severity": "high|medium|low"
|
|
304
|
+
},
|
|
305
|
+
"principle": { ... }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**⚠️ Write Report File Immediately After Phase 4**:
|
|
312
|
+
Once Phase 4 is complete, **immediately** merge the result (with `classification` and `principle`) into the report file. This is the final write — all Phases must now be present.
|
|
270
313
|
|
|
271
314
|
---
|
|
272
315
|
|
|
@@ -285,20 +328,27 @@ Your diagnostic report will be **auto-parsed as JSON**. Any format errors will c
|
|
|
285
328
|
|
|
286
329
|
**Self-check method**: Before outputting, mentally verify: every `"` must have matching `"` after it, if content contains `"` it must be escaped as `\"`.
|
|
287
330
|
|
|
288
|
-
|
|
331
|
+
The final report (written incrementally by each Phase) should look like:
|
|
289
332
|
|
|
290
333
|
```json
|
|
291
334
|
{
|
|
292
|
-
"
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"
|
|
296
|
-
"
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"
|
|
301
|
-
"
|
|
335
|
+
"taskId": "<TASK_ID>",
|
|
336
|
+
"completedAt": "2026-03-24T10:30:00Z",
|
|
337
|
+
"phases": {
|
|
338
|
+
"context_extraction": { "phase": "context_extraction", "session_id": "...", "context_source": "...", "conversation_summary": "..." },
|
|
339
|
+
"evidence_gathering": { "phase": "evidence_gathering", "evidence": { ... } },
|
|
340
|
+
"causal_chain": { "phase": "causal_chain", "chain": [...], "terminated_at": 3, "termination_reason": "..." },
|
|
341
|
+
"root_cause_classification": { "phase": "root_cause_classification", "root_cause": "...", "category": "Design", "guardrail_analysis": { ... } },
|
|
342
|
+
"principle_extraction": {
|
|
343
|
+
"phase": "principle_extraction",
|
|
344
|
+
"classification": { "category": "Design", "confidence": "high", "reproducible": false, "severity": "low" },
|
|
345
|
+
"principle": {
|
|
346
|
+
"trigger_pattern": "...",
|
|
347
|
+
"action": "...",
|
|
348
|
+
"abstracted_principle": "...",
|
|
349
|
+
"duplicate": false,
|
|
350
|
+
"coreAxiomId": "T-02"
|
|
351
|
+
}
|
|
302
352
|
}
|
|
303
353
|
}
|
|
304
354
|
}
|
|
@@ -306,6 +356,38 @@ Merge outputs from all four phases into one JSON object:
|
|
|
306
356
|
|
|
307
357
|
---
|
|
308
358
|
|
|
359
|
+
## ✅ Completion Protocol
|
|
360
|
+
|
|
361
|
+
### ✅ Checklist (ALL must be satisfied before writing marker)
|
|
362
|
+
|
|
363
|
+
Before writing the marker file, you MUST confirm all of the following:
|
|
364
|
+
|
|
365
|
+
1. **Report file exists**: `.diagnostician_report_<TASK_ID>.json` has been written to disk
|
|
366
|
+
2. **All Phase fields present**:
|
|
367
|
+
- [ ] `phases.context_extraction` ✅
|
|
368
|
+
- [ ] `phases.evidence_gathering` ✅
|
|
369
|
+
- [ ] `phases.causal_chain` ✅
|
|
370
|
+
- [ ] `phases.root_cause_classification` ✅
|
|
371
|
+
- [ ] `phases.principle_extraction` ✅
|
|
372
|
+
3. **Report is valid JSON**: Use read tool to verify file content parses correctly
|
|
373
|
+
|
|
374
|
+
### ✅ Write Marker (Final Step)
|
|
375
|
+
|
|
376
|
+
**ONLY after confirming all conditions above are satisfied**, write the marker file:
|
|
377
|
+
```
|
|
378
|
+
write: .state/.evolution_complete_<TASK_ID>
|
|
379
|
+
content: diagnostic_completed: <ISO timestamp>
|
|
380
|
+
outcome: <one-sentence summary>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### ❌ Forbidden
|
|
384
|
+
|
|
385
|
+
- **NEVER write marker before JSON** — marker means diagnosis is complete, JSON report must exist
|
|
386
|
+
- **NEVER skip any Phase** — even if a Phase seems inapplicable, write empty `{}`
|
|
387
|
+
- **NEVER use non-ASCII quotes in JSON** — must use `"`, not `"` `"`
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
309
391
|
## ⚠️ Execution Constraints
|
|
310
392
|
|
|
311
393
|
1. **NO skipping phases**: MUST attempt Phase 0 (context acquisition), then execute Phase 1 → 2 → 3 → 4 in order
|
|
@@ -313,6 +395,7 @@ Merge outputs from all four phases into one JSON object:
|
|
|
313
395
|
3. **NO vague conclusions**: Root cause must be specific and fixable
|
|
314
396
|
4. **NO skipping principle extraction**: Even for simple issues, extract principles
|
|
315
397
|
5. **NO skipping deduplication**: `duplicate` field MUST appear in principle_extraction output
|
|
398
|
+
6. **NO writing marker before all Phases complete**: Marker comes LAST, only after every Phase is written to JSON
|
|
316
399
|
|
|
317
400
|
---
|
|
318
401
|
|