@sdotwinter/openclaw-deterministic 0.13.0 → 0.14.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.
Files changed (2) hide show
  1. package/bin/doctor.js +105 -155
  2. package/package.json +1 -1
package/bin/doctor.js CHANGED
@@ -1,38 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require("fs");
4
+ const os = require("os");
4
5
  const path = require("path");
5
6
 
6
- const pkg = require("../package.json");
7
+ const JSON_MODE = process.argv.includes("--json");
7
8
 
8
- const args = process.argv.slice(2);
9
- const JSON_MODE = args.includes("--json");
10
-
11
- const HOME = process.env.HOME;
12
- const openclawRoot = path.join(HOME, ".openclaw");
13
- const workspace = path.join(openclawRoot, "workspace");
9
+ const pkg = require(path.join(__dirname, "..", "package.json"));
10
+ const CLI_VERSION = pkg.version;
14
11
 
15
12
  const DEFAULT_HARD_LIMIT = 1200;
16
- const DEFAULT_RISK_THRESHOLD = 1020;
13
+ const DEFAULT_RISK_PERCENT = 85;
17
14
 
18
- const episodicLogPath = path.join(
19
- openclawRoot,
20
- "workspace",
21
- "memory",
22
- "episodic",
23
- "governance-log.md"
24
- );
15
+ const home = os.homedir();
16
+ const openclawRoot = path.join(home, ".openclaw");
17
+ const workspace = path.join(openclawRoot, "workspace");
25
18
 
26
- const files = {
19
+ const paths = {
27
20
  operating: path.join(workspace, "OPERATING_RULES.md"),
28
21
  detSoul: path.join(workspace, "SOUL.deterministic.md"),
29
22
  soul: path.join(workspace, "SOUL.md"),
30
23
  compactor: path.join(workspace, "skills", "memory-compactor", "SKILL.md"),
24
+ semantic: path.join(workspace, "memory", "semantic", "openclaw.md"),
25
+ config: path.join(openclawRoot, ".deterministic.json"),
31
26
  };
32
27
 
33
28
  function exists(p) {
34
29
  try {
35
- fs.accessSync(p);
30
+ fs.accessSync(p, fs.constants.F_OK);
36
31
  return true;
37
32
  } catch {
38
33
  return false;
@@ -43,201 +38,156 @@ function read(p) {
43
38
  return fs.readFileSync(p, "utf8");
44
39
  }
45
40
 
46
- function appendGovernanceEvent(event) {
41
+ function readJsonSafe(p) {
47
42
  try {
48
- const timestamp = new Date().toISOString();
49
- const entry = `\n---\nTime: ${timestamp}\nType: ${event.type}\nDetails: ${event.details}\n---\n`;
50
-
51
- fs.mkdirSync(path.dirname(episodicLogPath), { recursive: true });
52
- fs.appendFileSync(episodicLogPath, entry);
43
+ if (!exists(p)) return null;
44
+ return JSON.parse(read(p));
53
45
  } catch {
54
- // Logging must never crash doctor
46
+ return { __invalid: true };
55
47
  }
56
48
  }
57
49
 
58
- function versionFromFile(content) {
59
- const match = content.match(/Installed by openclaw-deterministic v([0-9.]+)/);
60
- return match ? match[1] : null;
50
+ function estimateTokens(text) {
51
+ if (!text) return 0;
52
+ return Math.ceil(text.split(/\s+/).length * 1.3);
61
53
  }
62
54
 
63
- function overlayEnabled() {
64
- if (!exists(files.soul)) return false;
65
- const content = read(files.soul);
66
- return content.includes("SOUL.deterministic.md");
67
- }
68
-
69
- function estimateSemanticTokens() {
70
- const semanticPath = path.join(
71
- openclawRoot,
72
- "workspace",
73
- "memory",
74
- "semantic",
75
- "openclaw.md"
76
- );
55
+ function getLimits(cfg) {
56
+ const hard = cfg?.semantic?.HARD_LIMIT;
57
+ const pct = cfg?.semantic?.RISK_THRESHOLD_PERCENT;
77
58
 
78
- if (!exists(semanticPath)) return 0;
59
+ const HARD_LIMIT = Number.isFinite(hard) ? hard : DEFAULT_HARD_LIMIT;
60
+ const PERCENT = Number.isFinite(pct) ? pct : DEFAULT_RISK_PERCENT;
61
+ const RISK_THRESHOLD = Math.floor((HARD_LIMIT * PERCENT) / 100);
79
62
 
80
- const content = read(semanticPath);
81
- return Math.ceil(content.length / 4);
63
+ return { HARD_LIMIT, RISK_THRESHOLD, PERCENT };
82
64
  }
83
65
 
84
- function evaluateVersion(filePath) {
85
- if (!exists(filePath)) {
86
- return { status: "missing", version: null };
87
- }
88
-
89
- const content = read(filePath);
90
- const version = versionFromFile(content);
66
+ function appendGovernanceEvent(event) {
67
+ try {
68
+ const episodicDir = path.join(workspace, "memory", "episodic");
69
+ if (!exists(episodicDir)) return;
91
70
 
92
- if (!version) {
93
- return { status: "no-stamp", version: null };
94
- }
71
+ const file = path.join(
72
+ episodicDir,
73
+ `governance-${new Date().toISOString().slice(0, 10)}.md`
74
+ );
95
75
 
96
- if (version === pkg.version) {
97
- return { status: "match", version };
98
- }
76
+ const entry = `
77
+ ## Governance Event ${new Date().toISOString()}
99
78
 
100
- return { status: "mismatch", version };
101
- }
79
+ Type: ${event.type}
80
+ Details: ${event.details}
102
81
 
103
- function parseHardLimit() {
104
- if (!exists(files.compactor)) return null;
105
- const content = read(files.compactor);
106
- const match = content.match(/HARD_LIMIT[^0-9]*([0-9]+)/);
107
- return match ? parseInt(match[1], 10) : null;
108
- }
82
+ ---
83
+ `;
109
84
 
110
- function parseRiskThreshold() {
111
- if (!exists(files.compactor)) return null;
112
- const content = read(files.compactor);
113
- const match = content.match(/RISK_THRESHOLD[^0-9]*([0-9]+)/);
114
- return match ? parseInt(match[1], 10) : null;
85
+ fs.appendFileSync(file, entry);
86
+ } catch {
87
+ // never crash doctor
88
+ }
115
89
  }
116
90
 
117
91
  function evaluate() {
118
92
  const result = {
119
- cliVersion: pkg.version,
93
+ cliVersion: CLI_VERSION,
120
94
  openclawDetected: exists(openclawRoot),
121
95
  workspaceDetected: exists(workspace),
122
- files: {},
123
- overlayEnabled: false,
124
- semanticTokens: 0,
125
- semanticStatus: "safe",
126
- limits: {
127
- hardLimitConfigured: null,
128
- riskThresholdConfigured: null,
129
- hardLimitDefault: DEFAULT_HARD_LIMIT,
130
- riskThresholdDefault: DEFAULT_RISK_THRESHOLD,
131
- coherent: true,
132
- },
133
96
  };
134
97
 
135
98
  if (!result.openclawDetected || !result.workspaceDetected) {
136
99
  return result;
137
100
  }
138
101
 
139
- result.files.operating = evaluateVersion(files.operating);
140
- result.files.detSoul = evaluateVersion(files.detSoul);
141
- result.files.compactor = evaluateVersion(files.compactor);
102
+ result.files = {
103
+ operating: exists(paths.operating),
104
+ detSoul: exists(paths.detSoul),
105
+ compactor: exists(paths.compactor),
106
+ soul: exists(paths.soul),
107
+ };
142
108
 
143
- result.overlayEnabled = overlayEnabled();
109
+ const cfg = readJsonSafe(paths.config);
144
110
 
145
- const tokens = estimateSemanticTokens();
146
- result.semanticTokens = tokens;
111
+ result.config = {
112
+ present: exists(paths.config),
113
+ invalid: cfg?.__invalid === true,
114
+ };
147
115
 
148
- const hardLimit = parseHardLimit();
149
- const riskThreshold = parseRiskThreshold();
116
+ const limits = getLimits(cfg);
150
117
 
151
- result.limits.hardLimitConfigured = hardLimit;
152
- result.limits.riskThresholdConfigured = riskThreshold;
118
+ result.limits = limits;
153
119
 
154
- if (hardLimit && hardLimit !== DEFAULT_HARD_LIMIT) {
155
- result.limits.coherent = false;
156
- }
120
+ if (exists(paths.semantic)) {
121
+ const tokens = estimateTokens(read(paths.semantic));
122
+ result.semanticTokens = tokens;
157
123
 
158
- if (riskThreshold && riskThreshold !== DEFAULT_RISK_THRESHOLD) {
159
- result.limits.coherent = false;
160
- }
124
+ if (tokens > limits.HARD_LIMIT) {
125
+ result.semanticStatus = "hard-limit-exceeded";
161
126
 
162
- const effectiveHardLimit = hardLimit || DEFAULT_HARD_LIMIT;
163
- const effectiveRiskThreshold = riskThreshold || DEFAULT_RISK_THRESHOLD;
127
+ appendGovernanceEvent({
128
+ type: "semantic-hard-limit",
129
+ details: `Tokens ${tokens} > HARD_LIMIT ${limits.HARD_LIMIT}`,
130
+ });
131
+
132
+ } else if (tokens > limits.RISK_THRESHOLD) {
133
+ result.semanticStatus = "risk-threshold";
164
134
 
165
- if (tokens > effectiveHardLimit) {
166
- result.semanticStatus = "hard-limit-exceeded";
167
- } else if (tokens > effectiveRiskThreshold) {
168
- result.semanticStatus = "risk-threshold";
135
+ appendGovernanceEvent({
136
+ type: "semantic-risk-threshold",
137
+ details: `Tokens ${tokens} > RISK_THRESHOLD ${limits.RISK_THRESHOLD}`,
138
+ });
139
+
140
+ } else {
141
+ result.semanticStatus = "safe";
142
+ }
169
143
  } else {
144
+ result.semanticTokens = 0;
170
145
  result.semanticStatus = "safe";
171
146
  }
172
147
 
173
148
  return result;
174
149
  }
175
150
 
176
- function printHuman(result) {
151
+ function printHuman(r) {
177
152
  console.log("\nRunning deterministic doctor...\n");
178
153
 
179
- if (!result.openclawDetected) {
180
- console.log("❌ OpenClaw directory not found.");
181
- process.exit(1);
154
+ if (!r.openclawDetected) {
155
+ console.log("❌ OpenClaw not detected.");
156
+ return;
182
157
  }
183
158
 
184
- if (!result.workspaceDetected) {
159
+ if (!r.workspaceDetected) {
185
160
  console.log("❌ Workspace missing.");
186
- process.exit(1);
161
+ return;
187
162
  }
188
163
 
189
- for (const [name, info] of Object.entries(result.files)) {
190
- const label =
191
- name === "operating"
192
- ? "OPERATING_RULES.md"
193
- : name === "detSoul"
194
- ? "SOUL.deterministic.md"
195
- : "memory-compactor SKILL.md";
196
-
197
- if (info.status === "missing") {
198
- console.log(`❌ ${label} missing.`);
199
- appendGovernanceEvent({ type: "file-missing", details: label });
200
- } else if (info.status === "no-stamp") {
201
- console.log(`⚠ ${label} version stamp missing.`);
202
- appendGovernanceEvent({ type: "no-version-stamp", details: label });
203
- } else if (info.status === "mismatch") {
204
- console.log(
205
- `⚠ ${label} version mismatch (installed ${info.version}, CLI ${pkg.version})`
206
- );
207
- appendGovernanceEvent({
208
- type: "version-mismatch",
209
- details: `${label} installed ${info.version}, CLI ${pkg.version}`,
210
- });
164
+ console.log(`CLI version: ${r.cliVersion}\n`);
165
+
166
+ for (const [key, present] of Object.entries(r.files)) {
167
+ if (present) {
168
+ console.log(`✅ ${key} present.`);
211
169
  } else {
212
- console.log(`✅ ${label} version matches CLI (${info.version})`);
170
+ console.log(`⚠ ${key} missing.`);
213
171
  }
214
172
  }
215
173
 
216
- console.log("✅ SOUL.md present.");
217
- console.log(
218
- result.overlayEnabled
219
- ? " Deterministic overlay ENABLED in SOUL.md."
220
- : "⚠ Deterministic overlay NOT enabled in SOUL.md."
221
- );
222
-
223
- if (!result.limits.coherent) {
224
- console.log("⚠ Threshold configuration drift detected in SKILL.md.");
225
- appendGovernanceEvent({
226
- type: "threshold-drift",
227
- details: `HARD_LIMIT=${result.limits.hardLimitConfigured}, RISK_THRESHOLD=${result.limits.riskThresholdConfigured}`,
228
- });
174
+ if (!r.config.present) {
175
+ console.log("⚠ Deterministic config missing. Using defaults.");
176
+ } else if (r.config.invalid) {
177
+ console.log(" Deterministic config invalid JSON. Using defaults.");
178
+ } else {
179
+ console.log(
180
+ `✅ Config loaded. HARD_LIMIT=${r.limits.HARD_LIMIT}, ` +
181
+ `RISK_THRESHOLD=${r.limits.RISK_THRESHOLD} (${r.limits.PERCENT}%)`
182
+ );
229
183
  }
230
184
 
231
- console.log(`\nSemantic memory tokens (est): ${result.semanticTokens}`);
185
+ console.log(`\nSemantic memory tokens (est): ${r.semanticTokens}`);
232
186
 
233
- if (result.semanticStatus === "hard-limit-exceeded") {
234
- console.log("❌ Semantic memory exceeds HARD_LIMIT.");
235
- appendGovernanceEvent({
236
- type: "semantic-hard-limit-exceeded",
237
- details: `Tokens=${result.semanticTokens}`,
238
- });
239
- } else if (result.semanticStatus === "risk-threshold") {
240
- console.log("⚠ Semantic memory above risk threshold.");
187
+ if (r.semanticStatus === "hard-limit-exceeded") {
188
+ console.log(`❌ Semantic memory exceeds HARD_LIMIT (${r.limits.HARD_LIMIT}).`);
189
+ } else if (r.semanticStatus === "risk-threshold") {
190
+ console.log(`⚠ Semantic memory above risk threshold (${r.limits.RISK_THRESHOLD}).`);
241
191
  } else {
242
192
  console.log("✅ Semantic memory within safe bounds.");
243
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdotwinter/openclaw-deterministic",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Deterministic governance and memory compaction layer for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",