feat: preserve workspace continuity - #36
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e41a4da167
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| payload = { ...payload, cwd: resolveWorkspaceRoot(payload.cwd) }; | ||
| if (!envOn(process.env, "SUPERLOOPY_STOP_HOOK")) return ""; | ||
| return await decideContinuation(payload, { |
There was a problem hiding this comment.
Gate Stop continuation on repository binding
When SUPERLOOPY_STOP_HOOK=on and a copied or mismatched plan exists in the current workspace, statusLoop now returns {ok:false, binding} instead of throwing, but this path still passes that status into decideContinuation, which ignores ok/binding, writes loop-control.json/ledger entries, and emits commands to resume the copied state. This bypasses the new binding refusal specifically in the Stop hook path, so add a non-resumable binding check before continuation or teach decideContinuation to stop without mutation.
Useful? React with 👍 / 👎.
| const git = findNearestGitWorktree(origin); | ||
| if (git !== null) return git.root; |
There was a problem hiding this comment.
Preserve legacy subdirectory loop discovery
Because this returns the Git worktree root before looking for an existing .superloopy directory, any v1 plan created by the previous CLI from a package/subdirectory inside a Git repo becomes undiscoverable after upgrade: superloopy loop status or loop bind from that same directory is redirected to the repo root and fails with “No Superloopy plan found” instead of offering the legacy bind flow. Consider honoring an existing state directory at or above origin for legacy discovery before canonicalizing to the Git root.
Useful? React with 👍 / 👎.
|
|
||
| async function dispatchLoop(cwd, subcommand, argv) { | ||
| if (!["begin", "create", "status", "bind"].includes(subcommand)) { | ||
| const scope = scopeFromSessionId(readFlag(argv, "--session-id")); |
There was a problem hiding this comment.
Ignore --session-id after the command delimiter
For loop prove/loop capture, a validation command can legitimately contain its own --session-id after --, but this new preflight scans the entire argv before the subcommand parser splits options from command args. In that case it tries to load a Superloopy scoped plan for the validation command's flag and fails or blocks the wrong plan, even though the actual prove/capture operation would use the unscoped plan.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| function bindCommand(sessionId) { | ||
| const session = typeof sessionId === "string" && sessionId.length > 0 ? ` --session-id ${sessionId}` : ""; |
There was a problem hiding this comment.
Quote legacy session IDs in suggested bind command
For an untrusted legacy plan, sessionId is read directly from goals.json and interpolated into the copy-pasteable binding.next command. If a repo ships a v1 plan with a value such as abc; touch /tmp/pwn, loop status suggests a command that will execute the injected shell fragment, so the session id should be normalized/rejected or shell-quoted before being placed in guidance.
Useful? React with 👍 / 👎.
| return 0; | ||
| } | ||
| const result = await dispatchLoop(cwd, subcommand, argv); | ||
| const result = await dispatchLoop(resolveWorkspaceRoot(cwd), subcommand, argv); |
There was a problem hiding this comment.
Normalize PreToolUse cwd before completion checks
With this canonicalization, superloopy loop begin from a package subdirectory now creates the plan at the Git root, but runPreToolUseHook still looks for goals.json under the host-provided cwd. In a Codex session whose cwd remains packages/app, update_goal status=complete sees no plan and is allowed while the root Superloopy plan is incomplete, so the native completion guard is bypassed; normalize that hook or pass the resolved root too.
Useful? React with 👍 / 👎.
| "Only the deterministic Superloopy gate authorizes completion." | ||
| ].join("\n"); | ||
| if (mandatory.length <= maxChars) return mandatory; | ||
| return `${mandatory.slice(0, Math.max(0, maxChars - 32)).trimEnd()}\n[recovery capsule truncated]`; |
There was a problem hiding this comment.
Preserve recovery tail fields when truncating
This prefix-only truncation lets any long active goal title or unresolved list cut off later recovery fields, including Next action and the deterministic-gate warning, exactly when compacted sessions need them to resume safely. For example, a long goal title under a 700-character cap leaves the capsule without the resume command, so reserve or append the mandatory tail fields instead of slicing the whole string from the front.
Useful? React with 👍 / 👎.
What changed
Why
Repo-local state could previously be resumed from the wrong checkout, repeated hook delivery could apply steering twice, and compacted sessions lacked a small durable semantic recovery contract. Research guidance also treated saturation as a goal instead of making cost visible without blocking the loop.
Impact
New plans use schema version 2 and include an anonymous checkout identity. Version 1 plans remain readable but require the explicit loop bind confirmation before mutation. Context targets remain guidance only: they never block, delay, rewrite, or request approval.
Checks