svamp-cli 0.2.223-canary.1 → 0.2.223

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 (39) hide show
  1. package/dist/{agentCommands-C5_GGAjx.mjs → agentCommands-Be_5WSKW.mjs} +5 -5
  2. package/dist/{auth-DScdYtHm.mjs → auth-CKjLWEcA.mjs} +1 -1
  3. package/dist/cli.mjs +61 -61
  4. package/dist/{commands-CT2Tv6Co.mjs → commands-B_XZmurW.mjs} +1 -1
  5. package/dist/{commands-6BAe3ZN8.mjs → commands-CK3ZbA0p.mjs} +6 -6
  6. package/dist/{commands-CA93Z9KL.mjs → commands-ClmIatXG.mjs} +1 -1
  7. package/dist/{commands-DTauSAs3.mjs → commands-DGD5uSeq.mjs} +2 -2
  8. package/dist/{commands-VZ8zKJVz.mjs → commands-DeBy9HEU.mjs} +1 -1
  9. package/dist/{commands-CdXRrZ7r.mjs → commands-Dw2YesDl.mjs} +1 -1
  10. package/dist/{commands-w_IvSPsW.mjs → commands-DzwKn57T.mjs} +2 -2
  11. package/dist/{fleet-COxsFnsP.mjs → fleet-CPm4Bm3m.mjs} +1 -1
  12. package/dist/{frpc-DFR3Ur4i.mjs → frpc-C2eRCDNy.mjs} +1 -1
  13. package/dist/{headlessCli-WRgiWg8Y.mjs → headlessCli-Bt3ZIoTG.mjs} +2 -2
  14. package/dist/index.mjs +1 -1
  15. package/dist/package-BL4Hd940.mjs +63 -0
  16. package/dist/{rpc-KjAiYjUk.mjs → rpc-CekroPL2.mjs} +1 -1
  17. package/dist/{rpc-B1lMCUGO.mjs → rpc-CrjSXvID.mjs} +1 -1
  18. package/dist/{run-DG_ayhQu.mjs → run-BqvXq2yD.mjs} +17 -24
  19. package/dist/{run-CY8S9-Zp.mjs → run-DUquoPkl.mjs} +1 -1
  20. package/dist/{scheduler-Dl8KDbIn.mjs → scheduler-agjaeQjl.mjs} +1 -1
  21. package/dist/{serveCommands-BeJXNLS6.mjs → serveCommands-Bc4ZhEqi.mjs} +5 -5
  22. package/dist/{serveManager-DJzzl4mT.mjs → serveManager-Ddqi1kbZ.mjs} +2 -2
  23. package/dist/{sideband-BUucmUip.mjs → sideband-lLT0D1PF.mjs} +1 -1
  24. package/package.json +3 -3
  25. package/bin/skills/loop/IMPLEMENTATION_PROGRESS.md +0 -49
  26. package/bin/skills/loop/SKILL.md +0 -100
  27. package/bin/skills/loop/bin/channel-core.mjs +0 -161
  28. package/bin/skills/loop/bin/channel-server.mjs +0 -151
  29. package/bin/skills/loop/bin/inject-loop.mjs +0 -90
  30. package/bin/skills/loop/bin/loop-init.mjs +0 -194
  31. package/bin/skills/loop/bin/loop-status.mjs +0 -51
  32. package/bin/skills/loop/bin/precompact.mjs +0 -30
  33. package/bin/skills/loop/bin/routine-core.mjs +0 -126
  34. package/bin/skills/loop/bin/state-fp.mjs +0 -111
  35. package/bin/skills/loop/bin/stop-gate.mjs +0 -349
  36. package/bin/skills/loop/test/test-channel-core.mjs +0 -86
  37. package/bin/skills/loop/test/test-loop-gate.mjs +0 -489
  38. package/bin/skills/loop/test/test-routine-core.mjs +0 -54
  39. package/dist/package-CvP3Ipae.mjs +0 -63
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { c as connectToHypha } from './run-DG_ayhQu.mjs';
4
+ import { c as connectToHypha } from './run-BqvXq2yD.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-VIupR1NK.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-DG_ayhQu.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-BqvXq2yD.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { P as resolveModel, a1 as describeMisconfiguration, a2 as buildMachineDeps } from './run-DG_ayhQu.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-BUucmUip.mjs';
1
+ import { P as resolveModel, a1 as describeMisconfiguration, a2 as buildMachineDeps } from './run-BqvXq2yD.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-lLT0D1PF.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-DG_ayhQu.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-BqvXq2yD.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -0,0 +1,63 @@
1
+ var name = "svamp-cli";
2
+ var version = "0.2.223";
3
+ var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
+ var author = "Amun AI AB";
5
+ var license = "SEE LICENSE IN LICENSE";
6
+ var type = "module";
7
+ var bin = {
8
+ svamp: "./bin/svamp.mjs"
9
+ };
10
+ var files = [
11
+ "dist",
12
+ "bin"
13
+ ];
14
+ var main = "./dist/index.mjs";
15
+ var exports$1 = {
16
+ ".": "./dist/index.mjs",
17
+ "./cli": "./dist/cli.mjs"
18
+ };
19
+ var scripts = {
20
+ build: "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
21
+ typecheck: "tsc --noEmit",
22
+ test: "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs",
23
+ "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
+ dev: "tsx src/cli.ts",
25
+ "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
+ "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
+ "test:frpc": "npx tsx test/test-frpc-e2e.mjs"
28
+ };
29
+ var dependencies = {
30
+ "@agentclientprotocol/sdk": "^0.14.1",
31
+ "@modelcontextprotocol/sdk": "^1.25.3",
32
+ "hypha-rpc": "0.21.42",
33
+ "node-pty": "1.2.0-beta.11",
34
+ ws: "^8.18.0",
35
+ yaml: "^2.8.2",
36
+ zod: "^3.24.4"
37
+ };
38
+ var devDependencies = {
39
+ "@types/node": ">=20",
40
+ "@types/ws": "^8.5.14",
41
+ pkgroll: "^2.14.2",
42
+ tsx: "^4.20.6",
43
+ typescript: "5.9.3"
44
+ };
45
+ var packageManager = "yarn@1.22.22";
46
+ var _package = {
47
+ name: name,
48
+ version: version,
49
+ description: description,
50
+ author: author,
51
+ license: license,
52
+ type: type,
53
+ bin: bin,
54
+ files: files,
55
+ main: main,
56
+ exports: exports$1,
57
+ scripts: scripts,
58
+ dependencies: dependencies,
59
+ devDependencies: devDependencies,
60
+ packageManager: packageManager
61
+ };
62
+
63
+ export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-DG_ayhQu.mjs';
1
+ import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-BqvXq2yD.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot } from './run-DG_ayhQu.mjs';
1
+ import { m as resolveProjectRoot } from './run-BqvXq2yD.mjs';
2
2
  import { g as getWorkflow, s as setWorkflowEnabled, r as removeWorkflow, a as saveWorkflow, b as rawWorkflow, l as listWorkflows } from './store-BTs0H_y0.mjs';
3
3
  import { g as getRun, l as listRuns, r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -2912,7 +2912,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2912
2912
  const tunnels = handlers.tunnels;
2913
2913
  if (!tunnels) throw new Error("Tunnel management not available");
2914
2914
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2915
- const { FrpcTunnel } = await import('./frpc-DFR3Ur4i.mjs');
2915
+ const { FrpcTunnel } = await import('./frpc-C2eRCDNy.mjs');
2916
2916
  const tunnel = new FrpcTunnel({
2917
2917
  name: params.name,
2918
2918
  ports: params.ports,
@@ -3359,7 +3359,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3359
3359
  }
3360
3360
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3361
3361
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3362
- const { toolsForRole } = await import('./sideband-BUucmUip.mjs');
3362
+ const { toolsForRole } = await import('./sideband-lLT0D1PF.mjs');
3363
3363
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3364
3364
  return fmt(r2);
3365
3365
  }
@@ -3458,7 +3458,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3458
3458
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3459
3459
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3460
3460
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3461
- const { queryCore } = await import('./commands-CT2Tv6Co.mjs');
3461
+ const { queryCore } = await import('./commands-B_XZmurW.mjs');
3462
3462
  const timeout = c.reply?.timeout_sec || 120;
3463
3463
  let result;
3464
3464
  try {
@@ -11295,20 +11295,6 @@ async function ensureAutoInstalledSkills(logger) {
11295
11295
  },
11296
11296
  marketplaceVersion: async () => readBundledSkillVersion("artifact")
11297
11297
  },
11298
- {
11299
- // The self-verifying `loop` skill drives loop mode (Stop-hook gate +
11300
- // LOOP.md injection). Bundled in the npm package (bin/skills/loop/) so the
11301
- // daemon can run loop-init.mjs even offline / before marketplace publish.
11302
- name: "loop",
11303
- install: async () => {
11304
- try {
11305
- installBundledSkill("loop");
11306
- } catch {
11307
- await installSkillFromMarketplace("loop");
11308
- }
11309
- },
11310
- marketplaceVersion: async () => readBundledSkillVersion("loop")
11311
- },
11312
11298
  {
11313
11299
  // The `crew` skill: a lead session delegates features to managed worktree
11314
11300
  // children (svamp feature start/list/done/merge) and merges them back.
@@ -12188,7 +12174,7 @@ async function startDaemon(options) {
12188
12174
  saveExposedTunnels(list);
12189
12175
  }
12190
12176
  async function createExposedTunnel(spec) {
12191
- const { FrpcTunnel } = await import('./frpc-DFR3Ur4i.mjs');
12177
+ const { FrpcTunnel } = await import('./frpc-C2eRCDNy.mjs');
12192
12178
  const tunnel = new FrpcTunnel({
12193
12179
  name: spec.name,
12194
12180
  ports: spec.ports,
@@ -12208,7 +12194,7 @@ async function startDaemon(options) {
12208
12194
  return tunnel;
12209
12195
  }
12210
12196
  const tunnelRecreateState = /* @__PURE__ */ new Map();
12211
- const { ServeManager } = await import('./serveManager-DJzzl4mT.mjs');
12197
+ const { ServeManager } = await import('./serveManager-Ddqi1kbZ.mjs');
12212
12198
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
12213
12199
  ensureAutoInstalledSkills(logger).catch(() => {
12214
12200
  });
@@ -13271,6 +13257,13 @@ ${parts.join("\n")}`);
13271
13257
  }
13272
13258
  checkSvampConfig?.();
13273
13259
  clearInboundContext(sessionId);
13260
+ try {
13261
+ const ls = readLoopState(directory, sessionId);
13262
+ if (ls && ls.engine === "goal" && ls.active !== false) {
13263
+ writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
13264
+ }
13265
+ } catch {
13266
+ }
13274
13267
  maybeBootstrap();
13275
13268
  if (backgroundTaskCount > 0) {
13276
13269
  const taskInfo = `Background tasks still running (${backgroundTaskCount}): ${backgroundTaskNames.join(", ")}`;
@@ -14102,11 +14095,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14102
14095
  });
14103
14096
  },
14104
14097
  onIssue: async (params) => {
14105
- const { issueRpc } = await import('./rpc-KjAiYjUk.mjs');
14098
+ const { issueRpc } = await import('./rpc-CekroPL2.mjs');
14106
14099
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14107
14100
  },
14108
14101
  onWorkflow: async (params) => {
14109
- const { workflowRpc } = await import('./rpc-B1lMCUGO.mjs');
14102
+ const { workflowRpc } = await import('./rpc-CrjSXvID.mjs');
14110
14103
  return workflowRpc(params?.cwd || directory, params || {});
14111
14104
  },
14112
14105
  onRipgrep: async (args, cwd) => {
@@ -14639,11 +14632,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14639
14632
  });
14640
14633
  },
14641
14634
  onIssue: async (params) => {
14642
- const { issueRpc } = await import('./rpc-KjAiYjUk.mjs');
14635
+ const { issueRpc } = await import('./rpc-CekroPL2.mjs');
14643
14636
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14644
14637
  },
14645
14638
  onWorkflow: async (params) => {
14646
- const { workflowRpc } = await import('./rpc-B1lMCUGO.mjs');
14639
+ const { workflowRpc } = await import('./rpc-CrjSXvID.mjs');
14647
14640
  return workflowRpc(params?.cwd || directory, params || {});
14648
14641
  },
14649
14642
  onRipgrep: async (args, cwd) => {
@@ -15417,7 +15410,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15417
15410
  const PING_TIMEOUT_MS = 15e3;
15418
15411
  const POST_RECONNECT_GRACE_MS = 2e4;
15419
15412
  const RECONNECT_JITTER_MS = 2500;
15420
- const { WorkflowScheduler } = await import('./scheduler-Dl8KDbIn.mjs');
15413
+ const { WorkflowScheduler } = await import('./scheduler-agjaeQjl.mjs');
15421
15414
  const workflowScheduler = new WorkflowScheduler({
15422
15415
  projectRoots: () => {
15423
15416
  const dirs = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a3 as applyClaudeProxyEnv, a4 as composeSessionId, a5 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a6 as generateHookSettings } from './run-DG_ayhQu.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a3 as applyClaudeProxyEnv, a4 as composeSessionId, a5 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a6 as generateHookSettings } from './run-BqvXq2yD.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, y as cronMatches } from './run-DG_ayhQu.mjs';
1
+ import { m as resolveProjectRoot, y as cronMatches } from './run-BqvXq2yD.mjs';
2
2
  import { l as listWorkflows, i as isWorkflowEnabled, w as workflowCrons } from './store-BTs0H_y0.mjs';
3
3
  import { r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-CT2Tv6Co.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-B_XZmurW.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-CT2Tv6Co.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-B_XZmurW.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-CT2Tv6Co.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-B_XZmurW.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-CT2Tv6Co.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-B_XZmurW.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-CT2Tv6Co.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-B_XZmurW.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-DG_ayhQu.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-BqvXq2yD.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -733,7 +733,7 @@ class ServeManager {
733
733
  const mount = this.mounts.get(mountName);
734
734
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
735
735
  try {
736
- const { FrpcTunnel } = await import('./frpc-DFR3Ur4i.mjs');
736
+ const { FrpcTunnel } = await import('./frpc-C2eRCDNy.mjs');
737
737
  let tunnel;
738
738
  tunnel = new FrpcTunnel({
739
739
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-DG_ayhQu.mjs';
1
+ import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-BqvXq2yD.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.223-canary.1",
3
+ "version": "0.2.223",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -18,9 +18,9 @@
18
18
  "./cli": "./dist/cli.mjs"
19
19
  },
20
20
  "scripts": {
21
- "build": "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/loop bin/skills/loop && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
21
+ "build": "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs",
23
+ "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -1,49 +0,0 @@
1
- # Loops, Routines & Channels — implementation status
2
-
3
- Branch: `loop-engineering-phase1`. Design docs: `docs/loops-and-routines-design.md`, `docs/channels-design.md`.
4
-
5
- This consolidates the work into one accurate picture: what's built, how it was verified, and the single remaining step (deploy + UI QA).
6
-
7
- ---
8
-
9
- ## 1. Loop engine — DONE (tested + live-E2E + 2 adversarial reviews)
10
- Skill-first loop with a harness-enforced completion gate. Files in `skills/loop/`.
11
- - **`SKILL.md`** — loop-engineering protocol (actor ≠ judge; oracle + isolated evaluator).
12
- - **`bin/stop-gate.mjs`** — Claude Code `Stop` hook: blocks the turn ending unless the **oracle exits 0 AND a fingerprint-fresh evaluator verdict says "done."** Bounded by clamped `max_iterations` (hard ceiling 200), runtime budget, and token budget (summed from the transcript). Appends a per-iteration audit trail.
13
- - **`bin/state-fp.mjs`** — working-tree fingerprint (git diff, or filesystem walk incl. symlink targets for non-git dirs) tying a verdict to the exact code it judged.
14
- - **`bin/inject-loop.mjs`** (SessionStart/UserPromptSubmit) · **`bin/precompact.mjs`** (PreCompact handoff) · **`bin/loop-init.mjs`** (generates LOOP.md + `.claude/settings.json` hooks + optional `loop-evaluator` agent) · **`bin/loop-status.mjs`** (timeline reader).
15
- - **Tests:** `test/test-loop-gate.mjs` (29) — all pass. **Live-verified** in real svamp sessions: happy path, **early-quit blocked**, isolated evaluator subagent, full regression.
16
- - Skill↔gate↔inject **contract verified consistent** (verdict path/fields/`state_fp` command all agree).
17
-
18
- ## 2. Routines — DONE (tested + live-E2E)
19
- Session-scoped triggers → message/loop actions. `skills/loop/bin/routine-*.mjs` (JS engine + supervised server) and `packages/svamp-cli/src/routine/*.ts` (daemon).
20
- - Triggers: schedule (cron, tz-aware tick **and** catch-up), webhook (capability URL, GET/POST), manual. Actions: message | loop. Overlap (queue/skip/replace concurrent-guard), daily_cap (reserve-before-deliver), missed-run catch-up.
21
- - `svamp routine add/list/remove/enable/disable/run-now/serve`; daemon RPC (`hyphaSessionService`, 10 runtime tests); frontend **Routines tab**.
22
- - **Tests:** routine-core, routine-engine (26), cli routine (18), cli routine-rpc (10) — all pass. **Live-verified**: webhook → templated message delivered into a real session.
23
-
24
- ## 3. Channels — DONE (tested + live-E2E both transports + 2 security reviews)
25
- External / agent-to-agent inbound endpoints. `skills/loop/bin/channel-*.mjs` + `packages/svamp-cli/src/channel/store.ts`.
26
- - Identity: per-key (verified) / caller-supplied (unverified) / fixed / Hypha `context.user` (opt-in `hypha_allow`, deny-by-default, anonymous rejected). Default **XML provenance template** (all values XML-escaped + control-chars stripped → no forgery/tag-breakout). Auto-generated **skill** per channel.
27
- - Transports: **Hypha `svamp-channels` service** (verified identity, no key) **and** HTTP `/channel/<id>` (+ `/skill.md`, bearer key). Delivers as a **plain single-tag message**. Actions: **message and loop** (loop seeds `loop-init` + kickoff).
28
- - `hypha channel list/describe/send` CLI client; daemon RPC (10 runtime tests); frontend **Channels tab**.
29
- - **Tests:** channel-core (32), cli channel-rpc (10) — all pass. **Live-verified**: HTTP per-key (`from=alice verified`), Hypha (`from=<hypha-user> verified`, no key), loop-action (loop seeded + work completed).
30
-
31
- ## Verification summary
32
- All feature suites green; **svamp-cli + svamp-app + hypha-cli typecheck 0 errors**. Standalone/skill parts live-E2E-verified. Daemon RPC runtime-verified via `createSessionStore`. Two adversarial review rounds each on the loop gate and channels (caught + fixed 3 CRITICAL block-forever bugs and a CRITICAL provenance-forgery hole); daemon-code review confirmed no destabilization. Review has **converged** (latest pass found nothing).
33
-
34
- ---
35
-
36
- ## The ONE remaining step — deploy + UI QA (must be done from a SEPARATE session)
37
- The daemon RPC + the three frontend tabs (Routines / Channels / Loop-monitor) are typecheck-clean and production-bundle clean, but their **live rendering + RPC round-trip have not been visually confirmed** — and this repo has no component-render test infra (UI is verified by deploy/manual). Activating them restarts the daemon hosting this loop session, so it is **deliberately not done here**. To finish:
38
- ```
39
- yarn workspace svamp-cli build && svamp daemon restart # activates routine/channel RPC
40
- ./deploy/quick-deploy.sh # ships the web UI
41
- ```
42
- Then open a session → input → settings and confirm the **Routines / Channels / Loop-monitor** tabs render and round-trip. The standalone engines need no deploy — `node skills/loop/bin/loop-init.mjs …`, `svamp routine …`, and the supervised channel/routine servers already work today.
43
-
44
- ## Deferred (by design or low-risk-to-leave)
45
- - **await-reply (channels v1.1)** — request/response; v1 is async (two channels), per the design decision.
46
- - **Loop gate on an already-running session** — hooks load at Claude startup, so looping a live session is best-effort (self-contained kickoff); full enforcement needs a fresh/restarted session.
47
- - **#4/#6/#11 concurrency edges** — cross-process lost-update / git↔walk fp flip / store last-writer-wins. Low-likelihood for the single-session loop; a forced fix needs locking that could itself cause block-forever, so documented rather than fixed.
48
- - **Token/cost budget in USD** — token budget (from transcript) is implemented; USD cost would need daemon usage accounting.
49
- </content>
@@ -1,100 +0,0 @@
1
- ---
2
- name: loop
3
- version: 0.4.2
4
- description: Run a task as a reliable, self-verifying loop — iterate until objective exit conditions are met, with an independent evaluator instead of self-judging. Use when a task needs repeated iterations until "done" (fix until tests pass, refactor until clean, build until a spec is met, autonomous long-running work).
5
- ---
6
-
7
- # Loop — loop engineering for long-running, self-verifying agents
8
-
9
- This skill turns a task into a **loop**: the agent iterates until the work is *objectively* done, not until the agent *feels* done. It is built on one hard-won lesson from loop engineering — **the thing that decides "done" must be independent of the thing doing the work.**
10
-
11
- ## Why loops, and why this design
12
-
13
- Prompting an agent turn-by-turn doesn't scale to long or autonomous work. Loop engineering is the shift from *running the agent yourself* to *designing the system that runs it*. A good loop has a small number of durable parts, and most of the leverage is in **the loop, not the prompt**.
14
-
15
- The failure this design exists to prevent is **early quitting**: a model asked to judge its own output will confidently declare success on mediocre or incomplete work, and models also tend to "wrap up" as they approach context limits ("context anxiety"). Trusting the actor to say `DONE` is the single biggest source of unreliable loops.
16
-
17
- So this loop is built on these principles:
18
-
19
- 1. **Actor ≠ judge.** The agent doing the work never decides completion. An *independent* evaluator — a fresh, skeptical agent that only sees the artifacts, not your reasoning — does. Calibrating a separate evaluator toward skepticism is far more reliable than asking yourself to be self-critical.
20
- 2. **Verification needs an oracle.** The strongest signal is something *outside any model* that returns a hard yes/no: a test suite, a build, a linter, a type-check, a script. The loop runs the oracle itself; you cannot fake it.
21
- 3. **State lives on disk, not in context.** *The model forgets; the repo doesn't.* The task, plan, and progress live in **`LOOP.md`** in the project — readable and editable by both you and the human, surviving compaction, restarts, and fresh sessions. It is your durable memory and your self-improving harness.
22
- 4. **The harness enforces, the prompt only requests.** Completion is gated by a Claude Code **`Stop` hook** that the runtime *obeys* — it physically refuses to let the turn end until the exit conditions hold. This is what makes the loop reliable rather than aspirational.
23
- 5. **Bounded by construction.** Every loop has a `max_iterations` and a token/cost budget so it cannot run away.
24
- 6. **Stay auditable.** Every iteration leaves a trail (oracle result, evaluator verdict, progress notes) so a human can review *why* it stopped, not just *that* it stopped. Build the loop like someone who intends to stay the engineer.
25
-
26
- These principles are stable even as models get stronger. As the actor improves, you simply lean on lighter scaffolding (e.g. trust longer continuous context, fewer resets) — but *independent verification of "done"* and *durable on-disk state* remain correct regardless of model capability. Don't reimplement what the runtime already gives you (subagents, compaction, worktrees, usage accounting); compose them.
27
-
28
- ## The loop, in one picture
29
-
30
- ```
31
- read LOOP.md ─► do real work on the task ─► update LOOP.md progress
32
- ▲ │
33
- │ (think you're done?)
34
- │ ▼
35
- │ run ORACLE + spawn independent EVALUATOR
36
- │ │
37
- Stop hook blocks ◄── not done ── GATE ── done ──► loop ends
38
- (oracle/verdict fails)
39
- ```
40
-
41
- Each turn is one iteration. When you try to end your turn, the gate re-checks the exit conditions. If they don't hold, you are sent back with specific feedback. You converge by *fixing the blocking issue*, not by re-asserting that you're done.
42
-
43
- ## Exit conditions (what "done" means)
44
-
45
- The loop ends only when **all** configured conditions hold:
46
- - **Oracle passes** — the configured command exits 0. The gate runs it; do not stub or fake it.
47
- - **Independent evaluator says "done"** (when enabled) — a *fresh* verdict tied to the *current* code state. A verdict written before later edits is stale and rejected.
48
-
49
- ## Your protocol each iteration
50
-
51
- 1. **Read `LOOP.md`.** It holds the task, success criteria, plan, and progress. (It is auto-injected each turn.)
52
- 2. **Make real progress** on the task. Prefer the smallest change that advances a success criterion.
53
- 3. **Keep `LOOP.md` current** — refine the plan, append what you did, what passed, what's left. This is the memory the *next* iteration (or a post-restart fresh session) relies on. You may restructure it; you may **not** weaken the success criteria to make quitting easier (that's visible in git and to the human).
54
- 4. **Run the oracle yourself** to check you're actually passing before claiming done.
55
- 5. **When you believe it's complete, get an independent verdict — do not grade yourself:**
56
- - Spawn a fresh subagent with the `Task` tool (use the `loop-evaluator` agent if present, otherwise an inline *skeptical reviewer* prompt). Give it **only** the goal (from `LOOP.md`), the current diff, and the oracle output — never your chain of thought.
57
- - For an **issue-backlog** loop the evaluator must go further than the oracle: confirm EACH issue closed this round is genuinely resolved by the real change, AND judge **holistically from the user's perspective** — re-read the *original request* behind each closed issue and the *overall project goal*, and confirm we delivered what the user actually wanted end-to-end (built / published / deployed wherever the request implied it, not merely committed). A green oracle only means "no open issues"; reject `done` if the project goal isn't truly met for the user.
58
- - It returns `{"verdict":"done"|"continue","reason":...,"guidance":...}`. It should default to `continue` on any doubt.
59
- - Record it to `.claude/loop/evaluator-verdict.json`, adding the current state fingerprint:
60
- `{"verdict":"done","reason":"...","guidance":"...","state_fp":"<output of: node .claude/loop/bin/state-fp.mjs>"}`
61
- 6. **End your turn.** The Stop gate verifies oracle + fresh verdict and either ends the loop or sends you back with the exact blocking reason. Address that reason next iteration.
62
-
63
- If you're blocked by something genuinely outside the task (missing secret, ambiguous requirement, external outage), record it clearly in `LOOP.md` so the human sees it on review.
64
-
65
- ## Setting up a loop
66
-
67
- Initialise the loop scaffolding in a project (idempotent):
68
-
69
- ```bash
70
- node <skill>/bin/loop-init.mjs <project-dir> \
71
- --task "Make the failing auth tests pass without weakening them" \
72
- --criteria "All auth tests pass; no test was weakened or skipped" \
73
- --oracle "npm test --silent" \
74
- --evaluator on \
75
- --max 20
76
- ```
77
-
78
- This generates: `LOOP.md` (task/criteria/progress), `.claude/loop/loop.config.json`, machine state in `.claude/loop/loop-state.json`, the `Stop`/`SessionStart` hooks in `.claude/settings.json`, and (optionally) a materialised `.claude/agents/loop-evaluator.md` skeptical reviewer. Re-running with new flags retunes a live loop.
79
-
80
- ## Anatomy (files & ownership)
81
-
82
- | File | Owner | Purpose |
83
- |---|---|---|
84
- | `LOOP.md` | you + human | task · success criteria · plan · progress (durable memory, git-visible) |
85
- | `.claude/loop/loop.config.json` | config | oracle command, evaluator on/off + model, max_iterations |
86
- | `.claude/loop/loop-state.json` | the gate | iteration, phase, last oracle/eval result (machine state — don't hand-edit) |
87
- | `.claude/loop/evaluator-verdict.json` | written from the evaluator subagent | latest verdict + state fingerprint |
88
- | `.claude/settings.json` (hooks) | generated | `Stop` gate + `SessionStart`/`UserPromptSubmit` LOOP.md injection |
89
- | `.claude/agents/loop-evaluator.md` | generated (optional) | skeptical reviewer persona + model |
90
-
91
- ## Tuning & extending (future-proofing)
92
-
93
- - **No oracle?** Lean fully on the evaluator (weaker — prefer adding *some* objective check, even a smoke script).
94
- - **Stronger judgment?** Point the evaluator at a stronger model via `--model`.
95
- - **Cheaper?** Disable the evaluator (`--evaluator off`) and rely on the oracle alone for purely objective tasks.
96
- - **Different exit logic** (scores/thresholds, FINISH/REFINE/PIVOT strategies, multi-dimensional rubrics): the verdict file is just JSON and the gate reads it — richer verdicts can be added without changing the protocol.
97
- - **Scheduling & lifecycle** (run on a schedule): handled by Workflows (`svamp workflow add … --on schedule --cron "…"`, GitHub-Actions-style), not this skill — a workflow step can start a loop (`svamp session loop …`). The loop itself stays a pure iterate-until-done engine.
98
- - **Compaction handoff**: progress in `LOOP.md` is the handoff artifact; a `PreCompact` hook can flush it automatically for very long runs.
99
-
100
- The contract that must never change: **work is decided done by something other than the worker, and the loop's memory lives on disk.** Everything else is tunable.