@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 +105 -155
- package/bin/install.js +116 -39
- package/package.json +1 -1
- package/templates/config/.deterministic.json +10 -0
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
|
}
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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(
|
|
41
|
-
if (
|
|
42
|
-
|
|
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
|
-
|
|
52
|
-
fs.writeFileSync(p, content);
|
|
82
|
+
fs.writeFileSync(p, content, "utf8");
|
|
53
83
|
}
|
|
54
84
|
|
|
55
|
-
function
|
|
56
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
227
|
+
console.log("\nDeterministic governance installed successfully.");
|
|
151
228
|
}
|
|
152
229
|
|
|
153
230
|
process.exit(0);
|
package/package.json
CHANGED