principles-disciple 1.54.0 → 1.56.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 +2 -1
- package/src/core/event-log.ts +33 -0
- package/src/core/evolution-types.ts +1 -1
- package/src/core/observability.ts +1 -1
- package/src/core/pain-context-extractor.ts +12 -7
- package/src/service/event-log-auditor.ts +10 -2
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +31 -1
- package/src/tools/write-pain-flag.ts +0 -1
- package/tests/core/event-log.test.ts +74 -0
- package/tests/core/pain-context-extractor.test.ts +2 -1
- package/tests/core/pain-lifecycle.test.ts +2 -1
- package/tests/integration/pain-lifecycle-e2e.test.ts +2 -1
- package/tests/commands/evolver.test.ts +0 -22
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "principles-disciple",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "Native OpenClaw plugin for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/bundle.js",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
+
"@principles/core": "^0.1.0",
|
|
70
71
|
"@sinclair/typebox": "^0.34.48",
|
|
71
72
|
"better-sqlite3": "^12.9.0",
|
|
72
73
|
"lucide-react": "^1.7.0",
|
package/src/core/event-log.ts
CHANGED
|
@@ -51,6 +51,9 @@ export class EventLog {
|
|
|
51
51
|
private currentEventsFile: string | undefined;
|
|
52
52
|
private currentDate: string | undefined;
|
|
53
53
|
|
|
54
|
+
// Pain score sum per date (for avgScore calculation)
|
|
55
|
+
private readonly painScoreSums: Map<string, number> = new Map();
|
|
56
|
+
|
|
54
57
|
constructor(stateDir: string, logger?: PluginLogger) {
|
|
55
58
|
this.logsDir = path.join(stateDir, 'logs');
|
|
56
59
|
if (!fs.existsSync(this.logsDir)) {
|
|
@@ -156,6 +159,10 @@ export class EventLog {
|
|
|
156
159
|
recordEvolutionTask(data: EvolutionTaskEventData): void {
|
|
157
160
|
this.record('evolution_task', 'enqueued', undefined, data);
|
|
158
161
|
}
|
|
162
|
+
|
|
163
|
+
recordEvolutionTaskCompleted(data: EvolutionTaskEventData): void {
|
|
164
|
+
this.record('evolution_task', 'completed', undefined, data);
|
|
165
|
+
}
|
|
159
166
|
|
|
160
167
|
recordDeepReflection(sessionId: string | undefined, data: DeepReflectionEventData): void {
|
|
161
168
|
const category = data.passed ? 'passed' : data.timeout ? 'failure' : 'completed';
|
|
@@ -238,6 +245,18 @@ export class EventLog {
|
|
|
238
245
|
stats.pain.signalsDetected++;
|
|
239
246
|
stats.pain.maxScore = Math.max(stats.pain.maxScore, data.score);
|
|
240
247
|
|
|
248
|
+
// Track signals by source
|
|
249
|
+
if (data.source) {
|
|
250
|
+
stats.pain.signalsBySource[data.source] = (stats.pain.signalsBySource[data.source] || 0) + 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Accumulate score for avg calculation
|
|
254
|
+
const currentSum = this.painScoreSums.get(entry.date) ?? 0;
|
|
255
|
+
this.painScoreSums.set(entry.date, currentSum + (data.score || 0));
|
|
256
|
+
stats.pain.avgScore = stats.pain.signalsDetected > 0
|
|
257
|
+
? Math.round((currentSum + (data.score || 0)) / stats.pain.signalsDetected)
|
|
258
|
+
: 0;
|
|
259
|
+
|
|
241
260
|
// Update empathy stats for user_empathy source
|
|
242
261
|
if (data.source === 'user_empathy') {
|
|
243
262
|
if (data.deduped) {
|
|
@@ -291,6 +310,20 @@ export class EventLog {
|
|
|
291
310
|
const data = entry.data as unknown as EmpathyRollbackEventData;
|
|
292
311
|
stats.empathy.rollbackCount++;
|
|
293
312
|
stats.empathy.rolledBackScore += data.originalScore || 0;
|
|
313
|
+
} else if (entry.type === 'rule_match') {
|
|
314
|
+
const data = entry.data as unknown as RuleMatchEventData;
|
|
315
|
+
if (data.ruleId) {
|
|
316
|
+
stats.pain.rulesMatched[data.ruleId] = (stats.pain.rulesMatched[data.ruleId] || 0) + 1;
|
|
317
|
+
}
|
|
318
|
+
} else if (entry.type === 'rule_promotion') {
|
|
319
|
+
stats.pain.candidatesPromoted++;
|
|
320
|
+
stats.evolution.rulesPromoted++;
|
|
321
|
+
} else if (entry.type === 'evolution_task') {
|
|
322
|
+
if (entry.category === 'completed') {
|
|
323
|
+
stats.evolution.tasksCompleted++;
|
|
324
|
+
} else if (entry.category === 'enqueued') {
|
|
325
|
+
stats.evolution.tasksEnqueued++;
|
|
326
|
+
}
|
|
294
327
|
}
|
|
295
328
|
}
|
|
296
329
|
|
|
@@ -470,7 +470,7 @@ export type EvolutionLoopEvent =
|
|
|
470
470
|
|
|
471
471
|
// V2 Queue Types (moved from evolution-worker.ts for shared use)
|
|
472
472
|
export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
|
|
473
|
-
export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation' | 'success' | 'failure' | 'skipped';
|
|
473
|
+
export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation' | 'success' | 'failure' | 'skipped' | 'noise_classified';
|
|
474
474
|
|
|
475
475
|
export interface EvolutionQueueItem {
|
|
476
476
|
id: string;
|
|
@@ -235,7 +235,7 @@ function persistBaselines(stateDir: string, baselines: ObservabilityBaselines):
|
|
|
235
235
|
fs.mkdirSync(dir, { recursive: true });
|
|
236
236
|
}
|
|
237
237
|
atomicWriteFileSync(filePath, JSON.stringify(baselines, null, 2));
|
|
238
|
-
} catch
|
|
238
|
+
} catch {
|
|
239
239
|
// Baselines persistence is best-effort — don't crash the caller
|
|
240
240
|
// (the SystemLogger call above already logged the values)
|
|
241
241
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* SAFETY:
|
|
16
16
|
* - Never load entire file (tail-only, max 512KB)
|
|
17
17
|
* - Skip lines > 100KB (real files have 11MB single lines)
|
|
18
|
-
* - Cap total output at
|
|
18
|
+
* - Cap total output at 2000 chars
|
|
19
19
|
* - All errors caught silently — return empty string on failure
|
|
20
20
|
*/
|
|
21
21
|
|
|
@@ -35,9 +35,9 @@ const TAIL_READ_SIZE = 512_000; // 512KB
|
|
|
35
35
|
/** Max turns to extract */
|
|
36
36
|
const MAX_TURNS = 8;
|
|
37
37
|
/** Max chars per turn entry */
|
|
38
|
-
const MAX_TURN_CHARS =
|
|
38
|
+
const MAX_TURN_CHARS = 400;
|
|
39
39
|
/** Max total output */
|
|
40
|
-
const MAX_OUTPUT_CHARS =
|
|
40
|
+
const MAX_OUTPUT_CHARS = 2000;
|
|
41
41
|
|
|
42
42
|
/** Valid characters for session IDs and agent IDs — prevents path traversal */
|
|
43
43
|
const SAFE_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -155,19 +155,24 @@ function extractTurn(msg: ParsedMessage): string | null {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (msg.role === 'assistant') {
|
|
158
|
-
|
|
158
|
+
const parts: string[] = [];
|
|
159
|
+
|
|
160
|
+
// Priority 1: final text reply (if present)
|
|
159
161
|
if (msg.textParts.length > 0) {
|
|
160
162
|
const text = msg.textParts.join(' ').trim();
|
|
161
|
-
if (text)
|
|
163
|
+
if (text) parts.push(`[Assistant]: ${text.substring(0, MAX_TURN_CHARS)}`);
|
|
162
164
|
}
|
|
163
|
-
|
|
165
|
+
|
|
166
|
+
// Priority 2: tool call summary (always include if present, for context)
|
|
164
167
|
if (msg.toolCalls.length > 0) {
|
|
165
168
|
const tools = msg.toolCalls.map(tc => tc.name).filter(Boolean);
|
|
166
169
|
const uniqueTools = [...new Set(tools)];
|
|
167
170
|
if (uniqueTools.length > 0) {
|
|
168
|
-
|
|
171
|
+
parts.push(`[Assistant → ${uniqueTools.join(', ')}]`);
|
|
169
172
|
}
|
|
170
173
|
}
|
|
174
|
+
|
|
175
|
+
return parts.length > 0 ? parts.join(' ') : null;
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
if (msg.role === 'toolResult') {
|
|
@@ -179,8 +179,16 @@ export async function auditEventLogs(
|
|
|
179
179
|
recentEntries: recent,
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
// Determine primary path
|
|
183
|
-
|
|
182
|
+
// Determine primary path - prefer configured workspace over workspace-main
|
|
183
|
+
// The configured workspace path is {openclawDir}/workspace (without -main suffix)
|
|
184
|
+
const workspaceDir = path.join(openclawDir, 'workspace') + path.sep;
|
|
185
|
+
const workspaceMainDir = path.join(openclawDir, 'workspace-main') + path.sep;
|
|
186
|
+
|
|
187
|
+
if (filePath.startsWith(workspaceDir)) {
|
|
188
|
+
// Configured workspace (e.g., ~/.openclaw/workspace/) takes priority
|
|
189
|
+
primaryPath = filePath;
|
|
190
|
+
} else if (!primaryPath && filePath.startsWith(workspaceMainDir)) {
|
|
191
|
+
// Fallback to workspace-main only if no configured workspace found yet
|
|
184
192
|
primaryPath = filePath;
|
|
185
193
|
}
|
|
186
194
|
} catch {
|
|
@@ -26,7 +26,8 @@ export type TaskResolution =
|
|
|
26
26
|
| 'late_marker_principle_created'
|
|
27
27
|
| 'late_marker_no_principle'
|
|
28
28
|
| 'stub_fallback'
|
|
29
|
-
| 'skipped_thin_violation'
|
|
29
|
+
| 'skipped_thin_violation'
|
|
30
|
+
| 'noise_classified';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Recent pain context for sleep_reflection tasks.
|
|
@@ -109,7 +109,7 @@ let timeoutId: NodeJS.Timeout | null = null;
|
|
|
109
109
|
* Old queue items (without taskKind) are migrated to pain_diagnosis for compatibility.
|
|
110
110
|
*/
|
|
111
111
|
export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
|
|
112
|
-
export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation';
|
|
112
|
+
export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation' | 'noise_classified';
|
|
113
113
|
|
|
114
114
|
export interface EvolutionQueueItem {
|
|
115
115
|
// Core identity
|
|
@@ -926,6 +926,21 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
926
926
|
if (fs.existsSync(reportPath)) {
|
|
927
927
|
try {
|
|
928
928
|
const reportData = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
929
|
+
|
|
930
|
+
// ── Step 3: Noise Classification Filter ──
|
|
931
|
+
// Skip principle creation for low-value noise categories that don't represent
|
|
932
|
+
// systemic failures or behavioral issues worth encoding as principles.
|
|
933
|
+
const classification = reportData?.classification;
|
|
934
|
+
const noiseCategories: Record<string, boolean> = {
|
|
935
|
+
'development_transient': true, // CRLF drift, duplicate match, self-resolved dev issues
|
|
936
|
+
'user_error': true, // User mistakes, wrong file, bad input
|
|
937
|
+
};
|
|
938
|
+
if (classification?.category && noiseCategories[classification.category]) {
|
|
939
|
+
if (logger) logger.info(`[PD:EvolutionWorker] Skipping principle for noise category "${classification.category}" — pain was ${classification.severity || 'low'} severity, not a systemic failure`);
|
|
940
|
+
task.status = 'completed';
|
|
941
|
+
task.completed_at = new Date().toISOString();
|
|
942
|
+
task.resolution = 'noise_classified';
|
|
943
|
+
} else {
|
|
929
944
|
// Check ALL known nesting paths — matches subagent.ts parseDiagnosticianReport
|
|
930
945
|
const principle = reportData?.principle
|
|
931
946
|
|| reportData?.phases?.principle_extraction?.principle
|
|
@@ -1017,6 +1032,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1017
1032
|
} else {
|
|
1018
1033
|
logger.warn(`[PD:EvolutionWorker] Diagnostician report for task ${task.id} missing principle fields — diagnostician did not produce a principle`);
|
|
1019
1034
|
}
|
|
1035
|
+
}
|
|
1020
1036
|
} catch (err) {
|
|
1021
1037
|
logger.warn(`[PD:EvolutionWorker] Failed to parse diagnostician report for task ${task.id}: ${String(err)}`);
|
|
1022
1038
|
}
|
|
@@ -1051,6 +1067,13 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1051
1067
|
durationMs,
|
|
1052
1068
|
});
|
|
1053
1069
|
|
|
1070
|
+
// Record task completion in event stats
|
|
1071
|
+
eventLog.recordEvolutionTaskCompleted({
|
|
1072
|
+
taskId: task.id,
|
|
1073
|
+
taskType: task.source || 'unknown',
|
|
1074
|
+
reason: task.reason || '',
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1054
1077
|
// Update evolution_tasks table
|
|
1055
1078
|
wctx.trajectory?.updateEvolutionTask?.(task.id, {
|
|
1056
1079
|
status: 'completed',
|
|
@@ -1139,6 +1162,13 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1139
1162
|
durationMs: age,
|
|
1140
1163
|
});
|
|
1141
1164
|
|
|
1165
|
+
// Record task completion in event stats (for timeout path too)
|
|
1166
|
+
eventLog.recordEvolutionTaskCompleted({
|
|
1167
|
+
taskId: task.id,
|
|
1168
|
+
taskType: task.source || 'unknown',
|
|
1169
|
+
reason: task.reason || '',
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1142
1172
|
// Update evolution_tasks table - use task.resolution, not hardcoded value
|
|
1143
1173
|
wctx.trajectory?.updateEvolutionTask?.(task.id, {
|
|
1144
1174
|
status: 'completed',
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
2
2
|
import { Type } from '@sinclair/typebox';
|
|
3
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4
3
|
import { buildPainFlag } from '../core/pain.js';
|
|
5
4
|
import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
|
|
6
5
|
import { TrajectoryRegistry } from '../core/trajectory.js';
|
|
@@ -179,4 +179,78 @@ describe('EventLog', () => {
|
|
|
179
179
|
expect(stats.totalPenaltyScore).toBe(12);
|
|
180
180
|
});
|
|
181
181
|
});
|
|
182
|
+
|
|
183
|
+
describe('Evolution and rule stats', () => {
|
|
184
|
+
it('should count evolution_task enqueued events', () => {
|
|
185
|
+
eventLog.recordEvolutionTask({ taskId: 't1', taskType: 'pain_diagnosis', reason: 'test' });
|
|
186
|
+
eventLog.recordEvolutionTask({ taskId: 't2', taskType: 'pain_diagnosis', reason: 'test' });
|
|
187
|
+
|
|
188
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
189
|
+
const stats = eventLog.getDailyStats(today);
|
|
190
|
+
|
|
191
|
+
expect(stats.evolution.tasksEnqueued).toBe(2);
|
|
192
|
+
expect(stats.evolution.tasksCompleted).toBe(0);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should count evolution_task completed events', () => {
|
|
196
|
+
// First enqueue
|
|
197
|
+
eventLog.recordEvolutionTask({ taskId: 't1', taskType: 'pain_diagnosis', reason: 'test' });
|
|
198
|
+
// Then complete
|
|
199
|
+
eventLog.recordEvolutionTaskCompleted({ taskId: 't1', taskType: 'pain_diagnosis', reason: 'test' });
|
|
200
|
+
|
|
201
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
202
|
+
const stats = eventLog.getDailyStats(today);
|
|
203
|
+
|
|
204
|
+
expect(stats.evolution.tasksEnqueued).toBe(1);
|
|
205
|
+
expect(stats.evolution.tasksCompleted).toBe(1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should track rule_match events in rulesMatched', () => {
|
|
209
|
+
eventLog.recordRuleMatch('s1', { ruleId: 'edit-exact-match', layer: 'L2', severity: 0.8, textPreview: 'test' });
|
|
210
|
+
eventLog.recordRuleMatch('s1', { ruleId: 'edit-exact-match', layer: 'L2', severity: 0.8, textPreview: 'test' });
|
|
211
|
+
eventLog.recordRuleMatch('s1', { ruleId: 'path-traversal', layer: 'L1', severity: 0.9, textPreview: 'test2' });
|
|
212
|
+
|
|
213
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
214
|
+
const stats = eventLog.getDailyStats(today);
|
|
215
|
+
|
|
216
|
+
expect(stats.pain.rulesMatched['edit-exact-match']).toBe(2);
|
|
217
|
+
expect(stats.pain.rulesMatched['path-traversal']).toBe(1);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should track rule_promotion events', () => {
|
|
221
|
+
eventLog.recordRulePromotion({ fingerprint: 'fp1', ruleId: 'r1', phrase: 'test', sampleCount: 5, avgSimilarity: 0.9 });
|
|
222
|
+
eventLog.recordRulePromotion({ fingerprint: 'fp2', ruleId: 'r2', phrase: 'test2', sampleCount: 3, avgSimilarity: 0.8 });
|
|
223
|
+
|
|
224
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
225
|
+
const stats = eventLog.getDailyStats(today);
|
|
226
|
+
|
|
227
|
+
expect(stats.pain.candidatesPromoted).toBe(2);
|
|
228
|
+
expect(stats.evolution.rulesPromoted).toBe(2);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should track pain signals by source', () => {
|
|
232
|
+
eventLog.recordPainSignal('s1', { source: 'tool_failure', score: 50, reason: 'edit failed' });
|
|
233
|
+
eventLog.recordPainSignal('s2', { source: 'tool_failure', score: 60, reason: 'read failed' });
|
|
234
|
+
eventLog.recordPainSignal('s3', { source: 'user_empathy', score: 10, reason: 'user frustrated' });
|
|
235
|
+
|
|
236
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
237
|
+
const stats = eventLog.getDailyStats(today);
|
|
238
|
+
|
|
239
|
+
expect(stats.pain.signalsBySource['tool_failure']).toBe(2);
|
|
240
|
+
expect(stats.pain.signalsBySource['user_empathy']).toBe(1);
|
|
241
|
+
expect(stats.pain.signalsDetected).toBe(3);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should calculate avgScore for pain signals', () => {
|
|
245
|
+
eventLog.recordPainSignal('s1', { source: 'tool_failure', score: 50, reason: 'test' });
|
|
246
|
+
eventLog.recordPainSignal('s2', { source: 'tool_failure', score: 70, reason: 'test' });
|
|
247
|
+
eventLog.recordPainSignal('s3', { source: 'tool_failure', score: 60, reason: 'test' });
|
|
248
|
+
|
|
249
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
250
|
+
const stats = eventLog.getDailyStats(today);
|
|
251
|
+
|
|
252
|
+
expect(stats.pain.avgScore).toBe(60); // (50+70+60)/3 = 60
|
|
253
|
+
expect(stats.pain.maxScore).toBe(70);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
182
256
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
4
5
|
|
|
5
|
-
const TEST_AGENTS_DIR = path.join(
|
|
6
|
+
const TEST_AGENTS_DIR = path.join(os.tmpdir(), 'pd-test-agents-' + Date.now());
|
|
6
7
|
|
|
7
8
|
// Set env before module load
|
|
8
9
|
process.env.PD_TEST_AGENTS_DIR = TEST_AGENTS_DIR;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
4
5
|
import { clearPainFlag, PAIN_FLAG_FILENAME } from '../../src/core/pain-lifecycle.js';
|
|
5
6
|
import { resolvePdPath } from '../../src/core/paths.js';
|
|
6
7
|
|
|
7
8
|
describe('PainLifecycle', () => {
|
|
8
|
-
const workspaceDir = fs.mkdtempSync(path.join(
|
|
9
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pain-lifecycle-test-'));
|
|
9
10
|
const painFlagPath = resolvePdPath(workspaceDir, 'PAIN_FLAG');
|
|
10
11
|
|
|
11
12
|
beforeEach(() => {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
4
5
|
import { clearPainFlag } from '../../src/core/pain-lifecycle.js';
|
|
5
6
|
import { resolvePdPath } from '../../src/core/paths.js';
|
|
6
7
|
|
|
7
8
|
describe('Pain Lifecycle E2E', () => {
|
|
8
|
-
const workspaceDir = fs.mkdtempSync(path.join(
|
|
9
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pain-lifecycle-e2e-'));
|
|
9
10
|
const painFlagPath = resolvePdPath(workspaceDir, 'PAIN_FLAG');
|
|
10
11
|
const stateDir = path.dirname(painFlagPath);
|
|
11
12
|
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { handleEvolveTask } from '../../src/commands/evolver';
|
|
3
|
-
|
|
4
|
-
describe('Evolver Synergy Module', () => {
|
|
5
|
-
it('should handle /evolve-task command', () => {
|
|
6
|
-
const mockCtx = {
|
|
7
|
-
workspaceDir: '/mock/workspace',
|
|
8
|
-
commandBody: '/evolve-task',
|
|
9
|
-
channel: 'cli',
|
|
10
|
-
isAuthorizedSender: true,
|
|
11
|
-
config: {} as any,
|
|
12
|
-
args: 'Fix tests'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const result = handleEvolveTask(mockCtx as any);
|
|
16
|
-
|
|
17
|
-
expect(result).toBeDefined();
|
|
18
|
-
expect(result.text).toContain('Evolver Handoff Requested');
|
|
19
|
-
expect(result.text).toContain('sessions_spawn');
|
|
20
|
-
expect(result.text).toContain('Fix tests');
|
|
21
|
-
});
|
|
22
|
-
});
|