@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.
- package/bin/doctor.js +105 -155
- 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
|
|
7
|
+
const JSON_MODE = process.argv.includes("--json");
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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
|
|
13
|
+
const DEFAULT_RISK_PERCENT = 85;
|
|
17
14
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
|
41
|
+
function readJsonSafe(p) {
|
|
47
42
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
46
|
+
return { __invalid: true };
|
|
55
47
|
}
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
return
|
|
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
|
|
64
|
-
|
|
65
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
return Math.ceil(content.length / 4);
|
|
63
|
+
return { HARD_LIMIT, RISK_THRESHOLD, PERCENT };
|
|
82
64
|
}
|
|
83
65
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
const file = path.join(
|
|
72
|
+
episodicDir,
|
|
73
|
+
`governance-${new Date().toISOString().slice(0, 10)}.md`
|
|
74
|
+
);
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
76
|
+
const entry = `
|
|
77
|
+
## Governance Event — ${new Date().toISOString()}
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
}
|
|
79
|
+
Type: ${event.type}
|
|
80
|
+
Details: ${event.details}
|
|
102
81
|
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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:
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
109
|
+
const cfg = readJsonSafe(paths.config);
|
|
144
110
|
|
|
145
|
-
|
|
146
|
-
|
|
111
|
+
result.config = {
|
|
112
|
+
present: exists(paths.config),
|
|
113
|
+
invalid: cfg?.__invalid === true,
|
|
114
|
+
};
|
|
147
115
|
|
|
148
|
-
const
|
|
149
|
-
const riskThreshold = parseRiskThreshold();
|
|
116
|
+
const limits = getLimits(cfg);
|
|
150
117
|
|
|
151
|
-
result.limits
|
|
152
|
-
result.limits.riskThresholdConfigured = riskThreshold;
|
|
118
|
+
result.limits = limits;
|
|
153
119
|
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
120
|
+
if (exists(paths.semantic)) {
|
|
121
|
+
const tokens = estimateTokens(read(paths.semantic));
|
|
122
|
+
result.semanticTokens = tokens;
|
|
157
123
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
124
|
+
if (tokens > limits.HARD_LIMIT) {
|
|
125
|
+
result.semanticStatus = "hard-limit-exceeded";
|
|
161
126
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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(
|
|
151
|
+
function printHuman(r) {
|
|
177
152
|
console.log("\nRunning deterministic doctor...\n");
|
|
178
153
|
|
|
179
|
-
if (!
|
|
180
|
-
console.log("❌ OpenClaw
|
|
181
|
-
|
|
154
|
+
if (!r.openclawDetected) {
|
|
155
|
+
console.log("❌ OpenClaw not detected.");
|
|
156
|
+
return;
|
|
182
157
|
}
|
|
183
158
|
|
|
184
|
-
if (!
|
|
159
|
+
if (!r.workspaceDetected) {
|
|
185
160
|
console.log("❌ Workspace missing.");
|
|
186
|
-
|
|
161
|
+
return;
|
|
187
162
|
}
|
|
188
163
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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(
|
|
170
|
+
console.log(`⚠ ${key} missing.`);
|
|
213
171
|
}
|
|
214
172
|
}
|
|
215
173
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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): ${
|
|
185
|
+
console.log(`\nSemantic memory tokens (est): ${r.semanticTokens}`);
|
|
232
186
|
|
|
233
|
-
if (
|
|
234
|
-
console.log(
|
|
235
|
-
|
|
236
|
-
|
|
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
|
}
|