principles-disciple 1.34.1 → 1.34.2

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.
@@ -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.34.1",
5
+ "version": "1.34.2",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.34.1",
3
+ "version": "1.34.2",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -40,6 +40,15 @@ import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
40
40
  const WORKFLOW_TTL_MS = 5 * 60 * 1000; // 5 minutes default TTL for helper workflows
41
41
  import { OpenClawTrinityRuntimeAdapter } from '../core/nocturnal-trinity.js';
42
42
 
43
+ /**
44
+ * Atomic file write — write to temp then rename to prevent partial writes on crash.
45
+ */
46
+ function atomicWriteFileSync(filePath: string, data: string): void {
47
+ const tmpPath = filePath + '.tmp';
48
+ fs.writeFileSync(tmpPath, data, 'utf8');
49
+ fs.renameSync(tmpPath, filePath);
50
+ }
51
+
43
52
  // ── Workflow Watchdog ────────────────────────────────────────────────────────
44
53
  // Detects stale/orphaned workflows, invalid results, and cleanup failures.
45
54
  // Runs every heartbeat cycle, catching bugs like:
@@ -683,7 +692,7 @@ function shouldSkipForDedup(
683
692
  * Load and migrate the evolution queue. Returns empty array if file doesn't exist.
684
693
  */
685
694
  function loadEvolutionQueue(queuePath: string): EvolutionQueueItem[] {
686
-
695
+ // eslint-disable-next-line no-useless-assignment
687
696
  let rawQueue: RawQueueItem[] = [];
688
697
  try {
689
698
  rawQueue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
@@ -724,7 +733,7 @@ function enqueueNewSleepReflectionTask(
724
733
  recentPainContext,
725
734
  });
726
735
 
727
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
736
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
728
737
  logger?.info?.(`[PD:EvolutionWorker] Enqueued sleep_reflection task ${taskId}`);
729
738
  }
730
739
 
@@ -869,7 +878,7 @@ async function doEnqueuePainTask(
869
878
  retryCount: 0, maxRetries: 3,
870
879
  });
871
880
 
872
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
881
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
873
882
  fs.appendFileSync(painFlagPath, `\nstatus: queued\ntask_id: ${taskId}\n`, 'utf8');
874
883
  result.enqueued = true;
875
884
 
@@ -1658,7 +1667,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1658
1667
 
1659
1668
  // Write claimed state (includes any pain changes from above) and release lock
1660
1669
  if (queueChanged) {
1661
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
1670
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
1662
1671
  }
1663
1672
  releaseLock();
1664
1673
  for (const sleepTask of sleepReflectionTasks) {
@@ -1912,7 +1921,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1912
1921
  freshQueue[idx] = sleepTask;
1913
1922
  }
1914
1923
  }
1915
- fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2), 'utf8');
1924
+ atomicWriteFileSync(queuePath, JSON.stringify(freshQueue, null, 2));
1916
1925
 
1917
1926
  // Log completions to EvolutionLogger
1918
1927
  for (const sleepTask of sleepReflectionTasks) {
@@ -2097,7 +2106,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2097
2106
  }
2098
2107
 
2099
2108
  if (queueChanged) {
2100
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2109
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2101
2110
  }
2102
2111
 
2103
2112
  // Pipeline observability: log stage-level summary at end of cycle
@@ -2215,7 +2224,7 @@ export async function registerEvolutionTaskSession(
2215
2224
  if (!task.started_at) {
2216
2225
  task.started_at = new Date().toISOString();
2217
2226
  }
2218
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2227
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2219
2228
  return true;
2220
2229
  } finally {
2221
2230
  releaseLock();
@@ -2255,7 +2264,7 @@ interface WorkerStatusReport {
2255
2264
  function writeWorkerStatus(stateDir: string, report: WorkerStatusReport): void {
2256
2265
  try {
2257
2266
  const statusPath = path.join(stateDir, 'worker-status.json');
2258
- fs.writeFileSync(statusPath, JSON.stringify(report, null, 2), 'utf8');
2267
+ atomicWriteFileSync(statusPath, JSON.stringify(report, null, 2));
2259
2268
  } catch (statusErr) {
2260
2269
  // Non-critical: worker-status.json is for monitoring, failure is acceptable
2261
2270
  // (no logger available in this standalone helper)
@@ -2286,7 +2295,7 @@ async function processEvolutionQueueWithResult(
2286
2295
  const purgeResult = purgeStaleFailedTasks(queue, logger);
2287
2296
  if (purgeResult.purged > 0) {
2288
2297
  // Write back the cleaned queue
2289
- fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2298
+ atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2290
2299
  }
2291
2300
 
2292
2301
  queueResult.total = queue.length;
@@ -103,6 +103,15 @@ import { getPrincipleState, setPrincipleState } from '../core/principle-training
103
103
  import type { Implementation } from '../types/principle-tree-schema.js';
104
104
  import { validateNocturnalSnapshotIngress } from '../core/nocturnal-snapshot-contract.js';
105
105
 
106
+ /**
107
+ * Atomic file write — write to temp then rename to prevent partial writes on crash.
108
+ */
109
+ function atomicWriteFileSync(filePath: string, data: string): void {
110
+ const tmpPath = filePath + '.tmp';
111
+ fs.writeFileSync(tmpPath, data, 'utf8');
112
+ fs.renameSync(tmpPath, filePath);
113
+ }
114
+
106
115
  // ---------------------------------------------------------------------------
107
116
  // #251: Sync trainingStore sample counts after registration
108
117
  // ---------------------------------------------------------------------------
@@ -385,10 +394,7 @@ function persistArtifact(
385
394
  fs.mkdirSync(dir, { recursive: true });
386
395
  }
387
396
 
388
- // Atomic write: temp file + rename prevents corruption on crash
389
- const tmpPath = artifactPath + '.tmp';
390
- fs.writeFileSync(tmpPath, JSON.stringify(sampleRecord, null, 2), 'utf8');
391
- fs.renameSync(tmpPath, artifactPath);
397
+ atomicWriteFileSync(artifactPath, JSON.stringify(sampleRecord, null, 2));
392
398
  return artifactPath;
393
399
  }
394
400