@the-open-engine/zeroshot 6.1.0 → 6.3.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 (42) hide show
  1. package/README.md +141 -474
  2. package/cli/commands/cmdproof.js +180 -6
  3. package/cli/event-copy.js +12 -0
  4. package/cli/index.js +311 -96
  5. package/cli/message-formatters-normal.js +14 -2
  6. package/cli/message-formatters-watch.js +9 -2
  7. package/cluster-hooks/block-ask-user-question.py +53 -0
  8. package/cluster-hooks/block-dangerous-git.py +145 -0
  9. package/lib/clusters-registry.js +48 -0
  10. package/lib/compose-utils.js +101 -0
  11. package/lib/detached-startup.js +9 -0
  12. package/lib/id-detector.js +8 -9
  13. package/lib/path-check.js +63 -0
  14. package/lib/run-mode.js +22 -0
  15. package/lib/start-cluster.js +39 -23
  16. package/package.json +3 -4
  17. package/scripts/check-path.js +19 -0
  18. package/scripts/validate-templates.js +11 -29
  19. package/src/agent/agent-hook-executor.js +1 -1
  20. package/src/agent/agent-task-executor.js +4 -3
  21. package/src/agent/pr-verification.js +27 -1
  22. package/src/agents/git-pusher-template.js +121 -4
  23. package/src/attach/socket-discovery.js +2 -3
  24. package/src/claude-credentials.js +75 -0
  25. package/src/claude-task-runner.js +1 -0
  26. package/src/isolation-manager.js +12 -9
  27. package/src/issue-providers/README.md +45 -15
  28. package/src/issue-providers/index.js +2 -0
  29. package/src/issue-providers/jira-provider.js +8 -1
  30. package/src/issue-providers/linear-provider.js +405 -0
  31. package/src/ledger.js +42 -3
  32. package/src/lib/gc.js +2 -3
  33. package/src/message-bus.js +10 -0
  34. package/src/orchestrator.js +264 -144
  35. package/src/preflight.js +12 -16
  36. package/src/providers/base-provider.js +7 -6
  37. package/src/status-footer.js +13 -1
  38. package/src/template-validation/index.js +3 -0
  39. package/src/template-validation/report-formatter.js +55 -0
  40. package/src/worktree-claude-config.js +2 -13
  41. package/task-lib/runner.js +1 -0
  42. package/task-lib/watcher.js +1 -0
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
+ const crypto = require('crypto');
4
5
  const { spawnSync } = require('child_process');
5
6
 
6
7
  const {
@@ -153,6 +154,7 @@ function resolvePaths(env) {
153
154
  return {
154
155
  cacheDir,
155
156
  keyDir,
157
+ lockDir: env.ZEROSHOT_CMDPROOF_LOCK_DIR || path.join(root, 'locks'),
156
158
  privateKeyPath: path.join(keyDir, 'private-key.json'),
157
159
  publicKeyPath: path.join(keyDir, 'public-key.json'),
158
160
  };
@@ -180,6 +182,114 @@ function spawnCmdproof(args, options) {
180
182
  return result;
181
183
  }
182
184
 
185
+ function parsePositiveInt(value, fallback) {
186
+ const parsed = Number.parseInt(String(value || ''), 10);
187
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
188
+ }
189
+
190
+ function sleepMs(ms) {
191
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
192
+ }
193
+
194
+ function proofLockName(proof, verifyReport = null) {
195
+ const actionKey = getVerifyActionKey(verifyReport);
196
+ const digest = crypto
197
+ .createHash('sha256')
198
+ .update(`${proof.id}\0${proof.profile}\0${proof.command}\0${actionKey}`)
199
+ .digest('hex')
200
+ .slice(0, 16);
201
+ const safeId = proof.id.replace(/[^a-zA-Z0-9._-]/g, '_');
202
+ return `${safeId}-${digest}.lock`;
203
+ }
204
+
205
+ function getVerifyActionKey(verifyReport) {
206
+ return verifyReport?.actionKey || verifyReport?.action_key || '';
207
+ }
208
+
209
+ function proofLockPath(proof, paths, verifyReport = null) {
210
+ return path.join(paths.lockDir, proofLockName(proof, verifyReport));
211
+ }
212
+
213
+ function removeLock(lockPath) {
214
+ fs.rmSync(lockPath, { recursive: true, force: true });
215
+ }
216
+
217
+ function lockOwnerState(lockPath) {
218
+ try {
219
+ const metadata = JSON.parse(fs.readFileSync(path.join(lockPath, 'owner.json'), 'utf8'));
220
+ if (!Number.isInteger(metadata.pid) || metadata.pid <= 0) {
221
+ return 'unknown';
222
+ }
223
+ try {
224
+ process.kill(metadata.pid, 0);
225
+ return 'alive';
226
+ } catch (error) {
227
+ return error?.code === 'ESRCH' ? 'gone' : 'unknown';
228
+ }
229
+ } catch {
230
+ return 'unknown';
231
+ }
232
+ }
233
+
234
+ function lockIsStale(lockPath, staleMs) {
235
+ try {
236
+ const stat = fs.statSync(lockPath);
237
+ const ownerState = lockOwnerState(lockPath);
238
+ if (ownerState === 'alive') {
239
+ return false;
240
+ }
241
+ if (ownerState === 'gone') {
242
+ return true;
243
+ }
244
+ return Date.now() - stat.mtimeMs > staleMs;
245
+ } catch {
246
+ return false;
247
+ }
248
+ }
249
+
250
+ function writeLockMetadata(lockPath, proof) {
251
+ const metadata = {
252
+ pid: process.pid,
253
+ proofId: proof.id,
254
+ profile: proof.profile,
255
+ command: proof.command,
256
+ createdAt: new Date().toISOString(),
257
+ };
258
+ fs.writeFileSync(path.join(lockPath, 'owner.json'), `${JSON.stringify(metadata, null, 2)}\n`, {
259
+ mode: 0o600,
260
+ });
261
+ }
262
+
263
+ function tryAcquireProofLock(proof, paths, env, verifyReport = null) {
264
+ const lockPath = proofLockPath(proof, paths, verifyReport);
265
+ const staleMs = parsePositiveInt(env.ZEROSHOT_CMDPROOF_LOCK_STALE_MS, 6 * 60 * 60 * 1000);
266
+ fs.mkdirSync(paths.lockDir, { recursive: true, mode: 0o700 });
267
+
268
+ try {
269
+ fs.mkdirSync(lockPath, { mode: 0o700 });
270
+ writeLockMetadata(lockPath, proof);
271
+ return { acquired: true, lockPath };
272
+ } catch (error) {
273
+ if (error?.code !== 'EEXIST') {
274
+ throw error;
275
+ }
276
+ if (!lockIsStale(lockPath, staleMs)) {
277
+ return { acquired: false, lockPath };
278
+ }
279
+ removeLock(lockPath);
280
+ try {
281
+ fs.mkdirSync(lockPath, { mode: 0o700 });
282
+ writeLockMetadata(lockPath, proof);
283
+ return { acquired: true, lockPath };
284
+ } catch (retryError) {
285
+ if (retryError?.code === 'EEXIST') {
286
+ return { acquired: false, lockPath };
287
+ }
288
+ throw retryError;
289
+ }
290
+ }
291
+ }
292
+
183
293
  function ensureKeypair(paths, options) {
184
294
  fs.mkdirSync(paths.cacheDir, { recursive: true, mode: 0o700 });
185
295
  fs.mkdirSync(paths.keyDir, { recursive: true, mode: 0o700 });
@@ -231,6 +341,74 @@ function runMode(mode, proof, paths, options) {
231
341
  return statusOf(result);
232
342
  }
233
343
 
344
+ function runProveWithLock(proof, paths, options, lockPath) {
345
+ try {
346
+ return runMode('prove', proof, paths, options);
347
+ } finally {
348
+ removeLock(lockPath);
349
+ }
350
+ }
351
+
352
+ function verifyProof(proof, paths, options, { write = true } = {}) {
353
+ const result = spawnCmdproof(buildCmdproofArgs('verify', proof, paths), options);
354
+ if (write) {
355
+ writeResult(result, options.stdout, options.stderr);
356
+ }
357
+ return result;
358
+ }
359
+
360
+ function waitForProofOrProve(proof, paths, options, verifyReport) {
361
+ const startedAt = Date.now();
362
+ const waitMs = parsePositiveInt(options.env.ZEROSHOT_CMDPROOF_WAIT_MS, 30 * 60 * 1000);
363
+ const pollMs = parsePositiveInt(options.env.ZEROSHOT_CMDPROOF_POLL_MS, 2500);
364
+
365
+ while (Date.now() - startedAt < waitMs) {
366
+ sleepMs(pollMs);
367
+ const verifyResult = verifyProof(proof, paths, options, { write: false });
368
+ const latestReport = parseJsonObject(verifyResult.stdout) || verifyReport;
369
+ if (!shouldFallbackFromVerify(verifyResult)) {
370
+ writeResult(verifyResult, options.stdout, options.stderr);
371
+ return statusOf(verifyResult);
372
+ }
373
+
374
+ const lockReport = getVerifyActionKey(latestReport) ? latestReport : verifyReport;
375
+ const lock = tryAcquireProofLock(proof, paths, options.env, lockReport);
376
+ if (lock.acquired) {
377
+ options.stderr.write(
378
+ `zeroshot cmdproof check ${proof.id}: no reusable proof appeared; acquired proof lock after wait.\n`
379
+ );
380
+ return runProveWithLock(proof, paths, options, lock.lockPath);
381
+ }
382
+ }
383
+
384
+ options.stderr.write(
385
+ `zeroshot cmdproof check ${proof.id}: timed out waiting ${waitMs}ms for in-flight proof; leaving miss for caller.\n`
386
+ );
387
+ const finalVerify = verifyProof(proof, paths, options, { write: true });
388
+ return statusOf(finalVerify);
389
+ }
390
+
391
+ function runCheck(proof, paths, options) {
392
+ const verifyResult = verifyProof(proof, paths, options);
393
+ if (!shouldFallbackFromVerify(verifyResult)) {
394
+ return statusOf(verifyResult);
395
+ }
396
+ const verifyReport = parseJsonObject(verifyResult.stdout);
397
+ if (!getVerifyActionKey(verifyReport)) {
398
+ return runMode('prove', proof, paths, options);
399
+ }
400
+
401
+ const lock = tryAcquireProofLock(proof, paths, options.env, verifyReport);
402
+ if (lock.acquired) {
403
+ return runProveWithLock(proof, paths, options, lock.lockPath);
404
+ }
405
+
406
+ options.stderr.write(
407
+ `zeroshot cmdproof check ${proof.id}: proof miss; waiting for in-flight proof from another agent.\n`
408
+ );
409
+ return waitForProofOrProve(proof, paths, options, verifyReport);
410
+ }
411
+
234
412
  function runCmdproof({
235
413
  mode,
236
414
  id,
@@ -250,12 +428,7 @@ function runCmdproof({
250
428
  ensureKeypair(paths, options);
251
429
 
252
430
  if (mode === 'check') {
253
- const verifyResult = spawnCmdproof(buildCmdproofArgs('verify', proof, paths), options);
254
- writeResult(verifyResult, stdout, stderr);
255
- if (!shouldFallbackFromVerify(verifyResult)) {
256
- return statusOf(verifyResult);
257
- }
258
- return runMode('prove', proof, paths, options);
431
+ return runCheck(proof, paths, options);
259
432
  }
260
433
 
261
434
  return runMode(mode, proof, paths, options);
@@ -265,4 +438,5 @@ module.exports = {
265
438
  buildCmdproofArgs,
266
439
  parseCommandToArgv,
267
440
  runCmdproof,
441
+ proofLockName,
268
442
  };
@@ -0,0 +1,12 @@
1
+ const EVENT_COPY = {
2
+ IMPLEMENTATION_READY: 'Implementation ready',
3
+ PR_CREATED: 'Pull request created',
4
+ };
5
+
6
+ function formatMergeStatus(merged) {
7
+ if (merged === true || merged === 'true') return 'merged';
8
+ if (merged === false || merged === 'false') return 'auto-merge pending approval';
9
+ return null;
10
+ }
11
+
12
+ module.exports = { EVENT_COPY, formatMergeStatus };