principles-disciple 1.138.0 → 1.139.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.
@@ -16,10 +16,11 @@ function scanEnvironment(wctx) {
16
16
  const tools = {};
17
17
  for (const tool of TOOLS_TO_SCAN) {
18
18
  try {
19
- const [versionLine] = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n');
19
+ const lines = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n');
20
+ const versionLine = lines[0];
20
21
  tools[tool.name] = {
21
22
  available: true,
22
- version: versionLine.trim(),
23
+ version: versionLine ? versionLine.trim() : undefined,
23
24
  };
24
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch parameter intentionally unused - we only care that the command failed
25
26
  }
@@ -108,7 +108,7 @@ export function handleDisableImplCommand(ctx) {
108
108
  const subcommand = parts[0] || '';
109
109
  const implId = subcommand === 'list' ? '' : subcommand;
110
110
  const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
111
- const reason = reasonMatch ? reasonMatch[1] : null;
111
+ const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
112
112
  // Subcommand: list
113
113
  if (subcommand === 'list' || subcommand === '') {
114
114
  return _handleListActive(stateDir, isZh);
@@ -57,6 +57,8 @@ function compressFocusContent(content, workspaceDir) {
57
57
  };
58
58
  for (let i = 0; i < lines.length; i++) {
59
59
  const line = lines[i];
60
+ if (!line)
61
+ continue;
60
62
  const trimmedLine = line.trim();
61
63
  // 识别章节
62
64
  if (/^#{1,3}\s*.*状态快照|📍/.test(trimmedLine)) {
@@ -245,7 +247,8 @@ async function compressFocus(workspaceDir, isZh, api) {
245
247
  compressedContent = oldContent;
246
248
  }
247
249
  // 6. 更新版本号和日期
248
- const [majorVersion] = oldVersion.split('.');
250
+ const versionParts = oldVersion.split('.');
251
+ const majorVersion = versionParts[0] ?? '';
249
252
  const newVersion = `${(parseInt(majorVersion, 10) || 1) + 1}`;
250
253
  const [today] = new Date().toISOString().split('T');
251
254
  const newContent = compressedContent
@@ -314,6 +317,11 @@ function rollbackFocus(workspaceDir, index, isZh) {
314
317
  : `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
315
318
  }
316
319
  const targetFile = files[index - 1];
320
+ if (!targetFile) {
321
+ return isZh
322
+ ? `❌ 无效的序号: ${index}\n\n💡 请输入 1-${files.length} 之间的数字`
323
+ : `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
324
+ }
317
325
  const historyContent = fs.readFileSync(targetFile.path, 'utf-8');
318
326
  // 备份当前版本
319
327
  const currentContent = fs.existsSync(focusPath)
@@ -416,7 +424,7 @@ export async function handleFocusCommand(ctx, api) {
416
424
  break;
417
425
  case 'rollback':
418
426
  case 'rb': {
419
- const index = parseInt(args[1], 10);
427
+ const index = parseInt(args[1] ?? '', 10);
420
428
  if (isNaN(index)) {
421
429
  result = isZh
422
430
  ? '❌ 请指定要回滚的版本序号\n\n💡 输入 `/pd-focus history` 查看可用版本'
@@ -46,7 +46,7 @@ export function handleRollbackImplCommand(ctx) {
46
46
  const subcommand = args.split(/\s+/)[0] || '';
47
47
  const implId = subcommand === 'list' ? '' : subcommand;
48
48
  const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
49
- const reason = reasonMatch ? reasonMatch[1] : null;
49
+ const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
50
50
  // List active
51
51
  if (subcommand === 'list' || subcommand === '') {
52
52
  return _handleListActiveRollback(stateDir, isZh);
@@ -16,7 +16,11 @@ function getModels(wctx) {
16
16
  for (const line of lines) {
17
17
  const match = /^###\s*(T-\d+):\s*(.*)/.exec(line);
18
18
  if (match) {
19
- models[match[1]] = match[2].trim();
19
+ const key = match[1];
20
+ const value = match[2];
21
+ if (key === undefined || value === undefined)
22
+ continue;
23
+ models[key] = value.trim();
20
24
  }
21
25
  }
22
26
  }
@@ -103,6 +103,8 @@ export class CorrectionCueLearner {
103
103
  if (keywordIndex < 0)
104
104
  continue;
105
105
  const keyword = this.store.keywords[keywordIndex];
106
+ if (!keyword)
107
+ continue;
106
108
  this.store.keywords[keywordIndex] = {
107
109
  ...keyword,
108
110
  hitCount: (keyword.hitCount ?? 0) + 1,
@@ -30,7 +30,7 @@ export class EventLog {
30
30
  return path.join(this.logsDir, `events_${date}.jsonl`);
31
31
  }
32
32
  getTodayStr() {
33
- return new Date().toISOString().split('T')[0];
33
+ return new Date().toISOString().split('T')[0] ?? '';
34
34
  }
35
35
  ensureEventsFile() {
36
36
  const today = this.getTodayStr();
@@ -255,7 +255,7 @@ export class EventLog {
255
255
  }
256
256
  }
257
257
  formatDate(date) {
258
- return date.toISOString().split('T')[0];
258
+ return date.toISOString().split('T')[0] ?? '';
259
259
  }
260
260
  loadStats() {
261
261
  if (fs.existsSync(this.statsFile)) {
@@ -343,11 +343,14 @@ export class EventLog {
343
343
  if (!stats.hooks.byType[data.hook]) {
344
344
  stats.hooks.byType[data.hook] = { total: 0, success: 0, failure: 0 };
345
345
  }
346
- stats.hooks.byType[data.hook].total++;
347
- if (entry.category === 'success')
348
- stats.hooks.byType[data.hook].success++;
349
- else
350
- stats.hooks.byType[data.hook].failure++;
346
+ const hookStats = stats.hooks.byType[data.hook];
347
+ if (hookStats) {
348
+ hookStats.total++;
349
+ if (entry.category === 'success')
350
+ hookStats.success++;
351
+ else
352
+ hookStats.failure++;
353
+ }
351
354
  }
352
355
  }
353
356
  else if (entry.type === 'empathy_rollback') {
@@ -383,13 +386,13 @@ export class EventLog {
383
386
  const raw = entry.data;
384
387
  if (Object.prototype.hasOwnProperty.call(raw, 'category')) {
385
388
  const cat = raw['category'];
386
- if (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields') {
389
+ if (typeof cat === 'string' && (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields')) {
387
390
  stats.evolution.diagnosticianReportsWritten++;
388
391
  }
389
- if (cat === 'missing_json') {
392
+ if (typeof cat === 'string' && cat === 'missing_json') {
390
393
  stats.evolution.reportsMissingJson++;
391
394
  }
392
- if (cat === 'incomplete_fields') {
395
+ if (typeof cat === 'string' && cat === 'incomplete_fields') {
393
396
  stats.evolution.reportsIncompleteFields++;
394
397
  }
395
398
  }
@@ -651,6 +654,8 @@ export class EventLog {
651
654
  const allEvents = this.getMergedEvents();
652
655
  for (let i = allEvents.length - 1; i >= 0; i--) {
653
656
  const entry = allEvents[i];
657
+ if (!entry)
658
+ continue;
654
659
  if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
655
660
  const data = entry.data;
656
661
  if (data.source === 'user_empathy' && !data.deduped) {
@@ -664,6 +669,8 @@ export class EventLog {
664
669
  const allEvents = this.getMergedEvents();
665
670
  for (let i = allEvents.length - 1; i >= 0; i--) {
666
671
  const entry = allEvents[i];
672
+ if (!entry)
673
+ continue;
667
674
  if (entry.sessionId === sessionId && entry.type === "pain_signal") {
668
675
  return entry.data;
669
676
  }
@@ -503,7 +503,7 @@ export function recoverFromTemplate(focusPath, extensionRoot) {
503
503
  };
504
504
  }
505
505
  let template = fs.readFileSync(templatePath, 'utf-8');
506
- const [today] = new Date().toISOString().split('T');
506
+ const today = new Date().toISOString().split('T')[0] ?? '';
507
507
  template = template.replace(/{YYYY-MM-DD}/g, today);
508
508
  if (fs.existsSync(focusPath)) {
509
509
  const backupPath = `${focusPath}.corrupted.${Date.now()}.md`;
@@ -24,7 +24,7 @@ export class HygieneTracker {
24
24
  this.currentStats = this.loadStats();
25
25
  }
26
26
  loadStats() {
27
- const [today] = new Date().toISOString().split('T');
27
+ const today = new Date().toISOString().split('T')[0] ?? '';
28
28
  if (fs.existsSync(this.statsFile)) {
29
29
  try {
30
30
  const content = fs.readFileSync(this.statsFile, 'utf-8');
@@ -54,7 +54,7 @@ export class HygieneTracker {
54
54
  saveStats() {
55
55
  let allStats = {};
56
56
  // Check if we need to rotate date (reset currentStats if date changed)
57
- const [today] = new Date().toISOString().split('T');
57
+ const today = new Date().toISOString().split('T')[0] ?? '';
58
58
  if (this.currentStats.date !== today) {
59
59
  this.currentStats = createEmptyHygieneStats(today);
60
60
  }
@@ -101,7 +101,7 @@ export class HygieneTracker {
101
101
  }
102
102
  getStats() {
103
103
  // Check for date change on every get
104
- const [today] = new Date().toISOString().split('T');
104
+ const today = new Date().toISOString().split('T')[0] ?? '';
105
105
  if (this.currentStats.date !== today) {
106
106
  this.currentStats = createEmptyHygieneStats(today);
107
107
  }
@@ -244,6 +244,8 @@ export class PrincipleCompiler {
244
244
  if (patterns.length === 0)
245
245
  return [];
246
246
  const pattern = patterns[0];
247
+ if (!pattern)
248
+ return [];
247
249
  // Skip replay when the pattern has no regex qualifier -- the generated template
248
250
  // blocks ALL calls to the tool, making it impossible to construct a passing
249
251
  // positive case. Replay is only meaningful when the template is selective.
@@ -30,7 +30,7 @@ export class ReplayEngine {
30
30
  }
31
31
  getLatestReport(implementationId) {
32
32
  const reports = this.listReports(implementationId);
33
- return reports.length > 0 ? reports[0] : null;
33
+ return reports[0] ?? null;
34
34
  }
35
35
  hasPassingReport(implementationId) {
36
36
  return this.listReports(implementationId).some((report) => report.overallDecision === 'pass');
@@ -188,7 +188,9 @@ export class RuleHost {
188
188
  }
189
189
  }
190
190
  else {
191
- validRows.push(group[0]);
191
+ const row = group[0];
192
+ if (row)
193
+ validRows.push(row);
192
194
  }
193
195
  }
194
196
  const loaded = [];
@@ -23,7 +23,10 @@ function extractTag(content, tagName) {
23
23
  const match = content.match(regex);
24
24
  if (!match)
25
25
  return '';
26
- return match[1].trim().replace(/\s+/g, ' ');
26
+ const raw = match[1];
27
+ if (!raw)
28
+ return '';
29
+ return raw.trim().replace(/\s+/g, ' ');
27
30
  }
28
31
  /**
29
32
  * Parse THINKING_OS.md content and extract all <directive> blocks.
@@ -35,14 +38,20 @@ export function parseThinkingOsMd(content) {
35
38
  const directiveRegex = /<directive\s+([^>]*)>([\s\S]*?)<\/directive>/gi;
36
39
  let _match = null;
37
40
  while ((_match = directiveRegex.exec(content)) !== null) {
38
- const [, attrs, body] = _match;
41
+ const attrs = _match[1];
42
+ const body = _match[2];
43
+ if (!attrs || !body)
44
+ continue;
39
45
  const idMatch = /id="([^"]+)"/i.exec(attrs);
40
46
  const nameMatch = /name="([^"]+)"/i.exec(attrs);
41
47
  if (!idMatch)
42
48
  continue;
49
+ const id = idMatch[1];
50
+ if (!id)
51
+ continue;
43
52
  const directive = {
44
- id: idMatch[1],
45
- name: nameMatch ? nameMatch[1] : '',
53
+ id,
54
+ name: nameMatch ? (nameMatch[1] ?? '') : '',
46
55
  trigger: extractTag(body, 'trigger'),
47
56
  must: extractTag(body, 'must'),
48
57
  forbidden: extractTag(body, 'forbidden'),
@@ -93,7 +93,8 @@ export function purgeStaleFailedTasks(queue, logger) {
93
93
  // Remove purged items from the queue (mutates in place)
94
94
  const purgedIds = new Set(purged.map((t) => t.id));
95
95
  for (let i = queue.length - 1; i >= 0; i--) {
96
- if (purgedIds.has(queue[i].id))
96
+ const task = queue[i];
97
+ if (task && purgedIds.has(task.id))
97
98
  queue.splice(i, 1);
98
99
  }
99
100
  const summary = Object.entries(byReason)
@@ -377,17 +378,20 @@ async function processDetectionQueue(wctx, api, eventLog) {
377
378
  if (wctx.trajectory) {
378
379
  const searchResults = wctx.trajectory.searchPainEvents(text, 5);
379
380
  if (searchResults.length > 0) {
381
+ const topResult = searchResults[0];
382
+ if (!topResult)
383
+ continue;
380
384
  // Found similar pain events - record as L3 semantic hit
381
385
  if (eventLog) {
382
386
  eventLog.recordRuleMatch(undefined, {
383
387
  ruleId: 'l3_semantic',
384
388
  layer: 'L3',
385
- severity: searchResults[0].score,
389
+ severity: topResult.score,
386
390
  textPreview: text.substring(0, 100)
387
391
  });
388
392
  }
389
393
  // Update detection funnel cache with L3 hit result
390
- funnel.updateCache(text, { detected: true, severity: searchResults[0].score });
394
+ funnel.updateCache(text, { detected: true, severity: topResult.score });
391
395
  // Don't track as candidate - this is a confirmed L3 hit
392
396
  if (logger)
393
397
  logger.info(`[PD:EvolutionWorker] L3 semantic hit: found ${searchResults.length} similar pain events for "${text.substring(0, 50)}..."`);
@@ -290,7 +290,10 @@ export class RuntimeSummaryService {
290
290
  if (sessions.length === 0) {
291
291
  return { session: null, reason: 'none' };
292
292
  }
293
- return { session: sessions[0], reason: 'latest_active' };
293
+ const session = sessions[0];
294
+ if (!session)
295
+ return { session: null, reason: 'none' };
296
+ return { session, reason: 'latest_active' };
294
297
  }
295
298
  static mergeSessionSnapshots(persistedSessions, workspaceDir) {
296
299
  const merged = new Map();
@@ -392,6 +395,8 @@ export class RuntimeSummaryService {
392
395
  if (!m)
393
396
  continue;
394
397
  const fileDate = m[1];
398
+ if (!fileDate)
399
+ continue;
395
400
  if (fileDate > newestDate) {
396
401
  newestDate = fileDate;
397
402
  bestFile = path.join(dir, file);
@@ -463,6 +468,8 @@ export class RuntimeSummaryService {
463
468
  static findLastPainSignal(events, sessionId) {
464
469
  for (let i = events.length - 1; i >= 0; i--) {
465
470
  const entry = events[i];
471
+ if (!entry)
472
+ continue;
466
473
  if (entry.type !== 'pain_signal')
467
474
  continue;
468
475
  if (sessionId && entry.sessionId !== sessionId)
@@ -32,7 +32,8 @@ export async function runWorkflowWatchdog(wctx, api, logger) {
32
32
  const ageMin = Math.round((now - wf.created_at) / 60000);
33
33
  details.push(`stale_active: ${wf.workflow_id} (${wf.workflow_type}, ${ageMin}min old)`);
34
34
  const events = store.getEvents(wf.workflow_id);
35
- const lastEventReason = events.length > 0 ? events[events.length - 1].reason : 'unknown';
35
+ const lastEvent = events[events.length - 1];
36
+ const lastEventReason = lastEvent ? lastEvent.reason : 'unknown';
36
37
  if (isExpectedSubagentError(lastEventReason)) {
37
38
  logger?.debug?.(`[PD:Watchdog] Skipping stale active workflow ${wf.workflow_id}: expected subagent error (${lastEventReason})`);
38
39
  continue;
@@ -245,11 +245,13 @@ export function percentile(values, p) {
245
245
  const n = sorted.length;
246
246
  // For small samples, use median to avoid overfitting
247
247
  if (n < 10) {
248
- return sorted[Math.floor(n / 2)];
248
+ const median = sorted[Math.floor(n / 2)];
249
+ return median ?? 0;
249
250
  }
250
251
  // Standard percentile calculation (nearest-rank method)
251
252
  const rank = Math.ceil((p / 100) * n);
252
- return sorted[Math.min(rank, n) - 1];
253
+ const result = sorted[Math.min(rank, n) - 1];
254
+ return result ?? 0;
253
255
  }
254
256
  /**
255
257
  * Clamp timeout to safe bounds.
@@ -317,6 +319,9 @@ export function computeAdaptiveTimeout(history, fallbackMs, options = {}) {
317
319
  const sorted = [...history].sort((a, b) => a - b);
318
320
  const rank = Math.ceil((p / 100) * sorted.length);
319
321
  const pValue = sorted[Math.min(rank, sorted.length) - 1];
322
+ if (pValue === undefined) {
323
+ return Math.max(minTimeoutMs, Math.min(maxTimeoutMs, fallbackMs));
324
+ }
320
325
  const adaptive = pValue * safetyMultiplier;
321
326
  return Math.max(minTimeoutMs, Math.min(maxTimeoutMs, Math.round(adaptive)));
322
327
  }
@@ -13,6 +13,9 @@ export function extractAgentIdFromSessionKey(sessionKey) {
13
13
  const match = /^agent:([^:]+):/.exec(sessionKey);
14
14
  if (!match)
15
15
  return undefined;
16
- const agentId = match[1].trim();
16
+ const raw = match[1];
17
+ if (!raw)
18
+ return undefined;
19
+ const agentId = raw.trim();
17
20
  return agentId || undefined;
18
21
  }
@@ -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.138.0",
5
+ "version": "1.139.0",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.138.0",
3
+ "version": "1.139.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",