svamp-cli 0.2.202 → 0.2.203

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loop
3
- version: 0.4.1
3
+ version: 0.4.2
4
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
5
  ---
6
6
 
@@ -166,9 +166,25 @@ if (done) {
166
166
  block(`The loop's exit conditions are met (oracle empty + evaluator done), but you have ${pending} UNHANDLED inbox message(s). Handle them first — read/reply/triage/merge each (\`svamp session inbox list\`), then finish your turn. (This guard fires at most once per loop, then allows stop, so a flood can't trap the loop.)`);
167
167
  }
168
168
  }
169
+ // #0128: NON-BLOCKING open-crew hint. The loop is about to STOP — surface any active crew children
170
+ // the lead hasn't merged/closed so an orphaned/forgotten crew is visible at the natural checkpoint.
171
+ // Purely informational: it NEVER blocks or affects the decision (the loop still stops). Computed
172
+ // lazily only here (not every iteration) to avoid a per-turn subprocess. Disable with
173
+ // `crew_hint: false` in loop.config.json.
174
+ let openCrew = '';
175
+ if (cfg.crew_hint !== false) {
176
+ try {
177
+ const out = execSync('svamp feature list', { cwd: PROJECT, stdio: 'pipe', encoding: 'utf-8',
178
+ timeout: 15000, env: { ...process.env, ...(SID ? { SVAMP_SESSION_ID: SID } : {}) } }).toString();
179
+ const active = out.split('\n').map((l) => l.trim()).filter((l) => /\bactive\b/.test(l))
180
+ .map((l) => { const id = l.split(/\s+/)[0]; const iss = (l.match(/#\d{2,}/) || [])[0]; return iss ? `${id} (${iss})` : id; });
181
+ if (active.length) openCrew = `Open crew not merged/closed: ${active.join(', ')} — review with \`svamp feature merge <id>\`.`;
182
+ } catch { /* fail-open: no daemon / no crew → no hint */ }
183
+ }
184
+ if (openCrew) process.stderr.write(`[loop] ${openCrew}\n`);
169
185
  writeJSONAtomic(STATE, { ...state, active: false, phase: 'done', completed_at: now,
170
- last_oracle: oracleDetail });
171
- appendHistory({ ts: now, iteration: iterNum, decision: 'done', oracle: oraclePass, evaluator: evaluatorPass, detail: oracleDetail });
186
+ last_oracle: oracleDetail, ...(openCrew ? { open_crew: openCrew } : {}) });
187
+ appendHistory({ ts: now, iteration: iterNum, decision: 'done', oracle: oraclePass, evaluator: evaluatorPass, detail: oracleDetail, ...(openCrew ? { open_crew: openCrew } : {}) });
172
188
  allow();
173
189
  }
174
190
 
package/dist/cli.mjs CHANGED
@@ -386,7 +386,7 @@ async function main() {
386
386
  await handleWiseAgentCommand(args.slice(1));
387
387
  process.exit(0);
388
388
  } else if (subcommand === "feature" || subcommand === "crew") {
389
- const { crewCommand } = await import('./commands-BA8pUB80.mjs');
389
+ const { crewCommand } = await import('./commands-CKY0GfUp.mjs');
390
390
  await crewCommand(args.slice(1));
391
391
  process.exit(0);
392
392
  } else if (subcommand === "--help" || subcommand === "-h") {
@@ -394,7 +394,7 @@ async function main() {
394
394
  } else if (!subcommand || subcommand === "start") {
395
395
  await handleInteractiveCommand();
396
396
  } else if (subcommand === "--version" || subcommand === "-v") {
397
- const pkg = await import('./package-Df28hcfX.mjs').catch(() => ({ default: { version: "unknown" } }));
397
+ const pkg = await import('./package-BAOTsiw2.mjs').catch(() => ({ default: { version: "unknown" } }));
398
398
  console.log(`svamp version: ${pkg.default.version}`);
399
399
  } else {
400
400
  console.error(`Unknown command: ${subcommand}`);
@@ -55,23 +55,23 @@ function mergeBack(input) {
55
55
  if (!existsSync(projectRoot)) {
56
56
  return { ok: false, stage: "verify", detail: `project root not found: ${projectRoot}` };
57
57
  }
58
- if (!existsSync(worktreePath)) {
59
- return { ok: false, stage: "verify", detail: `worktree not found: ${worktreePath}` };
60
- }
61
- let childStatus;
62
- try {
63
- childStatus = worktreeStatus(worktreePath);
64
- } catch (e) {
65
- return { ok: false, stage: "verify", detail: `not a git worktree: ${worktreePath} (${e.message})` };
66
- }
67
- if (childStatus) {
68
- return {
69
- ok: false,
70
- stage: "verify",
71
- rework: true,
72
- detail: `feature worktree has uncommitted changes \u2014 commit (or run \`feature done\`) first:
58
+ const worktreeGone = !existsSync(worktreePath);
59
+ if (!worktreeGone) {
60
+ let childStatus;
61
+ try {
62
+ childStatus = worktreeStatus(worktreePath);
63
+ } catch (e) {
64
+ return { ok: false, stage: "verify", detail: `not a git worktree: ${worktreePath} (${e.message})` };
65
+ }
66
+ if (childStatus) {
67
+ return {
68
+ ok: false,
69
+ stage: "verify",
70
+ rework: true,
71
+ detail: `feature worktree has uncommitted changes \u2014 commit (or run \`feature done\`) first:
73
72
  ${childStatus}`
74
- };
73
+ };
74
+ }
75
75
  }
76
76
  const leadBranch = currentBranch(projectRoot);
77
77
  if (leadBranch !== baseBranch) {
@@ -108,8 +108,19 @@ ${detail.trim()}` };
108
108
  try {
109
109
  git(projectRoot, `worktree remove --force ${worktreePath}`);
110
110
  } catch (e) {
111
- const detail = e.stderr?.toString() || "" || e.message;
112
- return { ok: false, stage: "remove-worktree", detail: `merged OK but worktree removal failed: ${detail.trim()}` };
111
+ try {
112
+ git(projectRoot, "worktree prune");
113
+ } catch {
114
+ }
115
+ let stillRegistered = false;
116
+ try {
117
+ stillRegistered = git(projectRoot, "worktree list --porcelain").includes(worktreePath);
118
+ } catch {
119
+ }
120
+ if (stillRegistered) {
121
+ const detail = e.stderr?.toString() || "" || e.message;
122
+ return { ok: false, stage: "remove-worktree", detail: `merged OK but worktree removal failed: ${detail.trim()}` };
123
+ }
113
124
  }
114
125
  if (input.deleteBranch !== false) {
115
126
  try {
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.202";
2
+ var version = "0.2.203";
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.202",
3
+ "version": "0.2.203",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",