@oooooooooo/ch0de 0.0.0-stage → 1.9.55
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/API-TESTING.md +76 -0
- package/DEBTAUR-INTEGRATION.md +71 -0
- package/EDITING.md +80 -0
- package/EXPERT-PANEL.md +16 -0
- package/INTEGRATIONS.md +75 -0
- package/LICENSE +21 -0
- package/MODELS-DEV-LICENSE +21 -0
- package/PROVIDER-CONNECTIONS.md +48 -0
- package/PROVIDERS.md +17 -0
- package/README.md +523 -2
- package/RELIABILITY.md +95 -0
- package/UX-1.3.md +83 -0
- package/UX-1.4.1.md +13 -0
- package/UX-1.4.md +48 -0
- package/UX-1.5.md +13 -0
- package/UX-1.6.md +7 -0
- package/UX-1.7.3.md +13 -0
- package/UX-1.7.4.md +13 -0
- package/UX-1.7.md +19 -0
- package/acceptance-check.js +27 -0
- package/agent-budget.js +4 -0
- package/api-testing.js +140 -0
- package/automatic-re.js +19 -0
- package/benchmarks/public-baselines.json +29 -0
- package/benchmarks/render-1.4.0.json +8 -0
- package/benchmarks/targets.js +11 -0
- package/browser-check.js +1062 -0
- package/build-progress.js +10 -0
- package/build-quality.js +49 -0
- package/chode.js +7937 -0
- package/debtaur-engine.js +305 -0
- package/debtaur-orchestration.js +456 -0
- package/debtaur-worker.cjs +76 -0
- package/doctor.js +24 -0
- package/edit-transactions.js +79 -0
- package/elapsed.js +28 -0
- package/evidence-ledger.js +26 -0
- package/expert-panel.cjs +42 -0
- package/helios-ledger.js +405 -0
- package/install.cjs +25 -0
- package/integrations.js +30 -0
- package/json-output.js +8 -0
- package/local-memory.js +613 -0
- package/local-models.js +52 -0
- package/mcp-client.js +50 -0
- package/memory.js +511 -0
- package/model-catalog.js +46 -0
- package/native-tools.js +27 -0
- package/nosignups.js +325 -0
- package/observation-progress.js +33 -0
- package/orchestrator.js +299 -0
- package/package.json +158 -6
- package/plan-z6.js +490 -0
- package/project-memory.js +27 -0
- package/provider-capacity.js +13 -0
- package/provider-catalog.js +246 -0
- package/provider-connect.js +66 -0
- package/provider-credentials.js +5 -0
- package/provider-directory.js +8 -0
- package/provider-directory.json +32300 -0
- package/provider-errors.js +11 -0
- package/provider-picker.js +188 -0
- package/provider-policy.js +4 -0
- package/provider-race.js +25 -0
- package/provider-request.js +45 -0
- package/provider-store.js +13 -0
- package/rea-environment.js +5 -0
- package/read-window.js +21 -0
- package/recovery-context.js +3 -0
- package/reliability-memory.js +38 -0
- package/reliability-services.js +34 -0
- package/reliability-store.js +16 -0
- package/request-deadline.js +4 -0
- package/serve-frontend.html +172 -0
- package/settings-reset.js +11 -0
- package/skill-catalog.js +16 -0
- package/task-group.js +22 -0
- package/terminal-graphics.js +39 -0
- package/terminal-logo.js +76 -0
- package/terminal-ui.js +79 -0
- package/tool-call-validation.js +26 -0
- package/tool-health.js +22 -0
- package/tool-history.js +5 -0
- package/toolbox.js +156 -0
- package/tui-provider-picker.js +134 -0
- package/tui-worker.js +32 -0
- package/web-context.js +232 -0
- package/wolfram-math.js +6 -0
- package/worker-transport.js +11 -0
- package/workspace-commands.js +45 -0
- package/workspace-log.js +56 -0
- package/workspace-store.js +7 -0
- package/workspace-tui.js +113 -0
- package/workspace-view.js +109 -0
- package/worktrees.js +18 -0
- package/z6-integration.js +227 -0
package/plan-z6.js
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Z6Plan - Plan serialization with Z6P-inspired state encoding
|
|
5
|
+
*
|
|
6
|
+
* Plans as Z6 state machines with explicit dependencies and constraint validation.
|
|
7
|
+
* Each step has a Z6 status: 0=pending, 1=in-progress, 2=blocked, 3=done, 4=skipped, 5=failed
|
|
8
|
+
*
|
|
9
|
+
* Hash-bound integrity: every state change is anchored to a cryptographic hash.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// Z6 state values
|
|
17
|
+
const Z6_STATUS = {
|
|
18
|
+
PENDING: 0,
|
|
19
|
+
IN_PROGRESS: 1,
|
|
20
|
+
BLOCKED: 2,
|
|
21
|
+
DONE: 3,
|
|
22
|
+
SKIPPED: 4,
|
|
23
|
+
FAILED: 5
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const Z6_STATUS_LABELS = ['pending', 'in-progress', 'blocked', 'done', 'skipped', 'failed'];
|
|
27
|
+
|
|
28
|
+
// Z6 arithmetic operations
|
|
29
|
+
const Z6 = {
|
|
30
|
+
add: (a, b) => (a + b) % 6,
|
|
31
|
+
mul: (a, b) => (a * b) % 6,
|
|
32
|
+
wrap: (n) => ((n % 6) + 6) % 6, // Handle negative numbers
|
|
33
|
+
isValid: (n) => typeof n === 'number' && n >= 0 && n <= 5
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Z6Constraint - Pre/post condition validation
|
|
38
|
+
*/
|
|
39
|
+
class Z6Constraint {
|
|
40
|
+
constructor(type, params) {
|
|
41
|
+
if (!Z6.isValid(type)) {
|
|
42
|
+
throw new Error('Constraint type must be Z6 value (0-5)');
|
|
43
|
+
}
|
|
44
|
+
this.type = type;
|
|
45
|
+
this.params = params || {};
|
|
46
|
+
this.satisfied = null; // null=unchecked, true/false
|
|
47
|
+
this.checkedAt = null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
check(context) {
|
|
51
|
+
context = context || {};
|
|
52
|
+
const root = context.root || process.cwd();
|
|
53
|
+
|
|
54
|
+
switch(this.type) {
|
|
55
|
+
case Z6Constraint.Types.FILE_EXISTS:
|
|
56
|
+
this.satisfied = fs.existsSync(path.resolve(root, this.params.path));
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case Z6Constraint.Types.FILE_NOT_EXISTS:
|
|
60
|
+
this.satisfied = !fs.existsSync(path.resolve(root, this.params.path));
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case Z6Constraint.Types.SYNTAX_VALID:
|
|
64
|
+
this.satisfied = this.checkSyntax(path.resolve(root, this.params.file));
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case Z6Constraint.Types.TESTS_PASS:
|
|
68
|
+
// Defer to test runner - mark as checked
|
|
69
|
+
this.satisfied = context.testResults?.[this.params.file] === true;
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case Z6Constraint.Types.DEPENDENCY_DONE:
|
|
73
|
+
this.satisfied = context.stepStatus?.[this.params.stepId] === Z6_STATUS.DONE;
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case Z6Constraint.Types.CAPACITY_AVAILABLE:
|
|
77
|
+
this.satisfied = context.providerCapacity?.[this.params.provider] > 0;
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
default:
|
|
81
|
+
this.satisfied = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.checkedAt = Date.now();
|
|
85
|
+
return this.satisfied;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
checkSyntax(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
if (!fs.existsSync(filePath)) return false;
|
|
91
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
92
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
93
|
+
|
|
94
|
+
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
|
|
95
|
+
// Simple syntax check via Function constructor
|
|
96
|
+
new Function(content);
|
|
97
|
+
return true;
|
|
98
|
+
} else if (ext === '.json') {
|
|
99
|
+
JSON.parse(content);
|
|
100
|
+
return true;
|
|
101
|
+
} else if (ext === '.py') {
|
|
102
|
+
// Python syntax check via py_compile
|
|
103
|
+
const { spawnSync } = require('child_process');
|
|
104
|
+
const result = spawnSync('python', ['-m', 'py_compile', filePath], {
|
|
105
|
+
timeout: 5000,
|
|
106
|
+
windowsHide: true
|
|
107
|
+
});
|
|
108
|
+
return result.status === 0;
|
|
109
|
+
}
|
|
110
|
+
// Unknown extension - assume valid
|
|
111
|
+
return true;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static AND(constraints) {
|
|
118
|
+
return constraints.every(c => c.satisfied === true);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static OR(constraints) {
|
|
122
|
+
return constraints.some(c => c.satisfied === true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
toJSON() {
|
|
126
|
+
return {
|
|
127
|
+
type: this.type,
|
|
128
|
+
typeLabel: Z6Constraint.TYPES[this.type] || 'unknown',
|
|
129
|
+
params: this.params,
|
|
130
|
+
satisfied: this.satisfied,
|
|
131
|
+
checkedAt: this.checkedAt
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Z6Constraint.Types = {
|
|
137
|
+
FILE_EXISTS: 0,
|
|
138
|
+
FILE_NOT_EXISTS: 1,
|
|
139
|
+
SYNTAX_VALID: 2,
|
|
140
|
+
TESTS_PASS: 3,
|
|
141
|
+
DEPENDENCY_DONE: 4,
|
|
142
|
+
CAPACITY_AVAILABLE: 5
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
Z6Constraint.TYPES = {
|
|
146
|
+
0: 'FILE_EXISTS',
|
|
147
|
+
1: 'FILE_NOT_EXISTS',
|
|
148
|
+
2: 'SYNTAX_VALID',
|
|
149
|
+
3: 'TESTS_PASS',
|
|
150
|
+
4: 'DEPENDENCY_DONE',
|
|
151
|
+
5: 'CAPACITY_AVAILABLE'
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Z6PlanStep - Single step in the plan
|
|
156
|
+
*/
|
|
157
|
+
class Z6PlanStep {
|
|
158
|
+
constructor(id, description, options = {}) {
|
|
159
|
+
this.id = id;
|
|
160
|
+
this.description = description;
|
|
161
|
+
this.files = options.files || [];
|
|
162
|
+
this.status = Z6_STATUS.PENDING;
|
|
163
|
+
this.preConditions = (options.pre || []).map(c =>
|
|
164
|
+
c instanceof Z6Constraint ? c : new Z6Constraint(c.type, c.params)
|
|
165
|
+
);
|
|
166
|
+
this.postConditions = (options.post || []).map(c =>
|
|
167
|
+
c instanceof Z6Constraint ? c : new Z6Constraint(c.type, c.params)
|
|
168
|
+
);
|
|
169
|
+
this.dependsOn = options.dependsOn || []; // Array of step IDs
|
|
170
|
+
this.startedAt = null;
|
|
171
|
+
this.completedAt = null;
|
|
172
|
+
this.error = null;
|
|
173
|
+
this.modelCalls = 0;
|
|
174
|
+
this.tokens = 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
canExecute(plan) {
|
|
178
|
+
// All dependencies must be done
|
|
179
|
+
const depsOk = this.dependsOn.every(depId => {
|
|
180
|
+
const dep = plan.steps.find(s => s.id === depId);
|
|
181
|
+
return dep && dep.status === Z6_STATUS.DONE;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// All pre-conditions must be satisfied
|
|
185
|
+
const preOk = Z6Constraint.AND(this.preConditions);
|
|
186
|
+
|
|
187
|
+
return depsOk && preOk;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
start() {
|
|
191
|
+
if (this.status !== Z6_STATUS.PENDING) {
|
|
192
|
+
throw new Error(`Cannot start step ${this.id}: status is ${Z6_STATUS_LABELS[this.status]}`);
|
|
193
|
+
}
|
|
194
|
+
this.status = Z6_STATUS.IN_PROGRESS;
|
|
195
|
+
this.startedAt = Date.now();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
complete() {
|
|
199
|
+
if (this.status !== Z6_STATUS.IN_PROGRESS) {
|
|
200
|
+
throw new Error(`Cannot complete step ${this.id}: status is ${Z6_STATUS_LABELS[this.status]}`);
|
|
201
|
+
}
|
|
202
|
+
this.status = Z6_STATUS.DONE;
|
|
203
|
+
this.completedAt = Date.now();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fail(error) {
|
|
207
|
+
this.status = Z6_STATUS.FAILED;
|
|
208
|
+
this.error = String(error);
|
|
209
|
+
this.completedAt = Date.now();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
skip() {
|
|
213
|
+
this.status = Z6_STATUS.SKIPPED;
|
|
214
|
+
this.completedAt = Date.now();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
block() {
|
|
218
|
+
this.status = Z6_STATUS.BLOCKED;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
toJSON() {
|
|
222
|
+
return {
|
|
223
|
+
id: this.id,
|
|
224
|
+
description: this.description,
|
|
225
|
+
files: this.files,
|
|
226
|
+
status: this.status,
|
|
227
|
+
statusLabel: Z6_STATUS_LABELS[this.status],
|
|
228
|
+
preConditions: this.preConditions.map(c => c.toJSON()),
|
|
229
|
+
postConditions: this.postConditions.map(c => c.toJSON()),
|
|
230
|
+
dependsOn: this.dependsOn,
|
|
231
|
+
startedAt: this.startedAt,
|
|
232
|
+
completedAt: this.completedAt,
|
|
233
|
+
error: this.error,
|
|
234
|
+
modelCalls: this.modelCalls,
|
|
235
|
+
tokens: this.tokens
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Z6Plan - Complete plan with Z6 state machine
|
|
242
|
+
*/
|
|
243
|
+
class Z6Plan {
|
|
244
|
+
constructor(steps, options = {}) {
|
|
245
|
+
this.id = options.id || crypto.randomBytes(8).toString('hex');
|
|
246
|
+
this.createdAt = Date.now();
|
|
247
|
+
this.updatedAt = Date.now();
|
|
248
|
+
this.originalPrompt = options.prompt || '';
|
|
249
|
+
this.steps = steps.map((s, i) => {
|
|
250
|
+
if (s instanceof Z6PlanStep) return s;
|
|
251
|
+
return new Z6PlanStep(s.id || i, s.description, s);
|
|
252
|
+
});
|
|
253
|
+
this.status = Z6_STATUS.PENDING;
|
|
254
|
+
this.currentStep = null;
|
|
255
|
+
this.hash = this.computeHash();
|
|
256
|
+
this.selfHash = this.computeSelfHash();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
computeHash() {
|
|
260
|
+
const data = JSON.stringify(this.steps.map(s => s.toJSON()));
|
|
261
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
computeSelfHash() {
|
|
265
|
+
const data = JSON.stringify(this.steps.map(s => s.toJSON())) + this.hash;
|
|
266
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
verifyIntegrity() {
|
|
270
|
+
const currentHash = this.computeHash();
|
|
271
|
+
const currentSelfHash = this.computeSelfHash();
|
|
272
|
+
return {
|
|
273
|
+
hashValid: currentHash === this.hash,
|
|
274
|
+
selfHashValid: currentSelfHash === this.selfHash,
|
|
275
|
+
currentHash,
|
|
276
|
+
currentSelfHash
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getStep(id) {
|
|
281
|
+
return this.steps.find(s => s.id === id);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
getNextExecutable() {
|
|
285
|
+
const pending = this.steps.filter(s => s.status === Z6_STATUS.PENDING);
|
|
286
|
+
for (const step of pending) {
|
|
287
|
+
if (step.canExecute(this)) {
|
|
288
|
+
return step;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
getBlockedSteps() {
|
|
295
|
+
return this.steps.filter(s => s.status === Z6_STATUS.BLOCKED);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getFailedSteps() {
|
|
299
|
+
return this.steps.filter(s => s.status === Z6_STATUS.FAILED);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getCompletedSteps() {
|
|
303
|
+
return this.steps.filter(s => s.status === Z6_STATUS.DONE);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
getPendingSteps() {
|
|
307
|
+
return this.steps.filter(s => s.status === Z6_STATUS.PENDING);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
isComplete() {
|
|
311
|
+
return this.steps.every(s =>
|
|
312
|
+
[Z6_STATUS.DONE, Z6_STATUS.SKIPPED, Z6_STATUS.FAILED].includes(s.status)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
isFailed() {
|
|
317
|
+
return this.getFailedSteps().length > 0 && this.getPendingSteps().length === 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
updateStatus() {
|
|
321
|
+
this.updatedAt = Date.now();
|
|
322
|
+
|
|
323
|
+
if (this.isComplete()) {
|
|
324
|
+
if (this.getFailedSteps().length > 0) {
|
|
325
|
+
this.status = Z6_STATUS.FAILED;
|
|
326
|
+
} else {
|
|
327
|
+
this.status = Z6_STATUS.DONE;
|
|
328
|
+
}
|
|
329
|
+
} else if (this.steps.some(s => s.status === Z6_STATUS.IN_PROGRESS)) {
|
|
330
|
+
this.status = Z6_STATUS.IN_PROGRESS;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.hash = this.computeHash();
|
|
334
|
+
this.selfHash = this.computeSelfHash();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
startStep(stepId) {
|
|
338
|
+
const step = this.getStep(stepId);
|
|
339
|
+
if (!step) throw new Error(`Step ${stepId} not found`);
|
|
340
|
+
step.start();
|
|
341
|
+
this.currentStep = stepId;
|
|
342
|
+
this.updateStatus();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
completeStep(stepId) {
|
|
346
|
+
const step = this.getStep(stepId);
|
|
347
|
+
if (!step) throw new Error(`Step ${stepId} not found`);
|
|
348
|
+
step.complete();
|
|
349
|
+
this.currentStep = null;
|
|
350
|
+
this.updateStatus();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
failStep(stepId, error) {
|
|
354
|
+
const step = this.getStep(stepId);
|
|
355
|
+
if (!step) throw new Error(`Step ${stepId} not found`);
|
|
356
|
+
step.fail(error);
|
|
357
|
+
this.currentStep = null;
|
|
358
|
+
this.updateStatus();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
skipStep(stepId) {
|
|
362
|
+
const step = this.getStep(stepId);
|
|
363
|
+
if (!step) throw new Error(`Step ${stepId} not found`);
|
|
364
|
+
step.skip();
|
|
365
|
+
this.updateStatus();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Serialize plan for storage
|
|
370
|
+
*/
|
|
371
|
+
serialize() {
|
|
372
|
+
return {
|
|
373
|
+
id: this.id,
|
|
374
|
+
createdAt: this.createdAt,
|
|
375
|
+
updatedAt: this.updatedAt,
|
|
376
|
+
originalPrompt: this.originalPrompt,
|
|
377
|
+
steps: this.steps.map(s => s.toJSON()),
|
|
378
|
+
status: this.status,
|
|
379
|
+
statusLabel: Z6_STATUS_LABELS[this.status],
|
|
380
|
+
currentStep: this.currentStep,
|
|
381
|
+
hash: this.hash,
|
|
382
|
+
selfHash: this.selfHash
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get progress summary
|
|
388
|
+
*/
|
|
389
|
+
getProgress() {
|
|
390
|
+
const total = this.steps.length;
|
|
391
|
+
const done = this.getCompletedSteps().length;
|
|
392
|
+
const failed = this.getFailedSteps().length;
|
|
393
|
+
const pending = this.getPendingSteps().length;
|
|
394
|
+
const blocked = this.getBlockedSteps().length;
|
|
395
|
+
const inProgress = this.steps.filter(s => s.status === Z6_STATUS.IN_PROGRESS).length;
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
total,
|
|
399
|
+
done,
|
|
400
|
+
failed,
|
|
401
|
+
pending,
|
|
402
|
+
blocked,
|
|
403
|
+
inProgress,
|
|
404
|
+
percentComplete: Math.round((done / total) * 100),
|
|
405
|
+
isComplete: this.isComplete(),
|
|
406
|
+
isFailed: this.isFailed()
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Visual representation for TUI
|
|
412
|
+
*/
|
|
413
|
+
toVisual() {
|
|
414
|
+
const progress = this.getProgress();
|
|
415
|
+
const lines = [
|
|
416
|
+
`Plan: ${this.id} (${Z6_STATUS_LABELS[this.status]})`,
|
|
417
|
+
`Progress: ${progress.done}/${progress.total} steps (${progress.percentComplete}%)`,
|
|
418
|
+
''
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
for (const step of this.steps) {
|
|
422
|
+
const icon = {
|
|
423
|
+
[Z6_STATUS.PENDING]: '○',
|
|
424
|
+
[Z6_STATUS.IN_PROGRESS]: '◐',
|
|
425
|
+
[Z6_STATUS.BLOCKED]: '⊘',
|
|
426
|
+
[Z6_STATUS.DONE]: '●',
|
|
427
|
+
[Z6_STATUS.SKIPPED]: '○',
|
|
428
|
+
[Z6_STATUS.FAILED]: '✗'
|
|
429
|
+
}[step.status];
|
|
430
|
+
|
|
431
|
+
const deps = step.dependsOn.length > 0 ? ` [deps: ${step.dependsOn.join(', ')}]` : '';
|
|
432
|
+
lines.push(` ${icon} Step ${step.id}: ${step.description}${deps}`);
|
|
433
|
+
|
|
434
|
+
if (step.error) {
|
|
435
|
+
lines.push(` Error: ${step.error}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return lines.join('\n');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
static fromSerialized(data) {
|
|
443
|
+
// Reconstruct steps with their original state
|
|
444
|
+
const steps = data.steps.map(s => {
|
|
445
|
+
const step = new Z6PlanStep(s.id, s.description, {
|
|
446
|
+
files: s.files,
|
|
447
|
+
dependsOn: s.dependsOn
|
|
448
|
+
});
|
|
449
|
+
step.status = s.status;
|
|
450
|
+
step.startedAt = s.startedAt;
|
|
451
|
+
step.completedAt = s.completedAt;
|
|
452
|
+
step.error = s.error;
|
|
453
|
+
step.modelCalls = s.modelCalls;
|
|
454
|
+
step.tokens = s.tokens;
|
|
455
|
+
// Restore constraints
|
|
456
|
+
s.preConditions.forEach(c => {
|
|
457
|
+
step.preConditions.push(new Z6Constraint(c.type, c.params));
|
|
458
|
+
step.preConditions[step.preConditions.length - 1].satisfied = c.satisfied;
|
|
459
|
+
step.preConditions[step.preConditions.length - 1].checkedAt = c.checkedAt;
|
|
460
|
+
});
|
|
461
|
+
s.postConditions.forEach(c => {
|
|
462
|
+
step.postConditions.push(new Z6Constraint(c.type, c.params));
|
|
463
|
+
step.postConditions[step.postConditions.length - 1].satisfied = c.satisfied;
|
|
464
|
+
step.postConditions[step.postConditions.length - 1].checkedAt = c.checkedAt;
|
|
465
|
+
});
|
|
466
|
+
return step;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const plan = new Z6Plan(steps, {
|
|
470
|
+
id: data.id,
|
|
471
|
+
prompt: data.originalPrompt
|
|
472
|
+
});
|
|
473
|
+
plan.createdAt = data.createdAt;
|
|
474
|
+
plan.updatedAt = data.updatedAt;
|
|
475
|
+
plan.status = data.status;
|
|
476
|
+
plan.currentStep = data.currentStep;
|
|
477
|
+
plan.hash = data.hash;
|
|
478
|
+
plan.selfHash = data.selfHash;
|
|
479
|
+
return plan;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
Z6Plan,
|
|
485
|
+
Z6PlanStep,
|
|
486
|
+
Z6Constraint,
|
|
487
|
+
Z6_STATUS,
|
|
488
|
+
Z6_STATUS_LABELS,
|
|
489
|
+
Z6
|
|
490
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('fs'),path=require('path'),crypto=require('crypto');
|
|
3
|
+
function create(root,options={}){
|
|
4
|
+
const base=fs.realpathSync(root),file=path.join(base,'.chode','project-memory.json'),redact=options.redact||String;
|
|
5
|
+
function safe(relative){const full=path.resolve(base,relative),rel=path.relative(base,full);if(!rel||rel==='..'||rel.startsWith('..'+path.sep)||path.isAbsolute(rel))throw Error('Outside project');let at=base;for(const p of rel.split(path.sep)){at=path.join(at,p);try{if(fs.lstatSync(at).isSymbolicLink())throw Error('Linked memory path');}catch(e){if(e.code!=='ENOENT')throw e;}}return full;}
|
|
6
|
+
function load(){safe('.chode/project-memory.json');try{if(fs.statSync(file).size>64000)throw Error('Memory exceeds 64KB');const d=JSON.parse(fs.readFileSync(file,'utf8'));if(d.schema!==1||!Array.isArray(d.runs)||d.runs.length>20||!d.runs.every(r=>r&&r.status==='VERIFIED'&&typeof r.at==='string'&&Array.isArray(r.files)&&r.files.length<=20&&r.files.every(f=>f&&typeof f.file==='string'&&f.file.length<=500&&/^[a-f0-9]{64}$/.test(f.sha256))&&Array.isArray(r.checks)&&r.checks.length<=8))throw Error('Invalid project memory');return d;}catch(e){if(e.code==='ENOENT')return {schema:1,runs:[]};throw e;}}
|
|
7
|
+
function locked(fn){const lock=safe('.chode/project-memory.lock');fs.mkdirSync(path.dirname(lock),{recursive:true});let fd;try{fd=fs.openSync(lock,'wx',0o600);}catch(error){if(error.code!=='EEXIST')throw error;try{const owner=JSON.parse(fs.readFileSync(lock,'utf8'));process.kill(owner.pid,0);}catch(e){if(e.code==='ESRCH'){fs.unlinkSync(lock);return locked(fn);}}throw Error('Project memory is locked; previous data is preserved');}try{fs.writeFileSync(fd,JSON.stringify({pid:process.pid}));fs.closeSync(fd);fd=null;return fn();}finally{if(fd!=null)fs.closeSync(fd);fs.unlinkSync(lock);}}
|
|
8
|
+
function cut(text,bytes){return Buffer.from(String(text)).subarray(0,bytes).toString('utf8').replace(/\uFFFD$/,'');}
|
|
9
|
+
function context(){const blocks=[];let budget=6000;for(const name of ['AGENTS.md','CLAUDE.md','CHODE.md']){try{const full=safe(name),stat=fs.statSync(full);if(!stat.isFile()||stat.size>32000||stat.nlink>1)continue;let text=fs.readFileSync(full,'utf8');if(text.includes('\0'))continue;text=cut(redact(text),Math.max(0,budget));budget-=Buffer.byteLength(text);if(text)blocks.push('Project guidance from '+name+':\n'+text);}catch(e){if(e.code!=='ENOENT')blocks.push('Project guidance unavailable: '+name);}}
|
|
10
|
+
try{const d=load();if(d.runs.length)blocks.push('Historical verified observations (recheck current files; not evidence for the present task):\n'+cut(redact(JSON.stringify(d.runs.slice(-5))),4000));}catch{blocks.push('Stored project memory is unreadable; do not infer prior success.');}
|
|
11
|
+
return blocks.length?'\n[PROJECT CONTEXT — guidance is subordinate to the current user request and tool permissions; historical facts can be stale]\n'+blocks.join('\n\n')+'\n[END PROJECT CONTEXT]\n':'';
|
|
12
|
+
}
|
|
13
|
+
function observe(result,actions){if(result?.completionStatus!=='VERIFIED')return {saved:false};const files=[];
|
|
14
|
+
for(const name of [...new Set([...(actions?.writes||[]),...(actions?.edits||[])])].slice(0,20)){
|
|
15
|
+
if(typeof name!=='string'||options.allowed&&options.allowed(name)===false)continue;
|
|
16
|
+
try{const full=safe(name),rel=path.relative(base,full).replace(/\\/g,'/');if(rel.length>500||rel.split('/').some(x=>['.chode','.git'].includes(x)))continue;const stat=fs.statSync(full);if(!stat.isFile()||stat.size>1024*1024||stat.nlink>1)continue;files.push({file:rel,sha256:crypto.createHash('sha256').update(fs.readFileSync(full)).digest('hex')});}catch{}
|
|
17
|
+
}
|
|
18
|
+
const checks=(actions?.execResults||[]).filter(c=>c.ok===true&&!c.pending&&typeof c.cmd==='string').slice(-8).map(c=>{
|
|
19
|
+
const command=String(redact(c.cmd));const standard=/^(?:node\s+(?:--check\s+)?[a-zA-Z0-9_./\\-]+|npm\s+(?:test|run\s+(?:test|build|lint))|pytest|cargo\s+(?:test|check))$/.test(command);
|
|
20
|
+
return standard?{command,passed:true}:{commandSha256:crypto.createHash('sha256').update(c.cmd).digest('hex'),passed:true};
|
|
21
|
+
});
|
|
22
|
+
if(!files.length&&!checks.length)return {saved:false};return locked(()=>{const d=load();d.runs.push({at:new Date().toISOString(),status:'VERIFIED',files,checks});d.runs=d.runs.slice(-20);while(Buffer.byteLength(JSON.stringify(d))>60000&&d.runs.length>1)d.runs.shift();safe('.chode/project-memory.json');const temp=file+'.'+process.pid+'.'+crypto.randomBytes(4).toString('hex')+'.tmp';try{fs.writeFileSync(temp,JSON.stringify(d),{mode:0o600,flag:'wx'});fs.renameSync(temp,file);}finally{try{fs.unlinkSync(temp);}catch{}}return {saved:true};});
|
|
23
|
+
}
|
|
24
|
+
function clear(){return locked(()=>{safe('.chode/project-memory.json');try{fs.unlinkSync(file);}catch(e){if(e.code!=='ENOENT')throw e;}return {ok:true};});}
|
|
25
|
+
return {context,observe,load,clear};
|
|
26
|
+
}
|
|
27
|
+
module.exports={create};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('fs'),path=require('path'),crypto=require('crypto');
|
|
3
|
+
function retryAfter(value,now=Date.now()){if(value==null||String(value).trim()==='')return null;const raw=String(value).trim(),seconds=Number(raw);if(Number.isFinite(seconds))return seconds>=0?Math.ceil(seconds*1000):null;const date=Date.parse(raw);return Number.isFinite(date)?Math.max(0,date-now):null;}
|
|
4
|
+
function create(directory,clock=Date.now){
|
|
5
|
+
const file=id=>path.join(directory,crypto.createHash('sha256').update(String(id)).digest('hex')+'.json');
|
|
6
|
+
function read(id){try{const p=file(id);if(fs.statSync(p).size>10000)return null;const r=JSON.parse(fs.readFileSync(p,'utf8'));return r.id===id&&Number.isFinite(r.until)&&Number.isInteger(r.failures)?r:null;}catch{return null;}}
|
|
7
|
+
function remaining(id){const r=read(id);return r?Math.max(0,r.until-clock()):0;}
|
|
8
|
+
function record(id,hint){const old=read(id),failures=Math.min(10,(old&&old.failures||0)+1),delay=Number.isFinite(hint)&&hint>=0?hint:Math.min(900000,60000*2**(failures-1));const state={id,until:Math.max(old&&old.until||0,clock()+Math.max(1000,delay)),failures};fs.mkdirSync(directory,{recursive:true});const target=file(id),temp=target+'.'+process.pid+'.'+crypto.randomBytes(4).toString('hex')+'.tmp';try{fs.writeFileSync(temp,JSON.stringify(state),{mode:0o600,flag:'wx'});fs.renameSync(temp,target);}finally{try{fs.unlinkSync(temp);}catch{}}return state;}
|
|
9
|
+
function clear(id){try{fs.unlinkSync(file(id));}catch{}}
|
|
10
|
+
return {read,remaining,record,clear,next:ids=>{const waits=ids.map(remaining);return waits.length?Math.min(...waits):0;}};
|
|
11
|
+
}
|
|
12
|
+
function free(provider){return !!provider&&(provider.category==='keyless'||provider.category==='free_local')&&!provider.requiresKey;}
|
|
13
|
+
module.exports={retryAfter,create,free};
|