svamp-cli 0.2.163 → 0.2.165

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.
@@ -58,7 +58,7 @@ rmSync(join(loopDir, 'evaluator-verdict.json'), { force: true });
58
58
  rmSync(join(loopDir, 'history.jsonl'), { force: true });
59
59
 
60
60
  // 1. Copy hook scripts so the project is self-contained.
61
- for (const f of ['state-fp.mjs', 'stop-gate.mjs', 'checklist.mjs', 'inject-loop.mjs', 'loop-status.mjs', 'precompact.mjs']) {
61
+ for (const f of ['state-fp.mjs', 'stop-gate.mjs', 'inject-loop.mjs', 'loop-status.mjs', 'precompact.mjs']) {
62
62
  const dest = join(binDir, f);
63
63
  copyFileSync(join(HERE, f), dest);
64
64
  try { chmodSync(dest, 0o755); } catch {}
@@ -16,7 +16,6 @@ import { readFileSync, writeFileSync, renameSync, existsSync, appendFileSync, st
16
16
  import { dirname, join, resolve, relative } from 'node:path';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { stateFingerprint } from './state-fp.mjs';
19
- import { readEffectiveChecklist, evaluateChecklist, allPassing, summarize, writeChecklistStatuses } from './checklist.mjs';
20
19
 
21
20
  const HERE = dirname(fileURLToPath(import.meta.url));
22
21
  // Resolve the loop home from the per-process env the daemon injects
@@ -132,26 +131,10 @@ if (evaluatorOn) {
132
131
  }
133
132
  }
134
133
 
135
- // --- (3) Checklist (the loop-engineering criteria atom) -----------------
136
- // The effective checklist = project invariants session goals. Each item with an
137
- // oracle is re-evaluated here (regression check); refreshed statuses are persisted
138
- // so the UI + agent see live state. No-op when no criteria.json exists anywhere
139
- // (allPassing([]) === true) — fully backward-compatible with criteria-only loops.
140
- let checklistPass = true;
141
- let checklistDetail = 'no checklist';
142
- try {
143
- const items = evaluateChecklist(readEffectiveChecklist(LOOP_DIR, PROJECT), PROJECT);
144
- if (items.length > 0) {
145
- writeChecklistStatuses(LOOP_DIR, PROJECT, items);
146
- checklistPass = allPassing(items);
147
- const notDone = items.filter((i) => i.status !== 'done');
148
- checklistDetail = checklistPass
149
- ? `checklist: ${summarize(items)} — all done`
150
- : `checklist: ${summarize(items)}\n--- not yet done ---\n${notDone.map((i) => `[${i.scope}] ${i.text}${i._oracle ? ` (oracle: ${i._oracle})` : ''}`).join('\n')}`;
151
- }
152
- } catch { /* checklist is best-effort; never let it trap the gate */ }
153
-
154
- const done = oraclePass && evaluatorPass && checklistPass;
134
+ // The loop gate is now two conditions: a real pass/fail oracle AND an independent
135
+ // evaluator verdict. (The old best-effort "criteria atom" a separate criteria.json
136
+ // checklist — was removed: the backlog/oracle is the single source of success criteria.)
137
+ const done = oraclePass && evaluatorPass;
155
138
 
156
139
  // --- Decide -------------------------------------------------------------
157
140
  const now = new Date().toISOString();
@@ -190,7 +173,7 @@ if (giveUp) {
190
173
  }
191
174
 
192
175
  writeJSONAtomic(STATE, { ...state, iteration: nextIter, phase: 'continue',
193
- last_iteration_at: now, last_oracle: oracleDetail, last_eval: evaluatorDetail, last_checklist: checklistDetail, ...tokenField });
176
+ last_iteration_at: now, last_oracle: oracleDetail, last_eval: evaluatorDetail, ...tokenField });
194
177
 
195
178
  appendHistory({ ts: now, iteration: nextIter, decision: 'continue', oracle: oraclePass, evaluator: evaluatorPass, detail: oraclePass ? evaluatorDetail : oracleDetail });
196
179
 
@@ -198,7 +181,6 @@ const remaining = max != null ? ` (iteration ${nextIter}/${max})` : '';
198
181
  const VERDICT_REL = relative(PROJECT, VERDICT) || VERDICT;
199
182
  const STATEFP_REL = relative(PROJECT, join(LOOP_DIR, 'bin', 'state-fp.mjs')) || join(LOOP_DIR, 'bin', 'state-fp.mjs');
200
183
  const evalHint = evaluatorOn && !evaluatorPass && oraclePass
201
- ? `\n\nThe code looks like it may be ready, but you must get an independent verdict: spawn the \`loop-evaluator\` subagent (or a fresh Task agent with a skeptical reviewer prompt) to judge the current diff against LOOP.md, then write its result to \`${VERDICT_REL}\` as {"verdict":"done"|"continue","reason":"...","guidance":"...","state_fp":"<run: node ${STATEFP_REL}>"}. Do not write the verdict yourself.`
184
+ ? `\n\nThe code looks like it may be ready, but you must get an independent verdict: spawn the \`loop-evaluator\` subagent (or a fresh Task agent with a skeptical reviewer prompt) to judge the current diff against LOOP.md. If this loop works an issue backlog, the evaluator MUST also confirm that EACH issue closed during this loop is genuinely resolved by the actual change — a green oracle only means 'no open issues', not that each closed issue works; reject 'done' if any was closed without real resolution. Then write its result to \`${VERDICT_REL}\` as {"verdict":"done"|"continue","reason":"...","guidance":"...","state_fp":"<run: node ${STATEFP_REL}>"}. Do not write the verdict yourself.`
202
185
  : '';
203
- const checklistHint = !checklistPass ? `\n\n${checklistDetail}\nWork the items above until each one's oracle passes; finished items must stay green (regressions re-open).` : '';
204
- block(`Loop is not complete${remaining}. Keep working on the task in LOOP.md.\n\n${oracleDetail}\n${evaluatorOn ? '\n' + evaluatorDetail : ''}${checklistHint}${evalHint}\n\nUpdate LOOP.md progress, fix the blocking issue, then finish your turn again to be re-checked.`);
186
+ block(`Loop is not complete${remaining}. Keep working on the task in LOOP.md.\n\n${oracleDetail}\n${evaluatorOn ? '\n' + evaluatorDetail : ''}${evalHint}\n\nUpdate LOOP.md progress, fix the blocking issue, then finish your turn again to be re-checked.`);
package/dist/cli.mjs CHANGED
@@ -375,7 +375,7 @@ async function main() {
375
375
  }), machineId);
376
376
  process.exit(0);
377
377
  } else if (subcommand === "issue" || subcommand === "issues") {
378
- const { issueCommand } = await import('./commands-C8uqsoRM.mjs');
378
+ const { issueCommand } = await import('./commands-BQC2kAm0.mjs');
379
379
  await issueCommand(args.slice(1));
380
380
  process.exit(0);
381
381
  } else if (subcommand === "workflow" || subcommand === "workflows") {
@@ -398,7 +398,7 @@ async function main() {
398
398
  } else if (!subcommand || subcommand === "start") {
399
399
  await handleInteractiveCommand();
400
400
  } else if (subcommand === "--version" || subcommand === "-v") {
401
- const pkg = await import('./package-Dwhojj-r.mjs').catch(() => ({ default: { version: "unknown" } }));
401
+ const pkg = await import('./package-1hBv_QRg.mjs').catch(() => ({ default: { version: "unknown" } }));
402
402
  console.log(`svamp version: ${pkg.default.version}`);
403
403
  } else {
404
404
  console.error(`Unknown command: ${subcommand}`);
@@ -1,7 +1,7 @@
1
- import { r as resolveProjectRoot, s as searchIssues, l as listIssues, a as addComment, u as updateIssue, b as summarize, c as addIssue, g as getIssue } from './store-BQxdRyuI.mjs';
1
+ import { execSync } from 'node:child_process';
2
+ import { r as resolveProjectRoot, s as searchIssues, l as listIssues, a as addComment, u as updateIssue, g as getIssue, b as summarize, c as addIssue } from './store-BQxdRyuI.mjs';
2
3
  import 'node:fs';
3
4
  import 'node:path';
4
- import 'node:child_process';
5
5
 
6
6
  const STATUS_GLYPH = {
7
7
  backlog: "\u25CB",
@@ -166,6 +166,26 @@ ${issue.body}`);
166
166
  archive: "archived",
167
167
  backlog: "backlog"
168
168
  };
169
+ if (sub === "close" || sub === "done") {
170
+ const current = getIssue(root, id);
171
+ const vc = current?.verify?.type === "command" ? current.verify.text?.trim() : "";
172
+ if (vc) {
173
+ if (has(rest, "--force")) {
174
+ console.error(`\u26A0 #${id}: closing WITHOUT running verify-cmd (--force): \`${vc}\``);
175
+ } else {
176
+ try {
177
+ execSync(vc, { cwd: root, stdio: "pipe", timeout: 6e5, maxBuffer: 64 * 1024 * 1024 });
178
+ } catch (e) {
179
+ const tail = (String(e?.stdout || "") + String(e?.stderr || "")).split("\n").slice(-15).join("\n").trim();
180
+ console.error(`Refusing to close #${id}: its verify-cmd failed \u2014 the fix is not verified.
181
+ $ ${vc}
182
+ ${tail}
183
+ (Fix the issue, or pass --force to override.)`);
184
+ process.exit(1);
185
+ }
186
+ }
187
+ }
188
+ }
169
189
  const updated = updateIssue(root, id, { status: map[sub] });
170
190
  if (!updated) {
171
191
  console.error(`Issue not found: ${id}`);
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.163";
2
+ var version = "0.2.165";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist bin/skills && 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 && tsc --noEmit && pkgroll",
21
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-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-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.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-workflow-store.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-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.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-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-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
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-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-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.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-workflow-store.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-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.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-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-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.163",
3
+ "version": "0.2.165",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills && 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 && 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-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-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.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-workflow-store.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-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.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-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-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.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-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-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.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-workflow-store.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-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.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-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-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.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,129 +0,0 @@
1
- // checklist.mjs — the loop-engineering checklist atom, gate side.
2
- // See docs/checklist-atom-spec.md + docs/svamp-loop-engineering-vision.md. A checklist
3
- // is a list of evaluable goal items persisted as JSON, in two layered scopes:
4
- // session: <loopDir>/criteria.json (this session's goal)
5
- // project: <projectDir>/.svamp/criteria.json (durable invariants, all sessions)
6
- // Effective checklist a session enforces = project ∪ session. Each item is oracle-checked
7
- // (an eval cmd) or agent/human-evaluated. Done ≠ gone: a 'done' item STAYS and is
8
- // re-verified every loop, so it can regress to 'blocked'. The gate lets the turn end only
9
- // when ALL effective items are 'done'.
10
- //
11
- // This is the GATE runtime (a .mjs skill — it cannot import the TS atom in
12
- // checklist/core.ts), so it mirrors the canonical vocab by value: ItemStatus +
13
- // canonicalChecklistStatus are kept in sync with sync/checklistModel.ts + parseMarkdown.ts.
14
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
15
- import { join, dirname } from 'node:path';
16
- import { execSync } from 'node:child_process';
17
-
18
- // CANONICAL: the session checklist lives INSIDE the loop dir at
19
- // <project>/.svamp/<sid>/loop/criteria.json — beside the other loop-gate state
20
- // (loop-state.json, supervisor-verdict.json — the latter is the daemon's legacy
21
- // verdict file name). Matches the daemon writer
22
- // (checklist/core.ts checklistPath) + the frontend (sync/ops.ts sessionChecklistRel).
23
- export function sessionChecklistPath(loopDir) { return join(loopDir, 'criteria.json'); }
24
- export function projectChecklistPath(projectDir) { return join(projectDir, '.svamp', 'criteria.json'); }
25
-
26
- /**
27
- * Map any accepted token — canonical OR the legacy loop aliases (pending/passing/failing) —
28
- * to the canonical ItemStatus set. Mirrors checklistModel.canonicalChecklistStatus.
29
- */
30
- export function canonicalChecklistStatus(raw) {
31
- switch (String(raw ?? '').toLowerCase()) {
32
- case 'passing': case 'done': return 'done';
33
- case 'failing': case 'blocked': return 'blocked';
34
- case 'pending': case 'todo': case '': return 'todo';
35
- case 'active': case 'in_progress': case 'in-progress': return 'active';
36
- case 'verifying': return 'verifying';
37
- case 'awaiting_review': case 'awaiting-review': case 'review': return 'awaiting_review';
38
- case 'rework': return 'rework';
39
- default: return 'todo';
40
- }
41
- }
42
-
43
- /** The oracle command for an item: the atom's eval.cmd (type:'oracle'), else legacy item.oracle. */
44
- function itemOracle(it) {
45
- if (it?.eval?.type === 'oracle' && typeof it.eval.cmd === 'string' && it.eval.cmd.trim()) return it.eval.cmd.trim();
46
- return typeof it?.oracle === 'string' && it.oracle.trim() ? it.oracle.trim() : null;
47
- }
48
-
49
- function readOne(path, scope) {
50
- try {
51
- if (!existsSync(path)) return [];
52
- const j = JSON.parse(readFileSync(path, 'utf-8'));
53
- const items = Array.isArray(j) ? j : (Array.isArray(j?.items) ? j.items : []);
54
- return items.map((it, i) => ({
55
- // Preserve the full atom item (eval, child, disposition, order, …) so the gate
56
- // never strips fields the UI/core own; it only refreshes `status`.
57
- ...it,
58
- id: typeof it?.id === 'string' && it.id ? it.id : `${scope}-${i}`,
59
- text: String(it?.text ?? '').trim(),
60
- status: canonicalChecklistStatus(it?.status),
61
- // transient helpers (underscored) — used for evaluation, stripped before persist:
62
- scope,
63
- _oracle: itemOracle(it),
64
- _delegated: it?.disposition === 'delegated',
65
- })).filter((it) => it.text);
66
- } catch { return []; }
67
- }
68
-
69
- /** Effective checklist = project invariants ∪ session goals (project first, then session). */
70
- export function readEffectiveChecklist(loopDir, projectDir) {
71
- return [
72
- ...readOne(projectChecklistPath(projectDir), 'project'),
73
- ...readOne(sessionChecklistPath(loopDir), 'session'),
74
- ];
75
- }
76
-
77
- /**
78
- * Run each INLINE item's oracle (if any) and return items with refreshed status:
79
- * oracle pass → 'done', oracle fail → 'blocked'. This is the per-loop regression check
80
- * (a previously 'done' item whose oracle now fails flips to 'blocked'). Delegated items
81
- * (gated by their child) and non-oracle items (agent/human-evaluated) keep their status.
82
- */
83
- export function evaluateChecklist(items, projectDir, timeoutSec = 600) {
84
- return items.map((it) => {
85
- if (it._delegated || !it._oracle) return it;
86
- try {
87
- execSync(it._oracle, { cwd: projectDir, stdio: 'pipe', maxBuffer: 16 * 1024 * 1024, timeout: timeoutSec * 1000 });
88
- return { ...it, status: 'done' };
89
- } catch {
90
- return { ...it, status: 'blocked' };
91
- }
92
- });
93
- }
94
-
95
- /** True when every effective item is 'done' (an empty list is trivially satisfied). */
96
- export function allPassing(items) {
97
- return items.length === 0 ? true : items.every((it) => it.status === 'done');
98
- }
99
-
100
- /** A one-line summary for the gate's history/state. */
101
- export function summarize(items) {
102
- const done = items.filter((i) => i.status === 'done').length;
103
- const blocked = items.filter((i) => i.status === 'blocked' || i.status === 'rework').length;
104
- return `${done}/${items.length} done${blocked ? `, ${blocked} blocked` : ''}`;
105
- }
106
-
107
- /**
108
- * Persist refreshed statuses back to each scope's file so the UI + agent see live state.
109
- * Preserves the full atom item shape — only the transient helper fields (_scope/_oracle/
110
- * _delegated) are stripped; everything else (eval, child, disposition, order, …) round-trips.
111
- */
112
- export function writeChecklistStatuses(loopDir, projectDir, items) {
113
- const strip = (it) => {
114
- const { scope: _s, _oracle, _delegated, ...rest } = it;
115
- return rest;
116
- };
117
- const targets = [
118
- ['session', sessionChecklistPath(loopDir)],
119
- ['project', projectChecklistPath(projectDir)],
120
- ];
121
- for (const [scope, path] of targets) {
122
- const scoped = items.filter((it) => it.scope === scope).map(strip);
123
- if (scoped.length === 0 && !existsSync(path)) continue; // don't create empty files
124
- try {
125
- mkdirSync(dirname(path), { recursive: true });
126
- writeFileSync(path, JSON.stringify({ items: scoped }, null, 2));
127
- } catch { /* best-effort persistence */ }
128
- }
129
- }
@@ -1,86 +0,0 @@
1
- // test-checklist.mjs — the loop-engineering checklist atom (read/merge/evaluate/persist).
2
- import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import {
6
- readEffectiveChecklist, evaluateChecklist, allPassing, summarize,
7
- writeChecklistStatuses, sessionChecklistPath, projectChecklistPath,
8
- } from '../bin/checklist.mjs';
9
-
10
- let passed = 0, failed = 0;
11
- function ok(cond, msg) { if (cond) { passed++; console.log(` ✓ ${msg}`); } else { failed++; console.log(` ✗ ${msg}`); } }
12
- function eq(a, b, msg) { ok(JSON.stringify(a) === JSON.stringify(b), `${msg} (got ${JSON.stringify(a)})`); }
13
-
14
- const root = mkdtempSync(join(tmpdir(), 'cl-test-'));
15
- const projectDir = root;
16
- const loopDir = join(root, '.svamp', 'sess1', 'loop');
17
- mkdirSync(loopDir, { recursive: true });
18
- mkdirSync(join(root, '.svamp'), { recursive: true });
19
-
20
- console.log('scope merge + normalization');
21
- writeFileSync(projectChecklistPath(projectDir), JSON.stringify({ items: [
22
- { text: 'tests pass', oracle: 'true', status: 'done' },
23
- ] }));
24
- writeFileSync(sessionChecklistPath(loopDir), JSON.stringify({ items: [
25
- { text: 'add feature', status: 'done' }, // 'done' alias → passing
26
- { text: ' ', status: 'pending' }, // blank → dropped
27
- { text: 'no TODOs', oracle: 'false' }, // defaults to pending
28
- ] }));
29
- let eff = readEffectiveChecklist(loopDir, projectDir);
30
- eq(eff.length, 3, 'effective = project ∪ session, blanks dropped');
31
- eq(eff[0].scope, 'project', 'project items come first');
32
- eq(eff[0].text, 'tests pass', 'project item text');
33
- eq(eff[1].status, 'done', "'done' normalized to done");
34
- ok(eff.map(i => i.scope).join(',') === 'project,session,session', 'scope tags correct');
35
-
36
- console.log('evaluate — oracle pass/fail drives status (regression check)');
37
- const evaluated = evaluateChecklist(eff, projectDir);
38
- eq(evaluated.find(i => i.text === 'tests pass').status, 'done', 'oracle `true` → passing');
39
- eq(evaluated.find(i => i.text === 'no TODOs').status, 'blocked', 'oracle `false` → failing');
40
- eq(evaluated.find(i => i.text === 'add feature').status, 'done', 'no-oracle item keeps stored status');
41
-
42
- console.log('allPassing gate');
43
- ok(!allPassing(evaluated), 'not all done while one oracle fails');
44
- ok(allPassing([]), 'empty list is trivially satisfied');
45
- ok(allPassing(evaluated.map(i => ({ ...i, status: 'done' }))), 'all done → true');
46
-
47
- console.log('summarize');
48
- ok(summarize(evaluated).startsWith('2/3 done'), `summary reads "${summarize(evaluated)}"`);
49
-
50
- console.log('persist statuses back to the right scope files');
51
- writeChecklistStatuses(loopDir, projectDir, evaluated);
52
- const proj = JSON.parse(readFileSync(projectChecklistPath(projectDir), 'utf-8'));
53
- const sess = JSON.parse(readFileSync(sessionChecklistPath(loopDir), 'utf-8'));
54
- eq(proj.items.length, 1, 'project file holds only project items');
55
- eq(sess.items.length, 2, 'session file holds only session items');
56
- ok(proj.items[0].scope === undefined, 'scope stripped from persisted file');
57
- ok(sess.items.find(i => i.text === 'no TODOs').status === 'blocked', 'blocked status persisted (UI will show it)');
58
-
59
- // regression: a re-read after persist is stable
60
- const reEff = readEffectiveChecklist(loopDir, projectDir);
61
- eq(reEff.length, 3, 're-read after persist is stable');
62
-
63
- console.log('canonical atom shape — eval.cmd oracle, disposition, ItemStatus, field round-trip');
64
- const root2 = mkdtempSync(join(tmpdir(), 'cl-atom-'));
65
- const loopDir2 = join(root2, '.svamp', 'sessA', 'loop');
66
- mkdirSync(loopDir2, { recursive: true });
67
- writeFileSync(sessionChecklistPath(loopDir2), JSON.stringify({ items: [
68
- { id: 'a', text: 'build green', disposition: 'inline', eval: { type: 'oracle', cmd: 'true' }, status: 'todo', order: 0 },
69
- { id: 'b', text: 'lint clean', disposition: 'inline', eval: { type: 'oracle', cmd: 'false' }, status: 'todo' },
70
- { id: 'c', text: 'ship the API', disposition: 'delegated', status: 'active', child: { sessionId: 'x', branch: 'feat/api' } },
71
- ] }));
72
- const atom = evaluateChecklist(readEffectiveChecklist(loopDir2, root2), root2);
73
- eq(atom.find(i => i.id === 'a').status, 'done', 'eval.cmd `true` → done');
74
- eq(atom.find(i => i.id === 'b').status, 'blocked', 'eval.cmd `false` → blocked');
75
- eq(atom.find(i => i.id === 'c').status, 'active', 'delegated item NOT oracle-evaluated (child-gated), keeps status');
76
- ok(!allPassing(atom), 'not all done while an inline oracle fails');
77
- writeChecklistStatuses(loopDir2, root2, atom);
78
- const persisted = JSON.parse(readFileSync(sessionChecklistPath(loopDir2), 'utf-8')).items;
79
- const cItem = persisted.find(i => i.id === 'c');
80
- ok(cItem.disposition === 'delegated' && cItem.child?.branch === 'feat/api', 'atom fields (disposition/child) round-trip — gate never strips them');
81
- ok(persisted.find(i => i.id === 'a').eval?.cmd === 'true' && !('_oracle' in persisted.find(i => i.id === 'a')), 'eval preserved, transient _oracle stripped');
82
- rmSync(root2, { recursive: true, force: true });
83
-
84
- rmSync(root, { recursive: true, force: true });
85
- console.log(`\nchecklist: ${passed} passed, ${failed} failed`);
86
- process.exit(failed ? 1 : 0);