@sdotwinter/openclaw-deterministic 0.12.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.
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/bin/install.js CHANGED
@@ -1,70 +1,93 @@
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
-
8
- const HOME = process.env.HOME;
9
- const openclawRoot = path.join(HOME, ".openclaw");
10
- const workspace = path.join(openclawRoot, "workspace");
11
- const backupsRoot = path.join(openclawRoot, "backups", "deterministic");
12
-
7
+ // -----------------------------
8
+ // Args
9
+ // -----------------------------
13
10
  const args = process.argv.slice(2);
14
11
  const DRY_RUN = args.includes("--dry-run");
15
12
 
16
- const tpl = (name) => path.join(__dirname, "..", "templates", name);
13
+ // -----------------------------
14
+ // Version + markers
15
+ // -----------------------------
16
+ const pkg = require(path.join(__dirname, "..", "package.json"));
17
+ const CLI_VERSION = pkg.version;
18
+
19
+ const VERSION_STAMP = `<!-- Installed by openclaw-deterministic v${CLI_VERSION} -->`;
20
+
21
+ // NOTE: This block is intentionally minimal and deterministic.
22
+ // It only declares the overlay and does not replace user SOUL content.
23
+ const OVERLAY_BLOCK = `
24
+ ## Deterministic Governance Overlay
25
+
26
+ This system loads and adheres to SOUL.deterministic.md as a governing philosophical constraint.
27
+ `.trim();
28
+
29
+ // -----------------------------
30
+ // Paths
31
+ // -----------------------------
32
+ const home = os.homedir();
33
+ const openclawRoot = path.join(home, ".openclaw");
34
+ const workspace = path.join(openclawRoot, "workspace");
35
+
36
+ const backupsRoot = path.join(openclawRoot, "backups", "deterministic");
17
37
 
18
38
  const target = {
19
39
  operating: path.join(workspace, "OPERATING_RULES.md"),
20
40
  detSoul: path.join(workspace, "SOUL.deterministic.md"),
21
41
  soul: path.join(workspace, "SOUL.md"),
22
42
  compactor: path.join(workspace, "skills", "memory-compactor", "SKILL.md"),
43
+ config: path.join(openclawRoot, ".deterministic.json"),
23
44
  };
24
45
 
25
- const OVERLAY_BLOCK = `
26
- ## Deterministic Governance Overlay
27
-
28
- This system loads and adheres to SOUL.deterministic.md as a governing philosophical constraint.
29
- `;
46
+ function tpl(rel) {
47
+ return path.join(__dirname, "..", "templates", rel);
48
+ }
30
49
 
50
+ const configTemplatePath = path.join(
51
+ __dirname,
52
+ "..",
53
+ "templates",
54
+ "config",
55
+ ".deterministic.json"
56
+ );
57
+
58
+ // -----------------------------
59
+ // Helpers
60
+ // -----------------------------
31
61
  function exists(p) {
32
62
  try {
33
- fs.accessSync(p);
63
+ fs.accessSync(p, fs.constants.F_OK);
34
64
  return true;
35
65
  } catch {
36
66
  return false;
37
67
  }
38
68
  }
39
69
 
40
- function ensureDir(p) {
41
- if (!DRY_RUN) {
42
- fs.mkdirSync(p, { recursive: true });
43
- }
70
+ function ensureDir(dirPath) {
71
+ if (exists(dirPath)) return;
72
+ if (DRY_RUN) return;
73
+ fs.mkdirSync(dirPath, { recursive: true });
44
74
  }
45
75
 
46
76
  function writeFile(p, content) {
77
+ ensureDir(path.dirname(p));
47
78
  if (DRY_RUN) {
48
79
  console.log(`[DRY-RUN] Would write: ${p}`);
49
80
  return;
50
81
  }
51
- ensureDir(path.dirname(p));
52
- fs.writeFileSync(p, content);
82
+ fs.writeFileSync(p, content, "utf8");
53
83
  }
54
84
 
55
- function copyWithVersionStamp(templatePath, targetPath) {
56
- const versionStamp = `<!-- Installed by openclaw-deterministic v${pkg.version} -->\n`;
57
- const content = fs.readFileSync(templatePath, "utf8");
58
- const stamped = versionStamp + content;
59
-
60
- if (DRY_RUN) {
61
- console.log(`[DRY-RUN] Would install: ${targetPath}`);
62
- }
63
-
64
- writeFile(targetPath, stamped);
85
+ function readFile(p) {
86
+ return fs.readFileSync(p, "utf8");
65
87
  }
66
88
 
67
89
  function timestamp() {
90
+ // keep filesystem-safe
68
91
  return new Date().toISOString().replace(/:/g, "-");
69
92
  }
70
93
 
@@ -91,29 +114,79 @@ function backupSnapshot(pathsToBackup) {
91
114
  return snap;
92
115
  }
93
116
 
117
+ function copyWithVersionStamp(src, dest) {
118
+ const raw = readFile(src);
119
+ const stamped = `${VERSION_STAMP}\n${raw.replace(/^\uFEFF/, "")}`;
120
+ writeFile(dest, stamped);
121
+
122
+ if (!DRY_RUN) {
123
+ // keep output consistent with your CLI style
124
+ const pretty = dest.startsWith(workspace)
125
+ ? dest.replace(workspace + path.sep, "")
126
+ : dest;
127
+ console.log(`Installed: ${pretty}`);
128
+ } else {
129
+ console.log(`[DRY-RUN] Would install: ${dest}`);
130
+ }
131
+ }
132
+
133
+ // -----------------------------
134
+ // Install steps
135
+ // -----------------------------
94
136
  function installTemplates() {
137
+ // Ensure skill directory exists
138
+ ensureDir(path.dirname(target.compactor));
139
+
95
140
  copyWithVersionStamp(tpl("OPERATING_RULES.md"), target.operating);
96
141
  copyWithVersionStamp(tpl("SOUL.deterministic.md"), target.detSoul);
97
142
  copyWithVersionStamp(tpl("memory-compactor.SKILL.md"), target.compactor);
98
143
  }
99
144
 
145
+ function installConfigIfMissing() {
146
+ if (exists(target.config)) {
147
+ console.log("Config exists — NOT modified.");
148
+ return;
149
+ }
150
+
151
+ if (!exists(configTemplatePath)) {
152
+ console.error(
153
+ `Config template missing in package: ${configTemplatePath}\n` +
154
+ "Refusing to create an empty config."
155
+ );
156
+ process.exit(1);
157
+ }
158
+
159
+ if (DRY_RUN) {
160
+ console.log(`[DRY-RUN] Would write: ${target.config}`);
161
+ return;
162
+ }
163
+
164
+ fs.copyFileSync(configTemplatePath, target.config);
165
+ console.log(`Installed: ${target.config}`);
166
+ }
167
+
100
168
  function bootstrapSoulIfMissing() {
101
169
  if (exists(target.soul)) {
102
170
  console.log("User SOUL.md exists — NOT modified.");
103
171
  return;
104
172
  }
105
173
 
106
- console.log("No SOUL.md detected — bootstrapping fresh SOUL.md with deterministic overlay.");
174
+ console.log(
175
+ "No SOUL.md detected — bootstrapping fresh SOUL.md with deterministic overlay."
176
+ );
107
177
 
108
178
  if (DRY_RUN) {
109
179
  console.log("[DRY-RUN] Would create SOUL.md with deterministic overlay.");
110
180
  return;
111
181
  }
112
182
 
113
- writeFile(target.soul, OVERLAY_BLOCK.trim() + "\n");
183
+ writeFile(target.soul, OVERLAY_BLOCK + "\n");
114
184
  console.log("Created: SOUL.md (deterministic overlay enabled by default)");
115
185
  }
116
186
 
187
+ // -----------------------------
188
+ // Main
189
+ // -----------------------------
117
190
  if (!exists(openclawRoot)) {
118
191
  console.error("OpenClaw not found at ~/.openclaw");
119
192
  process.exit(1);
@@ -126,28 +199,32 @@ if (!exists(workspace)) {
126
199
 
127
200
  if (DRY_RUN) {
128
201
  console.log("\nRunning deterministic install (dry-run mode)...\n");
129
- } else {
130
- console.log("Creating deterministic backup snapshot...");
131
202
  }
132
203
 
133
- const snap = backupSnapshot([
204
+ const pathsToBackup = [
134
205
  target.operating,
135
206
  target.detSoul,
136
- target.soul,
137
207
  target.compactor,
138
- ]);
208
+ // NOTE: we do NOT back up SOUL.md here because install never modifies it.
209
+ // If you later add an "enable" flow that edits SOUL.md, that command should back it up there.
210
+ target.config,
211
+ ];
212
+
213
+ const snap = backupSnapshot(pathsToBackup);
139
214
 
140
- if (snap) {
215
+ if (!DRY_RUN) {
216
+ console.log("Creating deterministic backup snapshot...");
141
217
  console.log(`Backup location: ${snap}`);
142
218
  }
143
219
 
144
220
  installTemplates();
221
+ installConfigIfMissing();
145
222
  bootstrapSoulIfMissing();
146
223
 
147
224
  if (DRY_RUN) {
148
225
  console.log("\nDry-run complete. No changes were written.\n");
149
226
  } else {
150
- console.log("\nDeterministic governance installed successfully.\n");
227
+ console.log("\nDeterministic governance installed successfully.");
151
228
  }
152
229
 
153
230
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdotwinter/openclaw-deterministic",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Deterministic governance and memory compaction layer for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -0,0 +1,10 @@
1
+ {
2
+ "semantic": {
3
+ "HARD_LIMIT": 1200,
4
+ "RISK_THRESHOLD_PERCENT": 85
5
+ },
6
+ "governance": {
7
+ "strict_mode": false,
8
+ "violation_logging": true
9
+ }
10
+ }